Skip to content

v2.0.0 HTTP Support for uWestJS#27

Merged
VikramAditya33 merged 11 commits into
mainfrom
v2-http-support
Apr 24, 2026
Merged

v2.0.0 HTTP Support for uWestJS#27
VikramAditya33 merged 11 commits into
mainfrom
v2-http-support

Conversation

@VikramAditya33
Copy link
Copy Markdown
Collaborator

@VikramAditya33 VikramAditya33 commented Apr 24, 2026

Warning: Breaking Change

Includes all the PRs in which the work has been done. This is a big breaking change as everything has been re-organized as well to be domain-driven. Future things like SSE support and anything else can be easily added.

Make sure to keep yourself updated with this one.

  1. feat: Phase 0 & 1: Foundation for v2.0.0 HTTP Platform Support #10
  2. feat: implement HTTP request body parsing (Phase 2) #12
  3. feat: Implement Body Parsing and HTTP Response Enhancements #19
  4. feat: implement NestJS middleware pipeline #20
  5. feat: implement advanced HTTP features (multipart, static files, CORS, compression) #23
  6. refactor: migrate to domain-driven architecture separating HTTP and WebSocket concerns #26

Not currently maintaining the CHANGELOG.md as it would've required me to add an issue for every small thing, But after this whole thing we'll be maintaining CHANGELOG.md as well and also add some benchmark workflows and also include Documentation for everything.

Summary by CodeRabbit

  • New Features

    • Full HTTP platform: request/response APIs, streaming, body parsing (json/text/urlencoded/raw), multipart form parsing, and multipart/file handling.
    • Response utilities: cookies, attachments, redirects, and streaming with compression (brotli/gzip/deflate).
    • Static file serving with ETag, cache headers and byte-range support; worker-backed small-file reads.
  • Enhancements

    • Middleware/guards/pipes/filters accept both classes and instances; WebSocket room names normalized (trimmed).
  • Chores

    • Package/config updates, CI trigger narrowed to main, docs image path and TypeScript build tweaks.

…2.0.0 HTTP support

Phase 0: Project Setup
- Add HTTP dependencies: cookie, cookie-signature, mime-types, statuses, busboy
- Create src/platform/ directory structure for HTTP platform code
- Define HttpOptions and PlatformOptions interfaces
- Add isolatedModules to tsconfig.json for proper module resolution

Phase 1: Request/Response Wrappers
- Implement UwsRequest with stack-allocated uWS.HttpRequest caching
- Implement lazy header evaluation with HTTP/1.1 spec compliance
- Add query parameter parsing with URL-encoding and array support
- Implement UwsResponse with cork management for batched writes
- Add chainable API for status, headers, and cookies
- Implement cookie signing with cookie-signature
- Add auto JSON detection and serialization
- Handle connection abort scenarios
- Add BodyParser class with multi-mode parsing (awaiting, buffering, streaming)
- Implement backpressure management (pause/resume)
- Add size limit enforcement with connection closure
- Support chunked transfer encoding
- Add body parsing methods to UwsRequest (buffer, json, text, urlencoded)
- Implement promise caching for concurrent body access
- Add auto-detection via body getter based on content-type
- Add comprehensive test coverage (59 tests, all passing)
- Add response chunk batching with configurable watermark and flush interval
- Implement streaming support with backpressure handling for large files
- Add cookie support with signing and parsing capabilities
- Implement content-type helpers (type, attachment, redirect, location)
- Add proper filename escaping using content-disposition package
- Refactor test suites for DRY principles (53% reduction in test code)
- All 502 tests passing with >80% coverage
feat: Implement Body Parsing and HTTP Response Enhancements
Implement complete NestJS middleware integration with guards, pipes,
exception filters, and interceptors. Add HTTP execution context and
comprehensive test coverage.
feat: implement NestJS middleware pipeline
…, compression)

- Add multipart/form-data support with streaming file uploads
- Implement static file serving with range requests and caching
- Add CORS handler with preflight and origin validation
- Implement request/response compression (gzip/deflate/brotli)
- Add hybrid readable stream with lazy activation and backpressure
- Implement file worker pool for optimized static file serving
feat: implement advanced HTTP features (multipart, static files, CORS, compression)
…ebSocket concerns

- Reorganize src/ into domain-based structure (http/, websocket/, shared/)
- Move HTTP code from platform/ to http/ with subdirectories (core, routing, body, handlers, platform)
- Move WebSocket code to websocket/ with subdirectories (core, rooms, routing, decorators, middleware, adapter, exceptions)
- Extract shared code to shared/ (DI abstraction and interfaces)
- Fix all inline imports and update import paths across codebase
- Remove old flat directory structure (adapter/, decorators/, exceptions/, middleware/, platform/, rooms/, router/, socket/)
Fixes #25
refactor: migrate to domain-driven architecture separating HTTP and WebSocket concerns
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds comprehensive HTTP platform and WebSocket reorganizations: new UwsRequest/UwsResponse/BodyParser, multipart/compression/CORS/static handlers, RouteRegistry and UwsPlatformAdapter, many middleware improvements (guards/pipes/filters), shared interfaces/DI barrels, plus numerous tests and package/config updates.

Changes

