Skip to content

refactor: migrate to domain-driven architecture separating HTTP and WebSocket concerns#26

Merged
VikramAditya33 merged 1 commit into
v2-http-supportfrom
re-org
Apr 24, 2026
Merged

refactor: migrate to domain-driven architecture separating HTTP and WebSocket concerns#26
VikramAditya33 merged 1 commit into
v2-http-supportfrom
re-org

Conversation

@VikramAditya33
Copy link
Copy Markdown
Collaborator

@VikramAditya33 VikramAditya33 commented Apr 24, 2026

New Structure

src/
├── shared/                        # Shared code (DI + interfaces)
│   ├── di/
│   │   ├── module-ref.ts
│   │   └── index.ts
│   ├── interfaces/
│   │   ├── cors-options.interface.ts
│   │   ├── logger.interface.ts
│   │   ├── platform-options.interface.ts
│   │   └── index.ts
│   └── index.ts
│
├── http/                          # HTTP Domain
│   ├── core/                      # Request, Response, Context
│   ├── routing/                   # Route registry
│   ├── body/                      # Body parser, multipart
│   ├── handlers/                  # CORS, compression, static
│   │   ├── cors/
│   │   ├── compression/
│   │   └── static/
│   ├── platform/                  # Platform adapter
│   ├── interfaces/                # HTTP options
│   ├── test-helpers.ts
│   └── index.ts
│
├── websocket/                     # WebSocket Domain
│   ├── core/                      # Socket, BroadcastOperator
│   ├── rooms/                     # Room manager
│   ├── routing/                   # Message router, handler executor
│   ├── decorators/                # @ConnectedSocket, @MessageBody, etc.
│   ├── middleware/                # Guards, pipes, filters
│   │   ├── filters/
│   │   ├── guards/
│   │   └── pipes/
│   ├── adapter/                   # UwsAdapter
│   ├── exceptions/                # WsException
│   ├── interfaces/                # WS options
│   └── index.ts
│
├── interfaces/                    # Backward compatibility re-exports
│   └── index.ts
│
└── index.ts                       # Main entry point

Fixes #25

Summary by CodeRabbit

  • New Features

    • Added SSL/TLS certificate configuration options for secure connections.
    • Added file truncation tracking for multipart uploads.
    • Improved CORS and compression handler logic with database-backed content-type detection.
  • Bug Fixes

    • Fixed buffer corruption in body parsing from uWebSockets behavior.
    • Improved timer synchronization and race condition handling in tests.
    • Enhanced room name normalization by trimming whitespace.
    • Fixed guard denial exception propagation through filters.
  • Documentation

    • Improved JSDoc comments and inline guidance for socket data, compression, and validation.
  • Chores

    • Reorganized module exports via barrel files for improved import ergonomics.
    • Added compressible dependency for MIME-based content compression detection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 926d2f7b-237a-4e22-83b0-37d24e69422c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch re-org

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 8

Caution

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

⚠️ Outside diff range comments (3)
src/websocket/routing/metadata-scanner.ts (1)

181-191: ⚠️ Potential issue | 🟠 Major

Object keys inside arrays are not sorted — diverges from MessageRouter and breaks deterministic matching.

sortObjectKeys here short-circuits on Array.isArray(value), so arrays are embedded as-is and objects within them keep their original key order. Compare with MessageRouter.sortValue (lines 179–187 of message-router.ts), which recursively sorts through arrays.

Consequence: getMethodNameForEvent will fail to match patterns that MessageRouter.hasHandler/route happily match. For example, pattern { cmd: 'batch', items: [{ b: 1, a: 2 }] } vs event { cmd: 'batch', items: [{ a: 2, b: 1 }] } matches via MessageRouter but not via MetadataScanner. This is contrary to the docstring on getHandlerKey (claims sorted "recursively") and to the existing test at message-router.spec.ts lines 172–201.

🐛 Proposed fix — recurse into arrays
   private sortObjectKeys(obj: Record<string, unknown>): Record<string, unknown> {
     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>)
-          : value;
+      sorted[key] = this.sortValue(obj[key]);
     }
     return sorted;
   }
+
+  private sortValue(value: unknown): unknown {
+    if (value === null || typeof value !== 'object') {
+      return value;
+    }
+    if (Array.isArray(value)) {
+      return value.map((item) => this.sortValue(item));
+    }
+    return this.sortObjectKeys(value as Record<string, unknown>);
+  }

Also add a parity test in metadata-scanner.spec.ts mirroring the array-of-objects case from message-router.spec.ts.

🤖 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 - 191, The
sortObjectKeys implementation currently short-circuits on arrays so objects
inside arrays keep original key order; update sortObjectKeys to recurse into
arrays by mapping each element and, when an element is a non-null object, call
sortObjectKeys on it (mirror MessageRouter.sortValue behavior), leaving
primitives/other types unchanged; update getMethodNameForEvent usage remains the
same but will now produce deterministically sorted keys for array-contained
objects; add a parity unit test in metadata-scanner.spec.ts echoing the
array-of-objects case from message-router.spec.ts to ensure matching behavior.
src/websocket/adapter/uws.adapter.ts (1)

211-221: ⚠️ Potential issue | 🔴 Critical

Critical: lifecycle hooks are now invoked with the gateway name string, not the instance.

The gateways Map was flipped to Map<object, string> (key = instance, value = name), but Map.prototype.forEach invokes its callback with (value, key, map). So the parameter named gateway here is actually the name string, meaning callConnectionHook(gateway, extWs) is passed "ChatGateway" instead of the instance — the gateway's handleConnection will never fire, and the catch branch's gateway.constructor?.name evaluates to "String".

The same defect exists at lines 268–278 for the disconnect hook. findHandlerForEvent (line 688) correctly uses .keys(), which is why routing still works and this may be masked in the current tests.

🐛 Proposed fix — iterate keys (gateway instances) explicitly
-            this.gateways.forEach((gateway) => {
-              try {
-                this.lifecycleHooksManager.callConnectionHook(gateway, extWs);
-              } catch (error) {
-                this.logger.error(
-                  `Connection hook error for ${gateway.constructor?.name}: ${this.formatError(error)}`
-                );
-              }
-            });
+            for (const gateway of this.gateways.keys()) {
+              try {
+                this.lifecycleHooksManager.callConnectionHook(gateway, extWs);
+              } catch (error) {
+                this.logger.error(
+                  `Connection hook error for ${gateway.constructor?.name}: ${this.formatError(error)}`
+                );
+              }
+            }

