Skip to content

Conversation

@Oksamies
Copy link
Contributor

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a request-scoped promise deduper (deduplicatePromiseForRequest) and a request-aware browser Dapper singleton with per-request proxied instances (initializeClientDapper, getClientDapper, getDapperForRequest, resetDapperSingletonForTest). Updates security/publicEnvVariables.getSessionTools(preFetchedEnv?) (fixed typo and adjusted import path) and extends public env vars with VITE_COOKIE_DOMAIN. Wires client startup to call initializeClientDapper(...) using session tools (errors reported to Sentry). Includes unit tests for dapper singleton and request-cache behavior.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author. Add a brief description explaining the purpose and scope of request caching and dapper singleton changes to help reviewers understand the implementation details.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Request caching' is concise and directly references the primary feature being implemented—request-scoped caching for asynchronous functions.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 11-20-test_implemenation_of_cross-clientloader-request_dedupe

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0642cbe and 8869b58.

📒 Files selected for processing (1)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (1)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

1-114: LGTM - solid implementation.

All past review concerns have been addressed. The implementation correctly handles:

  • Request-scoped deduplication with WeakMap for automatic cleanup
  • Abort signal handling (both pre-aborted and during execution)
  • Memory leak prevention via timeout cleanup with WeakRef fallback
  • Cache removal on rejection to enable retries

The design choice to keep successful promises in cache (not removing on .then()) is correct for this use case—it allows multiple loaders running at different times within the same request to benefit from cached results, not just concurrent calls.

Documentation is thorough and clearly notes the performance characteristics of deep equality checking.


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
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 90.47619% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 10.70%. Comparing base (536487c) to head (fa0754a).

Files with missing lines Patch % Lines
apps/cyberstorm-remix/app/entry.client.tsx 0.00% 11 Missing ⚠️
...berstorm-remix/cyberstorm/utils/dapperSingleton.ts 95.12% 4 Missing ⚠️
.../cyberstorm-remix/cyberstorm/utils/requestCache.ts 98.57% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1636      +/-   ##
==========================================
+ Coverage    9.92%   10.70%   +0.78%     
==========================================
  Files         315      317       +2     
  Lines       22687    22848     +161     
  Branches      412      457      +45     
==========================================
+ Hits         2251     2447     +196     
+ Misses      20436    20401      -35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@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 (1)
apps/cyberstorm-remix/app/c/community.tsx (1)

85-98: Fix the og:url meta tag—it's stringifying an object, not the base URL

The function returns an object map ({ VITE_BETA_SITE_URL?: string }), so concatenating it directly produces [object Object]/c/foo instead of the intended URL. Extract the value first:

const { VITE_BETA_SITE_URL } = getPublicEnvVariables(["VITE_BETA_SITE_URL"]);

<meta
  property="og:url"
  content={`${VITE_BETA_SITE_URL ?? ""}${location.pathname}`}
/>
🧹 Nitpick comments (4)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)

57-95: Request‑scoped Proxy + caching behavior is correct but narrowly scoped

The getRequestScopedDapper(request) Proxy only wraps get* methods and delegates them through getCachedDapperPromise with a per‑Request cache, which is exactly what you want for cross‑clientLoader dedupe. Non‑get methods are passed through as normal bound functions, so POST‑like operations won’t be cached. Just keep in mind:

  • Any new DapperTs methods that should be deduped must keep the get* naming convention.
  • Cached promises (including rejections) are reused for the lifetime of the Request, so if you ever need “retry on same args within a request”, you’d need to evict on rejection.
apps/cyberstorm-remix/app/entry.client.tsx (1)

7-25: Dapper priming order is good; double‑check getConfig signature and env failure mode

Initializing sessionTools and calling primeClientDapper before Sentry.init is a good place to ensure the global Dapper instance has a session‑aware config early. Two things to confirm:

  • sessionTools.getConfig(publicEnvVariables.VITE_API_URL)—make sure getConfig is meant to accept an optional API host override; otherwise this argument may be ignored or cause a type mismatch.
  • getSessionTools() now runs at module evaluation and will throw if required env vars are missing, which will prevent the app from hydrating at all. If you ever want Sentry + a minimal UI even with broken env, you may want to lazy‑init sessionTools instead.
apps/cyberstorm-remix/app/p/tabs/Readme/Readme.tsx (1)

10-39: Loader/clientLoader community shape differs; consider unifying it

On the server loader you await dapper.getCommunity(...), but on the client loader you return community as a Promise from getRequestScopedDapper. Right now the component only logs community, so this mismatch isn’t user‑visible, but it does mean community is sometimes a plain object and sometimes a Promise.

If you plan to actually render from community here (e.g. via <Await>), it’d be safer to make both loaders return the same shape (either both promises or both resolved values) to avoid subtle runtime checks down the line.

apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts (1)

14-75: Per‑request cache + abort cleanup look solid; be explicit about rejected‑promise behavior

The WeakMap+AbortSignal approach in getCache gives you nice per‑Request lifetime for cached entries, and restrictedNames + label avoid issues with minified/anonymous functions. Two behavioral notes to be aware of:

  • getCachedDapperPromise keeps a rejected promise in the cache, so repeated calls with the same method+inputs during a single request will all see the same rejection (no implicit retry).
  • The global fallback cache is only used when request is undefined; currently getRequestScopedDapper always passes a request, so the global path is effectively unused.

