@morojs/moro 1.6.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/core/auth/morojs-adapter.js +33 -20
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-sources.js +71 -15
- package/dist/core/config/config-sources.js.map +1 -1
- package/dist/core/config/config-validator.js +201 -6
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/database/adapters/drizzle.js +5 -5
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/mongodb.js +5 -1
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.js +5 -1
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.js +1 -1
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.js +2 -2
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sqlite.js +5 -1
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.js +8 -9
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/simple-docs.js +2 -1
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.js +1 -0
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.js +4 -0
- 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 +2 -1
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/framework.d.ts +5 -3
- package/dist/core/framework.js +28 -25
- package/dist/core/framework.js.map +1 -1
- package/dist/core/graphql/adapter.d.ts +73 -0
- package/dist/core/graphql/adapter.js +2 -0
- package/dist/core/graphql/adapter.js.map +1 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.d.ts +26 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.js +229 -0
- package/dist/core/graphql/adapters/graphql-js-adapter.js.map +1 -0
- package/dist/core/graphql/core.d.ts +60 -0
- package/dist/core/graphql/core.js +191 -0
- package/dist/core/graphql/core.js.map +1 -0
- package/dist/core/graphql/index.d.ts +4 -0
- package/dist/core/graphql/index.js +4 -0
- package/dist/core/graphql/index.js.map +1 -0
- package/dist/core/graphql/loader.d.ts +9 -0
- package/dist/core/graphql/loader.js +32 -0
- package/dist/core/graphql/loader.js.map +1 -0
- package/dist/core/graphql/types.d.ts +211 -0
- package/dist/core/graphql/types.js +2 -0
- package/dist/core/graphql/types.js.map +1 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.d.ts +28 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.js +449 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.js.map +1 -0
- package/dist/core/grpc/adapters/index.d.ts +1 -0
- package/dist/core/grpc/adapters/index.js +6 -0
- package/dist/core/grpc/adapters/index.js.map +1 -0
- package/dist/core/grpc/grpc-adapter.d.ts +47 -0
- package/dist/core/grpc/grpc-adapter.js +4 -0
- package/dist/core/grpc/grpc-adapter.js.map +1 -0
- package/dist/core/grpc/grpc-manager.d.ts +59 -0
- package/dist/core/grpc/grpc-manager.js +218 -0
- package/dist/core/grpc/grpc-manager.js.map +1 -0
- package/dist/core/grpc/index.d.ts +7 -0
- package/dist/core/grpc/index.js +10 -0
- package/dist/core/grpc/index.js.map +1 -0
- package/dist/core/grpc/middleware/auth.d.ts +22 -0
- package/dist/core/grpc/middleware/auth.js +126 -0
- package/dist/core/grpc/middleware/auth.js.map +1 -0
- package/dist/core/grpc/middleware/logging.d.ts +19 -0
- package/dist/core/grpc/middleware/logging.js +57 -0
- package/dist/core/grpc/middleware/logging.js.map +1 -0
- package/dist/core/grpc/middleware/validation.d.ts +18 -0
- package/dist/core/grpc/middleware/validation.js +126 -0
- package/dist/core/grpc/middleware/validation.js.map +1 -0
- package/dist/core/grpc/types.d.ts +233 -0
- package/dist/core/grpc/types.js +36 -0
- package/dist/core/grpc/types.js.map +1 -0
- package/dist/core/http/http-server.d.ts +13 -84
- package/dist/core/http/http-server.js +216 -781
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +131 -0
- package/dist/core/http/http2-server.js +803 -0
- package/dist/core/http/http2-server.js.map +1 -0
- package/dist/core/http/index.d.ts +3 -1
- package/dist/core/http/index.js +2 -1
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.d.ts +28 -0
- package/dist/core/http/utils/uws-worker-clustering.js +313 -0
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -0
- package/dist/core/http/uws-http-server.d.ts +3 -1
- package/dist/core/http/uws-http-server.js +58 -34
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/cron-parser.d.ts +62 -0
- package/dist/core/jobs/cron-parser.js +239 -0
- package/dist/core/jobs/cron-parser.js.map +1 -0
- package/dist/core/jobs/index.d.ts +12 -0
- package/dist/core/jobs/index.js +9 -0
- package/dist/core/jobs/index.js.map +1 -0
- package/dist/core/jobs/job-executor.d.ts +134 -0
- package/dist/core/jobs/job-executor.js +418 -0
- package/dist/core/jobs/job-executor.js.map +1 -0
- package/dist/core/jobs/job-scheduler.d.ts +214 -0
- package/dist/core/jobs/job-scheduler.js +554 -0
- package/dist/core/jobs/job-scheduler.js.map +1 -0
- package/dist/core/jobs/job-state-manager.d.ts +158 -0
- package/dist/core/jobs/job-state-manager.js +444 -0
- package/dist/core/jobs/job-state-manager.js.map +1 -0
- package/dist/core/jobs/leader-election.d.ts +124 -0
- package/dist/core/jobs/leader-election.js +482 -0
- package/dist/core/jobs/leader-election.js.map +1 -0
- package/dist/core/jobs/types.d.ts +151 -0
- package/dist/core/jobs/types.js +4 -0
- package/dist/core/jobs/types.js.map +1 -0
- package/dist/core/jobs/utils.d.ts +95 -0
- package/dist/core/jobs/utils.js +258 -0
- package/dist/core/jobs/utils.js.map +1 -0
- package/dist/core/logger/filters.js +2 -0
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/logger.js +48 -21
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js +11 -3
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/mail/adapters/console-adapter.d.ts +14 -0
- package/dist/core/mail/adapters/console-adapter.js +89 -0
- package/dist/core/mail/adapters/console-adapter.js.map +1 -0
- package/dist/core/mail/adapters/index.d.ts +5 -0
- package/dist/core/mail/adapters/index.js +8 -0
- package/dist/core/mail/adapters/index.js.map +1 -0
- package/dist/core/mail/adapters/nodemailer-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/nodemailer-adapter.js +188 -0
- package/dist/core/mail/adapters/nodemailer-adapter.js.map +1 -0
- package/dist/core/mail/adapters/resend-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/resend-adapter.js +169 -0
- package/dist/core/mail/adapters/resend-adapter.js.map +1 -0
- package/dist/core/mail/adapters/sendgrid-adapter.d.ts +19 -0
- package/dist/core/mail/adapters/sendgrid-adapter.js +186 -0
- package/dist/core/mail/adapters/sendgrid-adapter.js.map +1 -0
- package/dist/core/mail/adapters/ses-adapter.d.ts +18 -0
- package/dist/core/mail/adapters/ses-adapter.js +167 -0
- package/dist/core/mail/adapters/ses-adapter.js.map +1 -0
- package/dist/core/mail/index.d.ts +5 -0
- package/dist/core/mail/index.js +8 -0
- package/dist/core/mail/index.js.map +1 -0
- package/dist/core/mail/mail-adapter.d.ts +62 -0
- package/dist/core/mail/mail-adapter.js +83 -0
- package/dist/core/mail/mail-adapter.js.map +1 -0
- package/dist/core/mail/mail-manager.d.ts +63 -0
- package/dist/core/mail/mail-manager.js +302 -0
- package/dist/core/mail/mail-manager.js.map +1 -0
- package/dist/core/mail/template-engine.d.ts +43 -0
- package/dist/core/mail/template-engine.js +239 -0
- package/dist/core/mail/template-engine.js.map +1 -0
- package/dist/core/mail/types.d.ts +237 -0
- package/dist/core/mail/types.js +4 -0
- package/dist/core/mail/types.js.map +1 -0
- package/dist/core/middleware/built-in/auth/helpers.js +1 -1
- package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.js +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/providers.js +1 -1
- package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/core.d.ts +12 -0
- package/dist/core/middleware/built-in/body-size/core.js +52 -0
- package/dist/core/middleware/built-in/body-size/core.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/body-size/hook.js +12 -0
- package/dist/core/middleware/built-in/body-size/hook.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/index.d.ts +6 -0
- package/dist/core/middleware/built-in/body-size/index.js +7 -0
- package/dist/core/middleware/built-in/body-size/index.js.map +1 -0
- package/dist/core/middleware/built-in/body-size/middleware.d.ts +14 -0
- package/dist/core/middleware/built-in/body-size/middleware.js +22 -0
- package/dist/core/middleware/built-in/body-size/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -3
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js +1 -0
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/cache/core.d.ts +20 -1
- package/dist/core/middleware/built-in/cache/core.js.map +1 -1
- package/dist/core/middleware/built-in/cache/hook.d.ts +38 -1
- package/dist/core/middleware/built-in/cache/hook.js +202 -16
- package/dist/core/middleware/built-in/cache/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cache/index.js +1 -1
- package/dist/core/middleware/built-in/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.d.ts +8 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js +100 -7
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.d.ts +6 -0
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js +97 -13
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +16 -0
- package/dist/core/middleware/built-in/compression/core.js +75 -0
- package/dist/core/middleware/built-in/compression/core.js.map +1 -0
- package/dist/core/middleware/built-in/compression/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/compression/hook.js +14 -0
- package/dist/core/middleware/built-in/compression/hook.js.map +1 -0
- package/dist/core/middleware/built-in/compression/index.d.ts +6 -0
- package/dist/core/middleware/built-in/compression/index.js +7 -0
- package/dist/core/middleware/built-in/compression/index.js.map +1 -0
- package/dist/core/middleware/built-in/compression/middleware.d.ts +20 -0
- package/dist/core/middleware/built-in/compression/middleware.js +28 -0
- package/dist/core/middleware/built-in/compression/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/cookie/core.js +37 -9
- package/dist/core/middleware/built-in/cookie/core.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/hook.d.ts +1 -1
- package/dist/core/middleware/built-in/cookie/hook.js +2 -2
- package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/core.js +1 -0
- package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/core.d.ts +11 -0
- package/dist/core/middleware/built-in/graphql/core.js +24 -0
- package/dist/core/middleware/built-in/graphql/core.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/helpers.d.ts +69 -0
- package/dist/core/middleware/built-in/graphql/helpers.js +187 -0
- package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/hook.d.ts +7 -0
- package/dist/core/middleware/built-in/graphql/hook.js +78 -0
- package/dist/core/middleware/built-in/graphql/hook.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/index.d.ts +5 -0
- package/dist/core/middleware/built-in/graphql/index.js +5 -0
- package/dist/core/middleware/built-in/graphql/index.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/middleware.d.ts +7 -0
- package/dist/core/middleware/built-in/graphql/middleware.js +54 -0
- package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.d.ts +20 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.js +37 -0
- package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/core.d.ts +19 -0
- package/dist/core/middleware/built-in/helmet/core.js +70 -0
- package/dist/core/middleware/built-in/helmet/core.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/helmet/hook.js +12 -0
- package/dist/core/middleware/built-in/helmet/hook.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/index.d.ts +6 -0
- package/dist/core/middleware/built-in/helmet/index.js +7 -0
- package/dist/core/middleware/built-in/helmet/index.js.map +1 -0
- package/dist/core/middleware/built-in/helmet/middleware.d.ts +22 -0
- package/dist/core/middleware/built-in/helmet/middleware.js +28 -0
- package/dist/core/middleware/built-in/helmet/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/http2/core.d.ts +35 -0
- package/dist/core/middleware/built-in/http2/core.js +128 -0
- package/dist/core/middleware/built-in/http2/core.js.map +1 -0
- package/dist/core/middleware/built-in/http2/hook.d.ts +5 -0
- package/dist/core/middleware/built-in/http2/hook.js +34 -0
- package/dist/core/middleware/built-in/http2/hook.js.map +1 -0
- package/dist/core/middleware/built-in/http2/index.d.ts +8 -0
- package/dist/core/middleware/built-in/http2/index.js +10 -0
- package/dist/core/middleware/built-in/http2/index.js.map +1 -0
- package/dist/core/middleware/built-in/http2/middleware.d.ts +20 -0
- package/dist/core/middleware/built-in/http2/middleware.js +31 -0
- package/dist/core/middleware/built-in/http2/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/index.d.ts +20 -1
- package/dist/core/middleware/built-in/index.js +31 -0
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/range/core.d.ts +16 -0
- package/dist/core/middleware/built-in/range/core.js +112 -0
- package/dist/core/middleware/built-in/range/core.js.map +1 -0
- package/dist/core/middleware/built-in/range/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/range/hook.js +12 -0
- package/dist/core/middleware/built-in/range/hook.js.map +1 -0
- package/dist/core/middleware/built-in/range/index.d.ts +6 -0
- package/dist/core/middleware/built-in/range/index.js +7 -0
- package/dist/core/middleware/built-in/range/index.js.map +1 -0
- package/dist/core/middleware/built-in/range/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/range/middleware.js +27 -0
- package/dist/core/middleware/built-in/range/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/session/core.js +14 -1
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +20 -0
- package/dist/core/middleware/built-in/static/core.js +143 -0
- package/dist/core/middleware/built-in/static/core.js.map +1 -0
- package/dist/core/middleware/built-in/static/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/static/hook.js +12 -0
- package/dist/core/middleware/built-in/static/hook.js.map +1 -0
- package/dist/core/middleware/built-in/static/index.d.ts +6 -0
- package/dist/core/middleware/built-in/static/index.js +7 -0
- package/dist/core/middleware/built-in/static/index.js.map +1 -0
- package/dist/core/middleware/built-in/static/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/static/middleware.js +26 -0
- package/dist/core/middleware/built-in/static/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/template/core.d.ts +19 -0
- package/dist/core/middleware/built-in/template/core.js +108 -0
- package/dist/core/middleware/built-in/template/core.js.map +1 -0
- package/dist/core/middleware/built-in/template/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/template/hook.js +12 -0
- package/dist/core/middleware/built-in/template/hook.js.map +1 -0
- package/dist/core/middleware/built-in/template/index.d.ts +6 -0
- package/dist/core/middleware/built-in/template/index.js +7 -0
- package/dist/core/middleware/built-in/template/index.js.map +1 -0
- package/dist/core/middleware/built-in/template/middleware.d.ts +21 -0
- package/dist/core/middleware/built-in/template/middleware.js +27 -0
- package/dist/core/middleware/built-in/template/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/upload/core.d.ts +29 -0
- package/dist/core/middleware/built-in/upload/core.js +66 -0
- package/dist/core/middleware/built-in/upload/core.js.map +1 -0
- package/dist/core/middleware/built-in/upload/hook.d.ts +2 -0
- package/dist/core/middleware/built-in/upload/hook.js +25 -0
- package/dist/core/middleware/built-in/upload/hook.js.map +1 -0
- package/dist/core/middleware/built-in/upload/index.d.ts +6 -0
- package/dist/core/middleware/built-in/upload/index.js +7 -0
- package/dist/core/middleware/built-in/upload/index.js.map +1 -0
- package/dist/core/middleware/built-in/upload/middleware.d.ts +18 -0
- package/dist/core/middleware/built-in/upload/middleware.js +41 -0
- package/dist/core/middleware/built-in/upload/middleware.js.map +1 -0
- package/dist/core/middleware/built-in/validation/core.js +4 -2
- package/dist/core/middleware/built-in/validation/core.js.map +1 -1
- package/dist/core/middleware/built-in/validation/middleware.js +2 -1
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -1
- package/dist/core/middleware/index.js +1 -0
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.js +5 -4
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js +1 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.js +61 -8
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.js +61 -19
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-manager.js +2 -0
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.js +12 -1
- package/dist/core/pooling/object-pool-manager.js.map +1 -1
- package/dist/core/queue/adapters/bull-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/bull-adapter.js +330 -0
- package/dist/core/queue/adapters/bull-adapter.js.map +1 -0
- package/dist/core/queue/adapters/index.d.ts +9 -0
- package/dist/core/queue/adapters/index.js +10 -0
- package/dist/core/queue/adapters/index.js.map +1 -0
- package/dist/core/queue/adapters/kafka-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/kafka-adapter.js +462 -0
- package/dist/core/queue/adapters/kafka-adapter.js.map +1 -0
- package/dist/core/queue/adapters/memory-adapter.d.ts +87 -0
- package/dist/core/queue/adapters/memory-adapter.js +415 -0
- package/dist/core/queue/adapters/memory-adapter.js.map +1 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.d.ts +86 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.js +436 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.js.map +1 -0
- package/dist/core/queue/adapters/sqs-adapter.d.ts +102 -0
- package/dist/core/queue/adapters/sqs-adapter.js +522 -0
- package/dist/core/queue/adapters/sqs-adapter.js.map +1 -0
- package/dist/core/queue/index.d.ts +11 -0
- package/dist/core/queue/index.js +14 -0
- package/dist/core/queue/index.js.map +1 -0
- package/dist/core/queue/middleware/index.d.ts +7 -0
- package/dist/core/queue/middleware/index.js +8 -0
- package/dist/core/queue/middleware/index.js.map +1 -0
- package/dist/core/queue/middleware/monitoring.d.ts +84 -0
- package/dist/core/queue/middleware/monitoring.js +145 -0
- package/dist/core/queue/middleware/monitoring.js.map +1 -0
- package/dist/core/queue/middleware/priority.d.ts +61 -0
- package/dist/core/queue/middleware/priority.js +90 -0
- package/dist/core/queue/middleware/priority.js.map +1 -0
- package/dist/core/queue/middleware/rate-limit.d.ts +34 -0
- package/dist/core/queue/middleware/rate-limit.js +109 -0
- package/dist/core/queue/middleware/rate-limit.js.map +1 -0
- package/dist/core/queue/queue-adapter.d.ts +73 -0
- package/dist/core/queue/queue-adapter.js +20 -0
- package/dist/core/queue/queue-adapter.js.map +1 -0
- package/dist/core/queue/queue-manager.d.ts +92 -0
- package/dist/core/queue/queue-manager.js +327 -0
- package/dist/core/queue/queue-manager.js.map +1 -0
- package/dist/core/queue/types.d.ts +205 -0
- package/dist/core/queue/types.js +6 -0
- package/dist/core/queue/types.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +3 -3
- package/dist/core/routing/app-integration.js +1 -1
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +1 -1
- package/dist/core/routing/index.js +42 -11
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/radix-tree.d.ts +48 -0
- package/dist/core/routing/radix-tree.js +211 -0
- package/dist/core/routing/radix-tree.js.map +1 -0
- package/dist/core/routing/router.d.ts +10 -9
- package/dist/core/routing/router.js +3 -1
- package/dist/core/routing/router.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +18 -12
- package/dist/core/routing/unified-router.js +220 -163
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/runtime/aws-lambda-adapter.js +21 -10
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.js +18 -8
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.js +36 -13
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +1 -1
- package/dist/core/runtime/node-adapter.js +7 -4
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.js +17 -6
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.d.ts +9 -2
- package/dist/core/utilities/circuit-breaker.js +32 -3
- package/dist/core/utilities/circuit-breaker.js.map +1 -1
- package/dist/core/utilities/container.js +9 -1
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.js +4 -0
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/validation/index.js +6 -1
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/workers/facade.d.ts +74 -0
- package/dist/core/workers/facade.js +98 -0
- package/dist/core/workers/facade.js.map +1 -0
- package/dist/core/workers/index.d.ts +2 -0
- package/dist/core/workers/index.js +6 -0
- package/dist/core/workers/index.js.map +1 -0
- package/dist/core/workers/worker-manager.d.ts +124 -0
- package/dist/core/workers/worker-manager.js +299 -0
- package/dist/core/workers/worker-manager.js.map +1 -0
- package/dist/core/workers/worker.d.ts +1 -0
- package/dist/core/workers/worker.js +225 -0
- package/dist/core/workers/worker.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +486 -1
- package/dist/moro.js +1194 -28
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +4 -0
- package/dist/types/config.d.ts +70 -0
- package/dist/types/core.d.ts +19 -1
- package/dist/types/events.d.ts +1 -1
- package/dist/types/events.js +1 -0
- package/dist/types/events.js.map +1 -1
- package/dist/types/module.d.ts +2 -2
- package/package.json +110 -16
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// src/core/http-server.ts
|
|
2
2
|
import { createServer } from 'http';
|
|
3
3
|
import * as zlib from 'zlib';
|
|
4
|
-
import { createReadStream } from 'fs';
|
|
5
|
-
import * as crypto from 'crypto';
|
|
6
4
|
import { promisify } from 'util';
|
|
7
5
|
import { createFrameworkLogger } from '../logger/index.js';
|
|
8
6
|
import { PathMatcher } from '../routing/path-matcher.js';
|
|
@@ -38,40 +36,6 @@ export class MoroHttpServer {
|
|
|
38
36
|
methodNotAllowed: Buffer.from('{"success":false,"error":"Method not allowed"}'),
|
|
39
37
|
rateLimited: Buffer.from('{"success":false,"error":"Rate limit exceeded"}'),
|
|
40
38
|
};
|
|
41
|
-
// Buffer pool for zero-copy operations
|
|
42
|
-
static BUFFER_SIZES = [64, 256, 1024, 4096, 16384];
|
|
43
|
-
static BUFFER_POOLS = new Map();
|
|
44
|
-
static {
|
|
45
|
-
// Pre-allocate buffer pools for zero-allocation responses
|
|
46
|
-
for (const size of MoroHttpServer.BUFFER_SIZES) {
|
|
47
|
-
MoroHttpServer.BUFFER_POOLS.set(size, []);
|
|
48
|
-
for (let i = 0; i < 50; i++) {
|
|
49
|
-
// 50 buffers per size
|
|
50
|
-
MoroHttpServer.BUFFER_POOLS.get(size).push(Buffer.allocUnsafe(size));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
static getOptimalBuffer(size) {
|
|
55
|
-
// Find the smallest buffer that fits
|
|
56
|
-
for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
|
|
57
|
-
if (size <= poolSize) {
|
|
58
|
-
const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize);
|
|
59
|
-
return pool.length > 0 ? pool.pop() : Buffer.allocUnsafe(poolSize);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return Buffer.allocUnsafe(size);
|
|
63
|
-
}
|
|
64
|
-
static returnBuffer(buffer) {
|
|
65
|
-
// Return buffer to appropriate pool
|
|
66
|
-
const size = buffer.length;
|
|
67
|
-
if (MoroHttpServer.BUFFER_POOLS.has(size)) {
|
|
68
|
-
const pool = MoroHttpServer.BUFFER_POOLS.get(size);
|
|
69
|
-
if (pool.length < 50) {
|
|
70
|
-
// Don't let pools grow too large
|
|
71
|
-
pool.push(buffer);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
39
|
constructor() {
|
|
76
40
|
this.server = createServer(this.handleRequest.bind(this));
|
|
77
41
|
// Optimize server for high performance (conservative settings for compatibility)
|
|
@@ -147,6 +111,7 @@ export class MoroHttpServer {
|
|
|
147
111
|
if (!this.routesBySegmentCount.has(segmentCount)) {
|
|
148
112
|
this.routesBySegmentCount.set(segmentCount, []);
|
|
149
113
|
}
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
150
115
|
this.routesBySegmentCount.get(segmentCount).push(route);
|
|
151
116
|
}
|
|
152
117
|
}
|
|
@@ -165,6 +130,7 @@ export class MoroHttpServer {
|
|
|
165
130
|
const originalParams = httpReq.params;
|
|
166
131
|
try {
|
|
167
132
|
// Optimized URL and query parsing with object pooling
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
168
134
|
const urlString = req.url;
|
|
169
135
|
const queryIndex = urlString.indexOf('?');
|
|
170
136
|
if (queryIndex === -1) {
|
|
@@ -200,6 +166,7 @@ export class MoroHttpServer {
|
|
|
200
166
|
return;
|
|
201
167
|
}
|
|
202
168
|
// Find matching route
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
203
170
|
const route = this.findRoute(req.method, httpReq.path);
|
|
204
171
|
if (!route) {
|
|
205
172
|
// 404 response with pre-compiled buffer
|
|
@@ -282,11 +249,12 @@ export class MoroHttpServer {
|
|
|
282
249
|
}
|
|
283
250
|
}
|
|
284
251
|
finally {
|
|
285
|
-
//
|
|
252
|
+
// Always release pooled objects back to the pool
|
|
286
253
|
// This prevents memory leaks and ensures consistent performance
|
|
287
|
-
//
|
|
254
|
+
// Check if object is empty without Object.keys()
|
|
288
255
|
if (originalParams) {
|
|
289
256
|
let isEmpty = true;
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
290
258
|
for (const _key in originalParams) {
|
|
291
259
|
isEmpty = false;
|
|
292
260
|
break;
|
|
@@ -297,6 +265,7 @@ export class MoroHttpServer {
|
|
|
297
265
|
}
|
|
298
266
|
if (httpReq.params && httpReq.params !== originalParams) {
|
|
299
267
|
let isEmpty = true;
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
300
269
|
for (const _key in httpReq.params) {
|
|
301
270
|
isEmpty = false;
|
|
302
271
|
break;
|
|
@@ -308,9 +277,10 @@ export class MoroHttpServer {
|
|
|
308
277
|
}
|
|
309
278
|
// Additional cleanup on response completion to ensure objects are returned to pool
|
|
310
279
|
res.once('finish', () => {
|
|
311
|
-
//
|
|
280
|
+
// Check if object is empty without Object.keys()
|
|
312
281
|
if (originalParams) {
|
|
313
282
|
let isEmpty = true;
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
314
284
|
for (const _key in originalParams) {
|
|
315
285
|
isEmpty = false;
|
|
316
286
|
break;
|
|
@@ -321,6 +291,7 @@ export class MoroHttpServer {
|
|
|
321
291
|
}
|
|
322
292
|
if (httpReq.params && httpReq.params !== originalParams) {
|
|
323
293
|
let isEmpty = true;
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
324
295
|
for (const _key in httpReq.params) {
|
|
325
296
|
isEmpty = false;
|
|
326
297
|
break;
|
|
@@ -356,18 +327,45 @@ export class MoroHttpServer {
|
|
|
356
327
|
streamLargeResponse(res, data) {
|
|
357
328
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
358
329
|
res.setHeader('Transfer-Encoding', 'chunked');
|
|
359
|
-
// Stream
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
330
|
+
// Stream large JSON responses to prevent memory issues
|
|
331
|
+
if (Array.isArray(data) && data.length > 100) {
|
|
332
|
+
// Stream large arrays element by element
|
|
333
|
+
res.write('[');
|
|
334
|
+
// Stream each array element
|
|
335
|
+
let first = true;
|
|
336
|
+
for (const item of data) {
|
|
337
|
+
if (!first)
|
|
338
|
+
res.write(',');
|
|
339
|
+
res.write(JSON.stringify(item));
|
|
340
|
+
first = false;
|
|
341
|
+
}
|
|
342
|
+
// Write closing bracket and end
|
|
343
|
+
res.end(']');
|
|
344
|
+
}
|
|
345
|
+
else if (typeof data === 'object' && data !== null && Object.keys(data).length > 50) {
|
|
346
|
+
// For large objects, stream key-value pairs
|
|
347
|
+
res.write('{');
|
|
348
|
+
const keys = Object.keys(data);
|
|
349
|
+
let first = true;
|
|
350
|
+
for (const key of keys) {
|
|
351
|
+
if (!first)
|
|
352
|
+
res.write(',');
|
|
353
|
+
// Properly escape the key using JSON.stringify
|
|
354
|
+
res.write(`${JSON.stringify(key)}:${JSON.stringify(data[key])}`);
|
|
355
|
+
first = false;
|
|
356
|
+
}
|
|
357
|
+
res.end('}');
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// For smaller data, still avoid the old chunking approach
|
|
361
|
+
const jsonString = JSON.stringify(data);
|
|
362
|
+
res.end(jsonString);
|
|
365
363
|
}
|
|
366
|
-
res.end();
|
|
367
364
|
}
|
|
368
365
|
normalizePath(path) {
|
|
369
366
|
// Check cache first
|
|
370
367
|
if (this.pathNormalizationCache.has(path)) {
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
371
369
|
return this.pathNormalizationCache.get(path);
|
|
372
370
|
}
|
|
373
371
|
// Normalization: remove trailing slash (except root), decode once
|
|
@@ -460,76 +458,21 @@ export class MoroHttpServer {
|
|
|
460
458
|
httpRes.json = async (data) => {
|
|
461
459
|
if (httpRes.headersSent)
|
|
462
460
|
return;
|
|
463
|
-
// JSON serialization
|
|
464
|
-
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
if (data && typeof data === 'object' && 'success' in data) {
|
|
468
|
-
// Check for common patterns using 'in' operator (faster than Object.keys for small objects)
|
|
469
|
-
const hasData = 'data' in data;
|
|
470
|
-
const hasError = 'error' in data;
|
|
471
|
-
const hasTotal = 'total' in data;
|
|
472
|
-
// Fast path: {success, data} - most common pattern
|
|
473
|
-
if (hasData && !hasError && !hasTotal) {
|
|
474
|
-
// Verify it's exactly 2 keys by checking no other common keys exist
|
|
475
|
-
if (!('message' in data) && !('code' in data) && !('status' in data)) {
|
|
476
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
jsonString = JSON.stringify(data);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
else if (hasError && !hasData && !hasTotal) {
|
|
483
|
-
// Fast path: {success, error}
|
|
484
|
-
if (!('message' in data) && !('code' in data) && !('status' in data)) {
|
|
485
|
-
jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
jsonString = JSON.stringify(data);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
else if (hasData && hasError && !hasTotal) {
|
|
492
|
-
// Fast path: {success, data, error}
|
|
493
|
-
if (!('message' in data) && !('code' in data) && !('status' in data)) {
|
|
494
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
jsonString = JSON.stringify(data);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
else if (hasData && hasTotal && !hasError) {
|
|
501
|
-
// Fast path: {success, data, total}
|
|
502
|
-
if (!('message' in data) && !('code' in data) && !('status' in data)) {
|
|
503
|
-
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
jsonString = JSON.stringify(data);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
// Complex object - use standard JSON.stringify
|
|
511
|
-
jsonString = JSON.stringify(data);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
jsonString = JSON.stringify(data);
|
|
516
|
-
}
|
|
517
|
-
// Use buffer pool for zero-allocation responses
|
|
518
|
-
const estimatedSize = jsonString.length;
|
|
519
|
-
if (estimatedSize > 32768) {
|
|
461
|
+
// Simple, optimized JSON serialization - let V8 handle the optimization
|
|
462
|
+
const jsonString = JSON.stringify(data);
|
|
463
|
+
// Large response check - stream if needed
|
|
464
|
+
if (jsonString.length > 32768) {
|
|
520
465
|
// Large response - stream it
|
|
521
466
|
return this.streamLargeResponse(httpRes, data);
|
|
522
467
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
// Slice to actual size to avoid sending extra bytes
|
|
526
|
-
const finalBuffer = actualLength === buffer.length ? buffer : buffer.subarray(0, actualLength);
|
|
468
|
+
// Use efficient buffer allocation - let Node.js handle optimization
|
|
469
|
+
const finalBuffer = Buffer.from(jsonString, 'utf8');
|
|
527
470
|
// Optimized header setting - set multiple headers at once when possible
|
|
528
471
|
const headers = {
|
|
529
472
|
'Content-Type': 'application/json; charset=utf-8',
|
|
530
473
|
};
|
|
531
474
|
// Compression with buffer pool - EARLY EXIT if disabled or below threshold
|
|
532
|
-
//
|
|
475
|
+
// Only make this async if compression is actually happening
|
|
533
476
|
if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
|
|
534
477
|
const acceptEncoding = httpRes.req.headers['accept-encoding'];
|
|
535
478
|
if (acceptEncoding && acceptEncoding.includes('gzip')) {
|
|
@@ -540,8 +483,6 @@ export class MoroHttpServer {
|
|
|
540
483
|
// Batch write all headers at once (50-100% faster)
|
|
541
484
|
httpRes.writeHead(httpRes.statusCode || 200, headers);
|
|
542
485
|
httpRes.end(compressed);
|
|
543
|
-
// Return buffer to pool after response
|
|
544
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
545
486
|
});
|
|
546
487
|
return;
|
|
547
488
|
}
|
|
@@ -553,8 +494,6 @@ export class MoroHttpServer {
|
|
|
553
494
|
// Batch write all headers at once
|
|
554
495
|
httpRes.writeHead(httpRes.statusCode || 200, headers);
|
|
555
496
|
httpRes.end(compressed);
|
|
556
|
-
// Return buffer to pool after response
|
|
557
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
558
497
|
});
|
|
559
498
|
return;
|
|
560
499
|
}
|
|
@@ -564,8 +503,6 @@ export class MoroHttpServer {
|
|
|
564
503
|
// Batch write all headers at once
|
|
565
504
|
httpRes.writeHead(httpRes.statusCode || 200, headers);
|
|
566
505
|
httpRes.end(finalBuffer);
|
|
567
|
-
// Return buffer to pool after response (zero-copy achievement!)
|
|
568
|
-
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
569
506
|
};
|
|
570
507
|
httpRes.send = (data) => {
|
|
571
508
|
if (httpRes.headersSent)
|
|
@@ -626,15 +563,31 @@ export class MoroHttpServer {
|
|
|
626
563
|
if (options.path)
|
|
627
564
|
cookieString += `; Path=${options.path}`;
|
|
628
565
|
const existingCookies = httpRes.getHeader('Set-Cookie') || [];
|
|
566
|
+
// Avoid spread operator - direct array manipulation
|
|
629
567
|
const cookies = Array.isArray(existingCookies)
|
|
630
|
-
?
|
|
568
|
+
? existingCookies
|
|
631
569
|
: [existingCookies];
|
|
632
570
|
cookies.push(cookieString);
|
|
633
571
|
httpRes.setHeader('Set-Cookie', cookies);
|
|
634
572
|
return httpRes;
|
|
635
573
|
};
|
|
636
574
|
httpRes.clearCookie = (name, options = {}) => {
|
|
637
|
-
|
|
575
|
+
// Avoid spread operator - manually set properties
|
|
576
|
+
const clearOptions = {
|
|
577
|
+
expires: new Date(0),
|
|
578
|
+
maxAge: 0,
|
|
579
|
+
};
|
|
580
|
+
// Copy other options manually
|
|
581
|
+
if (options.path !== undefined)
|
|
582
|
+
clearOptions.path = options.path;
|
|
583
|
+
if (options.domain !== undefined)
|
|
584
|
+
clearOptions.domain = options.domain;
|
|
585
|
+
if (options.httpOnly !== undefined)
|
|
586
|
+
clearOptions.httpOnly = options.httpOnly;
|
|
587
|
+
if (options.secure !== undefined)
|
|
588
|
+
clearOptions.secure = options.secure;
|
|
589
|
+
if (options.sameSite !== undefined)
|
|
590
|
+
clearOptions.sameSite = options.sameSite;
|
|
638
591
|
return httpRes.cookie(name, '', clearOptions);
|
|
639
592
|
};
|
|
640
593
|
httpRes.redirect = (url, status = 302) => {
|
|
@@ -665,7 +618,7 @@ export class MoroHttpServer {
|
|
|
665
618
|
httpRes.setHeader('Cache-Control', 'public, max-age=31536000'); // 1 year for static files
|
|
666
619
|
httpRes.end(data);
|
|
667
620
|
}
|
|
668
|
-
catch
|
|
621
|
+
catch {
|
|
669
622
|
httpRes.status(404).json({ success: false, error: 'File not found' });
|
|
670
623
|
}
|
|
671
624
|
};
|
|
@@ -676,15 +629,17 @@ export class MoroHttpServer {
|
|
|
676
629
|
// Note: removeHeader is inherited from ServerResponse, we don't override it
|
|
677
630
|
httpRes.setBulkHeaders = (headers) => {
|
|
678
631
|
if (httpRes.headersSent) {
|
|
632
|
+
// Only enumerate keys for warning if headers were already sent
|
|
633
|
+
const attemptedHeaderKeys = [];
|
|
634
|
+
for (const key in headers) {
|
|
635
|
+
attemptedHeaderKeys.push(key);
|
|
636
|
+
}
|
|
679
637
|
this.logger.warn('Cannot set headers - headers already sent', 'HeaderWarning', {
|
|
680
|
-
attemptedHeaders:
|
|
638
|
+
attemptedHeaders: attemptedHeaderKeys,
|
|
681
639
|
});
|
|
682
640
|
return httpRes;
|
|
683
641
|
}
|
|
684
|
-
const
|
|
685
|
-
const headerKeysLen = headerKeys.length;
|
|
686
|
-
for (let i = 0; i < headerKeysLen; i++) {
|
|
687
|
-
const key = headerKeys[i];
|
|
642
|
+
for (const key in headers) {
|
|
688
643
|
httpRes.setHeader(key, headers[key]);
|
|
689
644
|
}
|
|
690
645
|
return httpRes;
|
|
@@ -753,10 +708,18 @@ export class MoroHttpServer {
|
|
|
753
708
|
return mimeType;
|
|
754
709
|
}
|
|
755
710
|
async parseBody(req) {
|
|
711
|
+
const contentType = req.headers['content-type'] || '';
|
|
712
|
+
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
713
|
+
const maxSize = 10 * 1024 * 1024; // 10MB limit
|
|
714
|
+
// For very large payloads, return a streaming interface instead of buffering
|
|
715
|
+
if (contentLength > maxSize / 2) {
|
|
716
|
+
// Stream for payloads > 5MB
|
|
717
|
+
return this.createStreamingBodyParser(req, contentType, maxSize);
|
|
718
|
+
}
|
|
719
|
+
// Standard buffered parsing for smaller payloads
|
|
756
720
|
return new Promise((resolve, reject) => {
|
|
757
721
|
const chunks = [];
|
|
758
722
|
let totalLength = 0;
|
|
759
|
-
const maxSize = 10 * 1024 * 1024; // 10MB limit
|
|
760
723
|
req.on('data', (chunk) => {
|
|
761
724
|
totalLength += chunk.length;
|
|
762
725
|
if (totalLength > maxSize) {
|
|
@@ -768,7 +731,6 @@ export class MoroHttpServer {
|
|
|
768
731
|
req.on('end', () => {
|
|
769
732
|
try {
|
|
770
733
|
const body = Buffer.concat(chunks);
|
|
771
|
-
const contentType = req.headers['content-type'] || '';
|
|
772
734
|
if (contentType.includes('application/json')) {
|
|
773
735
|
resolve(JSON.parse(body.toString()));
|
|
774
736
|
}
|
|
@@ -789,6 +751,132 @@ export class MoroHttpServer {
|
|
|
789
751
|
req.on('error', reject);
|
|
790
752
|
});
|
|
791
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Create a streaming body parser for large payloads
|
|
756
|
+
* Returns a streaming interface instead of buffering
|
|
757
|
+
*/
|
|
758
|
+
createStreamingBodyParser(req, contentType, maxSize) {
|
|
759
|
+
let totalLength = 0;
|
|
760
|
+
const chunks = [];
|
|
761
|
+
return new Promise((resolve, reject) => {
|
|
762
|
+
const streamParser = {
|
|
763
|
+
// Streaming JSON parser for large JSON payloads
|
|
764
|
+
json: () => this.streamJsonParse(req, maxSize),
|
|
765
|
+
// Streaming form data parser
|
|
766
|
+
form: () => this.streamFormParse(req, maxSize),
|
|
767
|
+
// Raw stream access
|
|
768
|
+
stream: () => ({
|
|
769
|
+
onData: (callback) => {
|
|
770
|
+
req.on('data', (chunk) => {
|
|
771
|
+
totalLength += chunk.length;
|
|
772
|
+
if (totalLength > maxSize) {
|
|
773
|
+
reject(new Error('Request body too large'));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
callback(chunk);
|
|
777
|
+
});
|
|
778
|
+
},
|
|
779
|
+
onEnd: (callback) => {
|
|
780
|
+
req.on('end', callback);
|
|
781
|
+
},
|
|
782
|
+
onError: (callback) => {
|
|
783
|
+
req.on('error', callback);
|
|
784
|
+
},
|
|
785
|
+
}),
|
|
786
|
+
// Traditional buffered parsing (fallback)
|
|
787
|
+
buffer: async () => {
|
|
788
|
+
return new Promise((resolveBuffer, rejectBuffer) => {
|
|
789
|
+
req.on('data', (chunk) => {
|
|
790
|
+
totalLength += chunk.length;
|
|
791
|
+
if (totalLength > maxSize) {
|
|
792
|
+
rejectBuffer(new Error('Request body too large'));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
chunks.push(chunk);
|
|
796
|
+
});
|
|
797
|
+
req.on('end', () => {
|
|
798
|
+
try {
|
|
799
|
+
const body = Buffer.concat(chunks);
|
|
800
|
+
if (contentType.includes('application/json')) {
|
|
801
|
+
resolveBuffer(JSON.parse(body.toString()));
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
resolveBuffer(body.toString());
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (error) {
|
|
808
|
+
rejectBuffer(error);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
req.on('error', rejectBuffer);
|
|
812
|
+
});
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
// Auto-detect and return appropriate parser
|
|
816
|
+
if (contentType.includes('application/json')) {
|
|
817
|
+
resolve({ type: 'json', parser: streamParser.json });
|
|
818
|
+
}
|
|
819
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
820
|
+
resolve({ type: 'form', parser: streamParser.form });
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
resolve({ type: 'stream', parser: streamParser.stream });
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Streaming JSON parser for large payloads
|
|
829
|
+
*/
|
|
830
|
+
async streamJsonParse(req, maxSize) {
|
|
831
|
+
return new Promise((resolve, reject) => {
|
|
832
|
+
let jsonString = '';
|
|
833
|
+
let totalLength = 0;
|
|
834
|
+
req.on('data', (chunk) => {
|
|
835
|
+
totalLength += chunk.length;
|
|
836
|
+
if (totalLength > maxSize) {
|
|
837
|
+
reject(new Error('Request body too large'));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
jsonString += chunk.toString();
|
|
841
|
+
});
|
|
842
|
+
req.on('end', () => {
|
|
843
|
+
try {
|
|
844
|
+
// For very large JSON, consider streaming JSON parsing in the future
|
|
845
|
+
resolve(JSON.parse(jsonString));
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
848
|
+
reject(error);
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
req.on('error', reject);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Streaming form data parser
|
|
856
|
+
*/
|
|
857
|
+
async streamFormParse(req, maxSize) {
|
|
858
|
+
return new Promise((resolve, reject) => {
|
|
859
|
+
let formData = '';
|
|
860
|
+
let totalLength = 0;
|
|
861
|
+
req.on('data', (chunk) => {
|
|
862
|
+
totalLength += chunk.length;
|
|
863
|
+
if (totalLength > maxSize) {
|
|
864
|
+
reject(new Error('Request body too large'));
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
formData += chunk.toString();
|
|
868
|
+
});
|
|
869
|
+
req.on('end', () => {
|
|
870
|
+
try {
|
|
871
|
+
resolve(this.parseUrlEncoded(formData));
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
reject(error);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
req.on('error', reject);
|
|
878
|
+
});
|
|
879
|
+
}
|
|
792
880
|
parseMultipart(buffer, contentType) {
|
|
793
881
|
const boundary = contentType.split('boundary=')[1];
|
|
794
882
|
if (!boundary) {
|
|
@@ -875,6 +963,7 @@ export class MoroHttpServer {
|
|
|
875
963
|
const cacheKey = `${method}:${path}`;
|
|
876
964
|
// Check cache first (hot path optimization) - BEFORE any other work
|
|
877
965
|
if (this.routeCache.has(cacheKey)) {
|
|
966
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
878
967
|
return this.routeCache.get(cacheKey);
|
|
879
968
|
}
|
|
880
969
|
// Normalize path for consistent matching (only if not cached)
|
|
@@ -882,9 +971,10 @@ export class MoroHttpServer {
|
|
|
882
971
|
const normalizedCacheKey = normalizedPath !== path ? `${method}:${normalizedPath}` : cacheKey;
|
|
883
972
|
// Check cache again with normalized path
|
|
884
973
|
if (normalizedPath !== path && this.routeCache.has(normalizedCacheKey)) {
|
|
974
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
885
975
|
return this.routeCache.get(normalizedCacheKey);
|
|
886
976
|
}
|
|
887
|
-
//
|
|
977
|
+
// O(1) static route lookup
|
|
888
978
|
const staticRoute = this.staticRoutes.get(normalizedCacheKey);
|
|
889
979
|
if (staticRoute) {
|
|
890
980
|
this.routeCache.set(normalizedCacheKey, staticRoute);
|
|
@@ -893,7 +983,7 @@ export class MoroHttpServer {
|
|
|
893
983
|
}
|
|
894
984
|
return staticRoute;
|
|
895
985
|
}
|
|
896
|
-
//
|
|
986
|
+
// Dynamic route matching by segment count
|
|
897
987
|
let route = null;
|
|
898
988
|
const dynamicRoutesLen = this.dynamicRoutes.length;
|
|
899
989
|
if (dynamicRoutesLen > 0) {
|
|
@@ -997,659 +1087,4 @@ export class MoroHttpServer {
|
|
|
997
1087
|
};
|
|
998
1088
|
}
|
|
999
1089
|
}
|
|
1000
|
-
// Built-in middleware
|
|
1001
|
-
export const middleware = {
|
|
1002
|
-
cors: (options = {}) => {
|
|
1003
|
-
return (req, res, next) => {
|
|
1004
|
-
res.setHeader('Access-Control-Allow-Origin', options.origin || '*');
|
|
1005
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
1006
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
1007
|
-
if (options.credentials) {
|
|
1008
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
1009
|
-
}
|
|
1010
|
-
if (req.method === 'OPTIONS') {
|
|
1011
|
-
res.status(200).send('');
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
next();
|
|
1015
|
-
};
|
|
1016
|
-
},
|
|
1017
|
-
helmet: () => {
|
|
1018
|
-
return (req, res, next) => {
|
|
1019
|
-
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
1020
|
-
res.setHeader('X-Frame-Options', 'DENY');
|
|
1021
|
-
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
1022
|
-
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
1023
|
-
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
1024
|
-
res.setHeader('Content-Security-Policy', "default-src 'self'");
|
|
1025
|
-
next();
|
|
1026
|
-
};
|
|
1027
|
-
},
|
|
1028
|
-
compression: (options = {}) => {
|
|
1029
|
-
const threshold = options.threshold || 1024;
|
|
1030
|
-
const level = options.level || 6;
|
|
1031
|
-
return (req, res, next) => {
|
|
1032
|
-
const acceptEncoding = req.headers['accept-encoding'] || '';
|
|
1033
|
-
// Override res.json to compress responses
|
|
1034
|
-
const originalJson = res.json;
|
|
1035
|
-
const originalSend = res.send;
|
|
1036
|
-
const compressResponse = (data, isJson = false) => {
|
|
1037
|
-
const content = isJson ? JSON.stringify(data) : data;
|
|
1038
|
-
const buffer = Buffer.from(content);
|
|
1039
|
-
if (buffer.length < threshold) {
|
|
1040
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1041
|
-
}
|
|
1042
|
-
if (acceptEncoding.includes('gzip')) {
|
|
1043
|
-
zlib.gzip(buffer, { level }, (err, compressed) => {
|
|
1044
|
-
if (err) {
|
|
1045
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1046
|
-
}
|
|
1047
|
-
if (!res.headersSent) {
|
|
1048
|
-
res.setHeader('Content-Encoding', 'gzip');
|
|
1049
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1050
|
-
}
|
|
1051
|
-
res.end(compressed);
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
else if (acceptEncoding.includes('deflate')) {
|
|
1055
|
-
zlib.deflate(buffer, { level }, (err, compressed) => {
|
|
1056
|
-
if (err) {
|
|
1057
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1058
|
-
}
|
|
1059
|
-
if (!res.headersSent) {
|
|
1060
|
-
res.setHeader('Content-Encoding', 'deflate');
|
|
1061
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1062
|
-
}
|
|
1063
|
-
res.end(compressed);
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
res.json = function (data) {
|
|
1071
|
-
// Ensure charset is set for Safari compatibility
|
|
1072
|
-
this.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1073
|
-
compressResponse(data, true);
|
|
1074
|
-
return this;
|
|
1075
|
-
};
|
|
1076
|
-
res.send = function (data) {
|
|
1077
|
-
compressResponse(data, false);
|
|
1078
|
-
return this;
|
|
1079
|
-
};
|
|
1080
|
-
next();
|
|
1081
|
-
};
|
|
1082
|
-
},
|
|
1083
|
-
requestLogger: () => {
|
|
1084
|
-
return (req, res, next) => {
|
|
1085
|
-
const start = Date.now();
|
|
1086
|
-
res.on('finish', () => {
|
|
1087
|
-
const duration = Date.now() - start;
|
|
1088
|
-
// Request completed - logged by framework
|
|
1089
|
-
});
|
|
1090
|
-
next();
|
|
1091
|
-
};
|
|
1092
|
-
},
|
|
1093
|
-
bodySize: (options = {}) => {
|
|
1094
|
-
const limit = options.limit || '10mb';
|
|
1095
|
-
const limitBytes = parseSize(limit);
|
|
1096
|
-
return (req, res, next) => {
|
|
1097
|
-
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
1098
|
-
if (contentLength > limitBytes) {
|
|
1099
|
-
res.status(413).json({
|
|
1100
|
-
success: false,
|
|
1101
|
-
error: 'Request entity too large',
|
|
1102
|
-
limit: limit,
|
|
1103
|
-
});
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
next();
|
|
1107
|
-
};
|
|
1108
|
-
},
|
|
1109
|
-
static: (options) => {
|
|
1110
|
-
return async (req, res, next) => {
|
|
1111
|
-
// Only handle GET and HEAD requests
|
|
1112
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1113
|
-
next();
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
try {
|
|
1117
|
-
const fs = await import('fs/promises');
|
|
1118
|
-
const path = await import('path');
|
|
1119
|
-
const crypto = await import('crypto');
|
|
1120
|
-
let filePath = path.join(options.root, req.path);
|
|
1121
|
-
// Security: prevent directory traversal
|
|
1122
|
-
if (!filePath.startsWith(path.resolve(options.root))) {
|
|
1123
|
-
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
// Handle dotfiles
|
|
1127
|
-
const basename = path.basename(filePath);
|
|
1128
|
-
if (basename.startsWith('.')) {
|
|
1129
|
-
if (options.dotfiles === 'deny') {
|
|
1130
|
-
res.status(403).json({ success: false, error: 'Forbidden' });
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
else if (options.dotfiles === 'ignore') {
|
|
1134
|
-
next();
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
let stats;
|
|
1139
|
-
try {
|
|
1140
|
-
stats = await fs.stat(filePath);
|
|
1141
|
-
}
|
|
1142
|
-
catch (error) {
|
|
1143
|
-
next(); // File not found, let other middleware handle
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
// Handle directories
|
|
1147
|
-
if (stats.isDirectory()) {
|
|
1148
|
-
const indexFiles = options.index || ['index.html', 'index.htm'];
|
|
1149
|
-
let indexFound = false;
|
|
1150
|
-
for (const indexFile of indexFiles) {
|
|
1151
|
-
const indexPath = path.join(filePath, indexFile);
|
|
1152
|
-
try {
|
|
1153
|
-
const indexStats = await fs.stat(indexPath);
|
|
1154
|
-
if (indexStats.isFile()) {
|
|
1155
|
-
filePath = indexPath;
|
|
1156
|
-
stats = indexStats;
|
|
1157
|
-
indexFound = true;
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
catch (error) {
|
|
1162
|
-
// Continue to next index file
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
if (!indexFound) {
|
|
1166
|
-
next();
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
// Set headers with proper mime type and charset
|
|
1171
|
-
const ext = path.extname(filePath);
|
|
1172
|
-
const mimeTypes = {
|
|
1173
|
-
'.html': 'text/html',
|
|
1174
|
-
'.css': 'text/css',
|
|
1175
|
-
'.js': 'application/javascript',
|
|
1176
|
-
'.json': 'application/json',
|
|
1177
|
-
'.png': 'image/png',
|
|
1178
|
-
'.jpg': 'image/jpeg',
|
|
1179
|
-
'.jpeg': 'image/jpeg',
|
|
1180
|
-
'.gif': 'image/gif',
|
|
1181
|
-
'.svg': 'image/svg+xml',
|
|
1182
|
-
'.ico': 'image/x-icon',
|
|
1183
|
-
'.pdf': 'application/pdf',
|
|
1184
|
-
'.txt': 'text/plain',
|
|
1185
|
-
'.xml': 'application/xml',
|
|
1186
|
-
};
|
|
1187
|
-
const baseMimeType = mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
1188
|
-
// Add charset for text-based files
|
|
1189
|
-
const textTypes = [
|
|
1190
|
-
'text/',
|
|
1191
|
-
'application/json',
|
|
1192
|
-
'application/javascript',
|
|
1193
|
-
'application/xml',
|
|
1194
|
-
'image/svg+xml',
|
|
1195
|
-
];
|
|
1196
|
-
const needsCharset = textTypes.some(type => baseMimeType.startsWith(type));
|
|
1197
|
-
const contentType = needsCharset ? `${baseMimeType}; charset=utf-8` : baseMimeType;
|
|
1198
|
-
res.setHeader('Content-Type', contentType);
|
|
1199
|
-
res.setHeader('Content-Length', stats.size);
|
|
1200
|
-
// Cache headers
|
|
1201
|
-
if (options.maxAge) {
|
|
1202
|
-
res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
|
|
1203
|
-
}
|
|
1204
|
-
// ETag support
|
|
1205
|
-
if (options.etag !== false) {
|
|
1206
|
-
const etag = crypto
|
|
1207
|
-
.createHash('md5')
|
|
1208
|
-
.update(`${stats.mtime.getTime()}-${stats.size}`)
|
|
1209
|
-
.digest('hex');
|
|
1210
|
-
res.setHeader('ETag', `"${etag}"`);
|
|
1211
|
-
// Handle conditional requests
|
|
1212
|
-
const ifNoneMatch = req.headers['if-none-match'];
|
|
1213
|
-
if (ifNoneMatch === `"${etag}"`) {
|
|
1214
|
-
res.statusCode = 304;
|
|
1215
|
-
res.end();
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
// Handle HEAD requests
|
|
1220
|
-
if (req.method === 'HEAD') {
|
|
1221
|
-
res.end();
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
// Send file
|
|
1225
|
-
const data = await fs.readFile(filePath);
|
|
1226
|
-
res.end(data);
|
|
1227
|
-
}
|
|
1228
|
-
catch (error) {
|
|
1229
|
-
res.status(500).json({ success: false, error: 'Internal server error' });
|
|
1230
|
-
}
|
|
1231
|
-
};
|
|
1232
|
-
},
|
|
1233
|
-
upload: (options = {}) => {
|
|
1234
|
-
return (req, res, next) => {
|
|
1235
|
-
const contentType = req.headers['content-type'] || '';
|
|
1236
|
-
if (!contentType.includes('multipart/form-data')) {
|
|
1237
|
-
next();
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
// File upload handling is now built into parseBody method
|
|
1241
|
-
// This middleware can add additional validation
|
|
1242
|
-
if (req.body && req.body.files) {
|
|
1243
|
-
const files = req.body.files;
|
|
1244
|
-
const maxFileSize = options.maxFileSize || 5 * 1024 * 1024; // 5MB default
|
|
1245
|
-
const maxFiles = options.maxFiles || 10;
|
|
1246
|
-
const allowedTypes = options.allowedTypes;
|
|
1247
|
-
// Validate file count
|
|
1248
|
-
if (Object.keys(files).length > maxFiles) {
|
|
1249
|
-
res.status(400).json({
|
|
1250
|
-
success: false,
|
|
1251
|
-
error: `Too many files. Maximum ${maxFiles} allowed.`,
|
|
1252
|
-
});
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
// Validate each file
|
|
1256
|
-
for (const [fieldName, file] of Object.entries(files)) {
|
|
1257
|
-
const fileData = file;
|
|
1258
|
-
// Validate file size
|
|
1259
|
-
if (fileData.size > maxFileSize) {
|
|
1260
|
-
res.status(400).json({
|
|
1261
|
-
success: false,
|
|
1262
|
-
error: `File ${fileData.filename} is too large. Maximum ${maxFileSize} bytes allowed.`,
|
|
1263
|
-
});
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
// Validate file type
|
|
1267
|
-
if (allowedTypes && !allowedTypes.includes(fileData.mimetype)) {
|
|
1268
|
-
res.status(400).json({
|
|
1269
|
-
success: false,
|
|
1270
|
-
error: `File type ${fileData.mimetype} not allowed.`,
|
|
1271
|
-
});
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
// Store files in request for easy access
|
|
1276
|
-
req.files = files;
|
|
1277
|
-
}
|
|
1278
|
-
next();
|
|
1279
|
-
};
|
|
1280
|
-
},
|
|
1281
|
-
template: (options) => {
|
|
1282
|
-
const templateCache = new Map();
|
|
1283
|
-
return async (req, res, next) => {
|
|
1284
|
-
// Add render method to response
|
|
1285
|
-
res.render = async (template, data = {}) => {
|
|
1286
|
-
try {
|
|
1287
|
-
const fs = await import('fs/promises');
|
|
1288
|
-
const path = await import('path');
|
|
1289
|
-
const templatePath = path.join(options.views, `${template}.html`);
|
|
1290
|
-
let templateContent;
|
|
1291
|
-
// Check cache first
|
|
1292
|
-
if (options.cache && templateCache.has(templatePath)) {
|
|
1293
|
-
templateContent = templateCache.get(templatePath);
|
|
1294
|
-
}
|
|
1295
|
-
else {
|
|
1296
|
-
templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
1297
|
-
if (options.cache) {
|
|
1298
|
-
templateCache.set(templatePath, templateContent);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
// Simple template engine - replace {{variable}} with values
|
|
1302
|
-
let rendered = templateContent;
|
|
1303
|
-
// Handle basic variable substitution
|
|
1304
|
-
rendered = rendered.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
1305
|
-
return data[key] !== undefined ? String(data[key]) : match;
|
|
1306
|
-
});
|
|
1307
|
-
// Handle nested object properties like {{user.name}}
|
|
1308
|
-
rendered = rendered.replace(/\{\{([\w.]+)\}\}/g, (match, key) => {
|
|
1309
|
-
const value = key.split('.').reduce((obj, prop) => obj?.[prop], data);
|
|
1310
|
-
return value !== undefined ? String(value) : match;
|
|
1311
|
-
});
|
|
1312
|
-
// Handle loops: {{#each items}}{{name}}{{/each}}
|
|
1313
|
-
rendered = rendered.replace(/\{\{#each (\w+)\}\}(.*?)\{\{\/each\}\}/gs, (match, arrayKey, template) => {
|
|
1314
|
-
const array = data[arrayKey];
|
|
1315
|
-
if (!Array.isArray(array))
|
|
1316
|
-
return '';
|
|
1317
|
-
return array
|
|
1318
|
-
.map(item => {
|
|
1319
|
-
let itemTemplate = template;
|
|
1320
|
-
// Replace variables in the loop template
|
|
1321
|
-
itemTemplate = itemTemplate.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
1322
|
-
return item[key] !== undefined ? String(item[key]) : match;
|
|
1323
|
-
});
|
|
1324
|
-
return itemTemplate;
|
|
1325
|
-
})
|
|
1326
|
-
.join('');
|
|
1327
|
-
});
|
|
1328
|
-
// Handle conditionals: {{#if condition}}content{{/if}}
|
|
1329
|
-
rendered = rendered.replace(/\{\{#if (\w+)\}\}(.*?)\{\{\/if\}\}/gs, (match, conditionKey, content) => {
|
|
1330
|
-
const condition = data[conditionKey];
|
|
1331
|
-
return condition ? content : '';
|
|
1332
|
-
});
|
|
1333
|
-
// Handle layout
|
|
1334
|
-
if (options.defaultLayout) {
|
|
1335
|
-
const layoutPath = path.join(options.views, 'layouts', `${options.defaultLayout}.html`);
|
|
1336
|
-
try {
|
|
1337
|
-
let layoutContent;
|
|
1338
|
-
if (options.cache && templateCache.has(layoutPath)) {
|
|
1339
|
-
layoutContent = templateCache.get(layoutPath);
|
|
1340
|
-
}
|
|
1341
|
-
else {
|
|
1342
|
-
layoutContent = await fs.readFile(layoutPath, 'utf-8');
|
|
1343
|
-
if (options.cache) {
|
|
1344
|
-
templateCache.set(layoutPath, layoutContent);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
rendered = layoutContent.replace(/\{\{body\}\}/, rendered);
|
|
1348
|
-
}
|
|
1349
|
-
catch (error) {
|
|
1350
|
-
// Layout not found, use template as-is
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
res.setHeader('Content-Type', 'text/html');
|
|
1354
|
-
res.end(rendered);
|
|
1355
|
-
}
|
|
1356
|
-
catch (error) {
|
|
1357
|
-
res.status(500).json({ success: false, error: 'Template rendering failed' });
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
next();
|
|
1361
|
-
};
|
|
1362
|
-
},
|
|
1363
|
-
// HTTP/2 Server Push middleware
|
|
1364
|
-
http2Push: (options = {}) => {
|
|
1365
|
-
return (req, res, next) => {
|
|
1366
|
-
// Add HTTP/2 push capability to response
|
|
1367
|
-
res.push = (path, options = {}) => {
|
|
1368
|
-
// Check if HTTP/2 is supported
|
|
1369
|
-
if (req.httpVersion === '2.0' && res.stream && res.stream.pushAllowed) {
|
|
1370
|
-
try {
|
|
1371
|
-
const pushStream = res.stream.pushStream({
|
|
1372
|
-
':method': 'GET',
|
|
1373
|
-
':path': path,
|
|
1374
|
-
...options.headers,
|
|
1375
|
-
});
|
|
1376
|
-
if (pushStream) {
|
|
1377
|
-
// Handle push stream
|
|
1378
|
-
return pushStream;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
catch (error) {
|
|
1382
|
-
// Push failed, continue normally
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
return null;
|
|
1386
|
-
};
|
|
1387
|
-
// Auto-push configured resources
|
|
1388
|
-
if (options.resources && (!options.condition || options.condition(req))) {
|
|
1389
|
-
for (const resource of options.resources) {
|
|
1390
|
-
res.push?.(resource.path, {
|
|
1391
|
-
headers: {
|
|
1392
|
-
'content-type': resource.type || 'text/plain',
|
|
1393
|
-
},
|
|
1394
|
-
});
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
next();
|
|
1398
|
-
};
|
|
1399
|
-
},
|
|
1400
|
-
// Server-Sent Events middleware
|
|
1401
|
-
sse: (options = {}) => {
|
|
1402
|
-
return (req, res, next) => {
|
|
1403
|
-
// Only handle SSE requests
|
|
1404
|
-
if (req.headers.accept?.includes('text/event-stream')) {
|
|
1405
|
-
// Set SSE headers
|
|
1406
|
-
if (!res.headersSent) {
|
|
1407
|
-
res.writeHead(200, {
|
|
1408
|
-
'Content-Type': 'text/event-stream',
|
|
1409
|
-
'Cache-Control': 'no-cache',
|
|
1410
|
-
Connection: 'keep-alive',
|
|
1411
|
-
'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
|
|
1412
|
-
'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
// Add SSE methods to response
|
|
1416
|
-
res.sendEvent = (data, event, id) => {
|
|
1417
|
-
if (id)
|
|
1418
|
-
res.write(`id: ${id}\n`);
|
|
1419
|
-
if (event)
|
|
1420
|
-
res.write(`event: ${event}\n`);
|
|
1421
|
-
res.write(`data: ${typeof data === 'string' ? data : JSON.stringify(data)}\n\n`);
|
|
1422
|
-
};
|
|
1423
|
-
res.sendComment = (comment) => {
|
|
1424
|
-
res.write(`: ${comment}\n\n`);
|
|
1425
|
-
};
|
|
1426
|
-
res.sendRetry = (ms) => {
|
|
1427
|
-
res.write(`retry: ${ms}\n\n`);
|
|
1428
|
-
};
|
|
1429
|
-
// Set up heartbeat if configured
|
|
1430
|
-
let heartbeatInterval = null;
|
|
1431
|
-
if (options.heartbeat) {
|
|
1432
|
-
heartbeatInterval = setInterval(() => {
|
|
1433
|
-
res.sendComment('heartbeat');
|
|
1434
|
-
}, options.heartbeat);
|
|
1435
|
-
}
|
|
1436
|
-
// Set retry if configured
|
|
1437
|
-
if (options.retry) {
|
|
1438
|
-
res.sendRetry(options.retry);
|
|
1439
|
-
}
|
|
1440
|
-
// Clean up on close
|
|
1441
|
-
req.on('close', () => {
|
|
1442
|
-
if (heartbeatInterval) {
|
|
1443
|
-
clearInterval(heartbeatInterval);
|
|
1444
|
-
}
|
|
1445
|
-
});
|
|
1446
|
-
// Don't call next() - this middleware handles the response
|
|
1447
|
-
return;
|
|
1448
|
-
}
|
|
1449
|
-
next();
|
|
1450
|
-
};
|
|
1451
|
-
},
|
|
1452
|
-
// Range request middleware for streaming
|
|
1453
|
-
range: (options = {}) => {
|
|
1454
|
-
return async (req, res, next) => {
|
|
1455
|
-
// Add range support to response
|
|
1456
|
-
res.sendRange = async (filePath, stats) => {
|
|
1457
|
-
try {
|
|
1458
|
-
const fs = await import('fs/promises');
|
|
1459
|
-
const path = await import('path');
|
|
1460
|
-
if (!stats) {
|
|
1461
|
-
stats = await fs.stat(filePath);
|
|
1462
|
-
}
|
|
1463
|
-
const fileSize = stats.size;
|
|
1464
|
-
const range = req.headers.range;
|
|
1465
|
-
// Set Accept-Ranges header
|
|
1466
|
-
res.setHeader('Accept-Ranges', options.acceptRanges || 'bytes');
|
|
1467
|
-
if (!range) {
|
|
1468
|
-
// No range requested, send entire file
|
|
1469
|
-
res.setHeader('Content-Length', fileSize);
|
|
1470
|
-
const data = await fs.readFile(filePath);
|
|
1471
|
-
res.end(data);
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
// Parse range header
|
|
1475
|
-
const ranges = range
|
|
1476
|
-
.replace(/bytes=/, '')
|
|
1477
|
-
.split(',')
|
|
1478
|
-
.map(r => {
|
|
1479
|
-
const [start, end] = r.split('-');
|
|
1480
|
-
return {
|
|
1481
|
-
start: start ? parseInt(start) : 0,
|
|
1482
|
-
end: end ? parseInt(end) : fileSize - 1,
|
|
1483
|
-
};
|
|
1484
|
-
});
|
|
1485
|
-
// Validate ranges
|
|
1486
|
-
if (options.maxRanges && ranges.length > options.maxRanges) {
|
|
1487
|
-
res.status(416).json({ success: false, error: 'Too many ranges' });
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
if (ranges.length === 1) {
|
|
1491
|
-
// Single range
|
|
1492
|
-
const { start, end } = ranges[0];
|
|
1493
|
-
const chunkSize = end - start + 1;
|
|
1494
|
-
if (start >= fileSize || end >= fileSize) {
|
|
1495
|
-
res.status(416);
|
|
1496
|
-
res.setHeader('Content-Range', `bytes */${fileSize}`);
|
|
1497
|
-
res.json({ success: false, error: 'Range not satisfiable' });
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
res.status(206);
|
|
1501
|
-
res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
|
|
1502
|
-
res.setHeader('Content-Length', chunkSize);
|
|
1503
|
-
// Stream the range
|
|
1504
|
-
const stream = createReadStream(filePath, {
|
|
1505
|
-
start,
|
|
1506
|
-
end,
|
|
1507
|
-
});
|
|
1508
|
-
stream.pipe(res);
|
|
1509
|
-
}
|
|
1510
|
-
else {
|
|
1511
|
-
// Multiple ranges - multipart response
|
|
1512
|
-
const boundary = 'MULTIPART_BYTERANGES';
|
|
1513
|
-
res.status(206);
|
|
1514
|
-
res.setHeader('Content-Type', `multipart/byteranges; boundary=${boundary}`);
|
|
1515
|
-
for (const { start, end } of ranges) {
|
|
1516
|
-
if (start >= fileSize || end >= fileSize)
|
|
1517
|
-
continue;
|
|
1518
|
-
const chunkSize = end - start + 1;
|
|
1519
|
-
res.write(`\r\n--${boundary}\r\n`);
|
|
1520
|
-
res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
|
|
1521
|
-
const stream = createReadStream(filePath, {
|
|
1522
|
-
start,
|
|
1523
|
-
end,
|
|
1524
|
-
});
|
|
1525
|
-
await new Promise(resolve => {
|
|
1526
|
-
stream.on('end', () => resolve());
|
|
1527
|
-
stream.pipe(res, { end: false });
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
res.write(`\r\n--${boundary}--\r\n`);
|
|
1531
|
-
res.end();
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
catch (error) {
|
|
1535
|
-
res.status(500).json({ success: false, error: 'Range request failed' });
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
next();
|
|
1539
|
-
};
|
|
1540
|
-
},
|
|
1541
|
-
// CSRF Protection middleware
|
|
1542
|
-
csrf: (options = {}) => {
|
|
1543
|
-
const secret = options.secret || 'moro-csrf-secret';
|
|
1544
|
-
const tokenLength = options.tokenLength || 32;
|
|
1545
|
-
const cookieName = options.cookieName || '_csrf';
|
|
1546
|
-
const headerName = options.headerName || 'x-csrf-token';
|
|
1547
|
-
const ignoreMethods = options.ignoreMethods || ['GET', 'HEAD', 'OPTIONS'];
|
|
1548
|
-
const generateToken = () => {
|
|
1549
|
-
return crypto.randomBytes(tokenLength).toString('hex');
|
|
1550
|
-
};
|
|
1551
|
-
const verifyToken = (token, sessionToken) => {
|
|
1552
|
-
return token && sessionToken && token === sessionToken;
|
|
1553
|
-
};
|
|
1554
|
-
return (req, res, next) => {
|
|
1555
|
-
// Add CSRF token generation method
|
|
1556
|
-
req.csrfToken = () => {
|
|
1557
|
-
if (!req._csrfToken) {
|
|
1558
|
-
req._csrfToken = generateToken();
|
|
1559
|
-
// Set token in cookie
|
|
1560
|
-
res.cookie(cookieName, req._csrfToken, {
|
|
1561
|
-
httpOnly: true,
|
|
1562
|
-
sameSite: options.sameSite !== false ? 'strict' : undefined,
|
|
1563
|
-
secure: req.headers['x-forwarded-proto'] === 'https' || req.socket.encrypted,
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
return req._csrfToken;
|
|
1567
|
-
};
|
|
1568
|
-
// Skip verification for safe methods
|
|
1569
|
-
if (ignoreMethods.includes(req.method)) {
|
|
1570
|
-
next();
|
|
1571
|
-
return;
|
|
1572
|
-
}
|
|
1573
|
-
// Get token from header or body
|
|
1574
|
-
const token = req.headers[headerName] || (req.body && req.body._csrf) || (req.query && req.query._csrf);
|
|
1575
|
-
// Get session token from cookie
|
|
1576
|
-
const sessionToken = req.cookies?.[cookieName];
|
|
1577
|
-
if (!verifyToken(token, sessionToken || '')) {
|
|
1578
|
-
res.status(403).json({
|
|
1579
|
-
success: false,
|
|
1580
|
-
error: 'Invalid CSRF token',
|
|
1581
|
-
code: 'CSRF_TOKEN_MISMATCH',
|
|
1582
|
-
});
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
next();
|
|
1586
|
-
};
|
|
1587
|
-
},
|
|
1588
|
-
// Content Security Policy middleware
|
|
1589
|
-
csp: (options = {}) => {
|
|
1590
|
-
return (req, res, next) => {
|
|
1591
|
-
const directives = options.directives || {
|
|
1592
|
-
defaultSrc: ["'self'"],
|
|
1593
|
-
scriptSrc: ["'self'"],
|
|
1594
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1595
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
1596
|
-
connectSrc: ["'self'"],
|
|
1597
|
-
fontSrc: ["'self'"],
|
|
1598
|
-
objectSrc: ["'none'"],
|
|
1599
|
-
mediaSrc: ["'self'"],
|
|
1600
|
-
frameSrc: ["'none'"],
|
|
1601
|
-
};
|
|
1602
|
-
// Generate nonce if requested
|
|
1603
|
-
let nonce;
|
|
1604
|
-
if (options.nonce) {
|
|
1605
|
-
nonce = crypto.randomBytes(16).toString('base64');
|
|
1606
|
-
req.cspNonce = nonce;
|
|
1607
|
-
}
|
|
1608
|
-
// Build CSP header value
|
|
1609
|
-
const cspParts = [];
|
|
1610
|
-
for (const [directive, sources] of Object.entries(directives)) {
|
|
1611
|
-
if (directive === 'upgradeInsecureRequests' && sources === true) {
|
|
1612
|
-
cspParts.push('upgrade-insecure-requests');
|
|
1613
|
-
}
|
|
1614
|
-
else if (directive === 'blockAllMixedContent' && sources === true) {
|
|
1615
|
-
cspParts.push('block-all-mixed-content');
|
|
1616
|
-
}
|
|
1617
|
-
else if (Array.isArray(sources)) {
|
|
1618
|
-
let sourceList = sources.join(' ');
|
|
1619
|
-
// Add nonce to script-src and style-src if enabled
|
|
1620
|
-
if (nonce && (directive === 'scriptSrc' || directive === 'styleSrc')) {
|
|
1621
|
-
sourceList += ` 'nonce-${nonce}'`;
|
|
1622
|
-
}
|
|
1623
|
-
// Convert camelCase to kebab-case
|
|
1624
|
-
const kebabDirective = directive.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
1625
|
-
cspParts.push(`${kebabDirective} ${sourceList}`);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
// Add report-uri if specified
|
|
1629
|
-
if (options.reportUri) {
|
|
1630
|
-
cspParts.push(`report-uri ${options.reportUri}`);
|
|
1631
|
-
}
|
|
1632
|
-
const cspValue = cspParts.join('; ');
|
|
1633
|
-
const headerName = options.reportOnly
|
|
1634
|
-
? 'Content-Security-Policy-Report-Only'
|
|
1635
|
-
: 'Content-Security-Policy';
|
|
1636
|
-
res.setHeader(headerName, cspValue);
|
|
1637
|
-
next();
|
|
1638
|
-
};
|
|
1639
|
-
},
|
|
1640
|
-
};
|
|
1641
|
-
function parseSize(size) {
|
|
1642
|
-
const units = {
|
|
1643
|
-
b: 1,
|
|
1644
|
-
kb: 1024,
|
|
1645
|
-
mb: 1024 * 1024,
|
|
1646
|
-
gb: 1024 * 1024 * 1024,
|
|
1647
|
-
};
|
|
1648
|
-
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
1649
|
-
if (!match)
|
|
1650
|
-
return 1024 * 1024; // Default 1MB
|
|
1651
|
-
const value = parseFloat(match[1]);
|
|
1652
|
-
const unit = match[2] || 'b';
|
|
1653
|
-
return Math.round(value * units[unit]);
|
|
1654
|
-
}
|
|
1655
1090
|
//# sourceMappingURL=http-server.js.map
|