Apply the equivalent change at lines 270–278 for callDisconnectHook.

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

In `@src/websocket/adapter/uws.adapter.ts` around lines 211 - 221, The gateways
Map was flipped to Map<object, string>, so the forEach callback currently
receives the name string instead of the gateway instance; update the connection
and disconnect hook loops to iterate the Map's keys (gateway instances) and pass
those instances to lifecycleHooksManager.callConnectionHook and
lifecycleHooksManager.callDisconnectHook, and use the instance when computing
the logger message (gateway.constructor?.name) so it reports the real class
name; apply the same keys()-based iteration change for the disconnect hook block
that mirrors the connection hook and keep the try/catch per gateway as before.
src/http/body/multipart-handler.ts (1)

244-268: ⚠️ Potential issue | 🟠 Major

field.file.truncated captures the flag before the stream is consumed — it will almost always be stale.

busboy only sets stream.truncated = true when the per-file byte limit is hit during stream consumption, which can occur after the file event is emitted. By the time executeHandler runs (line 251), stream.truncated is still its initial undefined/false. Assigning it as a primitive on line 258 therefore yields a snapshot that is effectively always false, even for files that ultimately get truncated. Consumers who rely on field.file.truncated will silently miss truncations.

The spec already demonstrates the correct pattern: checking field.file.stream.truncated directly after consuming the stream (see line 474). The fix is to expose truncated as a getter that delegates to the live stream property:

