Skip to content

Improving the disposal of Diagnostics#2904

Merged
RobinTail merged 5 commits intomasterfrom
ext-diags-using
Aug 25, 2025
Merged

Improving the disposal of Diagnostics#2904
RobinTail merged 5 commits intomasterfrom
ext-diags-using

Conversation

@RobinTail
Copy link
Copy Markdown
Owner

@RobinTail RobinTail commented Aug 25, 2025

  • using keyword is not needed, since everything is sync in the Diagnostics and events-free

Summary by CodeRabbit

  • New Features

    • Added development-only runtime validation of schemas and path parameters to improve diagnostics.
    • HEAD requests are now automatically treated alongside GET where applicable.
  • Bug Fixes

    • CORS headers are applied before request parsers run when CORS is enabled.
  • Refactor

    • Routing initialization modularized for clearer separation of concerns; initialization props consolidated and may need minor updates.

@RobinTail RobinTail added the refactoring The better way to achieve the same result label Aug 25, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 25, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Introduces an InitProps type and a new collectSiblings helper that builds the path→method familiar map, scopes Diagnostics with non-production schema/path checks, optionally adds CORS "OPTIONS" entries, and updates initRouting to delegate to collectSiblings and adjust signature and CORS ordering.

Changes

Cohort / File(s) Summary
Routing initialization refactor
express-zod-api/src/routing.ts
Added InitProps type and collectSiblings helper to build the familiar route map. Moved Diagnostics into a scoped pattern inside collectSiblings with doc?.checkSchema() and doc?.checkPathParams() executed only in non-production. Optionally adds "options" entries when CORS is enabled, pushes "head" when "get" exists, and updates initRouting to delegate to collectSiblings and reorder CORS headers before parsers.
Changelog update
CHANGELOG.md
Added a new v25.3.1 changelog entry noting a small optimization for running diagnostics in non-production; retained v25.3.0 entry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Init as initRouting
  participant CS as collectSiblings
  participant Doc as Diagnostics (scoped, optional)
  participant App as Express App
  participant CORS as makeCorsHeaders
  participant EP as Endpoint

  Init->>CS: build familiar map (routing, parsers, config, getLogger)
  rect rgba(200,220,255,0.20)
    note right of CS: Non-production diagnostics
    CS->>Doc: open scoped diagnostics
    CS->>Doc: checkSchema(), checkPathParams()
    Doc-->>CS: diagnostics results
  end
  CS-->>Init: familiar map (path → method → [parsers, endpoint], +OPTIONS when CORS)

  loop register handlers
    Init->>App: register handler (method, path)
  end

  App->>App: incoming request
  alt config.cors enabled
    App->>CORS: set CORS headers (before parsers)
  end
  App->>EP: run parsers → execute endpoint
  EP-->>App: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I hopped through routes with map in paw,
Collected siblings, checked each law.
CORS waved first, parsers took their cue,
Diagnostics peeped when the sky wasn't blue.
Init props snug — a tidy warren view. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f2def0 and 4cb2894.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ext-diags-using

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coveralls-official
Copy link
Copy Markdown

coveralls-official Bot commented Aug 25, 2025

Coverage Status

coverage: 100.0%. remained the same
when pulling 4cb2894 on ext-diags-using
into f9b560f on master.

@RobinTail RobinTail marked this pull request as ready for review August 25, 2025 05:40
Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
express-zod-api/src/diagnostics.ts (1)

19-19: Consider documenting the empty dispose method.