Cohort / File(s) Summary
CI & Packaging
\.github/workflows/ci.yml, \.npmignore, README.md, package.json, tsconfig.build.json, tsconfig.json
Adjusted CI branch triggers; updated npmignore rules and README asset path; added runtime deps and types, keywords; enabled isolatedModules; expanded tsbuild excludes.
Top-level API & Barrels
src/index.ts, src/shared/index.ts, src/http/index.ts, src/websocket/index.ts
Reorganized package exports to wildcard re-exports for ./shared, ./http, ./websocket; added new shared/http/websocket barrels.
HTTP Core (request/response/context)
src/http/core/request.ts, src/http/core/response.ts, src/http/core/context.ts, src/http/core/index.ts, src/http/core/*.spec.ts
New UwsRequest (Readable with backpressure/body parsing), UwsResponse (Writable with headers/cookies/streaming), and HttpExecutionContext with tests.
HTTP Body & Multipart
src/http/body/body-parser.ts, src/http/body/multipart-handler.ts, src/http/body/index.ts, src/http/body/*.spec.ts
Introduced BodyParser (watermark, chunked/content-length, buffer()/memoization) and MultipartFormHandler (busboy-driven) with comprehensive tests.
HTTP Handlers: Compression & CORS
src/http/handlers/compression/*, src/http/handlers/cors/*, src/http/handlers/index.ts
Added CompressionHandler (decompress/stream-compress) and CorsHandler with barrels and tests.
Static Files & Worker Pool
src/http/handlers/static/static-file-handler.ts, src/http/handlers/static/file-worker-pool.ts, src/http/handlers/static/index.ts, src/http/handlers/static/*.spec.ts
Static file serving with security/ETag/range support and FileWorkerPool worker-thread reader plus tests and fallback behavior.
HTTP Routing & Platform Adapter
src/http/routing/route-registry.ts, src/http/platform/uws-platform.adapter.ts, src/http/routing/*.spec.ts, src/http/platform/*.spec.ts
New RouteRegistry (complex path regex support, middleware pipeline, CORS integration) and UwsPlatformAdapter implementing Nest AbstractHttpAdapter and static/CORS wiring.
HTTP Interfaces & Test Helpers
src/http/interfaces/*, src/http/test-helpers.ts
Added HttpOptions interface and uWS test helper utilities used across new http tests.
Shared Interfaces & DI
src/shared/interfaces/*, src/shared/di/module-ref.ts, src/shared/di/index.ts
New shared types (CorsOptions, Logger, PlatformOptions) and ModuleRef validation change; added shared barrels.
WebSocket Core & Routing
src/websocket/core/*, src/websocket/routing/*, src/websocket/adapter/uws.adapter.ts, src/websocket/adapter/*
Refactored websocket core (socket/broadcast), routing scanner optimizations (messageKey), adapter support for shared uws app, gateway storage refactor, and corresponding tests.
WebSocket Middleware (filters/guards/pipes)
src/websocket/middleware/filters/*, src/websocket/middleware/guards/*, src/websocket/middleware/pipes/*, src/websocket/middleware/index.ts
Expanded middleware to accept instances or classes, Observable support for guards, deduplication when merging metadata, added barrels and tests.
WebSocket Decorators & Exceptions
src/websocket/decorators/*, src/websocket/exceptions/*
Reworked decorator parameter normalization, moved/added decorator barrels, fixed WsException prototype handling, and updated tests.
Removed/Relocated Barrels & Interfaces
src/decorators/index.ts, src/exceptions/index.ts, src/interfaces/uws-options.interface.ts, src/interfaces/websocket-client.interface.ts, src/middleware/*, src/socket/index.ts
Deleted several old top-level barrels and interface files; related symbols moved into more specific shared/http/websocket modules.
Tests & Specs
many *.spec.ts across src/http and src/websocket
Extensive new and updated unit/integration tests covering new features, edge cases, and error paths.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant Adapter as UwsPlatformAdapter
    participant Registry as RouteRegistry
    participant Middleware as Middleware Pipeline
    participant Handler as Route Handler
    participant Response as UwsResponse

    Client->>Adapter: send HTTP request
    Adapter->>Registry: lookup route & metadata
    Registry->>Middleware: run guards/pipes/filters
    Middleware->>Handler: invoke handler
    Handler->>Response: write/send response
    Response->>Client: deliver HTTP response

    rect rgba(200, 100, 100, 0.5)
    Note over Middleware: on error run exception filters
    end
Loading
sequenceDiagram
    participant uws as uWS incoming
    participant UwsReq as UwsRequest
    participant Parser as BodyParser
    participant Stream as BufferStore

    uws->>UwsReq: onData(chunk, isLast)
    UwsReq->>Parser: route chunk
    Parser->>Stream: buffer/append
    alt buffer > WATERMARK
        Parser->>uws: pause()
    end
    uws->>UwsReq: onData(final, isLast=true)
    Parser->>Stream: finalize & resolve buffer()
    Stream-->>UwsReq: Buffer ready
Loading
sequenceDiagram
    participant Client as HTTP Client
    participant Static as StaticFileHandler
    participant Pool as FileWorkerPool
    participant Worker as WorkerThread

    Client->>Static: GET /path
    Static->>Static: validate path & conditionals
    Static->>Pool: readFile(path)
    Pool->>Worker: dispatch read task
    Worker-->>Pool: return ArrayBuffer (transferred)
    Pool-->>Static: Buffer
    Static->>Client: stream or send response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐇 I nibbled bytes and chased a stream,
Parsed every header, chased each dream.
Multipart fields and compressed delight,
Guarded pipes through day and night.
Hops of code — the server hums bright!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v2-http-support

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/websocket/decorators/decorators.spec.ts (1)

74-93: ⚠️ Potential issue | 🟡 Minor

toEqual will not detect data: undefined presence.

Jest’s toEqual treats { a: 1 } and { a: 1, data: undefined } as equal, so this assertion will pass whether the production code omits data or sets it to undefined. If the stated contract is that the data key is truly absent (not just undefined), add an explicit not.toHaveProperty('data') assertion. The same concern applies to the @Payload test at lines 124-130.

🔧 Proposed tightening
       expect(fullMetadata[0]).toEqual({
         index: 0,
         type: ParamType.MESSAGE_BODY,
         // Note: data property is omitted when no property name is provided
       });
+      expect(fullMetadata[0]).not.toHaveProperty('data');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/decorators/decorators.spec.ts` around lines 74 - 93, The test
uses toEqual which cannot distinguish a missing data key from data: undefined;
update the assertions to explicitly verify the absence of the data property:
after retrieving fullMetadata via getParamMetadata(TestGateway.prototype,
'handleWithMessageBody') add expect(fullMetadata[0]).not.toHaveProperty('data')
to ensure the key is omitted, and apply the same explicit
not.toHaveProperty('data') check in the `@Payload-related` test (the one around
lines 124-130) that inspects its metadata so it fails if the production code
sets data to undefined instead of omitting it.
src/websocket/middleware/filters/exception-filter-executor.ts (1)

148-160: ⚠️ Potential issue | 🟡 Minor

See related comment on handler-executor.ts guard-denial flow.

The generic Unhandled exception log + Internal server error payload is the downstream behavior that makes guard denials in HandlerExecutor.execute user-visible as server errors. Fix is preferably at the caller (route a WsException instead of ForbiddenException), but an alternative is to also handle HttpException here. Flagging only the downstream here for context; root cause comment is on handler-executor.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/middleware/filters/exception-filter-executor.ts` around lines
148 - 160, The serializeException method currently treats only WsException
specially and turns all other errors into a generic internal error; update
serializeException to also detect Nest's HttpException (e.g., check instanceof
HttpException) and return a client-appropriate payload/status when encountered
(use the exception's getResponse() or message/status data) while still logging
unexpected errors; reference serializeException and WsException and ensure this
complements the higher-level fix in HandlerExecutor.execute so guard-denials
(ForbiddenException/other HttpExceptions) are serialized as their intended
client-visible HTTP/WebSocket errors rather than masked as "Internal server
error."
src/websocket/routing/handler-executor.spec.ts (1)

337-343: ⚠️ Potential issue | 🟡 Minor

Tighten guard-denial assertions — current tests hide the response-shape regression.

expect(result.response).toBeDefined() and expect(filterCalled).toBe(true) pass even when the returned response is the generic { error: 'Internal server error', message: 'An unexpected error occurred' } produced by serializeException for non-WsException errors. That is exactly the regression called out on handler-executor.ts. Consider asserting:

  • the filter's catch receives a ForbiddenException (or whichever exception you decide on), and
  • result.response matches the intended forbidden payload (not the generic internal-server-error fallback).
💚 Example tightening
-      expect(result.response).toBeDefined(); // Guard denials now go through exception filters
+      expect(result.response).not.toEqual({
+        error: 'Internal server error',
+        message: 'An unexpected error occurred',
+      });
+      // Optionally: assert the intended forbidden payload, e.g.
+      // expect(result.response).toMatchObject({ status: 'error', message: 'Forbidden resource' });

And in should execute filter when guard fails, capture and assert the exception passed to the filter:

       class TestFilter implements ExceptionFilter {
-        catch(): void {
+        catch(exception: unknown): void {
           filterCalled = true;
+          expect(exception).toBeInstanceOf(ForbiddenException);
         }
       }

Also applies to: 737-764

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/routing/handler-executor.spec.ts` around lines 337 - 343,
Tighten the "should execute filter when guard fails" assertions: verify that the
filter's catch handler actually received the ForbiddenException (assert
mockFilter.catch was called with an instance of ForbiddenException) and assert
that result.response equals the intended forbidden payload (e.g., the payload
your exception filter returns for ForbiddenException rather than the generic
serializeException internal-server-error object); update the assertions around
executor.execute, mockFilter.catch and result.response to validate the exception
type and the specific forbidden response shape/message.
src/websocket/routing/metadata-scanner.ts (1)

181-199: ⚠️ Potential issue | 🟡 Minor

Cycle detection has a false-positive on shared (DAG) sub-objects.

The seen WeakSet accumulates every visited object and is never cleared when unwinding, so two sibling properties pointing at the same object throw "Circular reference detected" even though the graph is acyclic. Example pattern: { a: shared, b: shared } where shared = { x: 1 }. True cycle detection needs path-based tracking (add on entry, remove after processing the sub-tree) — or you can just rely on JSON.stringify to throw TypeError on genuine cycles.

🐛 Proposed fix (path-based tracking)
   private sortObjectKeys(
     obj: Record<string, unknown>,
     seen = new WeakSet<object>()
   ): Record<string, unknown> {
     if (seen.has(obj)) {
       throw new Error('Circular reference detected in message pattern');
     }
     seen.add(obj);

     const sorted: Record<string, unknown> = {};
     for (const key of Object.keys(obj).sort()) {
       const value = obj[key];
       sorted[key] =
         value !== null && typeof value === 'object' && !Array.isArray(value)
           ? this.sortObjectKeys(value as Record<string, unknown>, seen)
           : value;
     }
+    seen.delete(obj);
     return sorted;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/routing/metadata-scanner.ts` around lines 181 - 199, The
current sortObjectKeys uses a shared WeakSet (seen) that causes false-positive
circular detection for DAGs because entries are never removed; update
sortObjectKeys to perform path-based tracking by adding obj to seen on entry and
removing it before returning (or use a new WeakSet copy for each recursive
branch) so sibling references to the same sub-object don't trigger errors,
keeping the circular check only for true cycles; refer to the function
sortObjectKeys and its seen parameter when making this change.
🧹 Nitpick comments (25)
tsconfig.build.json (1)

3-10: LGTM — exclude list correctly omits test helpers and Jest mocks from the build output.

Minor consideration: the pattern **/test-helpers.ts is filename-based and will exclude any file named test-helpers.ts anywhere in src/. If you ever need a non-test helper with that exact filename it would be unintentionally omitted; consider co-locating helpers under a __tests__/ or test/ directory and excluding by directory (e.g. **/__tests__/**) for a more robust convention. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tsconfig.build.json` around lines 3 - 10, The exclude list currently uses a
filename-based pattern "**/test-helpers.ts" which will omit any file with that
name anywhere; update tsconfig.build.json's exclude array to remove the
filename-specific pattern and instead exclude test directories (e.g. add
patterns like "**/__tests__/**" and/or "**/test/**") so test helpers are
excluded by directory convention rather than by filename—modify the "exclude"
entry for tsconfig.build.json (look for the "exclude" array and the
"**/test-helpers.ts" pattern) accordingly.
src/http/handlers/static/file-worker-pool.ts (2)

309-321: Use Promise.allSettled so cleanup always completes.

If any worker's terminate() rejects, Promise.all short-circuits and this.workers = [] never runs, leaving size non-zero and stale FileWorker references in the pool. allSettled keeps cleanup reachable regardless of individual outcomes.

♻️ Proposed refactor
   async terminate(): Promise<void> {
     this.terminated = true;

     // Reject all pending tasks before terminating workers
     const terminationError = new Error('Worker pool terminated');
     for (const task of this.workerTasks.values()) {
       task.reject(terminationError);
     }
     this.workerTasks.clear();

-    await Promise.all(this.workers.map((w) => w.terminate()));
-    this.workers = [];
+    await Promise.allSettled(this.workers.map((w) => w.terminate()));
+    this.workers = [];
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/static/file-worker-pool.ts` around lines 309 - 321, The
terminate() method currently awaits Promise.all(this.workers.map((w) =>
w.terminate())) which can short-circuit if any worker.terminate() rejects and
prevent this.workers being cleared; change it to use Promise.allSettled on the
array of worker terminate promises so cleanup always runs, then clear
this.workers = [] regardless of individual rejection results; keep the existing
rejection of pending tasks via workerTasks and ensure the unique symbols
referenced are terminate(), this.workerTasks, and this.workers in
file-worker-pool.ts.

194-206: Consider a higher default poolSize.

Defaulting to 1 effectively serializes all static-file reads behind a single worker; under concurrent static-asset requests this becomes a queue bottleneck. A default tied to CPU count (e.g. Math.max(1, Math.min(4, os.cpus().length - 1))) or at minimum 2 would better fit a high-performance HTTP adapter. Keeping 1 as a floor is fine, but the default should likely scale.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/static/file-worker-pool.ts` around lines 194 - 206, The
FileWorkerPool constructor currently defaults to a single worker which
serializes static-file reads; change the default parameter in
FileWorkerPool.constructor to a CPU-aware value (e.g. use the Node/os module and
set size = Math.max(1, Math.min(4, os.cpus().length - 1))) so the pool scales
with available cores but stays between 1 and 4; add the required import for os
and keep the rest of the initialization (workers.push(this.createWorker()))
unchanged, ensuring poolSize still stores the resolved value.
src/websocket/middleware/guards/guard-executor.spec.ts (1)

270-366: Good coverage for instance-guard support.

Mix of synchronous, Observable, throwing, and combined-with-class cases exercises the broadened @UseGuards metadata contract well.

One small gap: there is no test for an instance guard whose canActivate returns a Promise. Since the executor claims to handle Promise/Observable/sync uniformly, adding a Promise-based instance case would round out the matrix and guard against future regressions in the async-awaiting path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/middleware/guards/guard-executor.spec.ts` around lines 270 -
366, Add a new spec in the "guard instances" suite that covers an instance guard
whose canActivate returns a Promise: create a guardInstance: CanActivate with
canActivate = () => Promise.resolve(true) (and optionally another case for
Promise.resolve(false)), apply it via `@UseGuards` on a TestGateway.handleMessage,
call createContext(new TestGateway()) and await executor.executeGuards(context),
then assert the Promise-resolving true yields result true (and for false yields
result false); reference executor.executeGuards, createContext, UseGuards,
CanActivate and the existing guard-instance tests to keep structure and
expectations consistent.
src/websocket/core/socket.spec.ts (1)

83-93: Consider asserting root cause too, not only the wrapper message.

The test verifies the wrapper message 'Failed to emit event "test"' for both send failure and JSON serialization failure. That’s useful, but both failure modes now produce an identical error message — which means a regression where e.g. JSON errors stop being caught (or are swallowed) could still pass this expectation if the string matches by coincidence.

Consider additionally asserting cause (or a message fragment from the original TypeError for the circular case) so the two code paths are distinguishable in the spec.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/core/socket.spec.ts` around lines 83 - 93, Update the test to
also assert the underlying cause of the thrown error so the two failure modes
are distinguishable: when mocking send to throw (mockNativeSocket.send) assert
the thrown error has a cause or message that includes 'Send failed' (or that the
error.cause is the original Error), and when emitting a circular structure
(socket.emit with circular), assert the thrown error.cause or message contains
the JSON serialization TypeError fragment (e.g., 'Converting circular structure
to JSON' or similar). Keep the existing wrapper assertion ('Failed to emit event
"test"') and add these additional checks on error.cause or error.message
fragments to ensure both code paths are validated.
src/http/body/index.ts (1)

1-3: Note: AI summary misattributes BodyParser's source file.

The AI summary claims BodyParser is re-exported from src/http/body/multipart-handler.ts, but the code correctly imports it from ./body-parser. The code is right; the summary is not. Flagging for awareness only — no action needed on this file.

Separately, exposing both a class MultipartFormHandler and a type MultipartHandler from the same barrel is slightly confusing for consumers (easy to import the wrong identifier). Consider renaming the type (e.g. MultipartHandlerOptions or IMultipartHandler) for clarity, if the type name is not already part of the public API contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/body/index.ts` around lines 1 - 3, The comment notes an incorrect AI
summary but no code change is required for the BodyParser export; however to
avoid consumer confusion between the class MultipartFormHandler and the exported
type MultipartHandler, rename the type in src/http/body/multipart-handler.ts and
this barrel to a clearer identifier (e.g., MultipartHandlerOptions or
IMultipartHandler) and update all references/usages accordingly; ensure you
update the export line in this file (export type { ... } from
'./multipart-handler') to use the new type name and run the
codebase/type-checker to fix any import sites.
src/websocket/middleware/pipes/pipe-executor.spec.ts (1)

358-358: Drop the as any casts — helper already accepts PipeTransform.

applyPipeToParam's rest parameter was widened to (Type<PipeTransform> | PipeTransform)[] (line 31), so pipeInstance can be passed directly without as any. Keeping the escape hatch hides future signature regressions.

♻️ Suggested cleanup (apply to each occurrence)
-      applyPipeToParam(gateway, 'handleMessage', 0, pipeInstance as any);
+      applyPipeToParam(gateway, 'handleMessage', 0, pipeInstance);
...
-      applyPipeToParam(gateway, 'handleMessage', 0, PipeClass, pipeInstance as any);
+      applyPipeToParam(gateway, 'handleMessage', 0, PipeClass, pipeInstance);

Also applies to: 388-388, 414-414, 439-439

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/middleware/pipes/pipe-executor.spec.ts` at line 358, The test
uses unnecessary type-erasures: remove the "as any" casts when calling
applyPipeToParam and pass pipeInstance directly since applyPipeToParam now
accepts (Type<PipeTransform> | PipeTransform)[]; update calls like
applyPipeToParam(gateway, 'handleMessage', 0, pipeInstance) (and the analogous
calls with the same pattern for other params) so the helper's PipeTransform
typing is preserved and casts are not hiding signature regressions.
src/websocket/middleware/guards/guards.integration.spec.ts (1)

103-105: Consider asserting the shape/content of the exception-filter response.

toBeDefined() only guarantees the response field is non-undefined. Given the behavioral change is that guard denials now flow through exception filters, a stronger assertion (e.g., expect(unauthResult.response).toMatchObject({ event: 'exception', ... }) or similar) would catch regressions where the error payload shape silently changes. Non-blocking.

Also applies to: 137-138, 181-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/middleware/guards/guards.integration.spec.ts` around lines 103
- 105, The test currently only checks unauthResult.response is defined;
strengthen it to assert the exception-filter payload shape (e.g., that
unauthResult.response contains an event:'exception' and the expected error
details) so regressions in the error payload are caught. Replace the loose
expect(unauthResult.response).toBeDefined() with a structural assertion (e.g.,
toMatchObject) that checks event: 'exception' and the relevant message/status
fields for unauthResult and the other two assertions referenced in the same file
(the checks around the unauthResult usage at the other occurrences).
src/websocket/core/socket.ts (2)

156-163: Bare catch hides unexpected errors.

Returning 0 is a reasonable fallback for a closed socket, but this swallows any other runtime error from getBufferedAmount() without visibility. Consider logging at debug level so unexpected failures don't go silent.

Proposed tweak
   getBufferedAmount(): number {
     try {
       return this.nativeSocket.getBufferedAmount();
-    } catch {
+    } catch (error) {
       // Socket is closed or invalid - return 0 as a safe fallback
+      // (e.g., uWS throws after close); log at debug for observability.
       return 0;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/core/socket.ts` around lines 156 - 163, The try/catch in
getBufferedAmount() swallows all errors; update it to catch the error as a
variable and log the unexpected exception at debug level before returning 0 so
non-closed-socket failures are visible. Locate getBufferedAmount() and the call
to this.nativeSocket.getBufferedAmount(), change the catch to capture the error
(e.g., catch (err)) and call the module/class debug logger (e.g.,
this.logger.debug(...) or the existing debug logger) with a concise message and
the error, then return 0 as the safe fallback.

17-45: Unsafe cast is acknowledged but still a latent footgun for non-object TData.

this._data = {} as TData will silently produce a broken data when TData is a primitive (string, number) or has required properties — accessors will return {} typed as something it isn't. The JSDoc warns users, which is good. If you want stronger guarantees, consider either (a) accepting an initialData?: TData constructor argument, or (b) making data TData | undefined in the type and forcing users to assign before read.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/core/socket.ts` around lines 17 - 45, Change the unsafe default
cast by making the socket data explicitly optional: change the field declaration
from "private _data: TData" to "private _data: TData | undefined", add an
optional constructor parameter "initialData?: TData" to the constructor
signature, and set "this._data = initialData" inside the constructor; also
update any internal getters/setters or methods that access "_data" (e.g.,
getData(), setData(), or any direct uses) to handle the possibly undefined value
(throw, return default, or require callers to set data) so we no longer silently
initialize a potentially invalid {} as TData.
src/websocket/middleware/pipes/pipe-executor.ts (1)

65-79: Reading design:paramtypes from the prototype is correct for instance methods.

Nice addition — populating metatype per parameter index makes ValidationPipe-style DTO validation work. Note that it still requires emitDecoratorMetadata: true in consumers' tsconfig.json; worth calling out in the docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/middleware/pipes/pipe-executor.ts` around lines 65 - 79, The
current implementation correctly reads parameter types from the prototype (using
prototype, Reflect.getMetadata('design:paramtypes', prototype, methodName),
paramTypes) so ValidationPipe-style DTO validation works when metatype is set
(metatype: paramTypes[paramPipe.index]), but this requires consumers to enable
TypeScript's emitDecoratorMetadata; update the project docs (and any relevant
README or middleware pipes documentation) to explicitly state that
emitDecoratorMetadata: true must be set in tsconfig.json for ValidationPipe/DTO
validation to work and reference the symbols/properties used here (prototype,
paramTypes, transformedArgs, toArgumentMetadataType, ArgumentMetadata,
paramPipes, metatype) so integrators know why the setting is needed.
src/http/interfaces/http-options.interface.ts (1)

73-77: Consider a richer trustProxy type for future flexibility.

A plain boolean limits proxy configuration compared to common expectations (Express supports boolean | number | string | string[] | (ip, hopCount) => boolean). As written, users cannot constrain trust to specific subnets or hop counts, which is a typical production requirement when X-Forwarded-* headers are involved. Since this is a new public interface, widening it now avoids a breaking change later.

♻️ Proposed type widening
-  /**
-   * Trust proxy headers (X-Forwarded-*)
-   * `@default` false
-   */
-  trustProxy?: boolean;
+  /**
+   * Trust proxy headers (X-Forwarded-*)
+   *
+   * - `false`: do not trust any proxy
+   * - `true`: trust all proxies
+   * - `number`: trust N hops
+   * - `string | string[]`: trust listed IPs/CIDRs
+   * - `(ip, hopIndex) => boolean`: custom predicate
+   *
+   * `@default` false
+   */
+  trustProxy?: boolean | number | string | string[] | ((ip: string, hopIndex: number) => boolean);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/interfaces/http-options.interface.ts` around lines 73 - 77, The
trustProxy property currently typed as boolean should be widened to support
richer Express-like values: change the trustProxy type on the interface
(trustProxy?) to a union such as boolean | number | string | string[] | ((ip:
string, hopCount: number) => boolean) so callers can specify hop counts, CIDRs,
hostnames or a custom function; also update the JSDoc comment above trustProxy
to reflect the expanded accepted types and example usage.
src/websocket/routing/metadata-scanner.ts (1)

156-172: Hoist eventKey out of the .find callback.

JSON.stringify(this.sortObjectKeys(event)) is recomputed for every handler iterated. With N object-pattern handlers this is O(N·k) for a lookup that only needs one serialization. Compute eventKey once before .find.

♻️ Proposed refactor
   getMethodNameForEvent(instance: object, event: string | Record<string, unknown>): string | null {
     // Auto-scan if not cached to prevent subtle bugs from forgetting to scan first
     const handlers = this.scanForMessageHandlers(instance);
     if (handlers.length === 0) return null;

+    const eventKey =
+      typeof event === 'object' && event !== null
+        ? JSON.stringify(this.sortObjectKeys(event))
+        : undefined;
+
     const handler = handlers.find((h) => {
       // String pattern matching
       if (typeof h.message === 'string' && typeof event === 'string') {
         return h.message === event;
       }
       // Object pattern matching - use pre-computed messageKey for performance
       if (
         typeof h.message === 'object' &&
         h.message !== null &&
         typeof event === 'object' &&
         event !== null
       ) {
-        const eventKey = JSON.stringify(this.sortObjectKeys(event));
         return h.messageKey === eventKey;
       }
       return false;
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/websocket/routing/metadata-scanner.ts` around lines 156 - 172, The find
callback recomputes JSON.stringify(this.sortObjectKeys(event)) for each handler;
compute the eventKey once before calling handlers.find to avoid repeated
serialization. Specifically, when event is an object, call
this.sortObjectKeys(event) and JSON.stringify it into a const eventKey outside
of the handlers.find, then inside the callback compare h.messageKey === eventKey
(keep the existing string and object branches and checks around h.message/event
types). This eliminates the per-handler O(N·k) serialization while preserving
the logic in the handlers.find path that examines h.message, h.messageKey, and
event.
src/http/body/body-parser.spec.ts (1)

85-115: Tests don't simulate uWS ArrayBuffer neutering, limiting regression coverage.

In real uWS, non-final onData chunks are neutered (detached/zeroed) after the callback returns, so any view-based buffering appears empty. The current toArrayBuffer helper produces independent ArrayBuffers that remain valid after the callback, so a regression that reverted Buffer.from(new Uint8Array(chunk)) back to Buffer.from(chunk) (zero-copy view) would still pass these tests. Consider adding a neutering simulation (e.g., structuredClone-based transfer or manually zeroing the backing array) for at least one multi-chunk test, so the copy semantics are actually validated.

Based on learnings from PR #23 (src/platform/uws-request.ts:223-234): uWS neuters non-final ArrayBuffer chunks after the onData callback returns, requiring Buffer.from(new Uint8Array(chunk)) for correct copy semantics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/body/body-parser.spec.ts` around lines 85 - 115, The tests don't
simulate uWS neutering of non-final ArrayBuffer chunks, so add a neutering
simulation to ensure BodyParser uses copy semantics: modify the test helper
(toArrayBuffer) or add a new helper (e.g., simulateNeuteredArrayBuffer) and use
it for at least one multi-chunk case so that after calling onDataCallback with a
non-final chunk the underlying ArrayBuffer is zeroed/transferred (simulate
structured clone transfer) before assertions; this will force the
Buffer.from(new Uint8Array(chunk)) code path in BodyParser to be used and
validate correct behavior (refer to BodyParser, onDataCallback, toArrayBuffer,
and BUFFER_WATERMARK to locate the relevant tests and helpers).
src/http/handlers/cors/cors-handler.ts (2)

161-182: Response casing for access-control-allow-headers is inconsistent across branches.

When allowedHeaders is explicitly configured AND Access-Control-Request-Headers is present (Line 163-172), the validated headers are lowercased before being joined, so the response contains 'content-type' even if the user configured ['Content-Type']. In the two sibling branches:

  • Line 175 (permissive echo): returns the client-sent casing verbatim.
  • Line 178 (explicit config, no requested headers): returns the user-configured casing verbatim.

Functionally harmless (HTTP header names are case-insensitive), but the inconsistent output is surprising and shows up in test assertions (e.g. cors-handler.spec.ts Line 910 expects 'content-type' while Line 948 expects 'Content-Type, Authorization'). Consider preserving the configured casing for validated names.

♻️ Proposed fix — preserve configured casing
     if (requestedHeaders && this.allowedHeadersExplicitlySet) {
       // User explicitly configured allowedHeaders - validate requested headers
-      const requested = requestedHeaders.split(',').map((h) => h.trim().toLowerCase());
-      const allowed = this.options.allowedHeaders.map((h) => h.toLowerCase());
-      const validated = requested.filter((h) => allowed.includes(h));
+      const requested = requestedHeaders.split(',').map((h) => h.trim());
+      const allowedLower = this.options.allowedHeaders.map((h) => h.toLowerCase());
+      // Keep only requested headers that are in the allowlist, preserving configured casing
+      const validated = requested
+        .map((h) => {
+          const idx = allowedLower.indexOf(h.toLowerCase());
+          return idx >= 0 ? this.options.allowedHeaders[idx] : null;
+        })
+        .filter((h): h is string => h !== null);

       if (validated.length === 0) {
         // Requested headers not allowed - reject preflight
         res.status(403).send();
         return true;
       }
       allowedHeadersToSend = validated.join(', ');
     }

(Test at cors-handler.spec.ts:910 would need to be updated to expect 'Content-Type'.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/cors/cors-handler.ts` around lines 161 - 182, The response
lowercases validated requested headers causing inconsistent casing; update the
validation in the block that checks this.allowedHeadersExplicitlySet and
requestedHeaders so it matches requested names against
this.options.allowedHeaders case-insensitively but builds allowedHeadersToSend
using the original casing from this.options.allowedHeaders (not the lowercased
requested values). In other words, when computing validated headers, map
requested header names to the configured allowed header entries
(this.options.allowedHeaders) by comparing toLowerCase() but push the configured
entry (preserving its casing) into allowedHeadersToSend; keep the other branches
(echoing requestedHeaders and joining this.options.allowedHeaders) unchanged.

161-172: Clarify the partial-allow design choice with a comment.

The code silently allows a subset of requested headers when only some are in allowedHeaders, matching Express cors middleware behavior. While standard, this can make misconfiguration harder to diagnose—the browser only blocks the actual request if a header was dropped from the preflight response. Add a comment explaining whether this partial-allow behavior is intentional (e.g., "Match Express cors behavior by allowing partial header sets") or consider whether you prefer stricter validation that rejects preflight if any requested header is disallowed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/cors/cors-handler.ts` around lines 161 - 172, Add a
clarifying inline comment above the block that handles requestedHeaders (the
code using this.allowedHeadersExplicitlySet, requestedHeaders,
this.options.allowedHeaders, and allowedHeadersToSend) stating the design
choice: that we intentionally match Express cors behavior by allowing a partial
subset of requested headers (silently dropping disallowed ones) and noting the
alternative stricter option (reject preflight if any requested header is
disallowed) so future readers know this is deliberate and where to change
behavior if stricter validation is desired.
src/http/routing/route-registry.spec.ts (1)

263-341: LGTM — good documentation of the "first match wins" gotcha via tests.

These tests clearly lock in registration-order semantics for complex routes sharing a wildcard prefix (e.g. /users/:id? registered before /users/:name? always wins). Given this is a footgun — users coming from Express may expect "most specific wins" — consider documenting this precedence rule in the public README/migration guide alongside merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/routing/route-registry.spec.ts` around lines 263 - 341, The tests
demonstrate and lock in a "first match wins" registration-order precedence for
semantically equivalent routes (e.g., registry.register('GET','/users/:id?')
before '/users/:name?') and you should document this behavior: update the public
README and migration guide to explicitly state that route precedence is
determined by registration order (not specificity), cite the registry.register
API and the wildcard matching behavior (e.g., GET:/users/* and GET:/posts/*) as
examples, warn that this differs from Express's "most specific wins"
expectation, and include a short example showing how to avoid the footgun
(register more specific handlers first or reorder registrations).
src/http/core/context.ts (1)

79-90: Minor: hoist the getNext no-op to avoid per-call allocation.

Every call to context.switchToHttp().getNext() allocates a fresh () => {} closure. On hot request paths under high RPS this is unnecessary allocation pressure. A module-level constant would serve the same purpose.

♻️ Proposed refactor
 import { ExecutionContext, Type } from '@nestjs/common';
 import { UwsRequest } from './request';
 import { UwsResponse } from './response';

 /**
  * Handler function type for route handlers
  */
 type RouteHandler = (req: UwsRequest, res: UwsResponse) => void | Promise<void>;
+
+// uWebSockets.js doesn't use Express-style next() middleware chaining,
+// so `getNext()` returns this shared no-op.
+const NOOP_NEXT = () => {};

@@
   switchToHttp(): {
     getRequest: <T = UwsRequest>() => T;
     getResponse: <T = UwsResponse>() => T;
     getNext: <T = () => void>() => T;
   } {
     return {
       getRequest: <T = UwsRequest>() => this.request as T,
       getResponse: <T = UwsResponse>() => this.response as T,
-      // uWebSockets.js doesn't use Express-style next() middleware chaining
-      getNext: <T = () => void>() => (() => {}) as T,
+      getNext: <T = () => void>() => NOOP_NEXT as T,
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/core/context.ts` around lines 79 - 90, The getNext implementation
inside switchToHttp currently returns a new noop closure on every call (getNext:
<T = () => void>() => (() => {}) as T), causing unnecessary allocations; hoist a
single module-level constant noop (e.g., const NOOP_NEXT = () => {}) and change
switchToHttp's getNext to return that constant (cast as T) so switchToHttp and
getNext keep the same API but avoid per-call closure allocation; reference
switchToHttp and getNext when making the change.
src/http/platform/uws-platform.adapter.spec.ts (1)

167-191: Fragile coupling to process.nextTick for the async-throw path.

The test assumes the adapter uses process.nextTick to surface the listen failure when no callback is provided. If the implementation is later refactored to use queueMicrotask, setImmediate, or Promise.reject, this test will hang or fail in ways that are hard to diagnose. Consider asserting on an uncaught-exception or unhandled-rejection event instead, or making the async-throw mechanism part of a stable public contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/platform/uws-platform.adapter.spec.ts` around lines 167 - 191, The
test currently spies on process.nextTick to catch the async throw from
UwsPlatformAdapter.listen which couples the test to an implementation detail;
change the test to listen for the platform-level async failure event instead
(e.g., process.once('unhandledRejection') or process.once('uncaughtException'))
and assert the Error message 'Failed to listen on 0.0.0.0:3000' from that
handler; implement the Promise wrapper so it registers the one-time event
listener, calls adapter.listen(3000) (with mockUwsApp.listen simulating
failure), resolves/rejects from the event callback, and always restores/cleans
up the listener to avoid leaks — refer to UwsPlatformAdapter.listen and the
existing test case name when updating the spec.
src/http/core/context.spec.ts (1)

100-106: Nit: test name is slightly misleading.

The test is titled "should return same instances on multiple calls" but asserts on the identity of the inner request/response returned by getRequest()/getResponse(), not on the identity of the object returned by switchToHttp() itself (which is re-created per call — see context.ts:84). Consider renaming to e.g. "should return the same request/response across multiple calls".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/core/context.spec.ts` around lines 100 - 106, Rename the test to
accurately reflect what it asserts: change the test description from "should
return same instances on multiple calls" to something like "should return the
same request/response across multiple calls" so it references the behavior of
context.switchToHttp()'s returned getRequest()/getResponse() identity; update
the it(...) string in the spec where context.switchToHttp(), getRequest(), and
getResponse() are used to match the new description.
src/http/body/body-parser.ts (1)

298-316: Premature disconnect returns a shorter buffer as if success.

When isLast=true fires but fewer bytes than Content-Length were delivered, buffer() resolves with a truncated slice (Line 314). Callers relying on Content-Length (JSON parsing, checksum, etc.) will silently accept a corrupted body. Consider rejecting with a specific error — or at minimum emitting a 'truncated' event — so consumers can distinguish truncation from a legitimately short payload.

In practice, uWS signals aborts via onAborted (handled at Line 84), so isLast=true with short bytes should be rare; this is defense-in-depth.

♻️ Sketch
         this.passthroughCallback = (chunk, isLast) => {
           const bytesToCopy = Math.min(chunk.length, buffer.length - offset);
           if (bytesToCopy > 0) {
             chunk.copy(buffer, offset, 0, bytesToCopy);
             offset += bytesToCopy;
           }
 
           if (isLast) {
             this.abortCallback = undefined;
             this.pendingReject = undefined;
-            resolve(offset === buffer.length ? buffer : buffer.subarray(0, offset));
+            if (offset < buffer.length) {
+              reject(
+                new Error(
+                  `Body truncated: received ${offset} of ${buffer.length} bytes`
+                )
+              );
+              return;
+            }
+            resolve(buffer);
           }
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/body/body-parser.ts` around lines 298 - 316, The current passthrough
handler in body-parser resolves with a truncated buffer when isLast is true but
offset < this.expectedBytes; change this behavior in the passthroughCallback
(the closure assigned to this.passthroughCallback) so that when isLast is true
and offset !== this.expectedBytes it does not resolve silently — instead reject
the pending promise (using this.pendingReject) with a specific
TruncatedBodyError (or emit a 'truncated' event on the parser instance) and
clear this.abortCallback/this.pendingReject as you currently do on normal
completion; update any code that relies on resolve to handle this
rejection/event so callers can distinguish truncated bodies from legitimate
shorter payloads.
src/http/body/multipart-handler.ts (1)

271-293: Clear multipartPromise in a finally block to avoid stale state after a handler error.

If handler(field) rejects, Line 292 is skipped, leaving multipartPromise set to the rejected promise. Any concurrent handler stuck in the while loop will re-await and re-throw that same rejection. In practice this is benign because .catch(finish) at Lines 196/202 destroys the uploader and stops further events — but a finally keeps state cleanly defined.

♻️ Proposed tweak
-    this.multipartPromise = handlerPromise;
-
-    // Now safe to resume - any new events will see multipartPromise and wait
-    this.request.resume();
-
-    // Wait for handler to complete
-    await handlerPromise;
-    this.multipartPromise = null;
+    this.multipartPromise = handlerPromise;
+
+    // Now safe to resume - any new events will see multipartPromise and wait
+    this.request.resume();
+
+    try {
+      // Wait for handler to complete
+      await handlerPromise;
+    } finally {
+      this.multipartPromise = null;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/body/multipart-handler.ts` around lines 271 - 293, The
executeHandler function can leave this.multipartPromise set to a rejected
promise if handler(field) throws; change the flow to ensure
this.multipartPromise is cleared in a finally block: create handlerPromise from
handler(field) as done now, assign this.multipartPromise = handlerPromise before
calling this.request.resume(), then await handlerPromise inside a try/finally
and set this.multipartPromise = null in the finally so a rejection cannot leave
stale state (refer to executeHandler, multipartPromise, handler, handlerPromise,
request.pause/request.resume).
src/http/handlers/compression/compression-handler.ts (1)

188-218: Stacked Content-Encoding with an unknown layer silently yields misleading errors.

For Content-Encoding: gzip, unknown, reverse-order decoding starts with unknown (no-op) and then tries to gunzip the outer unknown-wrapped bytes — which will almost always throw a confusing "Failed to decompress request body with encoding 'gzip, unknown'". Consider explicitly rejecting unknown encodings with an HTTP-appropriate error (e.g., surfacing as 415 Unsupported Media Type at the caller) rather than skipping them, so the failure mode is clear. Pure-unknown pass-through is already covered by tests and can remain intentional behavior.

♻️ Sketch
-          default:
-            // Unknown encoding - skip it and continue with other encodings
-            // This allows partial decompression when some encodings are recognized
-            break;
+          default:
+            // Unknown encoding: pass through only if it's the sole encoding
+            // (lenient), otherwise fail fast to surface the real problem.
+            if (encodings.length > 1) {
+              throw new Error(`Unsupported content-encoding: '${encoding}'`);
+            }
+            break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/compression/compression-handler.ts` around lines 188 - 218,
The current reverse-loop over encodings silently skips unknown values (variable
encodings) which leads to misleading decompression errors later; update the loop
in the decompression routine (the block that calls gunzip, inflate,
brotliDecompress) to detect any unknown encoding immediately and throw a clear,
specific error (include the offending encoding and full encodings list in the
message) instead of continuing, so callers can map it to HTTP 415; preserve
existing pass-through behavior for the single-case "unknown" test by only
rejecting when an unknown encoding appears alongside others (i.e., when
encodings.length > 1 or when the unknown is not the sole entry).
src/http/handlers/static/static-file-handler.ts (1)

664-696: parseTokenList ignores horizontal tab (0x09) as whitespace.

HTTP header OWS permits both SP (0x20) and HTAB (0x09). A header like If-None-Match: "abc",\t"def" will produce a token starting with \t that never matches any ETag. Low impact since most clients use only spaces, but easy to harden.

♻️ Proposed tweak
   private skipWhitespace(str: string, start: number, len: number): number {
     let i = start;
-    while (i < len && str.charCodeAt(i) === 0x20) i++;
+    while (i < len && (str.charCodeAt(i) === 0x20 || str.charCodeAt(i) === 0x09)) i++;
     return i;
   }

And consider tab in extractToken's unquoted-token terminator too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/handlers/static/static-file-handler.ts` around lines 664 - 696, The
parser currently treats only SP (0x20) as whitespace, so update skipWhitespace
and extractToken to recognize HTAB (0x09) as OWS: in skipWhitespace (method
skipWhitespace) change the loop condition so it advances while charCodeAt(i) is
0x20 OR 0x09; in extractToken (method extractToken) treat HTAB as
whitespace/terminator for unquoted tokens by adding 0x09 alongside the existing
0x20 and 0x2c checks so unquoted-token scanning stops on comma, space, or tab;
this will ensure tokens like "\t\"def\"" are not prefixed with a tab and will
match ETags correctly.
src/http/routing/route-registry.ts (1)

687-710: Optional: guard against ReDoS on registered patterns.

new RegExp(^${regexPattern}$) builds a pattern from the path string. In practice paths are app-developer-authored, so exploitability is low, but the .replace(/\*/g, '.*') produces a greedy .* segment per * token that can, when combined with adjacent optional groups, create catastrophic-backtracking candidates (e.g., /a/*/b/*/c/*/d). If you ever accept externally-provided route patterns (plugins, config), consider either .*? (non-greedy) or validating compiled patterns through a library like recheck. Safe to defer for now.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/routing/route-registry.ts` around lines 687 - 710, The current
pathToRegex builds regexPattern using .replace(/\*/g, '.*') which inserts greedy
dot-star tokens and can enable ReDoS; change the wildcard handling in
pathToRegex (the .replace for '*' on regexPattern) to a safer token such as a
non-greedy or segment-limited matcher (e.g., use a non-greedy '.*?' or better
'[^/]*' so '*' does not cross slashes), and if your app ever accepts external
patterns add validation/limits before creating the RegExp; update the .replace
call and keep the rest of pathToRegex (parameter handling) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 549039b9-6059-4c12-adc4-4917909829d7

📥 Commits

Reviewing files that changed from the base of the PR and between 7adca37 and eaf0deb.

⛔ Files ignored due to path filters (2)
  • assets/uWestJS.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (109)
  • .github/workflows/ci.yml
  • .npmignore
  • README.md
  • package.json
  • src/decorators/index.ts
  • src/exceptions/index.ts
  • src/http/body/body-parser.spec.ts
  • src/http/body/body-parser.ts
  • src/http/body/index.ts
  • src/http/body/multipart-handler.spec.ts
  • src/http/body/multipart-handler.ts
  • src/http/core/context.spec.ts
  • src/http/core/context.ts
  • src/http/core/index.ts
  • src/http/core/request.spec.ts
  • src/http/core/request.ts
  • src/http/core/response.spec.ts
  • src/http/core/response.ts
  • src/http/handlers/compression/compression-handler.spec.ts
  • src/http/handlers/compression/compression-handler.ts
  • src/http/handlers/compression/index.ts
  • src/http/handlers/cors/cors-handler.spec.ts
  • src/http/handlers/cors/cors-handler.ts
  • src/http/handlers/cors/index.ts
  • src/http/handlers/index.ts
  • src/http/handlers/static/file-worker-pool.spec.ts
  • src/http/handlers/static/file-worker-pool.ts
  • src/http/handlers/static/index.ts
  • src/http/handlers/static/static-file-handler.spec.ts
  • src/http/handlers/static/static-file-handler.ts
  • src/http/index.ts
  • src/http/interfaces/http-options.interface.ts
  • src/http/interfaces/index.ts
  • src/http/platform/index.ts
  • src/http/platform/uws-platform.adapter.spec.ts
  • src/http/platform/uws-platform.adapter.ts
  • src/http/routing/index.ts
  • src/http/routing/route-registry-middleware.spec.ts
  • src/http/routing/route-registry.spec.ts
  • src/http/routing/route-registry.ts
  • src/http/test-helpers.ts
  • src/index.ts
  • src/interfaces/uws-options.interface.ts
  • src/interfaces/websocket-client.interface.ts
  • src/middleware/filters/index.ts
  • src/middleware/guards/index.ts
  • src/middleware/index.ts
  • src/middleware/pipes/index.ts
  • src/shared/di/index.ts
  • src/shared/di/module-ref.ts
  • src/shared/index.ts
  • src/shared/interfaces/cors-options.interface.ts
  • src/shared/interfaces/index.ts
  • src/shared/interfaces/logger.interface.ts
  • src/shared/interfaces/platform-options.interface.ts
  • src/socket/index.ts
  • src/websocket/adapter/index.ts
  • src/websocket/adapter/lifecycle-hooks.spec.ts
  • src/websocket/adapter/lifecycle-hooks.ts
  • src/websocket/adapter/uws.adapter.integration.spec.ts
  • src/websocket/adapter/uws.adapter.spec.ts
  • src/websocket/adapter/uws.adapter.ts
  • src/websocket/core/broadcast-operator.spec.ts
  • src/websocket/core/broadcast-operator.ts
  • src/websocket/core/index.ts
  • src/websocket/core/socket.spec.ts
  • src/websocket/core/socket.ts
  • src/websocket/decorators/connected-socket.decorator.ts
  • src/websocket/decorators/decorators.spec.ts
  • src/websocket/decorators/index.ts
  • src/websocket/decorators/message-body.decorator.ts
  • src/websocket/decorators/param-decorator.utils.ts
  • src/websocket/decorators/payload.decorator.ts
  • src/websocket/exceptions/index.ts
  • src/websocket/exceptions/ws-exception.ts
  • src/websocket/index.ts
  • src/websocket/interfaces/index.ts
  • src/websocket/interfaces/uws-options.interface.ts
  • src/websocket/interfaces/uws-socket.interface.ts
  • src/websocket/interfaces/websocket-client.interface.ts
  • src/websocket/middleware/filters/exception-filter-executor.spec.ts
  • src/websocket/middleware/filters/exception-filter-executor.ts
  • src/websocket/middleware/filters/filters.integration.spec.ts
  • src/websocket/middleware/filters/index.ts
  • src/websocket/middleware/filters/use-filters.decorator.ts
  • src/websocket/middleware/guards/guard-executor.spec.ts
  • src/websocket/middleware/guards/guard-executor.ts
  • src/websocket/middleware/guards/guards.integration.spec.ts
  • src/websocket/middleware/guards/index.ts
  • src/websocket/middleware/guards/use-guards.decorator.ts
  • src/websocket/middleware/index.ts
  • src/websocket/middleware/pipes/index.ts
  • src/websocket/middleware/pipes/pipe-executor.spec.ts
  • src/websocket/middleware/pipes/pipe-executor.ts
  • src/websocket/middleware/pipes/pipes.integration.spec.ts
  • src/websocket/middleware/pipes/use-pipes.decorator.ts
  • src/websocket/middleware/ws-context.ts
  • src/websocket/rooms/index.ts
  • src/websocket/rooms/room-manager.spec.ts
  • src/websocket/rooms/room-manager.ts
  • src/websocket/routing/handler-executor.spec.ts
  • src/websocket/routing/handler-executor.ts
  • src/websocket/routing/index.ts
  • src/websocket/routing/message-router.spec.ts
  • src/websocket/routing/message-router.ts
  • src/websocket/routing/metadata-scanner.spec.ts
  • src/websocket/routing/metadata-scanner.ts
  • tsconfig.build.json
  • tsconfig.json
💤 Files with no reviewable changes (10)
  • src/websocket/interfaces/index.ts
  • src/socket/index.ts
  • src/exceptions/index.ts
  • src/middleware/guards/index.ts
  • src/middleware/filters/index.ts
  • src/interfaces/websocket-client.interface.ts
  • src/middleware/pipes/index.ts
  • src/middleware/index.ts
  • src/interfaces/uws-options.interface.ts
  • src/decorators/index.ts

Comment thread .github/workflows/ci.yml Outdated
Comment thread src/http/core/request.ts
Comment thread src/http/core/request.ts Outdated
Comment thread src/http/core/response.ts
Comment thread src/http/core/response.ts
Comment thread src/http/test-helpers.ts
Comment thread src/websocket/adapter/uws.adapter.ts
Comment thread src/websocket/rooms/room-manager.spec.ts
Comment thread src/websocket/rooms/room-manager.ts Outdated
Comment thread src/websocket/routing/handler-executor.ts
…andling, re-entrancy issue in the send() method.

Update the default poolSize to be CPU-aware
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Architectural plan for uWestJs v2.0.0

1 participant