Suggested fix
-    try {
-      await this.executeHandler(handler, {
-        name,
-        encoding: info.encoding,
-        mimeType: info.mimeType,
-        file: {
-          filename: info.filename,
-          stream,
-          truncated: stream.truncated,
-        },
-      });
+    try {
+      await this.executeHandler(handler, {
+        name,
+        encoding: info.encoding,
+        mimeType: info.mimeType,
+        file: {
+          filename: info.filename,
+          stream,
+          get truncated() {
+            return stream.truncated === true;
+          },
+        } as MultipartField['file'],
+      });

And update the interface to clarify it is a live flag:

   file?: {
     filename: string;
     stream: Readable;
-    truncated?: boolean;
+    /** Live flag — only meaningful after consuming the file stream. */
+    readonly truncated?: boolean;
   };
🤖 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 244 - 268, In handleFile,
the code captures stream.truncated as a primitive and passes it into
executeHandler (field.file.truncated) too early, so it will be stale; change the
file object passed to executeHandler to expose truncated as a getter that
returns stream.truncated (i.e., a live boolean delegating to the stream) and
update the corresponding interface/type used by executeHandler/MultipartHandler
to document truncated as a live flag (boolean getter) rather than a copied
value; keep the rest of handleFile behavior (including the resume in finally)
intact so consumers reading field.file.truncated after stream consumption see
the correct truncation state.
🧹 Nitpick comments (19)
src/websocket/routing/metadata-scanner.ts (1)

104-107: Extract stable-key serialization into a single shared helper.

MessageRouter and MetadataScanner both carry their own sortObjectKeys (and the router additionally has sortValue). The precomputed messageKey here is only safely interchangeable with the router's key if both implementations stay byte-for-byte identical — which they currently don't. A single stableStringify utility (ideally under src/shared, consistent with the PR's DDD goal) consumed by both would prevent future drift and make the pre-computed messageKey usable anywhere the router's key is expected.

Also applies to: 155-172

🤖 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 104 - 107, Extract
the deterministic serialization logic into a single shared helper named
stableStringify (e.g., under src/shared) and replace the local
sortObjectKeys/sortValue implementations in MetadataScanner and MessageRouter:
implement stableStringify to produce a byte-for-byte stable JSON string by
recursively sorting object keys and normalizing values consistently, then use
stableStringify when computing messageKey in MetadataScanner and wherever
MessageRouter builds its key so both components share identical behavior and you
can safely remove the duplicated sortObjectKeys/sortValue functions.
src/websocket/routing/message-router.spec.ts (1)

203-287: Solid expansion of error-path and object-pattern coverage.

Splitting sync/async/non-Error throws clarifies intent, and the new hasHandler cases for object patterns (including key-order insensitivity and negative mismatches) match the behavior documented in getHandlerKey.

One parity gap worth noting elsewhere: MessageRouter has test coverage for patterns with objects inside arrays (L172–201), but MetadataScanner does not — and its sortObjectKeys implementation diverges there (see the comment on metadata-scanner.ts). Consider mirroring that test in metadata-scanner.spec.ts once the scanner sort is fixed.

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

In `@src/websocket/routing/message-router.spec.ts` around lines 203 - 287, The
metadata scanner's sortObjectKeys implementation doesn't handle objects nested
inside arrays; update the MetadataScanner.sortObjectKeys method to recursively
sort keys for all plain objects encountered, including objects within arrays
(preserve arrays order but normalize any object elements), and ensure it treats
key order insensitively the same way getHandlerKey does. After fixing
sortObjectKeys, add tests in metadata-scanner.spec.ts mirroring the
MessageRouter object-in-array cases (create test cases that assert equivalent
normalized keys for object elements in arrays and that differing values still
produce different keys) to ensure parity with MessageRouter coverage.
src/shared/interfaces/cors-options.interface.ts (2)

26-37: Minor: clarify whether methods accepts comma-separated strings.

The type accepts string | string[], but the docstring doesn't specify whether a single-string form is expected to be a single method ('GET') or a comma-separated list ('GET,POST') as emitted in the Access-Control-Allow-Methods header. Worth pinning down to avoid handler ambiguity. Same applies to allowedHeaders and exposedHeaders below.

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

In `@src/shared/interfaces/cors-options.interface.ts` around lines 26 - 37,
Clarify the docs for the cors options by stating whether the string form for
methods, allowedHeaders, and exposedHeaders expects a single token (e.g., "GET")
or a comma-separated list (e.g., "GET,POST") and how it will be interpreted when
building the Access-Control-Allow-* headers; update the JSDoc for the methods?:
string | string[] field and the corresponding allowedHeaders and exposedHeaders
declarations to explicitly say that a single string may be either a single
method/name or a comma-separated list and describe whether the implementation
will split comma-separated strings into arrays (or treat the string verbatim) so
callers know which form to use.

18-24: Consider documenting the credentials: true + wildcard origin incompatibility.

When credentials: true, per the CORS spec browsers reject responses where Access-Control-Allow-Origin is *. Since this interface explicitly notes that origin: true and origin: '*' are equivalent and unrestricted, a short doc hint here (or enforcement in the handler) would save users from a common misconfiguration. Not blocking.

📝 Proposed doc addition
   /**
    * Allow credentials (cookies, authorization headers, TLS client certificates)
+   *
+   * Note: Per the CORS spec, `credentials: true` is incompatible with a wildcard
+   * origin (`'*'` or `true`). When credentials are enabled, the handler must echo
+   * a specific origin back to the client.
    * `@default` false
    */
   credentials?: boolean;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/interfaces/cors-options.interface.ts` around lines 18 - 24, The
CorsOptions interface should warn that credentials: true is incompatible with a
wildcard/unrestricted origin; update the doc comments for origin and/or
credentials in the CorsOptions (the origin property and credentials?: boolean)
to state that when credentials is true browsers reject
Access-Control-Allow-Origin: '*' (or an unrestricted origin equivalent such as
origin: true), and recommend using an explicit origin string or a dynamic
function that echoes the request origin; alternatively, add a runtime check in
the CORS handler to throw/log when credentials is true and origin is '*' or
origin is configured as unrestricted to prevent misconfiguration.
src/shared/index.ts (1)

1-2: LGTM — double-check for name collisions as the surface grows.

export * from two barrels is fine today but can silently cause ambiguity errors if both sides later export an identically named symbol. Consider switching to explicit named re-exports (or namespaced re-exports) if the shared surface expands. Optional.

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

In `@src/shared/index.ts` around lines 1 - 2, The current barrel uses wildcard
re-exports via "export * from './di';" and "export * from './interfaces';",
which can create ambiguous symbol collisions as the surface grows; update the
barrel to use explicit named re-exports (list the specific symbols you want to
expose from di and interfaces) or use namespaced re-exports (e.g., export * as
di from './di' and export * as interfaces from './interfaces') so consumers
cannot silently get conflicting identifiers and future additions won't cause
ambiguity.
src/websocket/adapter/uws.adapter.ts (1)

110-111: Redundant gatewaySet now that gateways is keyed by instance.

With gateways: Map<object, string>, membership checks (gatewaySet.has(gateway) at line 353, reset at 471) are equivalent to this.gateways.has(gateway) / the map's existing lifecycle. Dropping the parallel WeakSet removes a source of drift between the two collections.

Also applies to: 471-471

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

In `@src/websocket/adapter/uws.adapter.ts` around lines 110 - 111, The WeakSet
gatewaySet is redundant because gateways (Map<object,string>) already keys
gateway instances; remove the gatewaySet declaration and all uses (e.g.,
gatewaySet.has(gateway), gatewaySet.add(...), gatewaySet.delete(...), and any
reset logic) and replace them with the Map's lifecycle operations: use
this.gateways.has(gateway) for membership checks, this.gateways.set(gateway,
name) when registering, this.gateways.delete(gateway) when unregistering, and
clear/reset via this.gateways.clear() or appropriate map ops; update any
functions referencing gatewaySet accordingly so the Map remains the single
source of truth.
src/websocket/middleware/pipes/pipes.integration.spec.ts (1)

60-68: Fresh-copy pattern is a good improvement, but PIPES_METADATA and PARAM_ARGS_METADATA on the class itself are still mutated globally.

The cloned existingPipes map prevents shared-map mutation, but the subsequent Reflect.defineMetadata(PIPES_METADATA:params, existingPipes, gatewayClass, methodName) (line 68), Reflect.defineMetadata(PIPES_METADATA, classPipes, gatewayClass) (line 73), and Reflect.defineMetadata(PARAM_ARGS_METADATA, newParams, gatewayClass.prototype, methodName) (line 56) still write onto the class/prototype. Since many tests redeclare their TestGateway locally inside it(...) this is currently safe, but any test that reuses a class across invocations (e.g., the class-level pipes block at lines 231–257 which calls executeHandler twice on the same class) will accumulate/override metadata in order-dependent ways.

Consider either (a) instantiating a fresh class per call via a factory, or (b) capturing and restoring prior metadata in a try/finally, to make test isolation fully robust as the suite grows.

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

In `@src/websocket/middleware/pipes/pipes.integration.spec.ts` around lines 60 -
68, The test mutates metadata on the gateway class/prototype (PIPES_METADATA,
PIPES_METADATA:params, PARAM_ARGS_METADATA) and must restore prior state to
avoid cross-test pollution; capture current metadata values for keys
`${PIPES_METADATA}:params` (on gatewayClass+methodName), `PIPES_METADATA` (on
gatewayClass), and `PARAM_ARGS_METADATA` (on gatewayClass.prototype+methodName)
before changing them, then perform the existing modifications to existingPipes,
classPipes, and newParams, and finally in a try/finally restore the captured
metadata (using Reflect.defineMetadata or Reflect.deleteMetadata when value was
undefined) so executeHandler/TestGateway runs don’t leave global mutations; use
the same symbols (existingPipes, existingPipesMap, newParams, gatewayClass,
methodName) to locate the code to wrap.
src/shared/interfaces/platform-options.interface.ts (1)

1-2: Change to import type to clarify type-only usage and prevent bundler circular dependency issues.

platform-options.interface.ts imports from both http/interfaces and websocket/interfaces barrels, which in turn import back into shared/interfaces. While all usages here are type-only (interface composition), bundlers can struggle with cycles even for types. Using import type makes the intent explicit and allows bundlers to safely elide these imports:

🔧 Suggested fix
-import { UwsAdapterOptions } from '../../websocket/interfaces';
-import { HttpOptions } from '../../http/interfaces';
+import type { UwsAdapterOptions } from '../../websocket/interfaces';
+import type { HttpOptions } from '../../http/interfaces';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/interfaces/platform-options.interface.ts` around lines 1 - 2,
Change the two imports in platform-options.interface.ts to type-only imports to
avoid bundler circular dependency issues: replace the current imports that bring
in UwsAdapterOptions and HttpOptions with type-only imports (i.e., import type
for UwsAdapterOptions and HttpOptions) so the compiler treats them as erased
types and bundlers can safely elide the module edges; locate the import
statements referencing UwsAdapterOptions and HttpOptions and update them to use
import type.
src/http/core/request.spec.ts (1)

985-993: Timer restoration timing.

jest.useRealTimers() at line 992 runs synchronously right after jest.runAllTimers(), but the async for await ... of consumer and the done() callback complete on subsequent microtask ticks. That should be fine in practice since setImmediate was already drained by runAllTimers(), but if flakiness ever appears around this test, consider awaiting the async iteration explicitly (e.g., storing the IIFE's promise and awaiting it) before restoring real timers, so the test's lifecycle is deterministic regardless of future refactors.

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

In `@src/http/core/request.spec.ts` around lines 985 - 993, The test restores real
timers immediately after jest.runAllTimers(), which can race with the async
for-await consumer and done(); capture and await the async iteration's promise
(the IIFE that performs the for await ... of) instead of letting it run
implicitly, then call jest.useRealTimers() only after that promise resolves so
the test lifecycle is deterministic; reference the existing async IIFE/for-await
block and the jest.runAllTimers()/jest.useRealTimers() calls when locating where
to store and await the promise.
src/http/body/body-parser.spec.ts (1)

129-150: Test restructure looks correct.

Calling parser.buffer() before sending chunks now exercises the post-limit rejection path explicitly, and the second buffer() call at line 149 verifies the rejected state is memoized (not re-triggering another buffer collection). This aligns with the immediate-rejection behavior also tested at line 165.

Minor: since content-length: 100 with a 50 limit would already trigger the synchronous rejection path (per the test at line 165), the subsequent onDataCallback invocations at lines 137/140 are mostly exercising the "bytes still counted after rejection" invariant. Consider a brief comment or assertion that mockUwsRes.close was called exactly once to make the intent explicit.

🤖 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 129 - 150, Add an explicit
assertion that the uWS response was closed exactly once to make the test intent
clear: after triggering the chunks and before the final expectations, assert
mockUwsRes.close was called once (e.g.,
expect(mockUwsRes.close).toHaveBeenCalledTimes(1)); also add a one-line comment
near the onDataCallback calls stating these are only to exercise the "bytes
still counted after rejection" invariant.
src/http/core/index.ts (1)

1-3: LGTM — explicit named re-exports.

Explicit re-exports keep the public surface of core intentional. If request.ts/response.ts/context.ts expose companion types consumers may need (e.g., option/config interfaces), consider adding export type { ... } lines alongside these — otherwise consumers would still need to import those types from the deeper module paths.

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

In `@src/http/core/index.ts` around lines 1 - 3, The public core index currently
exports only the runtime symbols UwsRequest, UwsResponse, and
HttpExecutionContext; if the underlying modules (request, response, context)
also expose companion types or interfaces that consumers may need (for example
options/config types), add explicit type re-exports alongside the existing
exports — e.g., add export type { SomeRequestOptions } from './request' or
export type { ResponseConfig } from './response' and export type {
HttpContextOptions } from './context' to make those types part of the core
public surface while keeping imports shallow and intentional.
package.json (1)

29-30: Keyword additions LGTM (minor nit).

http and concurrency improve discoverability. uwestjs duplicates the package name — npm already indexes packages by their name, so this keyword is effectively a no-op for search. Not a blocker.

Also applies to: 40-40

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

In `@package.json` around lines 29 - 30, Remove the redundant "uwestjs" keyword
from the package.json keywords array (it duplicates the package name and is a
no-op for npm search); keep the other keywords such as "http" and "concurrency"
intact and ensure keywords remains a valid array in package.json.
src/http/core/response.spec.ts (1)

1160-1162: Two-tick setImmediate wait is fine; minor readability nit.

Replacing timer-based waits with two chained await new Promise(setImmediate) calls is a reasonable way to let the pipe error propagate through microtasks + one macrotask without being sensitive to timer values. Minor: new Promise(setImmediate) implicitly passes the Promise resolve as the setImmediate callback — works, but new Promise((r) => setImmediate(r)) is slightly more explicit and avoids readers wondering whether extra args are being forwarded. Purely a nit.

Also applies to: 1190-1192

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

In `@src/http/core/response.spec.ts` around lines 1160 - 1162, Replace the
implicit callback usage new Promise(setImmediate) with the more explicit form
new Promise((resolve) => setImmediate(resolve)) in the test to improve
readability; update both occurrences (the two chained awaits around the comment
and the similar block around lines ~1190-1192) so readers clearly see the
promise resolve being passed to setImmediate.
src/http/body/multipart-handler.spec.ts (1)

232-268: Redundant jest.useRealTimers() call with the new afterEach.

With the afterEach hook added at lines 233-235 that always restores real timers for this suite, the explicit jest.useRealTimers() at line 267 (inside the should handle async handlers sequentially test) is now redundant and can be removed for consistency with the new cleanup strategy.

♻️ Proposed cleanup
       // Verify pause/resume were called for backpressure
       expect(mockUwsRes.pause).toHaveBeenCalled();
       expect(mockUwsRes.resume).toHaveBeenCalled();
-
-      jest.useRealTimers();
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/body/multipart-handler.spec.ts` around lines 232 - 268, Remove the
redundant jest.useRealTimers() call inside the "should handle async handlers
sequentially" test since an afterEach hook already restores real timers for the
describe('backpressure handling') suite; specifically, delete the trailing
jest.useRealTimers() at the end of the it('should handle async handlers
sequentially', ...) test so cleanup is centralized, leaving the afterEach and
the rest of the test (setupMultipartRequest, req.multipart handler,
sendMultipartData, expectations including mockUwsRes.pause/resume checks)
intact.
src/http/core/request.ts (1)

1020-1026: Defensive re-check looks safe but likely unreachable.

Between the initial doneReadingData check at line 984 and the listener registrations at lines 1017-1019, there are no awaits or yields — the executor body runs synchronously, and doneReadingData is only flipped inside handleIncomingChunk (driven by uWS onData). In single-threaded JS, no onData callback can fire in the middle of this synchronous block, so the re-check should not be reachable in practice.

That said, the re-check is harmless (Promise resolution is idempotent and cleanup() is safe to call before the listeners fire) so this is fine to keep as a belt-and-suspenders guard. Consider a brief code comment clarifying the intended scenario, or removing it if you cannot identify a concrete race path.

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

In `@src/http/core/request.ts` around lines 1020 - 1026, The extra re-check of
this.doneReadingData after registering listeners is defensively safe but likely
unreachable; update the code around the block that contains the re-check to add
a brief comment explaining that doneReadingData can be flipped by
handleIncomingChunk (uWS onData) and that because the executor body runs
synchronously in single-threaded JS the race is unlikely, but the check is left
as a belt-and-suspenders guard before calling cleanup() and
resolve(this.getBufferedData()); mention the related symbols doneReadingData,
handleIncomingChunk, getBufferedData, and cleanup in the comment so future
readers understand the intent; alternatively, if you prefer removal, delete the
re-check and its cleanup/resolve branch.
src/http/handlers/static/file-worker-pool.ts (1)

92-122: Good consolidation and clean-exit rejection.

Extracting rejectAllPendingTasks removes duplication across error/exit handlers and guarantees pendingTaskKeys is cleared. Also rejecting pending tasks on the clean-exit branch is the right call — previously those promises would hang until the pool was torn down.

Optional nit: the two exit branches flip the order of markAsDead() vs rejectAllPendingTasks(...). It's behaviorally identical, but aligning them (e.g., always markAsDead()rejectAllPendingTasks(...)onDeath(this)) reads cleaner.

🧹 Optional ordering tweak
       } else {
         // Worker exited cleanly but unexpectedly - still mark as dead
         // Reject pending tasks since they'll never complete
-        this.rejectAllPendingTasks(new Error('Worker thread exited unexpectedly'));
-
-        this.markAsDead();
+        this.markAsDead();
+        this.rejectAllPendingTasks(new Error('Worker thread exited unexpectedly'));
         this.onDeath(this);
       }

Also applies to: 150-162

🤖 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 92 - 122, Unify
the ordering in the worker 'exit' handler to match the error path: call
markAsDead(), then rejectAllPendingTasks(...) and then this.onDeath(this) so
both non-zero and zero exit branches follow the same sequence; update the branch
where a clean exit currently calls rejectAllPendingTasks before markAsDead to
instead call markAsDead() first, then rejectAllPendingTasks, then
this.onDeath(this), keeping the same messages and using the existing methods
rejectAllPendingTasks, markAsDead, and onDeath for consistency.
src/http/test-helpers.ts (1)

96-101: Optional: clarify that tryEndComplete is independent of writeSuccess.

In real uWS, tryEnd() returns [ok, hasResponded] — two independent booleans: ok can be true while hasResponded remains false for a streaming, incomplete response. Defaulting tryEndComplete = writeSuccess is a handy shortcut, but the JSDoc on line 99 reads as if the two are semantically linked. Consider noting they're orthogonal in uWS so test authors remember they can override them independently.

   /**
    * Whether tryEnd should indicate completion (done=true)
-   * `@default` true when writeSuccess is true
+   *
+   * Mirrors the second element of uWS's `tryEnd()` return tuple (`hasResponded`),
+   * which is independent of the first (`ok` / writeSuccess). Defaulted to
+   * `writeSuccess` purely for ergonomic reasons in common test cases; override
+   * explicitly to simulate partial streaming (e.g., `{ writeSuccess: true, tryEndComplete: false }`).
    */
   tryEndComplete?: boolean;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/test-helpers.ts` around lines 96 - 101, Update the JSDoc for the
tryEndComplete property to clarify it's independent from writeSuccess: explain
that tryEndComplete corresponds to the uWS tryEnd() second boolean
(hasResponded) and can be set separately from writeSuccess (the first
boolean/ok), noting the default convenience of setting tryEndComplete to
writeSuccess but that they are orthogonal and may be overridden independently;
reference tryEnd(), tryEndComplete, and writeSuccess in the comment so test
authors understand the mapping to uWS semantics.
src/http/body/multipart-handler.ts (1)

280-292: Nit: normalize to a promise via Promise.resolve to also cover thenables.

instanceof Promise misses non-native thenables (e.g., handlers returning a Bluebird promise or an async function compiled by some older targets). Promise.resolve(handler(field)) flattens both promises and thenables and is idiomatic here.

-    // Execute handler and wrap in promise for consistent handling
-    const result = handler(field);
-    const handlerPromise = result instanceof Promise ? result : Promise.resolve();
+    // Execute handler and normalize the result (handles promises and thenables)
+    const handlerPromise = Promise.resolve(handler(field));
🤖 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 280 - 292, Replace the
instanceof Promise branching with Promise.resolve to normalize handler results
(including thenables) into a real Promise: call Promise.resolve(handler(field))
and assign that to handlerPromise, set this.multipartPromise = handlerPromise
before this.request.resume(), await handlerPromise, then clear
this.multipartPromise; this ensures functions like handler and the
multipartPromise handling in multipart-handler.ts correctly handle non-native
thenables.
src/http/platform/uws-platform.adapter.ts (1)

782-816: Minor: Consider checking .cause?.code for defensive error handling.

The current implementation only checks error.code for ENOENT detection. While this works for current error flows (worker pool errors are handled internally, and streaming errors preserve .code), error wrapping patterns exist elsewhere in the codebase (file-worker-pool.ts). If error wrapping evolves here, the check would miss wrapped ENOENT errors. The suggested fix adds robustness:

Suggested change
-        const isNotFound = (error as { code?: string })?.code === 'ENOENT';
+        const rootCode =
+          (error as { code?: string })?.code ??
+          (error as { cause?: { code?: string } })?.cause?.code;
+        const isNotFound = rootCode === 'ENOENT';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/http/platform/uws-platform.adapter.ts` around lines 782 - 816, In
staticAssetHandler, make the ENOENT detection defensive by also checking for a
wrapped error's cause code: when computing isNotFound (currently (error as {
code?: string })?.code === 'ENOENT'), extend it to check (error as
any).cause?.code === 'ENOENT' as well (or traverse a single .cause chain) so
that wrapped errors from handler.serve/file-worker-pool are recognized; keep the
existing logging and response behavior (res.status, res.send) and still guard
with !res.headersSent && !res.isAborted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/http/handlers/static/file-worker-pool.spec.ts`:
- Around line 127-139: The test's name claims it verifies completing an
in-flight task during termination, but the current test awaits
pool.readFile(...) to finish before calling pool.terminate(), so there is no
race; update the test to either rename it to reflect the sequential behavior
(e.g., "should read a file and then terminate cleanly") or change the flow to
start the read without awaiting (e.g., call pool.readFile(testFilePath) to get a
Promise, then call await pool.terminate() and finally await the read Promise) to
exercise the true in-flight termination behavior for FileWorkerPool.readFile and
FileWorkerPool.terminate.

In `@src/http/routing/route-registry.ts`:
- Around line 277-278: RouteRegistry now uses the nullish coalescing operator
allowing an explicit maxBodySize of 0 to be passed to _initBodyParser; add a
defensive invariant in RouteRegistry's constructor to validate
options.maxBodySize (> 0) and throw a clear Error if it's not (e.g.,
"RouteRegistry requires options.maxBodySize > 0"), so direct construction (e.g.,
in tests) cannot silently accept 0; leave fastAbort handling as-is.
- Around line 462-465: The guard handling should surface empty-observable or
explicit-false cases instead of silently denying; replace the current
lastValueFrom(..., { defaultValue: false }) usage for observable guard results
with a try/catch around await lastValueFrom(guardResult) so that an EmptyError
is caught and a warning is logged (include guard.constructor?.name and relevant
context info), and also log a warning whenever a non-observable or resolved
observable result is strictly false before returning false; keep behavior of
returning false but ensure warnings are emitted for empty/false guard results
(references: guard.canActivate, isObservable, lastValueFrom, ExecutionContext).

In `@src/websocket/adapter/uws.adapter.spec.ts`:
- Around line 89-93: The test's inline comment is misleading because
UwsAdapter.create() does not listen or use the passed _port and simply returns a
uWS.App(); update the test to remove or replace the "Let OS decide the port"
comment and either call create() without implying port selection or pass the
configured 8099 to match existing setup; ensure references to adapter.create()
and adapter.close() remain and the assertion behavior is unchanged.

In `@src/websocket/exceptions/ws-exception.ts`:
- Around line 19-21: The constructor in WsException currently restores the
prototype using Object.setPrototypeOf(this, WsException.prototype), which breaks
instanceof for subclasses; change it to Object.setPrototypeOf(this,
new.target.prototype) so the actual invoked constructor's prototype (not the
base class) is restored and subclass instanceof checks (e.g., for
AuthWsException) work correctly when constructing the error in WsException's
constructor.

In `@src/websocket/interfaces/uws-options.interface.ts`:
- Around line 108-134: ResolvedUwsAdapterOptions currently drops SSL fields and
UwsAdapter always calls uWS.App(), so SSL options passed to the constructor are
ignored; update ResolvedUwsAdapterOptions to include cert_file_name,
key_file_name, passphrase, dh_params_file_name, and ssl_prefer_low_memory_usage,
modify UwsAdapter.constructor to extract and store these fields from the
incoming UwsAdapterOptions, and change UwsAdapter.create() to conditionally
instantiate uWS.SSLApp(...) with the SSL config (cert_file_name, key_file_name,
passphrase, dh_params_file_name, ssl_prefer_low_memory_usage) when both cert and
key are present, falling back to uWS.App() otherwise.

In `@src/websocket/middleware/pipes/pipe-executor.ts`:
- Around line 72-76: paramPipe.type (from ParamType) is being unsafely cast to
Nest's ArgumentMetadata['type'] causing invalid runtime values (e.g.,
'messageBody'); replace the cast by mapping paramPipe.type to a valid NestJS
literal and fallback to 'custom'. Add a helper like
toArgumentMetadataType(paramPipe.type) that returns only 'body' | 'query' |
'param' or 'custom', and use it when constructing metadata (the metadata
variable and the code that reads paramPipe.type in pipe-executor.ts). Ensure all
branches of ParamType map to 'custom' when no direct match exists so downstream
pipes (e.g., ValidationPipe) see a valid ArgumentMetadata.type.

In `@src/websocket/routing/handler-executor.ts`:
- Around line 112-124: The guard denial is creating an HttpException
(ForbiddenException) which is serialized as a generic internal error; replace
that with a WebSocket-specific exception so the existing serialize path handles
it properly: in handler-executor.ts, construct and throw/pass a WsException
(e.g., new WsException('Forbidden resource')) instead of new
ForbiddenException('Forbidden resource') when building the host for
filterExecutor.catch (keep instance, methodName, client, data as-is), or
alternatively update exception-filter-executor.ts::serializeException to
special-case HttpException by extracting status/response so ForbiddenException
is serialized correctly; prefer the first approach (use WsException) to keep
semantics within the WS domain and to let filterExecutor.catch return the proper
getError() payload.

---

Outside diff comments:
In `@src/http/body/multipart-handler.ts`:
- Around line 244-268: In handleFile, the code captures stream.truncated as a
primitive and passes it into executeHandler (field.file.truncated) too early, so
it will be stale; change the file object passed to executeHandler to expose
truncated as a getter that returns stream.truncated (i.e., a live boolean
delegating to the stream) and update the corresponding interface/type used by
executeHandler/MultipartHandler to document truncated as a live flag (boolean
getter) rather than a copied value; keep the rest of handleFile behavior
(including the resume in finally) intact so consumers reading
field.file.truncated after stream consumption see the correct truncation state.

In `@src/websocket/adapter/uws.adapter.ts`:
- Around line 211-221: The gateways Map was flipped to Map<object, string>, so
the forEach callback currently receives the name string instead of the gateway
instance; update the connection and disconnect hook loops to iterate the Map's
keys (gateway instances) and pass those instances to
lifecycleHooksManager.callConnectionHook and
lifecycleHooksManager.callDisconnectHook, and use the instance when computing
the logger message (gateway.constructor?.name) so it reports the real class
name; apply the same keys()-based iteration change for the disconnect hook block
that mirrors the connection hook and keep the try/catch per gateway as before.

In `@src/websocket/routing/metadata-scanner.ts`:
- Around line 181-191: The sortObjectKeys implementation currently
short-circuits on arrays so objects inside arrays keep original key order;
update sortObjectKeys to recurse into arrays by mapping each element and, when
an element is a non-null object, call sortObjectKeys on it (mirror
MessageRouter.sortValue behavior), leaving primitives/other types unchanged;
update getMethodNameForEvent usage remains the same but will now produce
deterministically sorted keys for array-contained objects; add a parity unit
test in metadata-scanner.spec.ts echoing the array-of-objects case from
message-router.spec.ts to ensure matching behavior.

---

Nitpick comments:
In `@package.json`:
- Around line 29-30: Remove the redundant "uwestjs" keyword from the
package.json keywords array (it duplicates the package name and is a no-op for
npm search); keep the other keywords such as "http" and "concurrency" intact and
ensure keywords remains a valid array in package.json.

In `@src/http/body/body-parser.spec.ts`:
- Around line 129-150: Add an explicit assertion that the uWS response was
closed exactly once to make the test intent clear: after triggering the chunks
and before the final expectations, assert mockUwsRes.close was called once
(e.g., expect(mockUwsRes.close).toHaveBeenCalledTimes(1)); also add a one-line
comment near the onDataCallback calls stating these are only to exercise the
"bytes still counted after rejection" invariant.

In `@src/http/body/multipart-handler.spec.ts`:
- Around line 232-268: Remove the redundant jest.useRealTimers() call inside the
"should handle async handlers sequentially" test since an afterEach hook already
restores real timers for the describe('backpressure handling') suite;
specifically, delete the trailing jest.useRealTimers() at the end of the
it('should handle async handlers sequentially', ...) test so cleanup is
centralized, leaving the afterEach and the rest of the test
(setupMultipartRequest, req.multipart handler, sendMultipartData, expectations
including mockUwsRes.pause/resume checks) intact.

In `@src/http/body/multipart-handler.ts`:
- Around line 280-292: Replace the instanceof Promise branching with
Promise.resolve to normalize handler results (including thenables) into a real
Promise: call Promise.resolve(handler(field)) and assign that to handlerPromise,
set this.multipartPromise = handlerPromise before this.request.resume(), await
handlerPromise, then clear this.multipartPromise; this ensures functions like
handler and the multipartPromise handling in multipart-handler.ts correctly
handle non-native thenables.

In `@src/http/core/index.ts`:
- Around line 1-3: The public core index currently exports only the runtime
symbols UwsRequest, UwsResponse, and HttpExecutionContext; if the underlying
modules (request, response, context) also expose companion types or interfaces
that consumers may need (for example options/config types), add explicit type
re-exports alongside the existing exports — e.g., add export type {
SomeRequestOptions } from './request' or export type { ResponseConfig } from
'./response' and export type { HttpContextOptions } from './context' to make
those types part of the core public surface while keeping imports shallow and
intentional.

In `@src/http/core/request.spec.ts`:
- Around line 985-993: The test restores real timers immediately after
jest.runAllTimers(), which can race with the async for-await consumer and
done(); capture and await the async iteration's promise (the IIFE that performs
the for await ... of) instead of letting it run implicitly, then call
jest.useRealTimers() only after that promise resolves so the test lifecycle is
deterministic; reference the existing async IIFE/for-await block and the
jest.runAllTimers()/jest.useRealTimers() calls when locating where to store and
await the promise.