While the no-op implementation is correct for this use case (since WeakSet and WeakMap don't require explicit cleanup), consider adding a brief comment explaining why the dispose method is intentionally empty. This would help future maintainers understand that the empty implementation is deliberate.

-  public [Symbol.dispose]() {}
+  public [Symbol.dispose]() {
+    // No explicit cleanup needed - WeakSet/WeakMap handle GC automatically
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f9b560f and c45803e.

📒 Files selected for processing (2)
  • express-zod-api/src/diagnostics.ts (2 hunks)
  • express-zod-api/src/routing.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
express-zod-api/src/routing.ts (4)
express-zod-api/src/server-helpers.ts (1)
  • GetLogger (25-25)
express-zod-api/src/config-type.ts (1)
  • CommonConfig (35-84)
express-zod-api/src/common-helpers.ts (1)
  • isProduction (141-144)
express-zod-api/src/diagnostics.ts (1)
  • Diagnostics (10-86)
🔇 Additional comments (7)
express-zod-api/src/diagnostics.ts (1)

10-10: Good implementation of the Disposable pattern!

The class now properly implements the Disposable interface, aligning with TypeScript 5.2's explicit resource management feature. This enables automatic cleanup when used with the using keyword.

express-zod-api/src/routing.ts (6)

27-33: Well-structured interface for initialization properties!

The InitProps interface effectively consolidates all routing initialization parameters, improving type safety and making the function signatures cleaner.


68-68: Excellent use of the using declaration for automatic disposal!

The using declaration ensures that the Diagnostics instance is automatically disposed when it goes out of scope, even if an exception occurs. The conditional creation based on isProduction() is a smart optimization that avoids the overhead of diagnostics in production environments.


61-83: Clean separation of concerns with the new collectSiblings helper!

The extraction of the sibling collection logic into a dedicated function improves code modularity and readability. The scoped diagnostics lifetime is well-managed within this function.


99-111: Good fix for CORS header ordering!

Moving the CORS headers setup before the parsers (using unshift) correctly addresses issue #2706. This ensures that CORS headers are set even if parsers fail, which is crucial for proper cross-origin error handling.


85-86: Clean delegation pattern in initRouting!

The updated function signature using InitProps and the delegation to collectSiblings maintains backward compatibility while improving the internal structure. The rest parameter usage is elegant.


71-74: Production mode detection is correctly implemented and properly gated

The isProduction function in express-zod-api/src/common-helpers.ts uses Ramda’s memoizeWith, keyed by the build-time substituted process.env.TSDOWN_STATIC, and returns process.env.NODE_ENV === "production". Since environment variables are static at startup, this effectively caches a single boolean result and accurately reflects production mode.

All uses of isProduction() are consistently gating performance-sensitive or security-sensitive behavior:

  • routing.ts (lines 68–74) for enabling/disabling diagnostics
  • builtin-logger.ts (line 53) for setting log-level to “warn” in production
  • result-helpers.ts (line 86) for hiding error details in production

No changes are required to ensure correct production detection across deployment scenarios. Consider normalizing NODE_ENV with .toLowerCase() if you need to support non-standard casing.

@RobinTail RobinTail changed the title Truly Disposable Diagnostics Improving the disposal of Diagnostics Aug 25, 2025
Copy link
Copy Markdown
Contributor

@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: 1

Caution

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

⚠️ Outside diff range comments (2)
express-zod-api/src/routing.ts (2)

78-81: OPTIONS preflight is registered with endpoint + parsers; it will execute business logic.

By seeding the “options” entry with the same [parsers, endpoint], the inner loop later registers an OPTIONS handler that runs parsers and calls endpoint.execute. Preflight must not hit endpoint logic and should short-circuit with headers + 204.

Apply this change to ensure preflight doesn’t carry parsers over:

-    if (!familiar.has(path))
-      familiar.set(path, new Map(config.cors ? [["options", value]] : []));
+    if (!familiar.has(path)) {
+      const entries: [CORSMethod, [RequestHandler[], AbstractEndpoint]][] = [];
+      if (config.cors) {
+        // Preflight must not run parsers or endpoint logic; the actual registration
+        // will special-case OPTIONS in initRouting.
+        entries.push(["options", R.pair([], endpoint)]);
+      }
+      familiar.set(path, new Map(entries));
+    }

And finalize the fix in the registration loop (see separate comment for Lines 93-114).


93-114: Special-case OPTIONS in the registration loop: set headers and return 204 without parsers/endpoint.

Currently, the generated handlers for OPTIONS will run parsers and endpoint.execute. That’s incorrect for CORS preflight and may blow up on endpoints expecting bodies or specific methods.

-    for (const [method, [matchingParsers, endpoint]] of methods) {
-      const handlers = matchingParsers
-        .slice() // must be immutable
-        .concat(async (request, response) => {
-          const logger = getLogger(request);
-          return endpoint.execute({ request, response, logger, config });
-        });
-      if (config.cors) {
-        // issue #2706, must go before parsers:
-        handlers.unshift(async (request, response, next) => {
-          const logger = getLogger(request);
-          const defaultHeaders = makeCorsHeaders(accessMethods);
-          const headers =
-            typeof config.cors === "function"
-              ? await config.cors({ request, endpoint, logger, defaultHeaders })
-              : defaultHeaders;
-          response.set(headers);
-          next();
-        });
-      }
-      app[method](path, ...handlers);
-    }
+    for (const [method, [matchingParsers, endpoint]] of methods) {
+      // CORS preflight: do not run parsers or endpoint logic.
+      if (method === "options") {
+        const preflight: RequestHandler = async (request, response) => {
+          const logger = getLogger(request);
+          const defaultHeaders = makeCorsHeaders(accessMethods);
+          const headers =
+            typeof config.cors === "function"
+              ? await config.cors({ request, endpoint, logger, defaultHeaders })
+              : defaultHeaders;
+          response.set(headers);
+          response.sendStatus(204);
+        };
+        app.options(path, preflight);
+        continue;
+      }
+
+      const handlers = matchingParsers
+        .slice() // must be immutable
+        .concat(async (request, response) => {
+          const logger = getLogger(request);
+          return endpoint.execute({ request, response, logger, config });
+        });
+      if (config.cors) {
+        // issue #2706, must go before parsers:
+        handlers.unshift(async (request, response, next) => {
+          const logger = getLogger(request);
+          const defaultHeaders = makeCorsHeaders(accessMethods);
+          const headers =
+            typeof config.cors === "function"
+              ? await config.cors({ request, endpoint, logger, defaultHeaders })
+              : defaultHeaders;
+          response.set(headers);
+          next();
+        });
+      }
+      app[method](path, ...handlers);
+    }
🧹 Nitpick comments (2)
express-zod-api/src/routing.ts (2)

27-33: Good extraction of InitProps; consider immutability for safety.

Nice move pulling the param typing into a dedicated interface. Consider making properties readonly and tightening parsers to readonly arrays to reflect the intended immutability (you already clone handlers later).

-interface InitProps {
-  app: IRouter;
-  getLogger: GetLogger;
-  config: CommonConfig;
-  routing: Routing;
-  parsers?: Parsers;
-}
+interface InitProps {
+  readonly app: IRouter;
+  readonly getLogger: GetLogger;
+  readonly config: CommonConfig;
+  readonly routing: Routing;
+  readonly parsers?: { readonly [K in ContentType]?: readonly RequestHandler[] };
+}

71-76: Redundant isProduction guard; rely on doc presence.

You already gate Diagnostics construction by environment. Wrapping doc?.… calls with if (!isProduction()) { … } does double work and re-invokes a memoized lookup. You can just call doc?.check… directly.

-  if (!isProduction()) {
-    doc?.checkSchema(endpoint, { path, method });
-    doc?.checkPathParams(path, endpoint, { method });
-  }
+  doc?.checkSchema(endpoint, { path, method });
+  doc?.checkPathParams(path, endpoint, { method });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c45803e and 2ddd77f.

📒 Files selected for processing (1)
  • express-zod-api/src/routing.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
express-zod-api/src/routing.ts (4)
express-zod-api/src/server-helpers.ts (1)
  • GetLogger (25-25)
express-zod-api/src/config-type.ts (1)
  • CommonConfig (35-84)
express-zod-api/src/common-helpers.ts (1)
  • isProduction (141-144)
express-zod-api/src/diagnostics.ts (1)
  • Diagnostics (10-84)
🔇 Additional comments (1)
express-zod-api/src/routing.ts (1)

86-87: initRouting call sites verified—no internal breakages detected

I’ve checked all 21 internal invocations of initRouting across .ts/.tsx files and confirmed they’re already using the new object-destructured signature ({ app, getLogger, config, … }), with no positional-argument calls remaining.

• 21 call sites updated to initRouting({ … }), none rely on the old parameter order
• No internal references to the legacy signature found

You’re all set internally. As a best practice, please also review any external examples or documentation to ensure they reflect the new InitProps bag.

Comment thread express-zod-api/src/routing.ts
Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (1)
express-zod-api/src/routing.ts (1)

68-69: Diagnostics now constructed with an ActualLogger — previous issue resolved.

Passing getLogger() (called) ensures Diagnostics receives a logger instance, not the factory. Thanks for fixing that.

🧹 Nitpick comments (2)
express-zod-api/src/routing.ts (2)

27-33: Nice extraction of InitProps to clarify the API surface.

This reduces the cognitive load on initRouting. Optional: mark fields readonly to prevent accidental mutation from callers.

-interface InitProps {
-  app: IRouter;
-  getLogger: GetLogger;
-  config: CommonConfig;
-  routing: Routing;
-  parsers?: Parsers;
-}
+interface InitProps {
+  readonly app: IRouter;
+  readonly getLogger: GetLogger;
+  readonly config: CommonConfig;
+  readonly routing: Routing;
+  readonly parsers?: Parsers;
+}

88-91: Avoid duplicate HEAD in Allow/Access-Control headers.

When a route explicitly declares "head" and we also append "head" because "get" is present, the list can contain duplicates. Low impact, but easy to harden.

-const accessMethods = Array.from(methods.keys());
-/** @link https://github.com/RobinTail/express-zod-api/discussions/2791#discussioncomment-13745912 */
-if (accessMethods.includes("get")) accessMethods.push("head");
+const accessMethods = Array.from(methods.keys());
+/** @link https://github.com/RobinTail/express-zod-api/discussions/2791#discussioncomment-13745912 */
+if (accessMethods.includes("get") && !accessMethods.includes("head")) {
+  accessMethods.push("head");
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2ddd77f and 8f2def0.

📒 Files selected for processing (1)
  • express-zod-api/src/routing.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
express-zod-api/src/routing.ts (5)
express-zod-api/src/server-helpers.ts (1)
  • GetLogger (25-25)
express-zod-api/src/config-type.ts (1)
  • CommonConfig (35-84)
express-zod-api/src/common-helpers.ts (1)
  • isProduction (141-144)
express-zod-api/src/diagnostics.ts (1)
  • Diagnostics (10-84)
express-zod-api/src/routing-walker.ts (2)
  • OnEndpoint (8-12)
  • walkRouting (76-113)
🔇 Additional comments (4)
express-zod-api/src/routing.ts (4)

61-62: Good separation of concerns with collectSiblings().

Pulling discovery+grouping out of initRouting makes the main flow easier to follow and test.


72-73: Production-gated diagnostics checks are placed correctly.

The optional chaining keeps the hot path clean in production while still running helpful checks in non-prod. No action needed.


81-82: collectSiblings return value is well-scoped.

Returning the familiar map keeps initRouting focused on registration. No issues.


84-85: Verified: all initRouting calls use object-literal arguments
I searched the entire repo and confirmed that every invocation—including those in express-zod-api/src/server.ts (lines 56 and 88) and all test cases—passes a single object literal. There are no positional‐argument calls remaining, so the new destructured signature is source-compatible.

Comment thread express-zod-api/src/routing.ts
@RobinTail RobinTail merged commit 972b1b1 into master Aug 25, 2025
10 of 11 checks passed
@RobinTail RobinTail deleted the ext-diags-using branch August 25, 2025 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring The better way to achieve the same result

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant