@morojs/moro 1.6.1 → 1.6.3
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 +74 -256
- package/dist/core/auth/morojs-adapter.js +20 -20
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-manager.d.ts +44 -0
- package/dist/core/config/config-manager.js +104 -0
- package/dist/core/config/config-manager.js.map +1 -0
- package/dist/core/config/config-sources.d.ts +21 -0
- package/dist/core/config/config-sources.js +503 -0
- package/dist/core/config/config-sources.js.map +1 -0
- package/dist/core/config/config-validator.d.ts +21 -0
- package/dist/core/config/config-validator.js +791 -0
- package/dist/core/config/config-validator.js.map +1 -0
- package/dist/core/config/file-loader.d.ts +1 -6
- package/dist/core/config/file-loader.js +21 -249
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +41 -12
- package/dist/core/config/index.js +65 -54
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.d.ts +2 -2
- package/dist/core/config/schema.js +55 -44
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +10 -3
- package/dist/core/config/utils.js +31 -58
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/database/adapters/drizzle.d.ts +1 -1
- package/dist/core/database/adapters/drizzle.js +18 -11
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/index.d.ts +7 -7
- package/dist/core/database/adapters/index.js +19 -29
- package/dist/core/database/adapters/index.js.map +1 -1
- package/dist/core/database/adapters/mongodb.d.ts +13 -1
- package/dist/core/database/adapters/mongodb.js +46 -10
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +14 -1
- package/dist/core/database/adapters/mysql.js +19 -9
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.d.ts +12 -2
- package/dist/core/database/adapters/postgresql.js +19 -9
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.d.ts +12 -1
- package/dist/core/database/adapters/redis.js +48 -13
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sqlite.d.ts +3 -1
- package/dist/core/database/adapters/sqlite.js +19 -8
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/database/index.d.ts +2 -2
- package/dist/core/database/index.js +2 -18
- package/dist/core/database/index.js.map +1 -1
- package/dist/core/docs/index.d.ts +9 -9
- package/dist/core/docs/index.js +14 -35
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.d.ts +2 -2
- package/dist/core/docs/openapi-generator.js +11 -16
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.d.ts +2 -2
- package/dist/core/docs/schema-to-openapi.js +5 -11
- package/dist/core/docs/schema-to-openapi.js.map +1 -1
- package/dist/core/docs/simple-docs.d.ts +1 -1
- package/dist/core/docs/simple-docs.js +4 -9
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.d.ts +2 -2
- package/dist/core/docs/swagger-ui.js +26 -29
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.js +31 -28
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/events/event-bus.d.ts +1 -1
- package/dist/core/events/event-bus.js +7 -11
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/events/index.d.ts +2 -2
- package/dist/core/events/index.js +1 -5
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/framework.d.ts +20 -13
- package/dist/core/framework.js +285 -102
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +59 -7
- package/dist/core/http/http-server.js +202 -185
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/index.d.ts +4 -3
- package/dist/core/http/index.js +3 -8
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +46 -0
- package/dist/core/http/uws-http-server.js +523 -0
- package/dist/core/http/uws-http-server.js.map +1 -0
- package/dist/core/logger/filters.d.ts +1 -1
- package/dist/core/logger/filters.js +20 -23
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.d.ts +3 -3
- package/dist/core/logger/index.js +2 -24
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +30 -14
- package/dist/core/logger/logger.js +398 -223
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.d.ts +1 -1
- package/dist/core/logger/outputs.js +8 -17
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/middleware/built-in/auth/core.d.ts +78 -0
- package/dist/core/middleware/built-in/auth/core.js +358 -0
- package/dist/core/middleware/built-in/auth/core.js.map +1 -0
- package/dist/core/middleware/built-in/{auth-helpers.js → auth/helpers.js} +12 -23
- package/dist/core/middleware/built-in/auth/helpers.js.map +1 -0
- package/dist/core/middleware/built-in/auth/hook.d.ts +30 -0
- package/dist/core/middleware/built-in/auth/hook.js +99 -0
- package/dist/core/middleware/built-in/auth/hook.js.map +1 -0
- package/dist/core/middleware/built-in/auth/index.d.ts +7 -0
- package/dist/core/middleware/built-in/auth/index.js +15 -0
- package/dist/core/middleware/built-in/auth/index.js.map +1 -0
- package/dist/core/middleware/built-in/auth/jwt-helpers.d.ts +118 -0
- package/dist/core/middleware/built-in/auth/jwt-helpers.js +218 -0
- package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -0
- package/dist/core/middleware/built-in/auth/middleware.d.ts +23 -0
- package/dist/core/middleware/built-in/auth/middleware.js +71 -0
- package/dist/core/middleware/built-in/auth/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/{auth-providers.d.ts → auth/providers.d.ts} +1 -1
- package/dist/core/middleware/built-in/{auth-providers.js → auth/providers.js} +5 -10
- package/dist/core/middleware/built-in/auth/providers.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/file.js +10 -47
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/index.d.ts +5 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/index.js +21 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/memory.js +3 -7
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.d.ts +3 -1
- package/dist/core/middleware/built-in/{adapters → cache/adapters}/cache/redis.js +11 -9
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -0
- package/dist/core/middleware/built-in/cache/adapters/index.d.ts +2 -0
- package/dist/core/middleware/built-in/cache/adapters/index.js +5 -0
- package/dist/core/middleware/built-in/cache/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/cache/core.d.ts +37 -0
- package/dist/core/middleware/built-in/cache/core.js +87 -0
- package/dist/core/middleware/built-in/cache/core.js.map +1 -0
- package/dist/core/middleware/built-in/cache/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/{cache.js → cache/hook.js} +30 -14
- package/dist/core/middleware/built-in/cache/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cache/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cache/index.js +9 -0
- package/dist/core/middleware/built-in/cache/index.js.map +1 -0
- package/dist/core/middleware/built-in/cache/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/cache/middleware.js +44 -0
- package/dist/core/middleware/built-in/cache/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/azure.js +3 -7
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.d.ts +1 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudflare.js +3 -7
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -0
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.d.ts +3 -1
- package/dist/core/middleware/built-in/{adapters → cdn/adapters}/cdn/cloudfront.js +12 -10
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/index.d.ts +5 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/index.js +21 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/adapters/index.d.ts +2 -0
- package/dist/core/middleware/built-in/cdn/adapters/index.js +5 -0
- package/dist/core/middleware/built-in/cdn/adapters/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/core.d.ts +43 -0
- package/dist/core/middleware/built-in/cdn/core.js +144 -0
- package/dist/core/middleware/built-in/cdn/core.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/hook.d.ts +22 -0
- package/dist/core/middleware/built-in/cdn/hook.js +70 -0
- package/dist/core/middleware/built-in/cdn/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/index.d.ts +5 -0
- package/dist/core/middleware/built-in/cdn/index.js +11 -0
- package/dist/core/middleware/built-in/cdn/index.js.map +1 -0
- package/dist/core/middleware/built-in/cdn/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/cdn/middleware.js +52 -0
- package/dist/core/middleware/built-in/cdn/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/core.d.ts +37 -0
- package/dist/core/middleware/built-in/cookie/core.js +83 -0
- package/dist/core/middleware/built-in/cookie/core.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/cookie/hook.js +47 -0
- package/dist/core/middleware/built-in/cookie/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cookie/index.js +9 -0
- package/dist/core/middleware/built-in/cookie/index.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/cookie/middleware.js +36 -0
- package/dist/core/middleware/built-in/cookie/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cors/core.d.ts +23 -0
- package/dist/core/middleware/built-in/cors/core.js +51 -0
- package/dist/core/middleware/built-in/cors/core.js.map +1 -0
- package/dist/core/middleware/built-in/cors/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/cors/hook.js +37 -0
- package/dist/core/middleware/built-in/cors/hook.js.map +1 -0
- package/dist/core/middleware/built-in/cors/index.d.ts +3 -0
- package/dist/core/middleware/built-in/cors/index.js +9 -0
- package/dist/core/middleware/built-in/cors/index.js.map +1 -0
- package/dist/core/middleware/built-in/cors/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/cors/middleware.js +22 -0
- package/dist/core/middleware/built-in/cors/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/csp/core.d.ts +45 -0
- package/dist/core/middleware/built-in/csp/core.js +88 -0
- package/dist/core/middleware/built-in/csp/core.js.map +1 -0
- package/dist/core/middleware/built-in/csp/hook.d.ts +22 -0
- package/dist/core/middleware/built-in/csp/hook.js +47 -0
- package/dist/core/middleware/built-in/csp/hook.js.map +1 -0
- package/dist/core/middleware/built-in/csp/index.d.ts +3 -0
- package/dist/core/middleware/built-in/csp/index.js +9 -0
- package/dist/core/middleware/built-in/csp/index.js.map +1 -0
- package/dist/core/middleware/built-in/csp/middleware.d.ts +19 -0
- package/dist/core/middleware/built-in/csp/middleware.js +29 -0
- package/dist/core/middleware/built-in/csp/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/core.d.ts +28 -0
- package/dist/core/middleware/built-in/csrf/core.js +69 -0
- package/dist/core/middleware/built-in/csrf/core.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/csrf/hook.js +45 -0
- package/dist/core/middleware/built-in/csrf/hook.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/index.d.ts +3 -0
- package/dist/core/middleware/built-in/csrf/index.js +9 -0
- package/dist/core/middleware/built-in/csrf/index.js.map +1 -0
- package/dist/core/middleware/built-in/csrf/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/csrf/middleware.js +34 -0
- package/dist/core/middleware/built-in/csrf/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker/index.d.ts +1 -0
- package/dist/core/middleware/built-in/error-tracker/index.js +4 -0
- package/dist/core/middleware/built-in/error-tracker/index.js.map +1 -0
- package/dist/core/middleware/built-in/error-tracker/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/error-tracker/middleware.js +26 -0
- package/dist/core/middleware/built-in/error-tracker/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +28 -61
- package/dist/core/middleware/built-in/index.js +48 -78
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/performance-monitor/index.d.ts +1 -0
- package/dist/core/middleware/built-in/performance-monitor/index.js +4 -0
- package/dist/core/middleware/built-in/performance-monitor/index.js.map +1 -0
- package/dist/core/middleware/built-in/performance-monitor/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/performance-monitor/middleware.js +29 -0
- package/dist/core/middleware/built-in/performance-monitor/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/core.d.ts +33 -0
- package/dist/core/middleware/built-in/rate-limit/core.js +86 -0
- package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/hook.d.ts +20 -0
- package/dist/core/middleware/built-in/{rate-limit.js → rate-limit/hook.js} +24 -22
- package/dist/core/middleware/built-in/rate-limit/hook.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/index.d.ts +3 -0
- package/dist/core/middleware/built-in/rate-limit/index.js +9 -0
- package/dist/core/middleware/built-in/rate-limit/index.js.map +1 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.js +35 -0
- package/dist/core/middleware/built-in/rate-limit/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger/index.d.ts +1 -0
- package/dist/core/middleware/built-in/request-logger/index.js +4 -0
- package/dist/core/middleware/built-in/request-logger/index.js.map +1 -0
- package/dist/core/middleware/built-in/request-logger/middleware.d.ts +12 -0
- package/dist/core/middleware/built-in/request-logger/middleware.js +24 -0
- package/dist/core/middleware/built-in/request-logger/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/session/core.d.ts +73 -0
- package/dist/core/middleware/built-in/session/core.js +227 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -0
- package/dist/core/middleware/built-in/session/hook.d.ts +17 -0
- package/dist/core/middleware/built-in/session/hook.js +53 -0
- package/dist/core/middleware/built-in/session/hook.js.map +1 -0
- package/dist/core/middleware/built-in/session/index.d.ts +3 -0
- package/dist/core/middleware/built-in/session/index.js +9 -0
- package/dist/core/middleware/built-in/session/index.js.map +1 -0
- package/dist/core/middleware/built-in/session/middleware.d.ts +17 -0
- package/dist/core/middleware/built-in/session/middleware.js +38 -0
- package/dist/core/middleware/built-in/session/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/sse/core.d.ts +44 -0
- package/dist/core/middleware/built-in/sse/core.js +117 -0
- package/dist/core/middleware/built-in/sse/core.js.map +1 -0
- package/dist/core/middleware/built-in/sse/hook.d.ts +18 -0
- package/dist/core/middleware/built-in/sse/hook.js +60 -0
- package/dist/core/middleware/built-in/sse/hook.js.map +1 -0
- package/dist/core/middleware/built-in/sse/index.d.ts +3 -0
- package/dist/core/middleware/built-in/sse/index.js +9 -0
- package/dist/core/middleware/built-in/sse/index.js.map +1 -0
- package/dist/core/middleware/built-in/sse/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/sse/middleware.js +43 -0
- package/dist/core/middleware/built-in/sse/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/validation/core.d.ts +23 -0
- package/dist/core/middleware/built-in/validation/core.js +93 -0
- package/dist/core/middleware/built-in/validation/core.js.map +1 -0
- package/dist/core/middleware/built-in/validation/hook.d.ts +13 -0
- package/dist/core/middleware/built-in/{validation.js → validation/hook.js} +16 -9
- package/dist/core/middleware/built-in/validation/hook.js.map +1 -0
- package/dist/core/middleware/built-in/validation/index.d.ts +3 -0
- package/dist/core/middleware/built-in/validation/index.js +9 -0
- package/dist/core/middleware/built-in/validation/index.js.map +1 -0
- package/dist/core/middleware/built-in/validation/middleware.d.ts +16 -0
- package/dist/core/middleware/built-in/validation/middleware.js +27 -0
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -0
- package/dist/core/middleware/index.d.ts +4 -4
- package/dist/core/middleware/index.js +14 -28
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +19 -2
- package/dist/core/modules/auto-discovery.js +391 -74
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/index.d.ts +2 -2
- package/dist/core/modules/index.js +2 -9
- package/dist/core/modules/index.js.map +1 -1
- package/dist/core/modules/modules.d.ts +3 -3
- package/dist/core/modules/modules.js +23 -54
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/index.d.ts +4 -3
- package/dist/core/networking/adapters/index.js +3 -7
- package/dist/core/networking/adapters/index.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js +5 -40
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +44 -0
- package/dist/core/networking/adapters/uws-adapter.js +513 -0
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -0
- package/dist/core/networking/adapters/ws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/ws-adapter.js +8 -43
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/index.d.ts +3 -2
- package/dist/core/networking/index.js +2 -7
- package/dist/core/networking/index.js.map +1 -1
- package/dist/core/networking/service-discovery.js +8 -12
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/networking/websocket-adapter.js +1 -2
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/networking/websocket-manager.d.ts +3 -3
- package/dist/core/networking/websocket-manager.js +9 -11
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.d.ts +140 -0
- package/dist/core/pooling/object-pool-manager.js +502 -0
- package/dist/core/pooling/object-pool-manager.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +14 -12
- package/dist/core/routing/app-integration.js +49 -85
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +17 -11
- package/dist/core/routing/index.js +48 -237
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/path-matcher.d.ts +67 -0
- package/dist/core/routing/path-matcher.js +182 -0
- package/dist/core/routing/path-matcher.js.map +1 -0
- package/dist/core/routing/router.d.ts +38 -0
- package/dist/core/routing/router.js +68 -0
- package/dist/core/routing/router.js.map +1 -0
- package/dist/core/routing/unified-router.d.ts +132 -0
- package/dist/core/routing/unified-router.js +639 -0
- package/dist/core/routing/unified-router.js.map +1 -0
- package/dist/core/runtime/aws-lambda-adapter.d.ts +3 -3
- package/dist/core/runtime/aws-lambda-adapter.js +2 -6
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.d.ts +2 -2
- package/dist/core/runtime/base-adapter.js +3 -7
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.d.ts +3 -3
- package/dist/core/runtime/cloudflare-workers-adapter.js +2 -6
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/index.d.ts +12 -12
- package/dist/core/runtime/index.js +22 -35
- package/dist/core/runtime/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +4 -4
- package/dist/core/runtime/node-adapter.js +18 -49
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.d.ts +3 -3
- package/dist/core/runtime/vercel-edge-adapter.js +2 -6
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.js +1 -5
- package/dist/core/utilities/circuit-breaker.js.map +1 -1
- package/dist/core/utilities/container.js +12 -22
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.d.ts +2 -2
- package/dist/core/utilities/hooks.js +7 -12
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/utilities/index.d.ts +5 -4
- package/dist/core/utilities/index.js +5 -19
- package/dist/core/utilities/index.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +38 -0
- package/dist/core/utilities/package-utils.js +57 -0
- package/dist/core/utilities/package-utils.js.map +1 -0
- package/dist/core/validation/adapters.d.ts +1 -1
- package/dist/core/validation/adapters.js +15 -26
- package/dist/core/validation/adapters.js.map +1 -1
- package/dist/core/validation/index.d.ts +6 -4
- package/dist/core/validation/index.js +57 -28
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.js +3 -9
- package/dist/core/validation/schema-interface.js.map +1 -1
- package/dist/index.d.ts +52 -52
- package/dist/index.js +24 -132
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +70 -16
- package/dist/moro.js +650 -269
- package/dist/moro.js.map +1 -1
- package/dist/types/auth.js +3 -9
- package/dist/types/auth.js.map +1 -1
- package/dist/types/cache.js +1 -2
- package/dist/types/cdn.js +1 -2
- package/dist/types/config.d.ts +73 -2
- package/dist/types/config.js +1 -2
- package/dist/types/config.js.map +1 -1
- package/dist/types/core.d.ts +36 -42
- package/dist/types/core.js +1 -2
- package/dist/types/database.js +1 -2
- package/dist/types/discovery.js +1 -2
- package/dist/types/events.js +1 -2
- package/dist/types/hooks.d.ts +4 -1
- package/dist/types/hooks.js +1 -2
- package/dist/types/http.d.ts +16 -1
- package/dist/types/http.js +1 -2
- package/dist/types/logger.d.ts +7 -0
- package/dist/types/logger.js +1 -2
- package/dist/types/module.d.ts +11 -0
- package/dist/types/module.js +1 -2
- package/dist/types/runtime.d.ts +1 -1
- package/dist/types/runtime.js +1 -2
- package/dist/types/session.js +1 -2
- package/package.json +18 -55
- package/dist/core/config/loader.d.ts +0 -7
- package/dist/core/config/loader.js +0 -269
- package/dist/core/config/loader.js.map +0 -1
- package/dist/core/config/validation.d.ts +0 -17
- package/dist/core/config/validation.js +0 -131
- package/dist/core/config/validation.js.map +0 -1
- package/dist/core/http/router.d.ts +0 -14
- package/dist/core/http/router.js +0 -109
- package/dist/core/http/router.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/file.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/index.d.ts +0 -5
- package/dist/core/middleware/built-in/adapters/cache/index.js +0 -28
- package/dist/core/middleware/built-in/adapters/cache/index.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/memory.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cache/redis.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/azure.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudflare.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/cloudfront.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/cdn/index.d.ts +0 -5
- package/dist/core/middleware/built-in/adapters/cdn/index.js +0 -28
- package/dist/core/middleware/built-in/adapters/cdn/index.js.map +0 -1
- package/dist/core/middleware/built-in/adapters/index.d.ts +0 -4
- package/dist/core/middleware/built-in/adapters/index.js +0 -26
- package/dist/core/middleware/built-in/adapters/index.js.map +0 -1
- package/dist/core/middleware/built-in/auth-helpers.js.map +0 -1
- package/dist/core/middleware/built-in/auth-providers.js.map +0 -1
- package/dist/core/middleware/built-in/auth.d.ts +0 -30
- package/dist/core/middleware/built-in/auth.js +0 -281
- package/dist/core/middleware/built-in/auth.js.map +0 -1
- package/dist/core/middleware/built-in/cache.d.ts +0 -3
- package/dist/core/middleware/built-in/cache.js.map +0 -1
- package/dist/core/middleware/built-in/cdn.d.ts +0 -3
- package/dist/core/middleware/built-in/cdn.js +0 -113
- package/dist/core/middleware/built-in/cdn.js.map +0 -1
- package/dist/core/middleware/built-in/cookie.d.ts +0 -14
- package/dist/core/middleware/built-in/cookie.js +0 -68
- package/dist/core/middleware/built-in/cookie.js.map +0 -1
- package/dist/core/middleware/built-in/cors.d.ts +0 -2
- package/dist/core/middleware/built-in/cors.js +0 -29
- package/dist/core/middleware/built-in/cors.js.map +0 -1
- package/dist/core/middleware/built-in/csp.d.ts +0 -22
- package/dist/core/middleware/built-in/csp.js +0 -71
- package/dist/core/middleware/built-in/csp.js.map +0 -1
- package/dist/core/middleware/built-in/csrf.d.ts +0 -9
- package/dist/core/middleware/built-in/csrf.js +0 -63
- package/dist/core/middleware/built-in/csrf.js.map +0 -1
- package/dist/core/middleware/built-in/error-tracker.d.ts +0 -1
- package/dist/core/middleware/built-in/error-tracker.js +0 -19
- package/dist/core/middleware/built-in/error-tracker.js.map +0 -1
- package/dist/core/middleware/built-in/performance-monitor.d.ts +0 -1
- package/dist/core/middleware/built-in/performance-monitor.js +0 -22
- package/dist/core/middleware/built-in/performance-monitor.js.map +0 -1
- package/dist/core/middleware/built-in/rate-limit.d.ts +0 -6
- package/dist/core/middleware/built-in/rate-limit.js.map +0 -1
- package/dist/core/middleware/built-in/request-logger.d.ts +0 -1
- package/dist/core/middleware/built-in/request-logger.js +0 -16
- package/dist/core/middleware/built-in/request-logger.js.map +0 -1
- package/dist/core/middleware/built-in/session.d.ts +0 -41
- package/dist/core/middleware/built-in/session.js +0 -209
- package/dist/core/middleware/built-in/session.js.map +0 -1
- package/dist/core/middleware/built-in/sse.d.ts +0 -6
- package/dist/core/middleware/built-in/sse.js +0 -71
- package/dist/core/middleware/built-in/sse.js.map +0 -1
- package/dist/core/middleware/built-in/validation.d.ts +0 -2
- package/dist/core/middleware/built-in/validation.js.map +0 -1
- package/src/core/auth/README.md +0 -339
- package/src/core/auth/morojs-adapter.ts +0 -410
- package/src/core/config/file-loader.ts +0 -407
- package/src/core/config/index.ts +0 -60
- package/src/core/config/loader.ts +0 -633
- package/src/core/config/schema.ts +0 -150
- package/src/core/config/utils.ts +0 -251
- package/src/core/config/validation.ts +0 -140
- package/src/core/database/README.md +0 -228
- package/src/core/database/adapters/drizzle.ts +0 -403
- package/src/core/database/adapters/index.ts +0 -42
- package/src/core/database/adapters/mongodb.ts +0 -269
- package/src/core/database/adapters/mysql.ts +0 -207
- package/src/core/database/adapters/postgresql.ts +0 -201
- package/src/core/database/adapters/redis.ts +0 -326
- package/src/core/database/adapters/sqlite.ts +0 -247
- package/src/core/database/index.ts +0 -3
- package/src/core/docs/index.ts +0 -231
- package/src/core/docs/openapi-generator.ts +0 -576
- package/src/core/docs/schema-to-openapi.ts +0 -148
- package/src/core/docs/simple-docs.ts +0 -295
- package/src/core/docs/swagger-ui.ts +0 -351
- package/src/core/docs/zod-to-openapi.ts +0 -532
- package/src/core/events/event-bus.ts +0 -231
- package/src/core/events/index.ts +0 -12
- package/src/core/framework.ts +0 -636
- package/src/core/http/http-server.ts +0 -1787
- package/src/core/http/index.ts +0 -6
- package/src/core/http/router.ts +0 -141
- package/src/core/logger/filters.ts +0 -145
- package/src/core/logger/index.ts +0 -20
- package/src/core/logger/logger.ts +0 -814
- package/src/core/logger/outputs.ts +0 -134
- package/src/core/middleware/built-in/adapters/cache/file.ts +0 -104
- package/src/core/middleware/built-in/adapters/cache/index.ts +0 -23
- package/src/core/middleware/built-in/adapters/cache/memory.ts +0 -73
- package/src/core/middleware/built-in/adapters/cache/redis.ts +0 -100
- package/src/core/middleware/built-in/adapters/cdn/azure.ts +0 -60
- package/src/core/middleware/built-in/adapters/cdn/cloudflare.ts +0 -83
- package/src/core/middleware/built-in/adapters/cdn/cloudfront.ts +0 -83
- package/src/core/middleware/built-in/adapters/cdn/index.ts +0 -23
- package/src/core/middleware/built-in/adapters/index.ts +0 -7
- package/src/core/middleware/built-in/auth-helpers.ts +0 -401
- package/src/core/middleware/built-in/auth-providers.ts +0 -480
- package/src/core/middleware/built-in/auth.ts +0 -329
- package/src/core/middleware/built-in/cache.ts +0 -211
- package/src/core/middleware/built-in/cdn.ts +0 -124
- package/src/core/middleware/built-in/cookie.ts +0 -85
- package/src/core/middleware/built-in/cors.ts +0 -38
- package/src/core/middleware/built-in/csp.ts +0 -101
- package/src/core/middleware/built-in/csrf.ts +0 -82
- package/src/core/middleware/built-in/error-tracker.ts +0 -16
- package/src/core/middleware/built-in/index.ts +0 -79
- package/src/core/middleware/built-in/performance-monitor.ts +0 -25
- package/src/core/middleware/built-in/rate-limit.ts +0 -60
- package/src/core/middleware/built-in/request-logger.ts +0 -14
- package/src/core/middleware/built-in/session.ts +0 -288
- package/src/core/middleware/built-in/sse.ts +0 -86
- package/src/core/middleware/built-in/validation.ts +0 -33
- package/src/core/middleware/index.ts +0 -177
- package/src/core/modules/auto-discovery.ts +0 -263
- package/src/core/modules/index.ts +0 -3
- package/src/core/modules/modules.ts +0 -124
- package/src/core/networking/adapters/index.ts +0 -16
- package/src/core/networking/adapters/socketio-adapter.ts +0 -252
- package/src/core/networking/adapters/ws-adapter.ts +0 -430
- package/src/core/networking/index.ts +0 -3
- package/src/core/networking/service-discovery.ts +0 -304
- package/src/core/networking/websocket-adapter.ts +0 -217
- package/src/core/networking/websocket-manager.ts +0 -308
- package/src/core/routing/app-integration.ts +0 -216
- package/src/core/routing/index.ts +0 -488
- package/src/core/runtime/aws-lambda-adapter.ts +0 -147
- package/src/core/runtime/base-adapter.ts +0 -130
- package/src/core/runtime/cloudflare-workers-adapter.ts +0 -152
- package/src/core/runtime/index.ts +0 -62
- package/src/core/runtime/node-adapter.ts +0 -196
- package/src/core/runtime/vercel-edge-adapter.ts +0 -114
- package/src/core/utilities/circuit-breaker.ts +0 -46
- package/src/core/utilities/container.ts +0 -736
- package/src/core/utilities/hooks.ts +0 -142
- package/src/core/utilities/index.ts +0 -16
- package/src/core/validation/adapters.ts +0 -147
- package/src/core/validation/index.ts +0 -206
- package/src/core/validation/schema-interface.ts +0 -100
- package/src/index.ts +0 -226
- package/src/moro.ts +0 -1197
- package/src/types/auth.ts +0 -440
- package/src/types/cache.ts +0 -38
- package/src/types/cdn.ts +0 -22
- package/src/types/config.ts +0 -157
- package/src/types/core.ts +0 -56
- package/src/types/database.ts +0 -32
- package/src/types/discovery.ts +0 -7
- package/src/types/events.ts +0 -82
- package/src/types/hooks.ts +0 -47
- package/src/types/http.ts +0 -67
- package/src/types/logger.ts +0 -93
- package/src/types/module.ts +0 -87
- package/src/types/runtime.ts +0 -76
- package/src/types/session.ts +0 -89
- package/tsconfig.json +0 -21
- /package/dist/core/middleware/built-in/{auth-helpers.d.ts → auth/helpers.d.ts} +0 -0
|
@@ -1,1787 +0,0 @@
|
|
|
1
|
-
// src/core/http-server.ts
|
|
2
|
-
import { IncomingMessage, ServerResponse, createServer, Server } from 'http';
|
|
3
|
-
import * as zlib from 'zlib';
|
|
4
|
-
import { promisify } from 'util';
|
|
5
|
-
import { createFrameworkLogger } from '../logger';
|
|
6
|
-
import { HttpRequest, HttpResponse, HttpHandler, Middleware, RouteEntry } from '../../types/http';
|
|
7
|
-
|
|
8
|
-
const gzip = promisify(zlib.gzip);
|
|
9
|
-
const deflate = promisify(zlib.deflate);
|
|
10
|
-
|
|
11
|
-
export class MoroHttpServer {
|
|
12
|
-
private server: Server;
|
|
13
|
-
private routes: RouteEntry[] = [];
|
|
14
|
-
private globalMiddleware: Middleware[] = [];
|
|
15
|
-
private compressionEnabled = true;
|
|
16
|
-
private compressionThreshold = 1024;
|
|
17
|
-
private logger = createFrameworkLogger('HttpServer');
|
|
18
|
-
private hookManager: any;
|
|
19
|
-
private requestCounter = 0;
|
|
20
|
-
|
|
21
|
-
// Efficient object pooling with minimal overhead
|
|
22
|
-
private paramObjectPool: Record<string, string>[] = [];
|
|
23
|
-
private bufferPool: Buffer[] = [];
|
|
24
|
-
private readonly maxPoolSize = 50;
|
|
25
|
-
|
|
26
|
-
// Request handler pooling to avoid function creation overhead
|
|
27
|
-
private middlewareExecutionCache = new Map<string, Function>();
|
|
28
|
-
|
|
29
|
-
// String interning for common values (massive memory savings)
|
|
30
|
-
private static readonly INTERNED_METHODS = new Map([
|
|
31
|
-
['GET', 'GET'],
|
|
32
|
-
['POST', 'POST'],
|
|
33
|
-
['PUT', 'PUT'],
|
|
34
|
-
['DELETE', 'DELETE'],
|
|
35
|
-
['PATCH', 'PATCH'],
|
|
36
|
-
['HEAD', 'HEAD'],
|
|
37
|
-
['OPTIONS', 'OPTIONS'],
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
private static readonly INTERNED_HEADERS = new Map([
|
|
41
|
-
['content-type', 'content-type'],
|
|
42
|
-
['content-length', 'content-length'],
|
|
43
|
-
['authorization', 'authorization'],
|
|
44
|
-
['accept', 'accept'],
|
|
45
|
-
['user-agent', 'user-agent'],
|
|
46
|
-
['host', 'host'],
|
|
47
|
-
['connection', 'connection'],
|
|
48
|
-
['cache-control', 'cache-control'],
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
// Pre-compiled response templates for ultra-common responses
|
|
52
|
-
private static readonly RESPONSE_TEMPLATES = {
|
|
53
|
-
notFound: Buffer.from('{"success":false,"error":"Not found"}'),
|
|
54
|
-
unauthorized: Buffer.from('{"success":false,"error":"Unauthorized"}'),
|
|
55
|
-
forbidden: Buffer.from('{"success":false,"error":"Forbidden"}'),
|
|
56
|
-
internalError: Buffer.from('{"success":false,"error":"Internal server error"}'),
|
|
57
|
-
methodNotAllowed: Buffer.from('{"success":false,"error":"Method not allowed"}'),
|
|
58
|
-
rateLimited: Buffer.from('{"success":false,"error":"Rate limit exceeded"}'),
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Ultra-fast buffer pool for zero-copy operations (Rust-level performance)
|
|
62
|
-
private static readonly BUFFER_SIZES = [64, 256, 1024, 4096, 16384];
|
|
63
|
-
private static readonly BUFFER_POOLS = new Map<number, Buffer[]>();
|
|
64
|
-
|
|
65
|
-
static {
|
|
66
|
-
// Pre-allocate buffer pools for zero-allocation responses
|
|
67
|
-
for (const size of MoroHttpServer.BUFFER_SIZES) {
|
|
68
|
-
MoroHttpServer.BUFFER_POOLS.set(size, []);
|
|
69
|
-
for (let i = 0; i < 50; i++) {
|
|
70
|
-
// 50 buffers per size
|
|
71
|
-
MoroHttpServer.BUFFER_POOLS.get(size)!.push(Buffer.allocUnsafe(size));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private static getOptimalBuffer(size: number): Buffer {
|
|
77
|
-
// Find the smallest buffer that fits
|
|
78
|
-
for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
|
|
79
|
-
if (size <= poolSize) {
|
|
80
|
-
const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize)!;
|
|
81
|
-
return pool.length > 0 ? pool.pop()! : Buffer.allocUnsafe(poolSize);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return Buffer.allocUnsafe(size);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private static returnBuffer(buffer: Buffer): void {
|
|
88
|
-
// Return buffer to appropriate pool
|
|
89
|
-
const size = buffer.length;
|
|
90
|
-
if (MoroHttpServer.BUFFER_POOLS.has(size)) {
|
|
91
|
-
const pool = MoroHttpServer.BUFFER_POOLS.get(size)!;
|
|
92
|
-
if (pool.length < 50) {
|
|
93
|
-
// Don't let pools grow too large
|
|
94
|
-
pool.push(buffer);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
constructor() {
|
|
100
|
-
this.server = createServer(this.handleRequest.bind(this));
|
|
101
|
-
|
|
102
|
-
// Optimize server for high performance (conservative settings for compatibility)
|
|
103
|
-
this.server.keepAliveTimeout = 5000; // 5 seconds
|
|
104
|
-
this.server.headersTimeout = 6000; // 6 seconds
|
|
105
|
-
this.server.timeout = 30000; // 30 seconds request timeout
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Configure server for maximum performance (can disable all overhead)
|
|
109
|
-
configurePerformance(
|
|
110
|
-
config: {
|
|
111
|
-
compression?: { enabled: boolean; threshold?: number };
|
|
112
|
-
minimal?: boolean;
|
|
113
|
-
} = {}
|
|
114
|
-
) {
|
|
115
|
-
if (config.compression !== undefined) {
|
|
116
|
-
this.compressionEnabled = config.compression.enabled;
|
|
117
|
-
if (config.compression.threshold !== undefined) {
|
|
118
|
-
this.compressionThreshold = config.compression.threshold;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Minimal mode - disable ALL overhead for pure speed
|
|
123
|
-
if (config.minimal) {
|
|
124
|
-
this.compressionEnabled = false;
|
|
125
|
-
this.compressionThreshold = Infinity; // Never compress
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Middleware management
|
|
130
|
-
use(middleware: Middleware): void {
|
|
131
|
-
this.globalMiddleware.push(middleware);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Set hooks manager for request processing
|
|
135
|
-
setHookManager(hookManager: any): void {
|
|
136
|
-
this.hookManager = hookManager;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Routing methods
|
|
140
|
-
get(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
141
|
-
this.addRoute('GET', path, handlers);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
post(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
145
|
-
this.addRoute('POST', path, handlers);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
put(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
149
|
-
this.addRoute('PUT', path, handlers);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
delete(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
153
|
-
this.addRoute('DELETE', path, handlers);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
patch(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
157
|
-
this.addRoute('PATCH', path, handlers);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private addRoute(method: string, path: string, handlers: (Middleware | HttpHandler)[]): void {
|
|
161
|
-
const { pattern, paramNames } = this.pathToRegex(path);
|
|
162
|
-
const handler = handlers.pop() as HttpHandler;
|
|
163
|
-
const middleware = handlers as Middleware[];
|
|
164
|
-
|
|
165
|
-
const route = {
|
|
166
|
-
method,
|
|
167
|
-
path,
|
|
168
|
-
pattern,
|
|
169
|
-
paramNames,
|
|
170
|
-
handler,
|
|
171
|
-
middleware,
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
this.routes.push(route);
|
|
175
|
-
|
|
176
|
-
// Organize routes for optimal lookup
|
|
177
|
-
if (paramNames.length === 0) {
|
|
178
|
-
// Static route - O(1) lookup
|
|
179
|
-
const staticKey = `${method}:${path}`;
|
|
180
|
-
this.staticRoutes.set(staticKey, route);
|
|
181
|
-
} else {
|
|
182
|
-
// Dynamic route - organize by segment count for faster matching
|
|
183
|
-
this.dynamicRoutes.push(route);
|
|
184
|
-
|
|
185
|
-
const segments = path.split('/').filter(s => s.length > 0);
|
|
186
|
-
const segmentCount = segments.length;
|
|
187
|
-
|
|
188
|
-
if (!this.routesBySegmentCount.has(segmentCount)) {
|
|
189
|
-
this.routesBySegmentCount.set(segmentCount, []);
|
|
190
|
-
}
|
|
191
|
-
this.routesBySegmentCount.get(segmentCount)!.push(route);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
196
|
-
const paramNames: string[] = [];
|
|
197
|
-
|
|
198
|
-
// Convert parameterized routes to regex
|
|
199
|
-
const regexPattern = path
|
|
200
|
-
.replace(/\/:([^/]+)/g, (match, paramName) => {
|
|
201
|
-
paramNames.push(paramName);
|
|
202
|
-
return '/([^/]+)';
|
|
203
|
-
})
|
|
204
|
-
.replace(/\//g, '\\/');
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
pattern: new RegExp(`^${regexPattern}$`),
|
|
208
|
-
paramNames,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
213
|
-
const httpReq = this.enhanceRequest(req);
|
|
214
|
-
const httpRes = this.enhanceResponse(res);
|
|
215
|
-
|
|
216
|
-
// Store original params for efficient cleanup
|
|
217
|
-
const originalParams = httpReq.params;
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
// Optimized URL and query parsing
|
|
221
|
-
const urlString = req.url!;
|
|
222
|
-
const queryIndex = urlString.indexOf('?');
|
|
223
|
-
|
|
224
|
-
if (queryIndex === -1) {
|
|
225
|
-
// No query string - fast path
|
|
226
|
-
httpReq.path = urlString;
|
|
227
|
-
httpReq.query = {};
|
|
228
|
-
} else {
|
|
229
|
-
// Has query string - parse efficiently
|
|
230
|
-
httpReq.path = urlString.substring(0, queryIndex);
|
|
231
|
-
httpReq.query = this.parseQueryString(urlString.substring(queryIndex + 1));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Ultra-fast method checking - avoid array includes
|
|
235
|
-
const method = req.method!;
|
|
236
|
-
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
|
237
|
-
httpReq.body = await this.parseBody(req);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Execute hooks before request processing
|
|
241
|
-
if (this.hookManager) {
|
|
242
|
-
await this.hookManager.execute('request', {
|
|
243
|
-
request: httpReq,
|
|
244
|
-
response: httpRes,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Execute global middleware first
|
|
249
|
-
await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
|
|
250
|
-
|
|
251
|
-
// If middleware handled the request, don't continue
|
|
252
|
-
if (httpRes.headersSent) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Find matching route
|
|
257
|
-
const route = this.findRoute(req.method!, httpReq.path);
|
|
258
|
-
if (!route) {
|
|
259
|
-
// Ultra-fast 404 response with pre-compiled buffer
|
|
260
|
-
httpRes.statusCode = 404;
|
|
261
|
-
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
262
|
-
httpRes.setHeader('Content-Length', MoroHttpServer.RESPONSE_TEMPLATES.notFound.length);
|
|
263
|
-
httpRes.end(MoroHttpServer.RESPONSE_TEMPLATES.notFound);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Extract path parameters - optimized with object pooling
|
|
268
|
-
const matches = httpReq.path.match(route.pattern);
|
|
269
|
-
if (matches) {
|
|
270
|
-
// Use pooled object for parameters
|
|
271
|
-
httpReq.params = this.acquireParamObject();
|
|
272
|
-
route.paramNames.forEach((name, index) => {
|
|
273
|
-
httpReq.params[name] = matches[index + 1];
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Execute middleware chain
|
|
278
|
-
await this.executeMiddleware(route.middleware, httpReq, httpRes);
|
|
279
|
-
|
|
280
|
-
// Execute handler
|
|
281
|
-
await route.handler(httpReq, httpRes);
|
|
282
|
-
} catch (error) {
|
|
283
|
-
// Debug: Log the actual error and where it came from
|
|
284
|
-
this.logger.debug('🚨 MoroJS Request Error Details:', 'RequestHandler');
|
|
285
|
-
this.logger.debug(`📍 Error type: ${typeof error}`, 'RequestHandler');
|
|
286
|
-
this.logger.debug(
|
|
287
|
-
`📍 Error message: ${error instanceof Error ? error.message : String(error)}`,
|
|
288
|
-
'RequestHandler'
|
|
289
|
-
);
|
|
290
|
-
this.logger.debug(
|
|
291
|
-
`📍 Error stack: ${error instanceof Error ? error.stack : 'No stack trace'}`,
|
|
292
|
-
'RequestHandler'
|
|
293
|
-
);
|
|
294
|
-
this.logger.debug(`📍 Request path: ${req.url}`, 'RequestHandler');
|
|
295
|
-
this.logger.debug(`📍 Request method: ${req.method}`, 'RequestHandler');
|
|
296
|
-
|
|
297
|
-
this.logger.error('Request error', 'RequestHandler', {
|
|
298
|
-
error: error instanceof Error ? error.message : String(error),
|
|
299
|
-
requestId: httpReq.requestId,
|
|
300
|
-
method: req.method,
|
|
301
|
-
path: req.url,
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
if (!httpRes.headersSent) {
|
|
305
|
-
// Ensure response is properly enhanced before using custom methods
|
|
306
|
-
if (typeof httpRes.status === 'function' && typeof httpRes.json === 'function') {
|
|
307
|
-
httpRes.status(500).json({
|
|
308
|
-
success: false,
|
|
309
|
-
error: 'Internal server error',
|
|
310
|
-
requestId: httpReq.requestId,
|
|
311
|
-
});
|
|
312
|
-
} else {
|
|
313
|
-
// Ultra-defensive fallback - check each method individually
|
|
314
|
-
if (typeof httpRes.setHeader === 'function') {
|
|
315
|
-
httpRes.statusCode = 500;
|
|
316
|
-
httpRes.setHeader('Content-Type', 'application/json');
|
|
317
|
-
} else {
|
|
318
|
-
// Even setHeader doesn't exist - object is completely wrong
|
|
319
|
-
this.logger.error(
|
|
320
|
-
'❌ Response object is not a proper ServerResponse:',
|
|
321
|
-
'RequestHandler',
|
|
322
|
-
{
|
|
323
|
-
responseType: typeof httpRes,
|
|
324
|
-
responseKeys: Object.keys(httpRes),
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (typeof httpRes.end === 'function') {
|
|
330
|
-
httpRes.end(
|
|
331
|
-
JSON.stringify({
|
|
332
|
-
success: false,
|
|
333
|
-
error: 'Internal server error',
|
|
334
|
-
requestId: httpReq.requestId,
|
|
335
|
-
})
|
|
336
|
-
);
|
|
337
|
-
} else {
|
|
338
|
-
this.logger.error(
|
|
339
|
-
'❌ Cannot send error response - end() method missing',
|
|
340
|
-
'RequestHandler'
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
} finally {
|
|
346
|
-
// CRITICAL: Always release pooled objects back to the pool
|
|
347
|
-
// This prevents memory leaks and ensures consistent performance
|
|
348
|
-
if (originalParams && Object.keys(originalParams).length === 0) {
|
|
349
|
-
this.releaseParamObject(originalParams);
|
|
350
|
-
}
|
|
351
|
-
if (
|
|
352
|
-
httpReq.params &&
|
|
353
|
-
httpReq.params !== originalParams &&
|
|
354
|
-
Object.keys(httpReq.params).length === 0
|
|
355
|
-
) {
|
|
356
|
-
this.releaseParamObject(httpReq.params);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Additional cleanup on response completion to ensure objects are returned to pool
|
|
361
|
-
res.once('finish', () => {
|
|
362
|
-
if (originalParams && Object.keys(originalParams).length === 0) {
|
|
363
|
-
this.releaseParamObject(originalParams);
|
|
364
|
-
}
|
|
365
|
-
if (
|
|
366
|
-
httpReq.params &&
|
|
367
|
-
httpReq.params !== originalParams &&
|
|
368
|
-
Object.keys(httpReq.params).length === 0
|
|
369
|
-
) {
|
|
370
|
-
this.releaseParamObject(httpReq.params);
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Efficient object pooling for parameter objects with ES2022 optimizations
|
|
376
|
-
private acquireParamObject(): Record<string, string> {
|
|
377
|
-
const obj = this.paramObjectPool.pop();
|
|
378
|
-
if (obj) {
|
|
379
|
-
// ES2022: Use Object.hasOwn for safer property checks and faster clearing
|
|
380
|
-
// Clear existing properties more efficiently
|
|
381
|
-
for (const key in obj) {
|
|
382
|
-
if (Object.hasOwn(obj, key)) {
|
|
383
|
-
delete obj[key];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return obj;
|
|
387
|
-
}
|
|
388
|
-
return {};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
private releaseParamObject(params: Record<string, string>): void {
|
|
392
|
-
if (this.paramObjectPool.length < this.maxPoolSize) {
|
|
393
|
-
this.paramObjectPool.push(params);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Force cleanup of all pooled objects
|
|
398
|
-
private forceCleanupPools(): void {
|
|
399
|
-
// ES2022: More efficient array clearing
|
|
400
|
-
this.paramObjectPool.splice(0);
|
|
401
|
-
this.bufferPool.splice(0);
|
|
402
|
-
|
|
403
|
-
// Force garbage collection if available
|
|
404
|
-
// Use modern globalThis check with optional chaining
|
|
405
|
-
if (globalThis?.gc) {
|
|
406
|
-
globalThis.gc();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private acquireBuffer(size: number): Buffer {
|
|
411
|
-
// ES2022: Use findIndex for better performance than find + indexOf
|
|
412
|
-
const index = this.bufferPool.findIndex(b => b.length >= size);
|
|
413
|
-
if (index !== -1) {
|
|
414
|
-
const buffer = this.bufferPool.splice(index, 1)[0];
|
|
415
|
-
return buffer.subarray(0, size);
|
|
416
|
-
}
|
|
417
|
-
return Buffer.allocUnsafe(size);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private releaseBuffer(buffer: Buffer): void {
|
|
421
|
-
if (this.bufferPool.length < this.maxPoolSize && buffer.length <= 8192) {
|
|
422
|
-
this.bufferPool.push(buffer);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private streamLargeResponse(res: any, data: any): void {
|
|
427
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
428
|
-
res.setHeader('Transfer-Encoding', 'chunked');
|
|
429
|
-
|
|
430
|
-
// Stream the response in chunks
|
|
431
|
-
const jsonString = JSON.stringify(data);
|
|
432
|
-
const chunkSize = 8192; // 8KB chunks
|
|
433
|
-
|
|
434
|
-
for (let i = 0; i < jsonString.length; i += chunkSize) {
|
|
435
|
-
const chunk = jsonString.substring(i, i + chunkSize);
|
|
436
|
-
res.write(chunk);
|
|
437
|
-
}
|
|
438
|
-
res.end();
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
private normalizePath(path: string): string {
|
|
442
|
-
// Check cache first
|
|
443
|
-
if (this.pathNormalizationCache.has(path)) {
|
|
444
|
-
return this.pathNormalizationCache.get(path)!;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Fast normalization: remove trailing slash (except root), decode once
|
|
448
|
-
let normalized = path;
|
|
449
|
-
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
450
|
-
normalized = normalized.slice(0, -1);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Cache result (limit cache size)
|
|
454
|
-
if (this.pathNormalizationCache.size < 200) {
|
|
455
|
-
this.pathNormalizationCache.set(path, normalized);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return normalized;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private enhanceRequest(req: IncomingMessage): HttpRequest {
|
|
462
|
-
const httpReq = req as HttpRequest;
|
|
463
|
-
httpReq.params = this.acquireParamObject();
|
|
464
|
-
httpReq.query = {};
|
|
465
|
-
httpReq.body = null;
|
|
466
|
-
httpReq.path = '';
|
|
467
|
-
httpReq.ip = req.socket.remoteAddress || '';
|
|
468
|
-
// Faster request ID generation
|
|
469
|
-
httpReq.requestId = Date.now().toString(36) + (++this.requestCounter).toString(36);
|
|
470
|
-
httpReq.headers = req.headers as Record<string, string>;
|
|
471
|
-
|
|
472
|
-
// Parse cookies
|
|
473
|
-
httpReq.cookies = this.parseCookies(req.headers.cookie || '');
|
|
474
|
-
|
|
475
|
-
return httpReq;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private parseCookies(cookieHeader: string): Record<string, string> {
|
|
479
|
-
const cookies: Record<string, string> = {};
|
|
480
|
-
if (!cookieHeader) return cookies;
|
|
481
|
-
|
|
482
|
-
cookieHeader.split(';').forEach(cookie => {
|
|
483
|
-
const [name, value] = cookie.trim().split('=');
|
|
484
|
-
if (name && value) {
|
|
485
|
-
cookies[name] = decodeURIComponent(value);
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
return cookies;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
private enhanceResponse(res: ServerResponse): HttpResponse {
|
|
493
|
-
const httpRes = res as HttpResponse;
|
|
494
|
-
|
|
495
|
-
// BULLETPROOF status method - always works
|
|
496
|
-
httpRes.status = (code: number) => {
|
|
497
|
-
httpRes.statusCode = code;
|
|
498
|
-
return httpRes;
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
httpRes.json = async (data: any) => {
|
|
502
|
-
if (httpRes.headersSent) return;
|
|
503
|
-
|
|
504
|
-
// Ultra-fast JSON serialization with zero-copy buffers
|
|
505
|
-
let jsonString: string;
|
|
506
|
-
|
|
507
|
-
// Enhanced JSON optimization for common API patterns
|
|
508
|
-
if (data && typeof data === 'object' && 'success' in data) {
|
|
509
|
-
if ('data' in data && 'error' in data && !('total' in data)) {
|
|
510
|
-
// {success, data, error} pattern
|
|
511
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
|
|
512
|
-
} else if ('data' in data && 'total' in data && !('error' in data)) {
|
|
513
|
-
// {success, data, total} pattern
|
|
514
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
|
|
515
|
-
} else if ('data' in data && !('error' in data) && !('total' in data)) {
|
|
516
|
-
// {success, data} pattern
|
|
517
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
|
|
518
|
-
} else if ('error' in data && !('data' in data) && !('total' in data)) {
|
|
519
|
-
// {success, error} pattern
|
|
520
|
-
jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
|
|
521
|
-
} else {
|
|
522
|
-
// Complex object - use standard JSON.stringify
|
|
523
|
-
jsonString = JSON.stringify(data);
|
|
524
|
-
}
|
|
525
|
-
} else {
|
|
526
|
-
jsonString = JSON.stringify(data);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Use buffer pool for zero-allocation responses
|
|
530
|
-
const estimatedSize = jsonString.length;
|
|
531
|
-
if (estimatedSize > 32768) {
|
|
532
|
-
// Large response - stream it
|
|
533
|
-
return this.streamLargeResponse(httpRes, data);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const buffer = MoroHttpServer.getOptimalBuffer(estimatedSize);
|
|
537
|
-
const actualLength = buffer.write(jsonString, 0, 'utf8');
|
|
538
|
-
|
|
539
|
-
// Slice to actual size to avoid sending extra bytes
|
|
540
|
-
const finalBuffer =
|
|
541
|
-
actualLength === buffer.length ? buffer : buffer.subarray(0, actualLength);
|
|
542
|
-
|
|
543
|
-
// Optimized header setting - set multiple headers at once when possible
|
|
544
|
-
const headers: Record<string, string | number> = {
|
|
545
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
// Compression with buffer pool
|
|
549
|
-
if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
|
|
550
|
-
const acceptEncoding = httpRes.req.headers['accept-encoding'] || '';
|
|
551
|
-
|
|
552
|
-
if (acceptEncoding.includes('gzip')) {
|
|
553
|
-
const compressed = await gzip(finalBuffer);
|
|
554
|
-
headers['Content-Encoding'] = 'gzip';
|
|
555
|
-
headers['Content-Length'] = compressed.length;
|
|
556
|
-
|
|
557
|
-
// Set all headers at once
|
|
558
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
559
|
-
httpRes.setHeader(key, value);
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
httpRes.end(compressed);
|
|
563
|
-
// Return buffer to pool after response
|
|
564
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
565
|
-
return;
|
|
566
|
-
} else if (acceptEncoding.includes('deflate')) {
|
|
567
|
-
const compressed = await deflate(finalBuffer);
|
|
568
|
-
headers['Content-Encoding'] = 'deflate';
|
|
569
|
-
headers['Content-Length'] = compressed.length;
|
|
570
|
-
|
|
571
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
572
|
-
httpRes.setHeader(key, value);
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
httpRes.end(compressed);
|
|
576
|
-
// Return buffer to pool after response
|
|
577
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
headers['Content-Length'] = finalBuffer.length;
|
|
583
|
-
|
|
584
|
-
// Set all headers at once for better performance
|
|
585
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
586
|
-
httpRes.setHeader(key, value);
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
httpRes.end(finalBuffer);
|
|
590
|
-
// Return buffer to pool after response (zero-copy achievement!)
|
|
591
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
httpRes.send = (data: string | Buffer) => {
|
|
595
|
-
if (httpRes.headersSent) return;
|
|
596
|
-
|
|
597
|
-
// Auto-detect content type if not already set
|
|
598
|
-
if (!httpRes.getHeader('Content-Type')) {
|
|
599
|
-
if (typeof data === 'string') {
|
|
600
|
-
// Check if it's JSON
|
|
601
|
-
try {
|
|
602
|
-
JSON.parse(data);
|
|
603
|
-
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
604
|
-
} catch {
|
|
605
|
-
// Default to plain text
|
|
606
|
-
httpRes.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
607
|
-
}
|
|
608
|
-
} else {
|
|
609
|
-
// Buffer data - default to octet-stream
|
|
610
|
-
httpRes.setHeader('Content-Type', 'application/octet-stream');
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
httpRes.end(data);
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
httpRes.cookie = (name: string, value: string, options: any = {}) => {
|
|
618
|
-
const cookieValue = encodeURIComponent(value);
|
|
619
|
-
let cookieString = `${name}=${cookieValue}`;
|
|
620
|
-
|
|
621
|
-
if (options.maxAge) cookieString += `; Max-Age=${options.maxAge}`;
|
|
622
|
-
if (options.expires) cookieString += `; Expires=${options.expires.toUTCString()}`;
|
|
623
|
-
if (options.httpOnly) cookieString += '; HttpOnly';
|
|
624
|
-
if (options.secure) cookieString += '; Secure';
|
|
625
|
-
if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;
|
|
626
|
-
if (options.domain) cookieString += `; Domain=${options.domain}`;
|
|
627
|
-
if (options.path) cookieString += `; Path=${options.path}`;
|
|
628
|
-
|
|
629
|
-
const existingCookies = httpRes.getHeader('Set-Cookie') || [];
|
|
630
|
-
const cookies = Array.isArray(existingCookies)
|
|
631
|
-
? [...existingCookies]
|
|
632
|
-
: [existingCookies as string];
|
|
633
|
-
cookies.push(cookieString);
|
|
634
|
-
httpRes.setHeader('Set-Cookie', cookies);
|
|
635
|
-
|
|
636
|
-
return httpRes;
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
httpRes.clearCookie = (name: string, options: any = {}) => {
|
|
640
|
-
const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
|
|
641
|
-
return httpRes.cookie(name, '', clearOptions);
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
httpRes.redirect = (url: string, status: number = 302) => {
|
|
645
|
-
if (httpRes.headersSent) return;
|
|
646
|
-
httpRes.statusCode = status;
|
|
647
|
-
httpRes.setHeader('Location', url);
|
|
648
|
-
httpRes.end();
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
httpRes.sendFile = async (filePath: string) => {
|
|
652
|
-
if (httpRes.headersSent) return;
|
|
653
|
-
|
|
654
|
-
try {
|
|
655
|
-
const fs = await import('fs/promises');
|
|
656
|
-
const path = await import('path');
|
|
657
|
-
const extension = path.extname(filePath);
|
|
658
|
-
const mime = await this.getMimeType(extension);
|
|
659
|
-
|
|
660
|
-
const stats = await fs.stat(filePath);
|
|
661
|
-
const data = await fs.readFile(filePath);
|
|
662
|
-
|
|
663
|
-
// Add charset for text-based files
|
|
664
|
-
const contentType = this.addCharsetIfNeeded(mime);
|
|
665
|
-
httpRes.setHeader('Content-Type', contentType);
|
|
666
|
-
httpRes.setHeader('Content-Length', stats.size);
|
|
667
|
-
|
|
668
|
-
// Add security headers for file downloads
|
|
669
|
-
httpRes.setHeader('X-Content-Type-Options', 'nosniff');
|
|
670
|
-
|
|
671
|
-
// Add caching headers
|
|
672
|
-
httpRes.setHeader('Last-Modified', stats.mtime.toUTCString());
|
|
673
|
-
httpRes.setHeader('Cache-Control', 'public, max-age=31536000'); // 1 year for static files
|
|
674
|
-
|
|
675
|
-
httpRes.end(data);
|
|
676
|
-
} catch (error) {
|
|
677
|
-
httpRes.status(404).json({ success: false, error: 'File not found' });
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
return httpRes;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
private async getMimeType(ext: string): Promise<string> {
|
|
685
|
-
const mimeTypes: Record<string, string> = {
|
|
686
|
-
'.html': 'text/html',
|
|
687
|
-
'.css': 'text/css',
|
|
688
|
-
'.js': 'application/javascript',
|
|
689
|
-
'.json': 'application/json',
|
|
690
|
-
'.png': 'image/png',
|
|
691
|
-
'.jpg': 'image/jpeg',
|
|
692
|
-
'.jpeg': 'image/jpeg',
|
|
693
|
-
'.gif': 'image/gif',
|
|
694
|
-
'.svg': 'image/svg+xml',
|
|
695
|
-
'.ico': 'image/x-icon',
|
|
696
|
-
'.pdf': 'application/pdf',
|
|
697
|
-
'.txt': 'text/plain',
|
|
698
|
-
'.xml': 'application/xml',
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
private addCharsetIfNeeded(mimeType: string): string {
|
|
705
|
-
// Add charset for text-based content types
|
|
706
|
-
const textTypes = [
|
|
707
|
-
'text/',
|
|
708
|
-
'application/json',
|
|
709
|
-
'application/javascript',
|
|
710
|
-
'application/xml',
|
|
711
|
-
'image/svg+xml',
|
|
712
|
-
];
|
|
713
|
-
|
|
714
|
-
const needsCharset = textTypes.some(type => mimeType.startsWith(type));
|
|
715
|
-
|
|
716
|
-
if (needsCharset && !mimeType.includes('charset')) {
|
|
717
|
-
return `${mimeType}; charset=utf-8`;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return mimeType;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
private async parseBody(req: IncomingMessage): Promise<any> {
|
|
724
|
-
return new Promise((resolve, reject) => {
|
|
725
|
-
const chunks: Buffer[] = [];
|
|
726
|
-
let totalLength = 0;
|
|
727
|
-
const maxSize = 10 * 1024 * 1024; // 10MB limit
|
|
728
|
-
|
|
729
|
-
req.on('data', (chunk: Buffer) => {
|
|
730
|
-
totalLength += chunk.length;
|
|
731
|
-
if (totalLength > maxSize) {
|
|
732
|
-
reject(new Error('Request body too large'));
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
chunks.push(chunk);
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
req.on('end', () => {
|
|
739
|
-
try {
|
|
740
|
-
const body = Buffer.concat(chunks);
|
|
741
|
-
const contentType = req.headers['content-type'] || '';
|
|
742
|
-
|
|
743
|
-
if (contentType.includes('application/json')) {
|
|
744
|
-
resolve(JSON.parse(body.toString()));
|
|
745
|
-
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
746
|
-
resolve(this.parseUrlEncoded(body.toString()));
|
|
747
|
-
} else if (contentType.includes('multipart/form-data')) {
|
|
748
|
-
resolve(this.parseMultipart(body, contentType));
|
|
749
|
-
} else {
|
|
750
|
-
resolve(body.toString());
|
|
751
|
-
}
|
|
752
|
-
} catch (error) {
|
|
753
|
-
reject(error);
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
req.on('error', reject);
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private parseMultipart(
|
|
762
|
-
buffer: Buffer,
|
|
763
|
-
contentType: string
|
|
764
|
-
): { fields: Record<string, string>; files: Record<string, any> } {
|
|
765
|
-
const boundary = contentType.split('boundary=')[1];
|
|
766
|
-
if (!boundary) {
|
|
767
|
-
throw new Error('Invalid multipart boundary');
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const parts = buffer.toString('binary').split('--' + boundary);
|
|
771
|
-
const fields: Record<string, string> = {};
|
|
772
|
-
const files: Record<string, any> = {};
|
|
773
|
-
|
|
774
|
-
for (let i = 1; i < parts.length - 1; i++) {
|
|
775
|
-
const part = parts[i];
|
|
776
|
-
const [headers, content] = part.split('\r\n\r\n');
|
|
777
|
-
|
|
778
|
-
if (!headers || content === undefined) continue;
|
|
779
|
-
|
|
780
|
-
const nameMatch = headers.match(/name="([^"]+)"/);
|
|
781
|
-
const filenameMatch = headers.match(/filename="([^"]+)"/);
|
|
782
|
-
const contentTypeMatch = headers.match(/Content-Type: ([^\r\n]+)/);
|
|
783
|
-
|
|
784
|
-
if (nameMatch) {
|
|
785
|
-
const name = nameMatch[1];
|
|
786
|
-
|
|
787
|
-
if (filenameMatch) {
|
|
788
|
-
// This is a file
|
|
789
|
-
const filename = filenameMatch[1];
|
|
790
|
-
const mimeType = contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream';
|
|
791
|
-
const fileContent = content.substring(0, content.length - 2); // Remove trailing \r\n
|
|
792
|
-
|
|
793
|
-
files[name] = {
|
|
794
|
-
filename,
|
|
795
|
-
mimetype: mimeType,
|
|
796
|
-
data: Buffer.from(fileContent, 'binary'),
|
|
797
|
-
size: Buffer.byteLength(fileContent, 'binary'),
|
|
798
|
-
};
|
|
799
|
-
} else {
|
|
800
|
-
// This is a regular field
|
|
801
|
-
fields[name] = content.substring(0, content.length - 2); // Remove trailing \r\n
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
return { fields, files };
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
private parseUrlEncoded(body: string): Record<string, string> {
|
|
810
|
-
const params = new URLSearchParams(body);
|
|
811
|
-
const result: Record<string, string> = {};
|
|
812
|
-
for (const [key, value] of params) {
|
|
813
|
-
result[key] = value;
|
|
814
|
-
}
|
|
815
|
-
return result;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
private parseQueryString(queryString: string): Record<string, string> {
|
|
819
|
-
const result: Record<string, string> = {};
|
|
820
|
-
if (!queryString) return result;
|
|
821
|
-
|
|
822
|
-
const pairs = queryString.split('&');
|
|
823
|
-
for (const pair of pairs) {
|
|
824
|
-
const equalIndex = pair.indexOf('=');
|
|
825
|
-
if (equalIndex === -1) {
|
|
826
|
-
result[decodeURIComponent(pair)] = '';
|
|
827
|
-
} else {
|
|
828
|
-
const key = decodeURIComponent(pair.substring(0, equalIndex));
|
|
829
|
-
const value = decodeURIComponent(pair.substring(equalIndex + 1));
|
|
830
|
-
result[key] = value;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
return result;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Advanced route optimization: cache + static routes + segment grouping
|
|
837
|
-
private routeCache = new Map<string, RouteEntry | null>();
|
|
838
|
-
private staticRoutes = new Map<string, RouteEntry>();
|
|
839
|
-
private dynamicRoutes: RouteEntry[] = [];
|
|
840
|
-
private routesBySegmentCount = new Map<number, RouteEntry[]>();
|
|
841
|
-
private pathNormalizationCache = new Map<string, string>();
|
|
842
|
-
|
|
843
|
-
// Ultra-fast CPU cache-friendly optimizations (Rust-level performance)
|
|
844
|
-
private routeHitCount = new Map<string, number>(); // Track route popularity for cache optimization
|
|
845
|
-
private static readonly HOT_ROUTE_THRESHOLD = 100; // Routes accessed 100+ times get hot path treatment
|
|
846
|
-
|
|
847
|
-
private findRoute(method: string, path: string): RouteEntry | null {
|
|
848
|
-
// Normalize path for consistent matching
|
|
849
|
-
const normalizedPath = this.normalizePath(path);
|
|
850
|
-
const cacheKey = `${method}:${normalizedPath}`;
|
|
851
|
-
|
|
852
|
-
// Track route popularity for hot path optimization
|
|
853
|
-
const hitCount = (this.routeHitCount.get(cacheKey) || 0) + 1;
|
|
854
|
-
this.routeHitCount.set(cacheKey, hitCount);
|
|
855
|
-
|
|
856
|
-
// Check cache first (hot path optimization)
|
|
857
|
-
if (this.routeCache.has(cacheKey)) {
|
|
858
|
-
const cachedRoute = this.routeCache.get(cacheKey)!;
|
|
859
|
-
|
|
860
|
-
// Promote frequently accessed routes to front of cache (LRU-like)
|
|
861
|
-
if (hitCount > MoroHttpServer.HOT_ROUTE_THRESHOLD && this.routeCache.size > 100) {
|
|
862
|
-
this.routeCache.delete(cacheKey);
|
|
863
|
-
this.routeCache.set(cacheKey, cachedRoute); // Move to end (most recent)
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
return cachedRoute;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Phase 1: O(1) static route lookup
|
|
870
|
-
const staticRoute = this.staticRoutes.get(cacheKey);
|
|
871
|
-
if (staticRoute) {
|
|
872
|
-
this.routeCache.set(cacheKey, staticRoute);
|
|
873
|
-
return staticRoute;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// Phase 2: Optimized dynamic route matching by segment count
|
|
877
|
-
let route: RouteEntry | null = null;
|
|
878
|
-
if (this.dynamicRoutes.length > 0) {
|
|
879
|
-
const segments = normalizedPath.split('/').filter(s => s.length > 0);
|
|
880
|
-
const candidateRoutes = this.routesBySegmentCount.get(segments.length) || this.dynamicRoutes;
|
|
881
|
-
|
|
882
|
-
// Only test routes with matching method and segment count
|
|
883
|
-
for (const candidateRoute of candidateRoutes) {
|
|
884
|
-
if (candidateRoute.method === method && candidateRoute.pattern.test(normalizedPath)) {
|
|
885
|
-
route = candidateRoute;
|
|
886
|
-
break;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// Cache result (limit cache size to prevent memory leaks)
|
|
892
|
-
if (this.routeCache.size < 500) {
|
|
893
|
-
this.routeCache.set(cacheKey, route);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return route;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
private async executeMiddleware(
|
|
900
|
-
middleware: Middleware[],
|
|
901
|
-
req: HttpRequest,
|
|
902
|
-
res: HttpResponse
|
|
903
|
-
): Promise<void> {
|
|
904
|
-
for (const mw of middleware) {
|
|
905
|
-
// Short-circuit if response already sent
|
|
906
|
-
if (res.headersSent) return;
|
|
907
|
-
|
|
908
|
-
await new Promise<void>((resolve, reject) => {
|
|
909
|
-
let nextCalled = false;
|
|
910
|
-
|
|
911
|
-
const next = () => {
|
|
912
|
-
if (nextCalled) return;
|
|
913
|
-
nextCalled = true;
|
|
914
|
-
resolve();
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
try {
|
|
918
|
-
const result = mw(req, res, next);
|
|
919
|
-
|
|
920
|
-
// Handle async middleware
|
|
921
|
-
if (result instanceof Promise) {
|
|
922
|
-
result
|
|
923
|
-
.then(() => {
|
|
924
|
-
if (!nextCalled) next();
|
|
925
|
-
})
|
|
926
|
-
.catch(reject);
|
|
927
|
-
}
|
|
928
|
-
} catch (error) {
|
|
929
|
-
reject(error);
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
listen(port: number, callback?: () => void): void;
|
|
936
|
-
listen(port: number, host: string, callback?: () => void): void;
|
|
937
|
-
listen(port: number, host?: string | (() => void), callback?: () => void): void {
|
|
938
|
-
// Handle overloaded parameters (port, callback) or (port, host, callback)
|
|
939
|
-
if (typeof host === 'function') {
|
|
940
|
-
callback = host;
|
|
941
|
-
host = undefined;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
if (host) {
|
|
945
|
-
this.server.listen(port, host, callback);
|
|
946
|
-
} else {
|
|
947
|
-
this.server.listen(port, callback);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
close(): Promise<void> {
|
|
952
|
-
return new Promise(resolve => {
|
|
953
|
-
this.server.close(() => resolve());
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// Public method to force cleanup
|
|
958
|
-
forceCleanup(): void {
|
|
959
|
-
this.forceCleanupPools();
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
getServer(): Server {
|
|
963
|
-
return this.server;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// Built-in middleware
|
|
968
|
-
export const middleware = {
|
|
969
|
-
cors: (options: { origin?: string; credentials?: boolean } = {}): Middleware => {
|
|
970
|
-
return (req, res, next) => {
|
|
971
|
-
res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
|
|
972
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
973
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
974
|
-
|
|
975
|
-
if (options.credentials) {
|
|
976
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (req.method === 'OPTIONS') {
|
|
980
|
-
res.status(200).send('');
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
next();
|
|
985
|
-
};
|
|
986
|
-
},
|
|
987
|
-
|
|
988
|
-
helmet: (): Middleware => {
|
|
989
|
-
return (req, res, next) => {
|
|
990
|
-
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
991
|
-
res.setHeader('X-Frame-Options', 'DENY');
|
|
992
|
-
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
993
|
-
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
994
|
-
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
995
|
-
res.setHeader('Content-Security-Policy', "default-src 'self'");
|
|
996
|
-
next();
|
|
997
|
-
};
|
|
998
|
-
},
|
|
999
|
-
|
|
1000
|
-
compression: (options: { threshold?: number; level?: number } = {}): Middleware => {
|
|
1001
|
-
const zlib = require('zlib');
|
|
1002
|
-
const threshold = options.threshold || 1024;
|
|
1003
|
-
const level = options.level || 6;
|
|
1004
|
-
|
|
1005
|
-
return (req, res, next) => {
|
|
1006
|
-
const acceptEncoding = req.headers['accept-encoding'] || '';
|
|
1007
|
-
|
|
1008
|
-
// Override res.json to compress responses
|
|
1009
|
-
const originalJson = res.json;
|
|
1010
|
-
const originalSend = res.send;
|
|
1011
|
-
|
|
1012
|
-
const compressResponse = (data: any, isJson = false) => {
|
|
1013
|
-
const content = isJson ? JSON.stringify(data) : data;
|
|
1014
|
-
const buffer = Buffer.from(content);
|
|
1015
|
-
|
|
1016
|
-
if (buffer.length < threshold) {
|
|
1017
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
if (acceptEncoding.includes('gzip')) {
|
|
1021
|
-
res.setHeader('Content-Encoding', 'gzip');
|
|
1022
|
-
zlib.gzip(buffer, { level }, (err: any, compressed: Buffer) => {
|
|
1023
|
-
if (err) {
|
|
1024
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1025
|
-
}
|
|
1026
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1027
|
-
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
1028
|
-
res.end(compressed);
|
|
1029
|
-
});
|
|
1030
|
-
} else if (acceptEncoding.includes('deflate')) {
|
|
1031
|
-
res.setHeader('Content-Encoding', 'deflate');
|
|
1032
|
-
zlib.deflate(buffer, { level }, (err: any, compressed: Buffer) => {
|
|
1033
|
-
if (err) {
|
|
1034
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1035
|
-
}
|
|
1036
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1037
|
-
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
1038
|
-
res.end(compressed);
|
|
1039
|
-
});
|
|
1040
|
-
} else {
|
|
1041
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
res.json = function (data: any) {
|
|
1046
|
-
// Ensure charset is set for Safari compatibility
|
|
1047
|
-
this.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1048
|
-
compressResponse(data, true);
|
|
1049
|
-
return this;
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
res.send = function (data: any) {
|
|
1053
|
-
compressResponse(data, false);
|
|
1054
|
-
return this;
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
next();
|
|
1058
|
-
};
|
|
1059
|
-
},
|
|
1060
|
-
|
|
1061
|
-
requestLogger: (): Middleware => {
|
|
1062
|
-
return (req, res, next) => {
|
|
1063
|
-
const start = Date.now();
|
|
1064
|
-
res.on('finish', () => {
|
|
1065
|
-
const duration = Date.now() - start;
|
|
1066
|
-
// Request completed - logged by framework
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
next();
|
|
1070
|
-
};
|
|
1071
|
-
},
|
|
1072
|
-
|
|
1073
|
-
bodySize: (options: { limit?: string } = {}): Middleware => {
|
|
1074
|
-
const limit = options.limit || '10mb';
|
|
1075
|
-
const limitBytes = parseSize(limit);
|
|
1076
|
-
|
|
1077
|
-
return (req, res, next) => {
|
|
1078
|
-
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
1079
|
-
|
|
1080
|
-
if (contentLength > limitBytes) {
|
|
1081
|
-
res.status(413).json({
|
|
1082
|
-
success: false,
|
|
1083
|
-
error: 'Request entity too large',
|
|
1084
|
-
limit: limit,
|
|
1085
|
-
});
|
|
1086
|
-
return;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
next();
|
|
1090
|
-
};
|
|
1091
|
-
},
|
|
1092
|
-
|
|
1093
|
-
static: (options: {
|
|
1094
|
-
root: string;
|
|
1095
|
-
maxAge?: number;
|
|
1096
|
-
index?: string[];
|
|
1097
|
-
dotfiles?: 'allow' | 'deny' | 'ignore';
|
|
1098
|
-
etag?: boolean;
|
|
1099
|
-
}): Middleware => {
|
|
1100
|
-
return async (req, res, next) => {
|
|
1101
|
-
// Only handle GET and HEAD requests
|
|
1102
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1103
|
-
next();
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
try {
|
|
1108
|
-
const fs = await import('fs/promises');
|
|
1109
|
-
const path = await import('path');
|
|
1110
|
-
const crypto = await import('crypto');
|
|
1111
|
-
|
|
1112
|
-
let filePath = path.join(options.root, req.path);
|
|
1113
|
-
|
|
1114
|
-
// Security: prevent directory traversal
|
|
1115
|
-
if (!filePath.startsWith(path.resolve(options.root))) {
|
|
1116
|
-
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// Handle dotfiles
|
|
1121
|
-
const basename = path.basename(filePath);
|
|
1122
|
-
if (basename.startsWith('.')) {
|
|
1123
|
-
if (options.dotfiles === 'deny') {
|
|
1124
|
-
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
1125
|
-
return;
|
|
1126
|
-
} else if (options.dotfiles === 'ignore') {
|
|
1127
|
-
next();
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
let stats;
|
|
1133
|
-
try {
|
|
1134
|
-
stats = await fs.stat(filePath);
|
|
1135
|
-
} catch (error) {
|
|
1136
|
-
next(); // File not found, let other middleware handle
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// Handle directories
|
|
1141
|
-
if (stats.isDirectory()) {
|
|
1142
|
-
const indexFiles = options.index || ['index.html', 'index.htm'];
|
|
1143
|
-
let indexFound = false;
|
|
1144
|
-
|
|
1145
|
-
for (const indexFile of indexFiles) {
|
|
1146
|
-
const indexPath = path.join(filePath, indexFile);
|
|
1147
|
-
try {
|
|
1148
|
-
const indexStats = await fs.stat(indexPath);
|
|
1149
|
-
if (indexStats.isFile()) {
|
|
1150
|
-
filePath = indexPath;
|
|
1151
|
-
stats = indexStats;
|
|
1152
|
-
indexFound = true;
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
} catch (error) {
|
|
1156
|
-
// Continue to next index file
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
if (!indexFound) {
|
|
1161
|
-
next();
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// Set headers with proper mime type and charset
|
|
1167
|
-
const ext = path.extname(filePath);
|
|
1168
|
-
const mimeTypes: Record<string, string> = {
|
|
1169
|
-
'.html': 'text/html',
|
|
1170
|
-
'.css': 'text/css',
|
|
1171
|
-
'.js': 'application/javascript',
|
|
1172
|
-
'.json': 'application/json',
|
|
1173
|
-
'.png': 'image/png',
|
|
1174
|
-
'.jpg': 'image/jpeg',
|
|
1175
|
-
'.jpeg': 'image/jpeg',
|
|
1176
|
-
'.gif': 'image/gif',
|
|
1177
|
-
'.svg': 'image/svg+xml',
|
|
1178
|
-
'.ico': 'image/x-icon',
|
|
1179
|
-
'.pdf': 'application/pdf',
|
|
1180
|
-
'.txt': 'text/plain',
|
|
1181
|
-
'.xml': 'application/xml',
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
const baseMimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
1185
|
-
|
|
1186
|
-
// Add charset for text-based files
|
|
1187
|
-
const textTypes = [
|
|
1188
|
-
'text/',
|
|
1189
|
-
'application/json',
|
|
1190
|
-
'application/javascript',
|
|
1191
|
-
'application/xml',
|
|
1192
|
-
'image/svg+xml',
|
|
1193
|
-
];
|
|
1194
|
-
const needsCharset = textTypes.some(type => baseMimeType.startsWith(type));
|
|
1195
|
-
const contentType = needsCharset ? `${baseMimeType}; charset=utf-8` : baseMimeType;
|
|
1196
|
-
|
|
1197
|
-
res.setHeader('Content-Type', contentType);
|
|
1198
|
-
res.setHeader('Content-Length', stats.size);
|
|
1199
|
-
|
|
1200
|
-
// Cache headers
|
|
1201
|
-
if (options.maxAge) {
|
|
1202
|
-
res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// ETag support
|
|
1206
|
-
if (options.etag !== false) {
|
|
1207
|
-
const etag = crypto
|
|
1208
|
-
.createHash('md5')
|
|
1209
|
-
.update(`${stats.mtime.getTime()}-${stats.size}`)
|
|
1210
|
-
.digest('hex');
|
|
1211
|
-
res.setHeader('ETag', `"${etag}"`);
|
|
1212
|
-
|
|
1213
|
-
// Handle conditional requests
|
|
1214
|
-
const ifNoneMatch = req.headers['if-none-match'];
|
|
1215
|
-
if (ifNoneMatch === `"${etag}"`) {
|
|
1216
|
-
res.statusCode = 304;
|
|
1217
|
-
res.end();
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Handle HEAD requests
|
|
1223
|
-
if (req.method === 'HEAD') {
|
|
1224
|
-
res.end();
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// Send file
|
|
1229
|
-
const data = await fs.readFile(filePath);
|
|
1230
|
-
res.end(data);
|
|
1231
|
-
} catch (error) {
|
|
1232
|
-
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
1233
|
-
}
|
|
1234
|
-
};
|
|
1235
|
-
},
|
|
1236
|
-
|
|
1237
|
-
upload: (
|
|
1238
|
-
options: {
|
|
1239
|
-
dest?: string;
|
|
1240
|
-
maxFileSize?: number;
|
|
1241
|
-
maxFiles?: number;
|
|
1242
|
-
allowedTypes?: string[];
|
|
1243
|
-
} = {}
|
|
1244
|
-
): Middleware => {
|
|
1245
|
-
return (req, res, next) => {
|
|
1246
|
-
const contentType = req.headers['content-type'] || '';
|
|
1247
|
-
|
|
1248
|
-
if (!contentType.includes('multipart/form-data')) {
|
|
1249
|
-
next();
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// File upload handling is now built into parseBody method
|
|
1254
|
-
// This middleware can add additional validation
|
|
1255
|
-
if (req.body && req.body.files) {
|
|
1256
|
-
const files = req.body.files;
|
|
1257
|
-
const maxFileSize = options.maxFileSize || 5 * 1024 * 1024; // 5MB default
|
|
1258
|
-
const maxFiles = options.maxFiles || 10;
|
|
1259
|
-
const allowedTypes = options.allowedTypes;
|
|
1260
|
-
|
|
1261
|
-
// Validate file count
|
|
1262
|
-
if (Object.keys(files).length > maxFiles) {
|
|
1263
|
-
res.status(400).json({
|
|
1264
|
-
success: false,
|
|
1265
|
-
error: `Too many files. Maximum ${maxFiles} allowed.`,
|
|
1266
|
-
});
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// Validate each file
|
|
1271
|
-
for (const [fieldName, file] of Object.entries(files)) {
|
|
1272
|
-
const fileData = file as any;
|
|
1273
|
-
|
|
1274
|
-
// Validate file size
|
|
1275
|
-
if (fileData.size > maxFileSize) {
|
|
1276
|
-
res.status(400).json({
|
|
1277
|
-
success: false,
|
|
1278
|
-
error: `File ${fileData.filename} is too large. Maximum ${maxFileSize} bytes allowed.`,
|
|
1279
|
-
});
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// Validate file type
|
|
1284
|
-
if (allowedTypes && !allowedTypes.includes(fileData.mimetype)) {
|
|
1285
|
-
res.status(400).json({
|
|
1286
|
-
success: false,
|
|
1287
|
-
error: `File type ${fileData.mimetype} not allowed.`,
|
|
1288
|
-
});
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Store files in request for easy access
|
|
1294
|
-
req.files = files;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
next();
|
|
1298
|
-
};
|
|
1299
|
-
},
|
|
1300
|
-
|
|
1301
|
-
template: (options: {
|
|
1302
|
-
views: string;
|
|
1303
|
-
engine?: 'moro' | 'handlebars' | 'ejs';
|
|
1304
|
-
cache?: boolean;
|
|
1305
|
-
defaultLayout?: string;
|
|
1306
|
-
}): Middleware => {
|
|
1307
|
-
const templateCache = new Map<string, string>();
|
|
1308
|
-
|
|
1309
|
-
return async (req, res, next) => {
|
|
1310
|
-
// Add render method to response
|
|
1311
|
-
res.render = async (template: string, data: any = {}) => {
|
|
1312
|
-
try {
|
|
1313
|
-
const fs = await import('fs/promises');
|
|
1314
|
-
const path = await import('path');
|
|
1315
|
-
|
|
1316
|
-
const templatePath = path.join(options.views, `${template}.html`);
|
|
1317
|
-
|
|
1318
|
-
let templateContent: string;
|
|
1319
|
-
|
|
1320
|
-
// Check cache first
|
|
1321
|
-
if (options.cache && templateCache.has(templatePath)) {
|
|
1322
|
-
templateContent = templateCache.get(templatePath)!;
|
|
1323
|
-
} else {
|
|
1324
|
-
templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
1325
|
-
if (options.cache) {
|
|
1326
|
-
templateCache.set(templatePath, templateContent);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// Simple template engine - replace {{variable}} with values
|
|
1331
|
-
let rendered = templateContent;
|
|
1332
|
-
|
|
1333
|
-
// Handle basic variable substitution
|
|
1334
|
-
rendered = rendered.replace(/\{\{(\w+)\}\}/g, (match: string, key: string) => {
|
|
1335
|
-
return data[key] !== undefined ? String(data[key]) : match;
|
|
1336
|
-
});
|
|
1337
|
-
|
|
1338
|
-
// Handle nested object properties like {{user.name}}
|
|
1339
|
-
rendered = rendered.replace(/\{\{([\w.]+)\}\}/g, (match: string, key: string) => {
|
|
1340
|
-
const value = key.split('.').reduce((obj: any, prop: string) => obj?.[prop], data);
|
|
1341
|
-
return value !== undefined ? String(value) : match;
|
|
1342
|
-
});
|
|
1343
|
-
|
|
1344
|
-
// Handle loops: {{#each items}}{{name}}{{/each}}
|
|
1345
|
-
rendered = rendered.replace(
|
|
1346
|
-
/\{\{#each (\w+)\}\}(.*?)\{\{\/each\}\}/gs,
|
|
1347
|
-
(match, arrayKey, template) => {
|
|
1348
|
-
const array = data[arrayKey];
|
|
1349
|
-
if (!Array.isArray(array)) return '';
|
|
1350
|
-
|
|
1351
|
-
return array
|
|
1352
|
-
.map(item => {
|
|
1353
|
-
let itemTemplate = template;
|
|
1354
|
-
// Replace variables in the loop template
|
|
1355
|
-
itemTemplate = itemTemplate.replace(
|
|
1356
|
-
/\{\{(\w+)\}\}/g,
|
|
1357
|
-
(match: string, key: string) => {
|
|
1358
|
-
return item[key] !== undefined ? String(item[key]) : match;
|
|
1359
|
-
}
|
|
1360
|
-
);
|
|
1361
|
-
return itemTemplate;
|
|
1362
|
-
})
|
|
1363
|
-
.join('');
|
|
1364
|
-
}
|
|
1365
|
-
);
|
|
1366
|
-
|
|
1367
|
-
// Handle conditionals: {{#if condition}}content{{/if}}
|
|
1368
|
-
rendered = rendered.replace(
|
|
1369
|
-
/\{\{#if (\w+)\}\}(.*?)\{\{\/if\}\}/gs,
|
|
1370
|
-
(match, conditionKey, content) => {
|
|
1371
|
-
const condition = data[conditionKey];
|
|
1372
|
-
return condition ? content : '';
|
|
1373
|
-
}
|
|
1374
|
-
);
|
|
1375
|
-
|
|
1376
|
-
// Handle layout
|
|
1377
|
-
if (options.defaultLayout) {
|
|
1378
|
-
const layoutPath = path.join(options.views, 'layouts', `${options.defaultLayout}.html`);
|
|
1379
|
-
try {
|
|
1380
|
-
let layoutContent: string;
|
|
1381
|
-
|
|
1382
|
-
if (options.cache && templateCache.has(layoutPath)) {
|
|
1383
|
-
layoutContent = templateCache.get(layoutPath)!;
|
|
1384
|
-
} else {
|
|
1385
|
-
layoutContent = await fs.readFile(layoutPath, 'utf-8');
|
|
1386
|
-
if (options.cache) {
|
|
1387
|
-
templateCache.set(layoutPath, layoutContent);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
rendered = layoutContent.replace(/\{\{body\}\}/, rendered);
|
|
1392
|
-
} catch (error) {
|
|
1393
|
-
// Layout not found, use template as-is
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
res.setHeader('Content-Type', 'text/html');
|
|
1398
|
-
res.end(rendered);
|
|
1399
|
-
} catch (error) {
|
|
1400
|
-
res.status(500).json({ success: false, error: 'Template rendering failed' });
|
|
1401
|
-
}
|
|
1402
|
-
};
|
|
1403
|
-
|
|
1404
|
-
next();
|
|
1405
|
-
};
|
|
1406
|
-
},
|
|
1407
|
-
|
|
1408
|
-
// HTTP/2 Server Push middleware
|
|
1409
|
-
http2Push: (
|
|
1410
|
-
options: {
|
|
1411
|
-
resources?: Array<{ path: string; as: string; type?: string }>;
|
|
1412
|
-
condition?: (req: any) => boolean;
|
|
1413
|
-
} = {}
|
|
1414
|
-
): Middleware => {
|
|
1415
|
-
return (req, res, next) => {
|
|
1416
|
-
// Add HTTP/2 push capability to response
|
|
1417
|
-
(res as any).push = (path: string, options: any = {}) => {
|
|
1418
|
-
// Check if HTTP/2 is supported
|
|
1419
|
-
if (req.httpVersion === '2.0' && (res as any).stream && (res as any).stream.pushAllowed) {
|
|
1420
|
-
try {
|
|
1421
|
-
const pushStream = (res as any).stream.pushStream({
|
|
1422
|
-
':method': 'GET',
|
|
1423
|
-
':path': path,
|
|
1424
|
-
...options.headers,
|
|
1425
|
-
});
|
|
1426
|
-
|
|
1427
|
-
if (pushStream) {
|
|
1428
|
-
// Handle push stream
|
|
1429
|
-
return pushStream;
|
|
1430
|
-
}
|
|
1431
|
-
} catch (error) {
|
|
1432
|
-
// Push failed, continue normally
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
return null;
|
|
1436
|
-
};
|
|
1437
|
-
|
|
1438
|
-
// Auto-push configured resources
|
|
1439
|
-
if (options.resources && (!options.condition || options.condition(req))) {
|
|
1440
|
-
for (const resource of options.resources) {
|
|
1441
|
-
(res as any).push?.(resource.path, {
|
|
1442
|
-
headers: {
|
|
1443
|
-
'content-type': resource.type || 'text/plain',
|
|
1444
|
-
},
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
next();
|
|
1450
|
-
};
|
|
1451
|
-
},
|
|
1452
|
-
|
|
1453
|
-
// Server-Sent Events middleware
|
|
1454
|
-
sse: (
|
|
1455
|
-
options: {
|
|
1456
|
-
heartbeat?: number;
|
|
1457
|
-
retry?: number;
|
|
1458
|
-
cors?: boolean;
|
|
1459
|
-
} = {}
|
|
1460
|
-
): Middleware => {
|
|
1461
|
-
return (req, res, next) => {
|
|
1462
|
-
// Only handle SSE requests
|
|
1463
|
-
if (req.headers.accept?.includes('text/event-stream')) {
|
|
1464
|
-
// Set SSE headers
|
|
1465
|
-
res.writeHead(200, {
|
|
1466
|
-
'Content-Type': 'text/event-stream',
|
|
1467
|
-
'Cache-Control': 'no-cache',
|
|
1468
|
-
Connection: 'keep-alive',
|
|
1469
|
-
'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
|
|
1470
|
-
'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
|
|
1471
|
-
});
|
|
1472
|
-
|
|
1473
|
-
// Add SSE methods to response
|
|
1474
|
-
(res as any).sendEvent = (data: any, event?: string, id?: string) => {
|
|
1475
|
-
if (id) res.write(`id: ${id}\n`);
|
|
1476
|
-
if (event) res.write(`event: ${event}\n`);
|
|
1477
|
-
res.write(`data: ${typeof data === 'string' ? data : JSON.stringify(data)}\n\n`);
|
|
1478
|
-
};
|
|
1479
|
-
|
|
1480
|
-
(res as any).sendComment = (comment: string) => {
|
|
1481
|
-
res.write(`: ${comment}\n\n`);
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
(res as any).sendRetry = (ms: number) => {
|
|
1485
|
-
res.write(`retry: ${ms}\n\n`);
|
|
1486
|
-
};
|
|
1487
|
-
|
|
1488
|
-
// Set up heartbeat if configured
|
|
1489
|
-
let heartbeatInterval: NodeJS.Timeout | null = null;
|
|
1490
|
-
if (options.heartbeat) {
|
|
1491
|
-
heartbeatInterval = setInterval(() => {
|
|
1492
|
-
(res as any).sendComment('heartbeat');
|
|
1493
|
-
}, options.heartbeat);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
// Set retry if configured
|
|
1497
|
-
if (options.retry) {
|
|
1498
|
-
(res as any).sendRetry(options.retry);
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// Clean up on close
|
|
1502
|
-
req.on('close', () => {
|
|
1503
|
-
if (heartbeatInterval) {
|
|
1504
|
-
clearInterval(heartbeatInterval);
|
|
1505
|
-
}
|
|
1506
|
-
});
|
|
1507
|
-
|
|
1508
|
-
// Don't call next() - this middleware handles the response
|
|
1509
|
-
return;
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
next();
|
|
1513
|
-
};
|
|
1514
|
-
},
|
|
1515
|
-
|
|
1516
|
-
// Range request middleware for streaming
|
|
1517
|
-
range: (
|
|
1518
|
-
options: {
|
|
1519
|
-
acceptRanges?: string;
|
|
1520
|
-
maxRanges?: number;
|
|
1521
|
-
} = {}
|
|
1522
|
-
): Middleware => {
|
|
1523
|
-
return async (req, res, next) => {
|
|
1524
|
-
// Add range support to response
|
|
1525
|
-
(res as any).sendRange = async (filePath: string, stats?: any) => {
|
|
1526
|
-
try {
|
|
1527
|
-
const fs = await import('fs/promises');
|
|
1528
|
-
const path = await import('path');
|
|
1529
|
-
|
|
1530
|
-
if (!stats) {
|
|
1531
|
-
stats = await fs.stat(filePath);
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
const fileSize = stats.size;
|
|
1535
|
-
const range = req.headers.range;
|
|
1536
|
-
|
|
1537
|
-
// Set Accept-Ranges header
|
|
1538
|
-
res.setHeader('Accept-Ranges', options.acceptRanges || 'bytes');
|
|
1539
|
-
|
|
1540
|
-
if (!range) {
|
|
1541
|
-
// No range requested, send entire file
|
|
1542
|
-
res.setHeader('Content-Length', fileSize);
|
|
1543
|
-
const data = await fs.readFile(filePath);
|
|
1544
|
-
res.end(data);
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// Parse range header
|
|
1549
|
-
const ranges = range
|
|
1550
|
-
.replace(/bytes=/, '')
|
|
1551
|
-
.split(',')
|
|
1552
|
-
.map(r => {
|
|
1553
|
-
const [start, end] = r.split('-');
|
|
1554
|
-
return {
|
|
1555
|
-
start: start ? parseInt(start) : 0,
|
|
1556
|
-
end: end ? parseInt(end) : fileSize - 1,
|
|
1557
|
-
};
|
|
1558
|
-
});
|
|
1559
|
-
|
|
1560
|
-
// Validate ranges
|
|
1561
|
-
if (options.maxRanges && ranges.length > options.maxRanges) {
|
|
1562
|
-
res.status(416).json({ success: false, error: 'Too many ranges' });
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
if (ranges.length === 1) {
|
|
1567
|
-
// Single range
|
|
1568
|
-
const { start, end } = ranges[0];
|
|
1569
|
-
const chunkSize = end - start + 1;
|
|
1570
|
-
|
|
1571
|
-
if (start >= fileSize || end >= fileSize) {
|
|
1572
|
-
res.status(416).setHeader('Content-Range', `bytes */${fileSize}`);
|
|
1573
|
-
res.json({ success: false, error: 'Range not satisfiable' });
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
res.status(206);
|
|
1578
|
-
res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
|
|
1579
|
-
res.setHeader('Content-Length', chunkSize);
|
|
1580
|
-
|
|
1581
|
-
// Stream the range
|
|
1582
|
-
const stream = require('fs').createReadStream(filePath, {
|
|
1583
|
-
start,
|
|
1584
|
-
end,
|
|
1585
|
-
});
|
|
1586
|
-
stream.pipe(res);
|
|
1587
|
-
} else {
|
|
1588
|
-
// Multiple ranges - multipart response
|
|
1589
|
-
const boundary = 'MULTIPART_BYTERANGES';
|
|
1590
|
-
res.status(206);
|
|
1591
|
-
res.setHeader('Content-Type', `multipart/byteranges; boundary=${boundary}`);
|
|
1592
|
-
|
|
1593
|
-
for (const { start, end } of ranges) {
|
|
1594
|
-
if (start >= fileSize || end >= fileSize) continue;
|
|
1595
|
-
|
|
1596
|
-
const chunkSize = end - start + 1;
|
|
1597
|
-
res.write(`\r\n--${boundary}\r\n`);
|
|
1598
|
-
res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
|
|
1599
|
-
|
|
1600
|
-
const stream = require('fs').createReadStream(filePath, {
|
|
1601
|
-
start,
|
|
1602
|
-
end,
|
|
1603
|
-
});
|
|
1604
|
-
await new Promise(resolve => {
|
|
1605
|
-
stream.on('end', resolve);
|
|
1606
|
-
stream.pipe(res, { end: false });
|
|
1607
|
-
});
|
|
1608
|
-
}
|
|
1609
|
-
res.write(`\r\n--${boundary}--\r\n`);
|
|
1610
|
-
res.end();
|
|
1611
|
-
}
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
res.status(500).json({ success: false, error: 'Range request failed' });
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
next();
|
|
1618
|
-
};
|
|
1619
|
-
},
|
|
1620
|
-
|
|
1621
|
-
// CSRF Protection middleware
|
|
1622
|
-
csrf: (
|
|
1623
|
-
options: {
|
|
1624
|
-
secret?: string;
|
|
1625
|
-
tokenLength?: number;
|
|
1626
|
-
cookieName?: string;
|
|
1627
|
-
headerName?: string;
|
|
1628
|
-
ignoreMethods?: string[];
|
|
1629
|
-
sameSite?: boolean;
|
|
1630
|
-
} = {}
|
|
1631
|
-
): Middleware => {
|
|
1632
|
-
const secret = options.secret || 'moro-csrf-secret';
|
|
1633
|
-
const tokenLength = options.tokenLength || 32;
|
|
1634
|
-
const cookieName = options.cookieName || '_csrf';
|
|
1635
|
-
const headerName = options.headerName || 'x-csrf-token';
|
|
1636
|
-
const ignoreMethods = options.ignoreMethods || ['GET', 'HEAD', 'OPTIONS'];
|
|
1637
|
-
|
|
1638
|
-
const generateToken = () => {
|
|
1639
|
-
const crypto = require('crypto');
|
|
1640
|
-
return crypto.randomBytes(tokenLength).toString('hex');
|
|
1641
|
-
};
|
|
1642
|
-
|
|
1643
|
-
const verifyToken = (token: string, sessionToken: string) => {
|
|
1644
|
-
return token && sessionToken && token === sessionToken;
|
|
1645
|
-
};
|
|
1646
|
-
|
|
1647
|
-
return (req, res, next) => {
|
|
1648
|
-
// Add CSRF token generation method
|
|
1649
|
-
(req as any).csrfToken = () => {
|
|
1650
|
-
if (!(req as any)._csrfToken) {
|
|
1651
|
-
(req as any)._csrfToken = generateToken();
|
|
1652
|
-
// Set token in cookie
|
|
1653
|
-
res.cookie(cookieName, (req as any)._csrfToken, {
|
|
1654
|
-
httpOnly: true,
|
|
1655
|
-
sameSite: options.sameSite !== false ? 'strict' : undefined,
|
|
1656
|
-
secure: req.headers['x-forwarded-proto'] === 'https' || (req.socket as any).encrypted,
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
return (req as any)._csrfToken;
|
|
1660
|
-
};
|
|
1661
|
-
|
|
1662
|
-
// Skip verification for safe methods
|
|
1663
|
-
if (ignoreMethods.includes(req.method!)) {
|
|
1664
|
-
next();
|
|
1665
|
-
return;
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// Get token from header or body
|
|
1669
|
-
const token =
|
|
1670
|
-
req.headers[headerName] || (req.body && req.body._csrf) || (req.query && req.query._csrf);
|
|
1671
|
-
|
|
1672
|
-
// Get session token from cookie
|
|
1673
|
-
const sessionToken = req.cookies?.[cookieName];
|
|
1674
|
-
|
|
1675
|
-
if (!verifyToken(token as string, sessionToken || '')) {
|
|
1676
|
-
res.status(403).json({
|
|
1677
|
-
success: false,
|
|
1678
|
-
error: 'Invalid CSRF token',
|
|
1679
|
-
code: 'CSRF_TOKEN_MISMATCH',
|
|
1680
|
-
});
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
next();
|
|
1685
|
-
};
|
|
1686
|
-
},
|
|
1687
|
-
|
|
1688
|
-
// Content Security Policy middleware
|
|
1689
|
-
csp: (
|
|
1690
|
-
options: {
|
|
1691
|
-
directives?: {
|
|
1692
|
-
defaultSrc?: string[];
|
|
1693
|
-
scriptSrc?: string[];
|
|
1694
|
-
styleSrc?: string[];
|
|
1695
|
-
imgSrc?: string[];
|
|
1696
|
-
connectSrc?: string[];
|
|
1697
|
-
fontSrc?: string[];
|
|
1698
|
-
objectSrc?: string[];
|
|
1699
|
-
mediaSrc?: string[];
|
|
1700
|
-
frameSrc?: string[];
|
|
1701
|
-
childSrc?: string[];
|
|
1702
|
-
workerSrc?: string[];
|
|
1703
|
-
formAction?: string[];
|
|
1704
|
-
upgradeInsecureRequests?: boolean;
|
|
1705
|
-
blockAllMixedContent?: boolean;
|
|
1706
|
-
};
|
|
1707
|
-
reportOnly?: boolean;
|
|
1708
|
-
reportUri?: string;
|
|
1709
|
-
nonce?: boolean;
|
|
1710
|
-
} = {}
|
|
1711
|
-
): Middleware => {
|
|
1712
|
-
return (req, res, next) => {
|
|
1713
|
-
const directives = options.directives || {
|
|
1714
|
-
defaultSrc: ["'self'"],
|
|
1715
|
-
scriptSrc: ["'self'"],
|
|
1716
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1717
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
1718
|
-
connectSrc: ["'self'"],
|
|
1719
|
-
fontSrc: ["'self'"],
|
|
1720
|
-
objectSrc: ["'none'"],
|
|
1721
|
-
mediaSrc: ["'self'"],
|
|
1722
|
-
frameSrc: ["'none'"],
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
|
-
// Generate nonce if requested
|
|
1726
|
-
let nonce: string | undefined;
|
|
1727
|
-
if (options.nonce) {
|
|
1728
|
-
const crypto = require('crypto');
|
|
1729
|
-
nonce = crypto.randomBytes(16).toString('base64');
|
|
1730
|
-
(req as any).cspNonce = nonce;
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
// Build CSP header value
|
|
1734
|
-
const cspParts: string[] = [];
|
|
1735
|
-
|
|
1736
|
-
for (const [directive, sources] of Object.entries(directives)) {
|
|
1737
|
-
if (directive === 'upgradeInsecureRequests' && sources === true) {
|
|
1738
|
-
cspParts.push('upgrade-insecure-requests');
|
|
1739
|
-
} else if (directive === 'blockAllMixedContent' && sources === true) {
|
|
1740
|
-
cspParts.push('block-all-mixed-content');
|
|
1741
|
-
} else if (Array.isArray(sources)) {
|
|
1742
|
-
let sourceList = sources.join(' ');
|
|
1743
|
-
|
|
1744
|
-
// Add nonce to script-src and style-src if enabled
|
|
1745
|
-
if (nonce && (directive === 'scriptSrc' || directive === 'styleSrc')) {
|
|
1746
|
-
sourceList += ` 'nonce-${nonce}'`;
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
// Convert camelCase to kebab-case
|
|
1750
|
-
const kebabDirective = directive.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
1751
|
-
cspParts.push(`${kebabDirective} ${sourceList}`);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// Add report-uri if specified
|
|
1756
|
-
if (options.reportUri) {
|
|
1757
|
-
cspParts.push(`report-uri ${options.reportUri}`);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
const cspValue = cspParts.join('; ');
|
|
1761
|
-
const headerName = options.reportOnly
|
|
1762
|
-
? 'Content-Security-Policy-Report-Only'
|
|
1763
|
-
: 'Content-Security-Policy';
|
|
1764
|
-
|
|
1765
|
-
res.setHeader(headerName, cspValue);
|
|
1766
|
-
|
|
1767
|
-
next();
|
|
1768
|
-
};
|
|
1769
|
-
},
|
|
1770
|
-
};
|
|
1771
|
-
|
|
1772
|
-
function parseSize(size: string): number {
|
|
1773
|
-
const units: { [key: string]: number } = {
|
|
1774
|
-
b: 1,
|
|
1775
|
-
kb: 1024,
|
|
1776
|
-
mb: 1024 * 1024,
|
|
1777
|
-
gb: 1024 * 1024 * 1024,
|
|
1778
|
-
};
|
|
1779
|
-
|
|
1780
|
-
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
1781
|
-
if (!match) return 1024 * 1024; // Default 1MB
|
|
1782
|
-
|
|
1783
|
-
const value = parseFloat(match[1]);
|
|
1784
|
-
const unit = match[2] || 'b';
|
|
1785
|
-
|
|
1786
|
-
return Math.round(value * units[unit]);
|
|
1787
|
-
}
|