In `@src/http/core/request.ts`:
- Around line 1020-1026: The extra re-check of this.doneReadingData after
registering listeners is defensively safe but likely unreachable; update the
code around the block that contains the re-check to add a brief comment
explaining that doneReadingData can be flipped by handleIncomingChunk (uWS
onData) and that because the executor body runs synchronously in single-threaded
JS the race is unlikely, but the check is left as a belt-and-suspenders guard
before calling cleanup() and resolve(this.getBufferedData()); mention the
related symbols doneReadingData, handleIncomingChunk, getBufferedData, and
cleanup in the comment so future readers understand the intent; alternatively,
if you prefer removal, delete the re-check and its cleanup/resolve branch.

In `@src/http/core/response.spec.ts`:
- Around line 1160-1162: Replace the implicit callback usage new
Promise(setImmediate) with the more explicit form new Promise((resolve) =>
setImmediate(resolve)) in the test to improve readability; update both
occurrences (the two chained awaits around the comment and the similar block
around lines ~1190-1192) so readers clearly see the promise resolve being passed
to setImmediate.

In `@src/http/handlers/static/file-worker-pool.ts`:
- Around line 92-122: Unify the ordering in the worker 'exit' handler to match
the error path: call markAsDead(), then rejectAllPendingTasks(...) and then
this.onDeath(this) so both non-zero and zero exit branches follow the same
sequence; update the branch where a clean exit currently calls
rejectAllPendingTasks before markAsDead to instead call markAsDead() first, then
rejectAllPendingTasks, then this.onDeath(this), keeping the same messages and
using the existing methods rejectAllPendingTasks, markAsDead, and onDeath for
consistency.

In `@src/http/platform/uws-platform.adapter.ts`:
- Around line 782-816: In staticAssetHandler, make the ENOENT detection
defensive by also checking for a wrapped error's cause code: when computing
isNotFound (currently (error as { code?: string })?.code === 'ENOENT'), extend
it to check (error as any).cause?.code === 'ENOENT' as well (or traverse a
single .cause chain) so that wrapped errors from handler.serve/file-worker-pool
are recognized; keep the existing logging and response behavior (res.status,
res.send) and still guard with !res.headersSent && !res.isAborted.

In `@src/http/test-helpers.ts`:
- Around line 96-101: Update the JSDoc for the tryEndComplete property to
clarify it's independent from writeSuccess: explain that tryEndComplete
corresponds to the uWS tryEnd() second boolean (hasResponded) and can be set
separately from writeSuccess (the first boolean/ok), noting the default
convenience of setting tryEndComplete to writeSuccess but that they are
orthogonal and may be overridden independently; reference tryEnd(),
tryEndComplete, and writeSuccess in the comment so test authors understand the
mapping to uWS semantics.

In `@src/shared/index.ts`:
- Around line 1-2: The current barrel uses wildcard re-exports via "export *
from './di';" and "export * from './interfaces';", which can create ambiguous
symbol collisions as the surface grows; update the barrel to use explicit named
re-exports (list the specific symbols you want to expose from di and interfaces)
or use namespaced re-exports (e.g., export * as di from './di' and export * as
interfaces from './interfaces') so consumers cannot silently get conflicting
identifiers and future additions won't cause ambiguity.

In `@src/shared/interfaces/cors-options.interface.ts`:
- Around line 26-37: Clarify the docs for the cors options by stating whether
the string form for methods, allowedHeaders, and exposedHeaders expects a single
token (e.g., "GET") or a comma-separated list (e.g., "GET,POST") and how it will
be interpreted when building the Access-Control-Allow-* headers; update the
JSDoc for the methods?: string | string[] field and the corresponding
allowedHeaders and exposedHeaders declarations to explicitly say that a single
string may be either a single method/name or a comma-separated list and describe
whether the implementation will split comma-separated strings into arrays (or
treat the string verbatim) so callers know which form to use.
- Around line 18-24: The CorsOptions interface should warn that credentials:
true is incompatible with a wildcard/unrestricted origin; update the doc
comments for origin and/or credentials in the CorsOptions (the origin property
and credentials?: boolean) to state that when credentials is true browsers
reject Access-Control-Allow-Origin: '*' (or an unrestricted origin equivalent
such as origin: true), and recommend using an explicit origin string or a
dynamic function that echoes the request origin; alternatively, add a runtime
check in the CORS handler to throw/log when credentials is true and origin is
'*' or origin is configured as unrestricted to prevent misconfiguration.

In `@src/shared/interfaces/platform-options.interface.ts`:
- Around line 1-2: Change the two imports in platform-options.interface.ts to
type-only imports to avoid bundler circular dependency issues: replace the
current imports that bring in UwsAdapterOptions and HttpOptions with type-only
imports (i.e., import type for UwsAdapterOptions and HttpOptions) so the
compiler treats them as erased types and bundlers can safely elide the module
edges; locate the import statements referencing UwsAdapterOptions and
HttpOptions and update them to use import type.