If you ever want retries on identical args within the same request, consider evicting entries on rejection, e.g. by attaching a .catch that removes the entry before rethrowing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03b897a and 1d366e2.

📒 Files selected for processing (5)
  • apps/cyberstorm-remix/app/c/community.tsx (2 hunks)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/app/p/tabs/Readme/Readme.tsx (3 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
packages/thunderstore-api/src/index.ts (1)
  • RequestConfig (1-7)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (38-96)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-67)
apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts (1)
  • getCachedDapperPromise (39-71)
apps/cyberstorm-remix/app/c/community.tsx (3)
apps/cyberstorm-remix/app/p/tabs/Readme/Readme.tsx (1)
  • clientLoader (32-46)
apps/cyberstorm-remix/app/upload/upload.tsx (1)
  • clientLoader (78-88)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • getRequestScopedDapper (57-96)
apps/cyberstorm-remix/app/p/tabs/Readme/Readme.tsx (4)
apps/cyberstorm-remix/app/c/community.tsx (2)
  • loader (31-46)
  • clientLoader (48-57)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getPublicEnvVariables (19-48)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (38-96)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • getRequestScopedDapper (57-96)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-67)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • primeClientDapper (28-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (2)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)

11-26: Config factory resolution and window‑bound priming look sound

resolveConfigFactory + primeClientDapper correctly avoid touching window on the server, and centralize all config via currentConfigFactory. The factory override path in primeClientDapper also correctly flows through resolveConfigFactory() so the provided sessionTools‑based config is what backs the initial DapperTs instance.

apps/cyberstorm-remix/app/c/community.tsx (1)

48-55: clientLoader now correctly participates in per‑request Dapper caching

Switching clientLoader to accept { params, request } and using getRequestScopedDapper(request) means community loads can now be deduped across client loaders that share the same Request. Returning the unresolved Promise and consuming it via <Await resolve={community}> is consistent with the pattern used elsewhere.

@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from 1d366e2 to 1fd696b Compare November 24, 2025 17:02
Copy link

@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

🧹 Nitpick comments (3)
apps/cyberstorm-remix/app/c/community.tsx (1)

31-46: Consider refactoring the server loader for consistency.

The server-side loader (lines 31-46) still manually constructs DapperTs while clientLoader uses the new getRequestScopedDapper pattern. For maintainability, consider creating a server-side equivalent (e.g., getServerDapper) or a unified accessor that works in both environments.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (2)

42-45: Remove commented placeholder code.

The commented line in afterEach serves no purpose and should be removed for cleaner code.

Apply this diff:

  afterEach(() => {
-   // Restore window
-   // globalThis.window = originalWindow; // This might be read-only in some envs
  });

48-55: Skipped window undefined tests are acceptable for browser environment.

Since this module is browser-focused and window is always defined in the test environment, it's reasonable to skip these tests. Consider removing the empty test functions and replacing them with a single comment explaining that these scenarios are not applicable in browser mode.

Example:

  describe("primeClientDapper", () => {
-   it("does nothing if window is undefined", () => {
-     // Simulating window undefined is hard in browser env.
-     // We can skip this test or try to mock it if possible.
-     // In browser mode, window is always defined.
-     // We can try to spy on window property access if possible, but 'window' is a global.
-     // If we are in browser mode, we can't easily delete window.
-     // So we might skip this test for browser environment.
-   });
+   // Note: Tests for window undefined scenarios are omitted as this module is browser-only

    it("initializes window.Dapper if it does not exist", () => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d366e2 and 1fd696b.

📒 Files selected for processing (7)
  • apps/cyberstorm-remix/app/c/community.tsx (2 hunks)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperRequestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts
  • apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts
🧰 Additional context used
🧬 Code graph analysis (4)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperRequestCache.test.ts (1)
apps/cyberstorm-remix/cyberstorm/utils/dapperRequestCache.ts (2)
  • clearGlobalDapperCache (73-75)
  • getCachedDapperPromise (39-71)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (2)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
  • resetDapperSingletonForTest (98-100)
  • primeClientDapper (28-43)
  • getClientDapper (45-55)
  • getRequestScopedDapper (57-96)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/app/c/community.tsx (1)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • getRequestScopedDapper (57-96)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-67)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • primeClientDapper (28-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build
  • GitHub Check: ESLint
  • GitHub Check: Generate visual diffs
🔇 Additional comments (4)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)

2-2: LGTM! Import path consolidation.

The updated import path correctly references the typeChecks utility module.

apps/cyberstorm-remix/app/c/community.tsx (1)

48-57: LGTM! Request-scoped Dapper simplifies the clientLoader.

The refactor to use getRequestScopedDapper(request) is clean and enables request deduplication as intended. The promise is correctly returned unwrapped for Suspense streaming.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperRequestCache.test.ts (1)

1-109: LGTM! Comprehensive test coverage for request cache.

The test suite covers all critical behaviors:

  • Global and per-request caching
  • Cache isolation across different requests and inputs
  • Restricted name handling with and without labels
  • Cache clearing mechanisms including abort signal cleanup

The abort signal test (lines 92-108) is particularly valuable for preventing memory leaks.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

1-160: LGTM! Solid test coverage for the singleton module.

The test suite comprehensively covers:

  • Dapper initialization and factory configuration
  • Client dapper getter with lazy initialization
  • Request-scoped proxy creation and caching
  • Proxy behavior for get vs non-get methods

The tests appropriately use mocks and verify the singleton pattern works as expected.

@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from 1fd696b to 5562cf2 Compare November 25, 2025 20:21
@Oksamies Oksamies changed the title TEST IMPLEMENATION OF CROSS-CLIENTLOADER-REQUEST DEDUPE Request caching Nov 25, 2025
@Oksamies Oksamies marked this pull request as ready for review November 25, 2025 20:21
Copy link

@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 (2)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

44-50: Cache lookup uses linear search.

The current implementation iterates through all cached entries to find a match. For typical use cases with a handful of cached functions per request, this is acceptable. If you anticipate many cached functions per request, consider using a Map keyed by a hash of funcName + inputs for O(1) lookups.

apps/cyberstorm-remix/app/entry.client.tsx (1)

23-23: Clarify or remove TODO about Sentry.init synchronicity.

Sentry.init is synchronous for core setup in the browser SDK, so errors thrown in the same event loop tick (like your try-catch below) will be captured. If this has been verified, remove the TODO. Otherwise, consider testing or checking Sentry docs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd696b and 5562cf2.

📒 Files selected for processing (6)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2)
  • getPublicEnvVariables (19-48)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • initializeClientDapper (28-43)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • getCachedRequestPromise (33-61)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (2)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
  • resetDapperSingletonForTest (98-100)
  • initializeClientDapper (28-43)
  • getClientDapper (45-55)
  • getRequestScopedDapper (57-96)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Generate visual diffs
🔇 Additional comments (4)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

18-27: LGTM - proper abort handling.

The abort listener correctly clears the per-request cache while allowing in-flight promises to complete naturally. Good defensive checks for signal existence.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1)

1-76: Excellent test coverage.

The test suite thoroughly validates caching semantics, request isolation, named function enforcement, and abort behavior. Well structured.

apps/cyberstorm-remix/app/entry.client.tsx (1)

78-86: Past review concern is resolved.

The initialization now correctly happens after Sentry.init (line 24), and publicEnvVariables is passed to getSessionTools to avoid redundant fetching. Error handling properly captures exceptions via Sentry.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

1-147: Comprehensive test suite with good coverage.

The tests properly validate singleton initialization, client retrieval, request-scoped proxy behavior, and method interception semantics. Setup/teardown ensures proper isolation. Well done.

@Oksamies
Copy link
Contributor Author

Updated PR based on the days meeting.

  • Updated request cache logic a bit
  • Updated dapper singleton logic a bit
  • Updated tests for both
  • Tested that WeakMaps items are dropped when new requests are done (though there might be some cases where we want to manually purge, but that's to be tested in QA too (speculative future proofing))
  • Dropped the community.tsx example usage as we don't want to use these features yet

Copilot finished reviewing on behalf of Oksamies November 25, 2025 20:27
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces request-scoped caching for API calls to improve performance by memoizing responses within a single request lifecycle. The implementation uses a WeakMap-based cache that automatically cleans up when requests are garbage collected and clears on request abort signals.

Key Changes

  • Implements a generic request-scoped promise caching utility (requestCache.ts)
  • Creates a Dapper singleton pattern with automatic caching for GET methods via Proxy (dapperSingleton.ts)
  • Initializes the Dapper client at application entry point with proper error handling

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts New utility providing request-scoped promise caching with automatic cleanup on request abort
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts New singleton pattern for DapperTs with request-scoped proxies that cache GET methods
apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts Comprehensive tests for the caching mechanism including abort scenarios
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts Tests for Dapper initialization and proxy-based caching behavior
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts Updated import path to use relative imports and added optional parameter to getSessionTools
apps/cyberstorm-remix/app/entry.client.tsx Initializes Dapper client at application entry with environment configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from 5562cf2 to 7488a24 Compare November 25, 2025 20:54
Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)

59-59: Fix typo in error message.

"Enviroment" should be "Environment".

-      "Enviroment variables did not load correctly, please hard refresh page"
+      "Environment variables did not load correctly, please hard refresh page"
♻️ Duplicate comments (3)
apps/cyberstorm-remix/app/entry.client.tsx (1)

23-23: Clarify or remove vague TODO.

This TODO doesn't provide actionable information. Sentry.init is synchronous in its setup but integrations may register async handlers. Consider:

  1. Removing it if the synchronous behavior has been verified
  2. Making it specific: "TODO: Verify initializeClientDapper errors are captured after Sentry.init completes"
  3. Linking to a tracking issue
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

105-123: Consider adding promise reference equality test.

The test verifies caching behavior by checking call count, but doesn't confirm that both invocations receive the same promise instance. Adding this check would strengthen the test:

const promise1 = dapper.getCommunities();
const promise2 = dapper.getCommunities();
expect(promise1).toBe(promise2);

Additionally, consider testing that the same method with different arguments creates separate cache entries (e.g., getCommunity("1") vs getCommunity("2")).

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)

57-96: LGTM - Per-request proxy with caching.

The proxy implementation correctly:

  • Caches proxies per Request using WeakMap (automatic cleanup)
  • Returns non-function properties directly
  • Binds non-getter methods without interception
  • Wraps getter methods (starting with "get") with getCachedRequestPromise

Note: As mentioned in previous reviews, this assumes all query methods follow the "get" prefix naming convention. Non-conforming query methods won't be cached.

🧹 Nitpick comments (1)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)

11-19: Minor: Environment variables refetched in fallback path.

When resolveConfigFactory is called without a provided factory (line 16), it calls getSessionTools() which refetches environment variables. While entry.client.tsx correctly passes pre-fetched env to getSessionTools(publicEnvVariables), this internal fallback path doesn't benefit from that optimization.

Given this is initialization code that runs once and only in the fallback case, the performance impact is negligible.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5562cf2 and 7488a24.

📒 Files selected for processing (6)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts
  • apps/cyberstorm-remix/cyberstorm/utils/tests/requestCache.test.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2)
  • getPublicEnvVariables (19-48)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • initializeClientDapper (28-43)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
  • resetDapperSingletonForTest (98-100)
  • initializeClientDapper (28-43)
  • getClientDapper (45-55)
  • getRequestScopedDapper (57-96)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
packages/thunderstore-api/src/index.ts (1)
  • RequestConfig (1-7)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • getCachedRequestPromise (41-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (4)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)

2-2: LGTM - Improved testability.

The relative import path and optional preFetchedEnv parameter address the previous feedback about redundant environment variable fetching and improve testability.

Also applies to: 50-53

apps/cyberstorm-remix/app/entry.client.tsx (1)

78-86: LGTM - Error handling and optimization implemented.

The try/catch block properly captures initialization errors and routes them to Sentry. Passing publicEnvVariables to getSessionTools avoids redundant environment variable fetching as addressed in previous review feedback.

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (2)

28-55: LGTM - Browser guards and lazy initialization.

The browser runtime guards and lazy initialization pattern are implemented correctly. The separation between explicit initialization (initializeClientDapper) and lazy initialization (getClientDapper) provides flexibility for different usage patterns.


98-100: LGTM - Test reset function.

Resetting only currentConfigFactory is sufficient. The requestScopedProxies WeakMap doesn't need explicit clearing since Request objects created in tests will be garbage collected, automatically removing their WeakMap entries.

@Oksamies
Copy link
Contributor Author

Ah looks like copilot review request is different from its auto-scan review, I'll go through those tomorrow

Copy link
Contributor

@anttimaki anttimaki left a comment

Choose a reason for hiding this comment

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

Some comments, another review is needed when AI comments have been resolved.

Important: check that the implementation doesn't use an old value from cache when Remix does its revalidation thing.

Oksamies added a commit that referenced this pull request Nov 27, 2025
- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove resolved TODO in entry.client.tsx
@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from 7488a24 to ffb0b04 Compare November 27, 2025 14:32
Copy link

@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 (5)
apps/cyberstorm-remix/app/entry.client.tsx (1)

77-85: Consider whether Dapper init failures should block hydration or surface a user-facing fallback

The try/catch here ensures env/session/Dapper init errors are captured by Sentry and hydration proceeds, but it also means the app can start without a configured Dapper instance and only fail later when something calls getClientDapper/getDapperForRequest. If a missing/invalid VITE_API_URL / VITE_COOKIE_DOMAIN is considered a fatal misconfiguration, consider either rethrowing after Sentry.captureException(error) or gating startTransition behind successful initialization so failures are more immediate and predictable.

apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (2)

13-37: Abort handling correctly clears per-request cache but still allows new work on aborted Requests

The updated getCache logic looks solid for avoiding stale entries: it clears requestScopedCaches immediately if request.signal.aborted and installs a one-shot abort listener when the cache is first created. Note that for already-aborted requests you still get a fresh, non-persisted cache array, so deduplicatePromiseForRequest will happily execute func but won’t reuse or retain any entries. If the intent is “no further work at all once a Request is aborted”, you may want an early error or short‑circuit in deduplicatePromiseForRequest instead of just skipping persistence; otherwise this current behavior is fine and worth documenting as “cache cleanup only, not cancellation.”


71-84: Rejection cleanup is correct but could be documented as part of the cache contract

The pattern of attaching promise.catch purely for side effects (removing the failed entry from the WeakMap cache) is correct here and doesn’t interfere with consumers awaiting the original promise. It might be useful to briefly document that failed calls are evicted from the cache to allow retries, while successful ones remain cached for the lifetime of the Request, so callers know what to expect for transient vs permanent failures.

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (2)

64-104: Strengthen test reset by also clearing window.Dapper and per-request proxies

resetDapperSingletonForTest only resets currentConfigFactory, but window.Dapper and requestScopedProxies can still carry state between tests. For deterministic test runs, consider extending the reset to also remove the global singleton and per-request proxies, e.g. by deleting window.Dapper (when defined) and reinitializing requestScopedProxies (which would require changing it from const to let). That keeps each test run isolated from any prior Dapper configuration or proxies.


64-75: Make the browser-only nature of getDapperForRequest explicit

getDapperForRequest always routes through getClientDapper, so on the server it will throw getClientDapper can only run in the browser, even when a Request is provided. If this function is intended strictly for client loaders, consider either:

  • adding a short guard (if (typeof window === "undefined") throw ...) with an error message that names getDapperForRequest, and/or
  • mentioning in the docstring that it’s browser-only and not for SSR loaders.

This will make misuse easier to diagnose if someone accidentally wires it into a server-side loader.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7488a24 and ffb0b04.

📒 Files selected for processing (6)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/cyberstorm-remix/cyberstorm/utils/tests/dapperSingleton.test.ts
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts
  • apps/cyberstorm-remix/cyberstorm/utils/tests/requestCache.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2)
  • getPublicEnvVariables (19-48)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • initializeClientDapper (28-43)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
packages/thunderstore-api/src/index.ts (1)
  • RequestConfig (1-7)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • deduplicatePromiseForRequest (48-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs

Oksamies added a commit that referenced this pull request Nov 27, 2025
- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove resolved TODO in entry.client.tsx
@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from ffb0b04 to b2fd318 Compare November 27, 2025 15:19
Copy link

@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

🧹 Nitpick comments (3)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (2)

106-124: Consider verifying promise instance equality.

The test correctly verifies caching via mock call count. For additional rigor, you could verify that both calls return the same promise instance:

const promise1 = dapper.getCommunities();
const promise2 = dapper.getCommunities();
expect(promise1).toBe(promise2);

This would strengthen the guarantee that promise deduplication is working at the reference level, not just the result level.


157-157: Optional: Use a more specific type instead of any.

The mock could use a proper type for better type safety:

-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        .mockResolvedValue({ identifier: "1", name: "Test Community" } as any);
+        .mockResolvedValue({ identifier: "1", name: "Test Community" } as { identifier: string; name: string });

Given this is a test mock, the current approach is acceptable if you want to keep it simple.

apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

56-58: Reconsider the func.name validation.

The check validates func.name (the function's intrinsic name property) even though the funcName parameter is what's actually used as the cache key (line 66). This creates confusion:

  1. The error message says "Must be named functions" but funcName is explicitly provided
  2. Legitimate use cases are blocked where developers intentionally provide a custom funcName for anonymous functions

If the check is meant as a safety guardrail against accidental misuse, consider:

  • Removing it entirely since funcName is explicit
  • Or clarifying the error message to explain the actual concern
  • Or checking that funcName is not empty/generic instead

Based on learnings (anttimaki's past comment), this validation was questioned for this exact reason.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffb0b04 and b2fd318.

📒 Files selected for processing (5)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/cyberstorm-remix/app/entry.client.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • deduplicatePromiseForRequest (50-95)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
packages/thunderstore-api/src/index.ts (1)
  • RequestConfig (1-7)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • deduplicatePromiseForRequest (50-95)
🪛 GitHub Check: ESLint
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts

[failure] 157-157: Disallow the any type
Unexpected any. Specify a different type.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (5)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

37-104: Solid test coverage for initialization and proxy lifecycle.

The tests properly verify factory resolution, proxy caching per request, and isolation between different requests.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1)

1-103: Excellent test coverage addressing previous feedback.

The tests comprehensively cover:

  • Per-request caching and promise instance equality
  • Argument differentiation
  • Rejection handling and cache removal
  • Request isolation
  • Abort signal behavior

All scenarios from previous review comments have been addressed.

apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

11-35: LGTM on abort signal handling.

The implementation correctly handles pre-aborted signals by returning an empty array that's not persisted in the WeakMap (lines 12-14), effectively preventing caching for aborted requests. The tests in requestCache.test.ts lines 143-161 verify this behavior.

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (2)

28-55: Clean initialization and singleton management.

The browser-check guards (lines 29-30, 46-48) appropriately prevent SSR usage, and the lazy initialization pattern with optional factory override provides good flexibility for testing.


64-104: Proxy implementation correctly integrates with request caching.

The handler properly:

  • Returns the base client Dapper when no request is provided
  • Caches proxies per request using WeakMap
  • Intercepts only "get" methods (line 83) to avoid caching mutations
  • Delegates to deduplicatePromiseForRequest for per-request promise deduplication

The comment on line 82 documents the "get" prefix assumption, addressing the concern from past reviews.

Oksamies added a commit that referenced this pull request Nov 27, 2025
- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove- Rename `getCachedRequestPromise` to `deduplicatePromiseForRequest` to better reflect its purpose.
- Rename `getRequestScopedDapper` to `getDapperForRequest`.
- Improve cache management:
  - Immediately handle aborted requests to prevent race conditions.
  - Remove rejected promises from cache to allow retries.
  - Use `WeakMap.delete` for safer cache clearing instead of array splicing.
- Add docstrings explaining usage, performance implications, and limitations (e.g., only caching "get" methods).
- Expand test coverage:
  - Verify promise reference equality.
  - Ensure different arguments create distinct cache entries.
  - Test behavior with aborted signals.
- Remove resolved TODO in entry.client.tsx
@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from b2fd318 to 9fe7186 Compare November 27, 2025 15:30
@Oksamies Oksamies requested a review from Copilot November 27, 2025 15:30
Copilot finished reviewing on behalf of Oksamies November 27, 2025 15:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 56 to 63
if (func.name === "default" || func.name === "anonymous") {
throw new Error("Must be named functions to support caching.");
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The validation logic and error message are inconsistent. The code checks if func.name is "default" or "anonymous", but the funcName parameter is what's actually used as the cache key (not func.name). This means:

  1. An arrow function (with func.name === "") would pass this check even though it's unnamed
  2. The error message "Must be named functions to support caching" is misleading since you can pass any function as long as you provide a valid funcName parameter

Consider either: (a) removing this check entirely since funcName is explicitly provided, or (b) checking if the provided funcName matches these restricted names, with an error message like "funcName cannot be 'default' or 'anonymous' as they may cause cache collisions".

Suggested change
if (func.name === "default" || func.name === "anonymous") {
throw new Error("Must be named functions to support caching.");
if (funcName === "default" || funcName === "anonymous") {
throw new Error("funcName cannot be 'default' or 'anonymous' as they may cause cache collisions.");

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the my earlier review comment was wrong and the bot is right. As func.name is not part of the cache key, it being anonymous doesn't affect caching. This check should probably be removed. Or alternatively the cache key should include the actual function name (as the bot suggest in a comment below), but I think that defeats the point of the funcName parameter. Either way, don't trust my comments this time, think this through yourself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thought this through myself when I changed it and decided to not make function grab the given functions name. This makes using the reqeustCache more flexible

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, but if we choose to do the caching based on funcName and inputs, then this whole check makes no sense and should be removed.

In usePromise label like funcName was not used, and in there passing two anonymous functions would have had the same cache key even if the functions were completely unrelated. If we go with the more flexible route of using a label here, we can also cache anonymous functions since the caching doesn't depend on the function name. So this check just makes our cache implementation less flexible for no reason.

Copy link
Contributor

Choose a reason for hiding this comment

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

And asking this here as posting it in the review's main comment box seems to go unnoticed. Sorry if you answered this at some point already and I missed it.

Did you ever check that the implementation doesn't use an old value from cache when Remix does its revalidation thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair, removed the check. And yep, whenever shouldRevalidate of RR returns true during a triggered revalidation, a new request is made for the Route.

Copy link

@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

♻️ Duplicate comments (1)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

50-59: Validate funcName instead of func.name for caching safety

The cache key is funcName, but the guard checks func.name. This allows anonymous/default-export functions as long as a “good” funcName is passed, and also doesn’t prevent empty or auto-generated funcName values. It also doesn’t match the intent described in the JSDoc and prior tests.

I’d switch the guard to validate funcName directly:

-export function deduplicatePromiseForRequest<Args extends unknown[], Result>(
+export function deduplicatePromiseForRequest<Args extends unknown[], Result>(
   funcName: string,
   func: (...inputs: Args) => Promise<Result>,
   inputs: Args,
   request: Request
 ): Promise<Result> {
-  if (func.name === "default" || func.name === "anonymous") {
-    throw new Error("Must be named functions to support caching.");
-  }
+  if (funcName === "" || funcName === "default" || funcName === "anonymous") {
+    throw new Error(
+      "funcName must be a stable, non-empty identifier for caching."
+    );
+  }

This aligns the validation with the actual cache key and with the tests’ expectations.

🧹 Nitpick comments (3)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

73-88: Avoid capturing request in rejection handler to reduce retention

The rejection cleanup currently closes over request and hits the WeakMap again. That’s not necessary and keeps the Request strongly reachable via the Promisecatch closure chain, which can lengthen the lifetime of per-request state.

You can instead capture the concrete cache entry and manipulate the already-known cache array:

-  const promise = func(...inputs) as Promise<Result>;
-
-  // Remove from cache if it rejects to allow retrying
-  promise.catch(() => {
-    const currentCache = requestScopedCaches.get(request);
-    if (currentCache) {
-      const index = currentCache.findIndex(
-        (entry) => entry.promise === promise
-      );
-      if (index !== -1) {
-        currentCache.splice(index, 1);
-      }
-    }
-  });
-
-  cache.push({
-    funcName,
-    inputs,
-    promise,
-  });
+  const promise = func(...inputs) as Promise<Result>;
+
+  const cacheEntry: CacheEntry = {
+    funcName,
+    inputs,
+    promise,
+  };
+
+  cache.push(cacheEntry);
+
+  // Remove from cache if it rejects to allow retrying
+  promise.catch(() => {
+    const index = cache.indexOf(cacheEntry);
+    if (index !== -1) {
+      cache.splice(index, 1);
+    }
+  });

This keeps the behavior the same while avoiding extra references back to the Request.

apps/cyberstorm-remix/app/entry.client.tsx (1)

7-10: Session tools + Dapper init ordering looks good; consider explicit failure behavior

Passing the pre-fetched publicEnvVariables into getSessionTools and running this inside a try after Sentry.init fixes the earlier ordering and duplication issues. The only open design question is what should happen if env/session setup fails: right now you log to Sentry and continue hydrating without Dapper, which may surface as harder-to-diagnose downstream errors.

If you want a stricter behavior, you could either:

  • Re-throw after Sentry.captureException(error) to fail fast, or
  • Gate later Dapper usage on having successfully initialized here and show a user-facing error when not.

Not mandatory, but worth confirming the intended UX.

Also applies to: 14-21, 77-85

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)

106-108: Consider clearing request-scoped proxies in the test reset helper

resetDapperSingletonForTest currently only resets currentConfigFactory. For isolation it’s probably enough, since tests don’t reuse Request instances and requestScopedProxies is a WeakMap.

If you ever add tests that reuse the same Request across cases, you might also want to clear the proxies:

-export function resetDapperSingletonForTest() {
-  currentConfigFactory = undefined;
-}
+export function resetDapperSingletonForTest() {
+  currentConfigFactory = undefined;
+  requestScopedProxies.clear?.();
+}

(Or reassign a new WeakMap.) Optional, but can make test behavior more explicit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2fd318 and 9fe7186.

📒 Files selected for processing (5)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/cyberstorm-remix/cyberstorm/utils/tests/requestCache.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2)
  • getPublicEnvVariables (19-48)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • initializeClientDapper (28-43)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)
packages/thunderstore-api/src/index.ts (1)
  • RequestConfig (1-7)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (1)
  • getSessionTools (50-66)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • deduplicatePromiseForRequest (50-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (10)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)

11-35: Abort-aware cache initialization looks solid

Clearing the WeakMap entry for already-aborted requests and registering a one-shot abort listener on first cache creation gives the cache a clear lifecycle per Request and removes the earlier race where pre-aborted signals weren’t handled. No issues here.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (5)

12-20: Test setup/mocking keeps Dapper state nicely isolated

Mocking getSessionTools and resetting window.Dapper plus resetDapperSingletonForTest() in beforeEach gives each test a clean slate and avoids leaking config across cases. afterEach’s vi.restoreAllMocks() rounds this out. No changes needed here.

Also applies to: 22-36


37-56: initializeClientDapper tests exercise both factory paths

These cases validate:

  • Default path pulls config from getSessionTools, and
  • Custom factory path wires through to DapperTs.config().

That covers the main configuration branches of initializeClientDapper and should catch regressions in how the factory is resolved.


73-104: Per-request proxy tests cover identity and isolation

The getDapperForRequest tests that:

  • No-request calls return the global client,
  • A Request yields a distinct proxy, and
  • The same Request reuses the proxy while different Request instances get separate proxies.

That matches the intended WeakMap-per-request behavior; looks good.


106-146: Proxy interception behavior is well-covered

The “intercepts 'get' methods and caches promises” and “does not intercept non-'get' methods” tests clearly pin the contract:

  • get* methods are deduped,
  • non-get* methods like postTeamCreate bypass caching.

Given the separate integration test with deduplicatePromiseForRequest, this is sufficient coverage for the proxy behavior.


148-179: Nice integration test with deduplicatePromiseForRequest

This test explicitly proves that:

  • A proxied getCommunity("1") call populates the request cache, and
  • A manual deduplicatePromiseForRequest("getCommunity", ...) with matching args hits that cache instead of invoking its own function.

That’s a strong end-to-end guarantee that the proxy and the generic cache share the same keying scheme.

apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (4)

11-19: Lazy config factory resolution aligns with session tools

resolveConfigFactory defers to getSessionTools() only once and then caches a ConfigFactory that simply calls tools.getConfig(). That keeps the Dapper wiring decoupled from env loading while still centralizing session/context logic in publicEnvVariables. Looks good.


28-43: initializeClientDapper correctly handles custom factories and browser guard

The browser check at the top prevents accidental SSR usage, and the branching between:

  • updateDapperConfig(factory) when a factory is provided, and
  • resolveConfigFactory() when not,

ensures currentConfigFactory is always set before window.Dapper is constructed. The second resolveConfigFactory() call reuses the cached factory and won’t hit getSessionTools() again, which is what you want.


64-103: Request-scoped Proxy behavior matches intended caching contract

The proxy handler:

  • Returns the raw (bound) method for non-functions or non-get* methods, avoiding accidental caching of mutating calls.
  • Wraps get* methods in deduplicatePromiseForRequest, using the property name as the cache key and the original args array.

Given Dapper’s get* methods return promises, this is a good fit and lines up with the tests that assert per-request deduping and separation across different Request instances.


45-55: I'll help you verify that getClientDapper is only called in browser contexts. Let me search the codebase for all usages of this function and related server-side alternatives.
<function_calls>

#!/bin/bash

Find all usages of getClientDapper and getDapperForRequest

echo "=== Searching for getClientDapper usages ==="
rg -nP "getClientDapper\s*(" --type ts --type tsx -A 2 -B 2

echo ""
echo "=== Searching for getDapperForRequest usages ==="
rg -nP "getDapperForRequest\s*(" --type ts --type tsx -A 2 -B 2


</function_calls>

@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from 9fe7186 to fa0754a Compare November 27, 2025 16:30
@Oksamies Oksamies requested a review from Copilot November 27, 2025 16:35
Copilot finished reviewing on behalf of Oksamies November 27, 2025 16:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Oksamies Oksamies requested a review from anttimaki December 1, 2025 11:27
Copy link
Contributor

@anttimaki anttimaki left a comment

Choose a reason for hiding this comment

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

Did you ever check this one? I remember it being discussed but don't remember if I ever got a confirmation for it.

Important: check that the implementation doesn't use an old value from cache when Remix does its revalidation thing.

Comment on lines 56 to 63
if (func.name === "default" || func.name === "anonymous") {
throw new Error("Must be named functions to support caching.");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the my earlier review comment was wrong and the bot is right. As func.name is not part of the cache key, it being anonymous doesn't affect caching. This check should probably be removed. Or alternatively the cache key should include the actual function name (as the bot suggest in a comment below), but I think that defeats the point of the funcName parameter. Either way, don't trust my comments this time, think this through yourself.

@Oksamies Oksamies force-pushed the 11-20-test_implemenation_of_cross-clientloader-request_dedupe branch from fa0754a to 0642cbe Compare December 3, 2025 12:10
@Oksamies Oksamies requested a review from anttimaki December 3, 2025 12:12
Copy link

@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

♻️ Duplicate comments (1)
apps/cyberstorm-remix/app/entry.client.tsx (1)

77-85: Tighten failure handling for Dapper init and double‑check getConfig usage

This block now correctly initializes Dapper only after Sentry, so errors are captured, but failures are silently swallowed and the app hydrates with Dapper potentially uninitialized. If Dapper is required for core flows, consider either re‑throwing after Sentry.captureException(error) or guarding downstream usage behind an explicit “Dapper not available” path so you fail fast instead of in obscure places later.

Also, please verify that sessionTools.getConfig(publicEnvVariables.VITE_API_URL) matches the declared signature of getConfig. It appears to be used without arguments elsewhere, so passing an API URL only here could be inconsistent or misleading if the parameter is not actually part of the API.

🧹 Nitpick comments (1)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

38-196: Comprehensive coverage of singleton config and per‑request proxy caching

The tests under initializeClientDapper, getClientDapper, and especially getDapperForRequest do a good job verifying:

  • Config factory handling (including updates without replacing the instance and resolution via mocked getSessionTools).
  • That request-scoped proxies are reused per Request and distinct across different Request objects.
  • That only get* methods are deduped while mutation methods like postTeamCreate bypass the cache.
  • That cache entries are shared between the proxy and direct deduplicatePromiseForRequest calls (the getCommunity + manualFunc test nicely exercises the core dedupe contract).

If you ever want to extend coverage, an extra test for the “non-browser” guard (e.g., typeof window === "undefined" in getClientDapper) would be a reasonable future addition, but what’s here already gives strong confidence in the new behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa0754a and 0642cbe.

📒 Files selected for processing (6)
  • apps/cyberstorm-remix/app/entry.client.tsx (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/__tests__/requestCache.test.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1 hunks)
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts
  • apps/cyberstorm-remix/cyberstorm/utils/tests/requestCache.test.ts
  • apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts
  • apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/cyberstorm-remix/app/entry.client.tsx (2)
apps/cyberstorm-remix/cyberstorm/security/publicEnvVariables.ts (2)
  • getPublicEnvVariables (19-48)
  • getSessionTools (50-67)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (1)
  • initializeClientDapper (28-41)
apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (3)
apps/cyberstorm-remix/cyberstorm/utils/dapperSingleton.ts (3)
  • initializeClientDapper (28-41)
  • getClientDapper (43-53)
  • getDapperForRequest (62-102)
packages/dapper-ts/src/index.ts (1)
  • DapperTs (41-102)
apps/cyberstorm-remix/cyberstorm/utils/requestCache.ts (1)
  • deduplicatePromiseForRequest (56-118)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Generate visual diffs
🔇 Additional comments (2)
apps/cyberstorm-remix/app/entry.client.tsx (1)

7-21: Dapper/env wiring after Sentry init looks solid

The added imports plus inclusion of VITE_COOKIE_DOMAIN in publicEnvVariables cleanly hook Dapper initialization into the existing Sentry-aware client bootstrap. The flow of: load env → init Sentry → later derive session tools/Dapper config is coherent and matches the new singleton design; no issues from my side here.

apps/cyberstorm-remix/cyberstorm/utils/__tests__/dapperSingleton.test.ts (1)

1-37: Good isolation of Dapper singleton and session tools in test setup

Mocking getSessionTools, explicitly deleting window.Dapper, and calling resetDapperSingletonForTest() in beforeEach give you clean state for every test, and clearAllMocks/restoreAllMocks keep spies localized. This is a solid foundation for reliably testing the singleton behavior.

Comment on lines 56 to 63
if (func.name === "default" || func.name === "anonymous") {
throw new Error("Must be named functions to support caching.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, but if we choose to do the caching based on funcName and inputs, then this whole check makes no sense and should be removed.

In usePromise label like funcName was not used, and in there passing two anonymous functions would have had the same cache key even if the functions were completely unrelated. If we go with the more flexible route of using a label here, we can also cache anonymous functions since the caching doesn't depend on the function name. So this check just makes our cache implementation less flexible for no reason.

Comment on lines 56 to 63
if (func.name === "default" || func.name === "anonymous") {
throw new Error("Must be named functions to support caching.");
Copy link
Contributor

Choose a reason for hiding this comment

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

And asking this here as posting it in the review's main comment box seems to go unnoticed. Sorry if you answered this at some point already and I missed it.

Did you ever check that the implementation doesn't use an old value from cache when Remix does its revalidation thing?

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.

3 participants