In `@src/websocket/adapter/uws.adapter.ts`:
- Around line 110-111: The WeakSet gatewaySet is redundant because gateways
(Map<object,string>) already keys gateway instances; remove the gatewaySet
declaration and all uses (e.g., gatewaySet.has(gateway), gatewaySet.add(...),
gatewaySet.delete(...), and any reset logic) and replace them with the Map's
lifecycle operations: use this.gateways.has(gateway) for membership checks,
this.gateways.set(gateway, name) when registering, this.gateways.delete(gateway)
when unregistering, and clear/reset via this.gateways.clear() or appropriate map
ops; update any functions referencing gatewaySet accordingly so the Map remains
the single source of truth.

In `@src/websocket/middleware/pipes/pipes.integration.spec.ts`:
- Around line 60-68: The test mutates metadata on the gateway class/prototype
(PIPES_METADATA, PIPES_METADATA:params, PARAM_ARGS_METADATA) and must restore
prior state to avoid cross-test pollution; capture current metadata values for
keys `${PIPES_METADATA}:params` (on gatewayClass+methodName), `PIPES_METADATA`
(on gatewayClass), and `PARAM_ARGS_METADATA` (on
gatewayClass.prototype+methodName) before changing them, then perform the
existing modifications to existingPipes, classPipes, and newParams, and finally
in a try/finally restore the captured metadata (using Reflect.defineMetadata or
Reflect.deleteMetadata when value was undefined) so executeHandler/TestGateway
runs don’t leave global mutations; use the same symbols (existingPipes,
existingPipesMap, newParams, gatewayClass, methodName) to locate the code to
wrap.

In `@src/websocket/routing/message-router.spec.ts`:
- Around line 203-287: The metadata scanner's sortObjectKeys implementation
doesn't handle objects nested inside arrays; update the
MetadataScanner.sortObjectKeys method to recursively sort keys for all plain
objects encountered, including objects within arrays (preserve arrays order but
normalize any object elements), and ensure it treats key order insensitively the
same way getHandlerKey does. After fixing sortObjectKeys, add tests in
metadata-scanner.spec.ts mirroring the MessageRouter object-in-array cases
(create test cases that assert equivalent normalized keys for object elements in
arrays and that differing values still produce different keys) to ensure parity
with MessageRouter coverage.

In `@src/websocket/routing/metadata-scanner.ts`:
- Around line 104-107: Extract the deterministic serialization logic into a
single shared helper named stableStringify (e.g., under src/shared) and replace
the local sortObjectKeys/sortValue implementations in MetadataScanner and
MessageRouter: implement stableStringify to produce a byte-for-byte stable JSON
string by recursively sorting object keys and normalizing values consistently,
then use stableStringify when computing messageKey in MetadataScanner and
wherever MessageRouter builds its key so both components share identical
behavior and you can safely remove the duplicated sortObjectKeys/sortValue
functions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0dd42810-6eb9-4f66-af18-07a2485b6d2f

📥 Commits

Reviewing files that changed from the base of the PR and between 2627f74 and 15e6eea.

⛔ 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 (107)
  • .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/index.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/platform/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
💤 Files with no reviewable changes (10)
  • src/interfaces/websocket-client.interface.ts
  • src/middleware/guards/index.ts
  • src/socket/index.ts
  • src/exceptions/index.ts
  • src/middleware/pipes/index.ts
  • src/middleware/filters/index.ts
  • src/platform/index.ts
  • src/middleware/index.ts
  • src/decorators/index.ts
  • src/interfaces/index.ts

Comment thread src/http/handlers/static/file-worker-pool.spec.ts
Comment thread src/http/routing/route-registry.ts
Comment thread src/http/routing/route-registry.ts
Comment thread src/websocket/adapter/uws.adapter.spec.ts
Comment thread src/websocket/exceptions/ws-exception.ts Outdated
Comment thread src/websocket/interfaces/uws-options.interface.ts
Comment thread src/websocket/middleware/pipes/pipe-executor.ts
Comment thread src/websocket/routing/handler-executor.ts
…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
@VikramAditya33 VikramAditya33 merged commit eaf0deb into v2-http-support Apr 24, 2026
3 checks passed
@VikramAditya33 VikramAditya33 deleted the re-org branch April 24, 2026 17:35
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.

1 participant