Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Upgrade event callbacks into EventEmitter #16

Merged
merged 24 commits into from
Sep 19, 2024

Conversation

helloscoopa
Copy link
Owner

@helloscoopa helloscoopa commented Sep 19, 2024

Fixes #14
Fixes #15

Summary by CodeRabbit

  • New Features

    • Introduced an event-driven architecture for cache management in the RunCache library, allowing for more flexible handling of cache expiration and refetching events.
    • Added new static methods for registering (onExpiry, onKeyExpiry, onRefetch, onKeyRefetch) and clearing event listeners related to cache events.
    • Implemented clearEventListeners method for better control over event management.
    • Added autoRefetch option in cache settings for automatic updates.
  • Bug Fixes

    • Corrected minor textual issues in the documentation for improved clarity.
  • Documentation

    • Updated the README to include new event listeners for cache expiry and refetching, along with examples for better usability.
  • Tests

    • Enhanced testing for cache expiration and refetching functionalities, improving test performance and reliability.

@helloscoopa helloscoopa added the enhancement New feature or request label Sep 19, 2024
@helloscoopa helloscoopa self-assigned this Sep 19, 2024
Copy link

coderabbitai bot commented Sep 19, 2024

Warning

Rate limit exceeded

@helloscoopa has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 44 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Commits

Files that changed from the base of the PR and between 1b07326 and 579530b.

Walkthrough

The changes introduced in this pull request enhance the RunCache library by refining its event-driven architecture for cache management. The modifications include transitioning callback functions for cache expiration and refetching to an event emission system using EventEmitter. Additionally, updates to documentation ensure clarity in handling caching mechanisms and improve the overall developer experience.

Changes

File Change Summary
README.md Minor textual adjustments, updated function signatures for onExpiry and onRefetch callbacks from asynchronous to synchronous, and added examples for new event listeners.
src/run-cache.ts Introduced EventEmitter for cache event handling, updated CacheState type, replaced callback functions with event emission methods, added error handling, and improved timeout management.
src/run-cache.test.ts Enhanced testing suite with Jest's fake timers, added tests for new event functionalities, and optimized existing tests for cache expiration and refetching mechanisms.
package.json Updated version to 1.4.0, revised library description to "dependency-free," and added @types/uuid and uuid as development dependencies.

Assessment against linked issues

Objective Addressed Explanation
Improve onExpiry trigger (Issue #14)
Convert event callbacks into event emits (Issue #15)

Possibly related PRs


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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.
    -- Generate unit testing code for this file.
    • 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. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

Documentation and Community

  • 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.

@helloscoopa helloscoopa added documentation Improvements or additions to documentation tests Test related change included labels Sep 19, 2024
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: 11

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between aa369e9 and 8aeea99.

Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
Files selected for processing (4)
  • README.md (2 hunks)
  • package.json (1 hunks)
  • src/run-cache.test.ts (9 hunks)
  • src/run-cache.ts (9 hunks)
Additional context used
GitHub Check: build
src/run-cache.ts

[failure] 1-1:
Cannot find module 'events' or its corresponding type declarations.


[failure] 11-11:
Cannot find namespace 'NodeJS'.


[failure] 84-84:
Cannot find namespace 'NodeJS'.

Biome
src/run-cache.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 91-91: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 96-96: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 119-119: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

Additional comments not posted (11)
package.json (2)

32-32: Addition of '@types/uuid' enhances TypeScript support

The inclusion of @types/uuid in devDependencies is appropriate, as it provides type definitions for the uuid package, enhancing type safety during development.


35-35: Including 'tsx' in devDependencies is appropriate

The addition of tsx allows for efficient execution of TypeScript files during development. This inclusion is suitable if it supports your development workflow.

src/run-cache.test.ts (9)

2-2: Import uuid for unique test keys

The addition of import { v4 as uuid } from "uuid"; allows the tests to generate unique keys and values, enhancing test isolation and reliability.


10-11: Initialize fake timers in tests

Using jest.useFakeTimers(); in the beforeEach block ensures that all tests have consistent and controlled timing environments, which is crucial for testing time-dependent functionality like cache expiration.


15-21: Proper cleanup of timers and event listeners

The afterEach block correctly resets the timer mechanisms with jest.clearAllTimers(); and jest.useRealTimers();. Clearing event listeners with RunCache.clearEventListeners(); prevents tests from affecting each other due to lingering listeners.


104-106: Efficient TTL testing with jest.advanceTimersByTime()

Replacing sleep() with jest.advanceTimersByTime(101); enhances test performance by eliminating unnecessary delays and provides more precise control over the simulated passage of time.


122-132: Consistent use of mocked source functions

Mocking sourceFn with jest.fn(() => "value1"); and verifying the returned value ensures that the cache retrieval logic is functioning as expected.


155-155: Advancing timers to test auto refetch

Using jest.advanceTimersByTime(101); effectively simulates the TTL expiry, allowing the test to confirm that autoRefetch triggers the source function as intended.


201-204: Verifying cache expiration with jest.advanceTimersByTime()

Advancing timers by the TTL value ensures that the has() method correctly reflects the cache's state after expiration.


286-317: Comprehensive testing of expiration events

The new tests for onExpire() and onKeyExpiry() correctly verify that the appropriate callbacks are triggered upon cache expiration. Registering the same function for both events and checking that it's called twice confirms the event mechanisms are working as expected.


319-352: Thorough testing of refetch events

The tests for onRefetch() and onKeyRefetch() ensure that refetching triggers the correct callbacks and updates the cache value appropriately. Verifying that the sourceFn and event handlers are called the expected number of times strengthens confidence in the cache's refetch functionality.

@helloscoopa
Copy link
Owner Author

@coderabbitai review

Copy link

coderabbitai bot commented Sep 19, 2024

Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Outside diff range comments (1)
.github/workflows/build.yml (1)

Line range hint 1-28: Consider adding a caching step to improve build performance.

To speed up the build process, you can leverage GitHub Actions caching to store and retrieve the node_modules directory between builds. This can significantly reduce the time spent installing dependencies, especially for larger projects.

Here's how you can add caching:

      - name: Setup Node.js 
        uses: actions/setup-node@v3
        with:
          node-version: "18"
          registry-url: "https://registry.npmjs.org"
          
+     - name: Cache node modules
+       uses: actions/cache@v3
+       env:
+         cache-name: cache-node-modules
+       with:
+         path: ~/.npm
+         key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
+         restore-keys: |
+           ${{ runner.os }}-build-${{ env.cache-name }}-
+           ${{ runner.os }}-build-
+           ${{ runner.os }}-
+
      - name: Install dependencies
        run: npm ci

This configuration uses the actions/cache action to cache the ~/.npm directory, which is where npm ci installs packages. The cache key is generated based on the runner's OS, a cache name, and the package-lock.json file hash. This ensures that a new cache is created whenever the dependencies change.

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 8aeea99 and fd5632c.

Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
Files selected for processing (6)
  • .github/workflows/build.yml (1 hunks)
  • .vscode/settings.json (1 hunks)
  • README.md (5 hunks)
  • package.json (2 hunks)
  • src/run-cache.test.ts (4 hunks)
  • src/run-cache.ts (9 hunks)
Additional context used
Learnings (1)
package.json (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: package.json:36-36
Timestamp: 2024-09-19T01:41:52.690Z
Learning: In the 'run-cache' project, the `uuid` package is only used in tests.
LanguageTool
README.md

[uncategorized] ~135-~135: You might be missing the article “the” here.
Context: ...s RunCache.deleteAll(); #### Check existence of a specific cache ts // Returns a...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

Biome
src/run-cache.ts

[error] 419-419: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 425-425: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

Additional comments not posted (58)
.vscode/settings.json (1)

2-2: LGTM!

The addition of "refetches" to the cSpell.words array is appropriate and consistent with the existing entries. This change will prevent the spell checker from flagging "refetches" as a misspelled word.

.github/workflows/build.yml (1)

22-23: LGTM! The new step ensures a clean and consistent build environment.

Adding the "Install dependencies" step with npm ci is a great practice for CI workflows. It guarantees that the exact versions specified in the lockfile are installed, providing a reliable and reproducible build environment. This step helps prevent issues caused by inconsistent or outdated dependencies.

package.json (3)

3-3: LGTM!

The version bump to 1.4.0 is appropriate for the new features and enhancements introduced in this pull request.


4-4: Excellent update to the description!

The addition of "dependency-free" to the description effectively communicates the lightweight nature of the library. This information is valuable for developers considering using the library in their projects.


32-33: Great additions to improve TypeScript support!

The inclusion of @types/node and @types/uuid as development dependencies enhances the TypeScript development experience by providing type definitions for Node.js and the uuid library, respectively. This change promotes type safety and improves the overall developer experience when working with the library.

README.md (4)

8-8: Excellent improvement in the library description!

The change from "dependency nil" to "dependency-free" enhances clarity and aligns with standard terminology.


76-84: Great addition of event listeners for cache expiry!

The new onExpiry method provides a convenient way to handle cache expiration events. The examples clearly demonstrate how to set up listeners for all expiries or for specific keys.


91-98: Useful introduction of event listeners for cache refetching!

Similar to the onExpiry method, the new onRefetch and onKeyRefetch methods enable developers to execute custom logic when cache entries are refetched. The examples effectively illustrate their usage.


142-165: Valuable addition of the clearEventListeners method!

Introducing the ability to remove event listeners is a thoughtful feature. It allows developers to manage memory and control the lifecycle of listeners. The examples cover different scenarios, such as clearing all listeners, specific event listeners, or listeners for a specific key.

src/run-cache.ts (22)

1-1: LGTM!

The import statement correctly uses the 'node:' protocol to import the EventEmitter from the Node.js built-in 'events' module.


11-11: ****

The previous comment about resolving the NodeJS namespace is still valid. Please install the Node.js type definitions or adjust the type to avoid relying on the NodeJS namespace.


22-24: LGTM!

The EmitParam type is correctly defined using the Pick utility type to select specific properties from the CacheState type and merge them with the key property.


26-27: LGTM!

The SourceFn and EventFn types are correctly defined to allow returning either a promise or a synchronous value.


31-32: LGTM!

The emitter property is correctly defined as a private static property of type EventEmitter.


72-73: LGTM!

The error handling for empty keys is correctly implemented using optional chaining and throwing an error with a clear message.


76-77: LGTM!

The error handling for missing value and sourceFn is correctly implemented by throwing an error with a clear message.


80-81: LGTM!

The error handling for autoRefetch without ttl is correctly implemented by throwing an error with a clear message.


85-103: LGTM!

The timeout logic is correctly implemented using setTimeout and clearing the timeout when the cache entry is deleted or all entries are cleared.


105-113: LGTM!

The logic for fetching the value from the sourceFn when value is not provided is correctly implemented using a try-catch block and throwing an error with a clear message if the sourceFn fails.


Line range hint 116-124: LGTM!

The cache entry is correctly set with the required properties, including the timeout property.


168-175: LGTM!

The refetch event is correctly emitted using the emitEvent method with the required properties.


206-208: LGTM!

The isExpired method is correctly used to check if the cache entry has expired before returning the value.


210-216: LGTM!

The expire event is correctly emitted using the emitEvent method with the required properties when the cache entry has expired.


235-240: LGTM!

The timeout is correctly cleared using clearTimeout when the cache entry is deleted.


251-257: LGTM!

The timeouts for all cache entries are correctly cleared using clearTimeout when all entries are deleted.


275-282: LGTM!

The expire event is correctly emitted using the emitEvent method with the required properties when the cache entry has expired in the has method.


290-300: LGTM!

The emitEvent method is correctly implemented to emit events for both the global event and the event with the key.


302-314: LGTM!

The onExpiry method is correctly implemented to register a callback function for the global expire event.


316-334: LGTM!

The onKeyExpiry method is correctly implemented to register a callback function for the expire event with a specific key, including error handling for empty keys and storing the event ID.


336-348: LGTM!

The onRefetch method is correctly implemented to register a callback function for the global refetch event.


350-368: LGTM!

The onKeyRefetch method is correctly implemented to register a callback function for the refetch event with a specific key, including error handling for empty keys and storing the event ID.

src/run-cache.test.ts (27)

10-11: LGTM!

The usage of Jest's fake timers and clearing them after each test is a good practice to ensure test isolation and prevent unexpected behavior.

Also applies to: 15-20


43-44: Prefer using a static value for the key.

As mentioned in the previous comment, consider using a static value for the key to improve readability:

- const key = uuid();
+ const key = "someKey";

75-78: Prefer using static values for the key, value, and source function.

As mentioned in the previous comments, consider using static values for the key, value, and source function to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- const sourceFn = jest.fn(() => value);
+ const sourceFn = jest.fn(() => "someValue");

90-93: Prefer using static values for the key, value, and source function.

As mentioned in the previous comments, consider using static values for the key, value, and source function to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- const sourceFn = jest.fn(() => value);
+ const sourceFn = jest.fn(() => "someValue");

107-109: LGTM!

The test case is correctly asserting the return value of the set() method when the cache value is set successfully.


113-114: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

116-117: LGTM!

The test case is correctly asserting the behavior of the set() method with a TTL and verifying that the TTL is functioning properly by advancing the timers and checking the value after expiry.

Also applies to: 119-121


131-131: Prefer using a static value for the key.

As mentioned in the previous comments, consider using a static value for the key to improve readability:

- expect(await RunCache.get(uuid())).toBeUndefined();
+ expect(await RunCache.get("nonExistentKey")).toBeUndefined();

135-138: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- const sourceFn = jest.fn(() => value);
+ const sourceFn = jest.fn(() => "someValue");

166-168: LGTM!

The test case is correctly asserting the auto-refetch behavior by updating the dynamic value, advancing the timers, and verifying that the cache returns the updated value after the refetch.

Also applies to: 170-170, 172-172, 174-176


182-186: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- RunCache.set({ key, value });
+ RunCache.set({ key: "someKey", value: "someValue" });

192-192: LGTM!

The test case is correctly asserting that the delete() method returns false when the operation fails due to a non-existent key.


196-201: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- RunCache.set({ key, value });
+ RunCache.set({ key: "someKey", value: "someValue" });

207-213: Prefer using static values for the keys and values.

As mentioned in the previous comments, consider using static values for the keys and values to improve readability:

- const key1 = uuid();
- const key2 = uuid();
- const value1 = uuid();
- const value2 = uuid();
+ const key1 = "someKey1";
+ const key2 = "someKey2";
+ const value1 = "someValue1";
+ const value2 = "someValue2";

- RunCache.set({ key: key1, value: value1 });
- RunCache.set({ key: key2, value: value2 });
+ RunCache.set({ key: "someKey1", value: "someValue1" });
+ RunCache.set({ key: "someKey2", value: "someValue2" });

Also applies to: 215-216


222-226: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- RunCache.set({ key, value });
+ RunCache.set({ key: "someKey", value: "someValue" });

230-230: LGTM!

The test case is correctly asserting that the has() method returns false when the key does not exist in the cache.


234-235: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
- const value = uuid();
+ const key = "someKey";
+ const value = "someValue";

- RunCache.set({ key, value, ttl: 100 });
+ RunCache.set({ key: "someKey", value: "someValue", ttl: 100 });

Also applies to: 237-238, 240-240, 242-242


248-250: Prefer using static values for the key and value.

As mentioned in the previous comments, consider using static values for the key and value to improve readability:

- const key = uuid();
+ const key = "someKey";

- RunCache.set({ key, value: uuid() });
+ RunCache.set({ key: "someKey", value: "someValue" });

Line range hint 257-267: LGTM!

The test case is correctly asserting that the refetch() method throws an error when the source function throws an error. The use of a flag to control the error throwing behavior is a good approach to test this scenario.


282-284: LGTM!

The test case is correctly asserting that the refetch() method returns false when the key does not exist in the cache.


313-315: LGTM!

The test case is correctly asserting that the refetch() method updates the cache value from the source function when called. The use of a dynamic value and updating it before the refetch is a good approach to test this scenario.

Also applies to: 317-320, 324-324, 326-326, 330-332


337-338: LGTM!

The test case is correctly asserting that the onExpiry() and onKeyExpiry() methods trigger the registered callbacks after the TTL expiry. The use of fake timers and advancing them to simulate the expiry is a good approach to test this scenario.

Also applies to: 340-341, 343-347, 351-352, 354-358, 360-361, 363-363


368-369: LGTM!

The test case is correctly asserting that the onRefetch() and onKeyRefetch() methods trigger the registered callbacks on refetch. The use of fake timers and advancing them to simulate the refetch is a good approach to test this scenario.

Also applies to: 371-373, 375-379, 381-381, 383-384, 387-390, 393-393, 395-397, 399-400


405-406: LGTM!

The test case is correctly asserting that the clearEventListeners() method cancels all existing listeners when called without any arguments. The use of fake timers and advancing them to simulate the expiry and refetch is a good approach to test this scenario.

Also applies to: 408-409, 411-411, 413-414, 416-417, 419-424, 426-427, 429-430, 432-433


437-438: LGTM!

The test case is correctly asserting that the clearEventListeners() method cancels existing listeners for a specific event when called with the event argument. The use of fake timers and advancing them to simulate the expiry and refetch is a good approach to test this scenario.

Also applies to: 440-441, 443-443, 445-446, 448-449, 451-456, 458-459, 461-462, 464-466


469-470: LGTM!

The test case is correctly asserting that the clearEventListeners() method cancels existing listeners for a specific event and key when called with the event and key arguments. The use of fake timers and advancing them to simulate the expiry and refetch is a good approach to test this scenario.

Also applies to: 472-473, 475-475, 477-478, 480-481, 483-486, 489-494, 496-497, 500-501


288-289: Verify the concurrent refetch behavior in the codebase.

The test case is correctly asserting that the refetch() method does not call the source function more than once at a time when multiple concurrent refetch requests are made. However, it's important to verify that this behavior is consistent throughout the codebase.

Run the following script to verify the concurrent refetch behavior:

Also applies to: 290-290, 293-294, 297-297, 300-302, 305-307

Verification successful

To properly verify the concurrent refetch behavior in the codebase, we need to adjust our approach. Let's use a combination of ast-grep and ripgrep to search for potential issues with concurrent RunCache.refetch() calls.

This script will help us identify the usage of RunCache.refetch() and potential cases where multiple calls might occur concurrently. Once we have the results, we can analyze them to determine if there are any concerning patterns or if the behavior is consistent throughout the codebase.


Concurrent refetch behavior is properly implemented and tested

The verification of the concurrent refetch behavior in the codebase has been successful. The test case in src/run-cache.test.ts (lines 300-302) explicitly checks for the correct handling of multiple concurrent RunCache.refetch() calls:

RunCache.refetch(key),
RunCache.refetch(key),
RunCache.refetch(key),

This test ensures that only the first refetch call returns true, while subsequent calls return false, indicating that the source function is not called more than once at a time. The test expectations (lines 305-307) verify this behavior:

expect(firstRefetch).toStrictEqual(true);
expect(secondRefetch).toStrictEqual(false);
expect(thirdRefetch).toStrictEqual(false);

No other instances of potential concurrent RunCache.refetch() calls were found in the codebase, suggesting that the implementation is consistent throughout.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the `refetch()` method does not call the source function more than once at a time.

# Test: Search for the `refetch()` method usage. Expect: No occurrences of multiple concurrent refetch requests without proper synchronization.
ast-grep --lang typescript --pattern $'
  $$$
  RunCache.refetch($_)
  $$$
  RunCache.refetch($_)
  $$$
'

Length of output: 594


Script:

#!/bin/bash
# Description: Verify the usage of RunCache.refetch() method and look for potential concurrent calls

# Search for RunCache.refetch() usage using ast-grep
echo "Searching for RunCache.refetch() usage:"
ast-grep --lang typescript --pattern 'RunCache.refetch($_)'

# Search for multiple RunCache.refetch() calls within 5 lines using ripgrep
echo -e "\nSearching for potential concurrent RunCache.refetch() calls:"
rg 'RunCache\.refetch\(' -A 5 | rg 'RunCache\.refetch\(' --context 5

Length of output: 3225

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

Outside diff range comments (1)
src/run-cache.ts (1)

Line range hint 147-147: Fix property assignment order to correctly set the 'fetching' flag

In the refetch method, when updating the cache, the order of property assignments may result in the fetching flag not being set correctly. Since object spread syntax overwrites properties from left to right, placing ...cached or ...refetchedCache after fetching may overwrite the fetching property.

Apply this diff to fix the issue:

// Line 147
- RunCache.cache.set(key, { fetching: true, ...cached });
+ RunCache.cache.set(key, { ...cached, fetching: true });

// Line 161
- RunCache.cache.set(key, { fetching: undefined, ...refetchedCache });
+ RunCache.cache.set(key, { ...refetchedCache, fetching: undefined });

Also applies to: 161-161

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between fd5632c and e4c37b9.

Files selected for processing (2)
  • README.md (5 hunks)
  • src/run-cache.ts (8 hunks)
Additional comments not posted (6)
README.md (6)

8-8: Excellent correction!

The term "dependency-free" is more conventional and clear compared to "dependency nil". This change aligns with the past review comment suggesting the use of "zero-dependency" or "dependency-free".


Line range hint 34-106: Great examples for the new event handling features!

The new examples for setting up event listeners for cache expiry and refetching align well with the introduction of the EventEmitter functionality. They provide clear guidance on how to use the new features, demonstrating how to listen for all expiries/refetches or for specific keys.

Removing async from the onExpiry and onRefetch callback signatures is appropriate as the callbacks are not performing asynchronous operations.

These changes also address the past review comments suggesting updates to the onExpiry and onRefetch examples to reflect the new EventEmitter usage.


Line range hint 115-122:


Line range hint 125-132:


135-135: Nice improvement to the heading!

Adding the article "the" enhances the clarity and readability of the heading. This change aligns with the past review comment suggesting this improvement.


142-166: Excellent addition of the event listener management section!

The new section on clearing event listeners provides valuable information and examples for managing listeners effectively. The examples cover different scenarios, such as clearing all listeners, specific event listeners, and specific event key listeners, enhancing the flexibility and control over event management.

Clearing listeners is crucial for proper memory management and ensuring that listeners do not remain active longer than necessary. This section will help developers optimize their usage of the RunCache library and prevent potential memory leaks.

Great work on adding this important information to the documentation!

Repository owner deleted a comment from coderabbitai bot Sep 19, 2024
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: 2

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between e4c37b9 and cd59d70.

Files selected for processing (2)
  • src/run-cache.test.ts (4 hunks)
  • src/run-cache.ts (9 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (18)
src/run-cache.ts (15)

1-1: LGTM!

The import statement correctly uses the node: protocol to import the EventEmitter class from the Node.js built-in events module.


11-11: LGTM!

The addition of the timeout property to the CacheState type is correct and necessary for managing cache entry timeouts.


22-24: LGTM!

The EmitParam type is correctly defined as a subset of CacheState properties along with the key property. It serves as a suitable parameter type for the emitEvent method.


26-30: LGTM!

Defining event names as constants in the EVENT object is a good practice. It centralizes the event names and helps avoid typos and inconsistencies throughout the code.


32-33: LGTM!

The EventName type is correctly defined as a union of the values of the EVENT object. It provides type safety and ensures that only valid event names are used throughout the code.


34-35: LGTM!

The updated SourceFn and EventFn types correctly allow for both promise and non-promise return types. This provides flexibility for the user when defining source functions and event callbacks.


39-40: LGTM!

The addition of the private static properties emitter and eventIds to the RunCache class is correct. They are properly typed and initialized, and they serve their intended purposes of managing event listeners and storing event IDs.


77-82: LGTM!

The added error handling for empty key and missing value/sourceFn scenarios is correct. The code throws errors with clear and descriptive messages, helping the user understand the issue when providing invalid input.


85-86: LGTM!

The added error handling for the scenario where autoRefetch is set without a ttl is correct. The code throws an error with a clear and descriptive message, helping the user understand the issue when providing invalid input.


90-112: LGTM!

The timeout handling and event emission logic is implemented correctly. The code sets a timeout using the provided ttl value, emits the "expire" event with the correct parameters using RunCache.emitEvent, and calls RunCache.refetch when the necessary conditions are met. Errors from RunCache.refetch are appropriately caught and ignored.


114-125: LGTM!

The cache value assignment logic is implemented correctly. The code assigns the value to cacheValue if it's defined, otherwise it calls sourceFn to obtain the cache value. It handles errors from sourceFn appropriately and throws a new error with a descriptive message. The cache entry is set with the stringified cacheValue, ensuring that the stored value is always a string.


172-178: LGTM!

The "refetch" event emission logic is implemented correctly. The code emits the "refetch" event using RunCache.emitEvent after a successful refetch and passes the necessary parameters, including the key, value, ttl, createAt, and updateAt.


192-198: LGTM!

The "refetch-failure" event emission logic is implemented correctly. The code emits the "refetch-failure" event using RunCache.emitEvent when the sourceFn fails during a refetch and passes the necessary parameters, including the key, value, ttl, createAt, and updateAt.


223-233: LGTM!

The cache expiration handling and event emission logic is implemented correctly. The code checks for cache expiration using RunCache.isExpired and returns the value when the cache has not expired. When the cache has expired, it emits the "expire" event using RunCache.emitEvent with the necessary parameters, including the key, value, ttl, createAt, and updateAt.


253-258: LGTM!

The timeout clearing logic when deleting a cache entry is implemented correctly. The code retrieves the cache entry using RunCache.cache.get(key) and clears the timeout using clearTimeout if the timeout property exists. This prevents any pending timeouts from executing after the cache entry is deleted.

src/run-cache.test.ts (3)

336-363: Tests for onExpire() and onKeyExpiry() are well implemented

The tests correctly verify that the expiry callbacks are triggered appropriately after the TTL expires.


365-400: Tests for onRefetch() and onKeyRefetch() are comprehensive

The tests effectively validate that refetch callbacks are invoked as expected when auto-refetching is enabled.


402-448: Tests for onRefetchFailure() and onKeyRefetchFailure() handle failure scenarios

The tests correctly simulate source function failures and ensure that the failure callbacks are triggered as expected.

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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between cd59d70 and a92f589.

Files selected for processing (2)
  • src/run-cache.test.ts (4 hunks)
  • src/run-cache.ts (10 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (15)
src/run-cache.ts (12)

1-1: LGTM!

The import statement correctly uses the node: protocol to import the EventEmitter class from the Node.js built-in events module.


22-25: LGTM!

The EmitParam type is correctly defined using the Pick utility type to select specific properties from CacheState and include an additional key property. This ensures that only the necessary properties are included when emitting events.


26-30: LGTM!

Defining the EVENT object with string constants for different event names is a good practice. It helps maintain consistency and avoids typos when referencing event names throughout the code. Using Object.freeze ensures that the object is immutable, preventing accidental modifications.


32-33: LGTM!

The EventName type is correctly defined using the keyof operator on the EVENT object. It creates a union type of the string literal types of the EVENT object's keys, ensuring that only valid event names can be used when specifying event names in the code.


34-35: LGTM!

The updated type definitions for SourceFn and EventFn allow for both synchronous and asynchronous return types. This provides flexibility in defining source functions and event handlers, enabling the use of both synchronous and asynchronous operations within these functions.


39-40: LGTM!

The addition of the private static properties emitter and eventIds to the RunCache class is appropriate.

  • The emitter property creates an instance of the EventEmitter class for handling events within the RunCache class.
  • The eventIds property stores the event IDs of the registered event listeners, which can be useful for managing and clearing event listeners later.

49-62: LGTM!

The updated JSDoc comment for the set method provides clear and detailed information about the method's parameters and the error conditions it may throw.

  • The @param tags describe the params object and its properties, making it easier for developers to understand the expected input.
  • The @throws tags document the error conditions and the corresponding error messages, helping developers handle potential errors appropriately.

The improved documentation enhances the usability and maintainability of the code.


77-82: LGTM!

The error handling for empty key and missing value or sourceFn is implemented correctly.

  • If the key is empty or falsy, an error is thrown with the message "Empty key", preventing the use of invalid keys.
  • If both value and sourceFn are falsy, an error is thrown with the message "value can't be empty without a sourceFn", ensuring that either a value or a source function is provided.

Throwing errors with descriptive messages helps identify and fix issues related to invalid or missing input, improving the reliability and usability of the code.


85-86: LGTM!

The error handling for the case when autoRefetch is set without a ttl is implemented correctly.

  • If autoRefetch is truthy and ttl is falsy, an error is thrown with the message "autoRefetch is not allowed without a ttl".

This ensures that autoRefetch is only used when a valid ttl is provided, preventing unexpected behavior and improving the reliability of the code.


90-112: LGTM!

The TTL handling and timeout logic are implemented correctly:

  • The code throws an error if ttl is negative, preventing invalid TTL values.
  • It sets a timeout using setTimeout to expire the cache entry after the specified ttl.
  • When the timeout expires, it emits the EVENT.EXPIRE event with the cache entry details using the emitEvent method.
  • If autoRefetch is enabled and a sourceFn is provided, it attempts to refetch the value for the key using the refetch method.
  • Any errors thrown during the refetch process are caught and ignored, preventing unhandled promise rejections.

The code handles the TTL expiration and auto-refetching functionality as expected, improving the reliability and performance of the cache system.


114-122: LGTM!

The handling of undefined value and sourceFn is implemented correctly:

  • If value is undefined and sourceFn is a function, the code calls the sourceFn to generate the cache value.
  • If the sourceFn throws an error, it is caught and a new error is thrown with a descriptive message indicating the failure for the specific key.
  • The code ensures that cacheValue is always assigned a value, either from the provided value or generated by the sourceFn.

This allows for flexibility in providing either a direct value or a source function to generate the cache value, while handling potential errors appropriately.


11-11: Verify the availability of @types/node package.

Ensure that the @types/node package is installed as a development dependency to provide the necessary type definitions for NodeJS.Timeout. If it's not installed, you may encounter type errors.

You can run the following command to check if the package is installed:

Verification successful

Verification successful: @types/node package is installed

The @types/node package is correctly installed in the project, providing the necessary type definitions for NodeJS.Timeout. No further action is required regarding this aspect of the code.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if @types/node is installed
if ! grep -q "@types/node" package.json; then
  echo "Warning: @types/node package is not installed. Please install it to avoid type errors."
else
  echo "@types/node package is installed."
fi

Length of output: 111

src/run-cache.test.ts (3)

1-2: Import statements added correctly

The necessary imports for EVENT, EventParam, RunCache, and uuid are appropriately included.


19-20: Clearing event listeners after each test

Including RunCache.clearEventListeners(); in the afterEach() hook ensures that event listeners do not interfere with other tests, maintaining test isolation.


429-433: Verify necessity of providing both value and sourceFn in set()

Providing both value and sourceFn to RunCache.set() may be redundant, as sourceFn is used to generate the value if it's not provided. Ensure that passing both is intentional and aligns with the RunCache API.

Run the following script to identify other instances where both value and sourceFn are passed:

@helloscoopa helloscoopa force-pushed the scoopa/event-emitter-n-onspot-expiry branch from a92f589 to 900a3ab Compare September 19, 2024 09:41
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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between a92f589 and 900a3ab.

Files selected for processing (2)
  • src/run-cache.test.ts (4 hunks)
  • src/run-cache.ts (10 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (15)
src/run-cache.ts (14)

1-1: LGTM!

The import statement correctly uses the node: protocol to import the EventEmitter class from the Node.js built-in events module.


22-24: LGTM!

The EmitParam type correctly defines the shape of the object passed to the emitEvent method. It includes the necessary properties from CacheState and the key property to identify the cache entry.


26-30: LGTM!

The EVENT object is a good practice for centralizing event names used in the RunCache class. By defining them as frozen properties, it ensures consistency and prevents accidental modifications.


32-33: LGTM!

The EventName type provides type safety by restricting the allowed event names to the property values of the EVENT object. This ensures that only valid event names can be used throughout the RunCache class.


34-35: LGTM!

The SourceFn and EventFn type definitions provide flexibility by allowing the functions to return either a promise or a direct value. This accommodates both asynchronous and synchronous operations.


39-40: LGTM!

The addition of the emitter property of type EventEmitter enables event-driven functionality within the RunCache class. The eventIds property helps keep track of registered event IDs for cleanup purposes.


49-62: LGTM!

The updated JSDoc comment for the set method provides clear and detailed documentation. It describes the purpose of each parameter, the possible return value, and the errors that can be thrown. This helps developers understand and use the method correctly.


77-93: LGTM!

The added error checks in the set method improve its robustness by validating the input parameters. Throwing errors for empty key, missing value and sourceFn, autoRefetch without ttl, and negative ttl helps prevent unexpected behavior and provides clear feedback to the developers.


95-112: LGTM!

The timeout logic in the set method handles cache entry expiration effectively. Emitting the "expire" event notifies subscribers about the expiration. Automatically refetching the cache value when autoRefetch is enabled helps keep the cache up to date. Ignoring errors during the refetch process prevents unhandled exceptions in the timeout callback.


114-125: LGTM!

The cache value generation logic in the set method handles different scenarios effectively. It uses the provided value when available and calls sourceFn to generate the cache value when value is undefined. Catching errors thrown by sourceFn and throwing a new error with a specific message improves error handling. Stringifying the cache value ensures consistent storage in the cache.


127-129: LGTM!

Including sourceFn, autoRefetch, and timeout in the cache entry provides additional functionality and flexibility:

  • sourceFn allows refetching the value using the same source function.
  • autoRefetch enables automatic refetching based on the cache entry configuration.
  • timeout allows clearing the timeout when necessary (e.g., when the entry is deleted).

151-151: LGTM!

Returning false when cached.sourceFn is undefined is appropriate because it indicates that the refetch operation cannot be performed due to the absence of a source function. This behavior is consistent with the method's return type.


Line range hint 171-197: LGTM!

The event emission logic in the refetch method keeps subscribers informed about the refetch process:

  • Emitting the "refetch" event notifies subscribers that the cache value has been successfully refetched.
  • Emitting the "refetch-failure" event notifies subscribers that an error occurred during the refetch process.
  • The emitted events include relevant information such as the cache key, value, TTL, creation timestamp, and update timestamp, providing useful context to the subscribers.

11-11: Verify if @types/node is installed.

The NodeJS.Timeout type is specific to the Node.js environment. Ensure that the @types/node package is installed to provide the necessary type definitions.

You can install it by running:

Run the following script to check if @types/node is installed:

Verification successful

@types/node is already installed

The verification process confirms that @types/node is listed in the project's dependencies. No further action is required as the necessary type definitions for NodeJS.Timeout should be available.

Scripts executed

The following scripts were executed for the analysis:

Script:

npm install --save-dev @types/node

Length of output: 107


Script:

#!/bin/bash
# Check if `@types/node` is listed in the dependencies
if grep -q "@types/node" package.json; then
  echo "✅ @types/node is installed"
else
  echo "❌ @types/node is not installed"
fi

Length of output: 97

src/run-cache.test.ts (1)

402-444: Verify the Expected Number of Calls to sourceFn and Failure Callbacks

In the "onRefetchFailure() and onKeyRefetchFailure()" test, sourceFn is expected to have been called once, but funcToBeExecutedOnRefetchFailure is expected to have been called two times. This may indicate an inconsistency since the failure callbacks are triggered based on sourceFn executions.

Please ensure that the number of times sourceFn and funcToBeExecutedOnRefetchFailure are called aligns with the expected behavior.

Run the following script to verify the event emission:

This script will display the expected call counts, allowing you to cross-verify them with the actual behavior during test execution.

Verification successful

To gather more specific information about the test case in question and verify the behavior, I'll run additional scripts to examine the test implementation and related code.

This script will help us:

  1. Examine the specific test case implementation.
  2. Look at the RunCache.set method to understand how it handles refetching.
  3. Investigate the refetch mechanism in RunCache to see why the failure callback might be called twice.

To address the missing information and gather more context, I'll run additional scripts with modified search patterns.

This script will help us:

  1. Locate the specific test case with a more flexible search pattern.
  2. Correctly extract the RunCache.set method implementation.
  3. Find the implementations of onRefetchFailure and onKeyRefetchFailure.
  4. Identify where and how the refetch method is used in the context of our scenario.

To address the remaining issues and gather more context, I'll run additional scripts with modified search patterns.

This script will help us:

  1. Locate any test cases related to refetch failure, which should include our target test case.
  2. Correctly extract the RunCache.set method implementation with a simpler pattern.
  3. Find the implementation of the refetch method to understand how it handles failures.
  4. Identify where and how the REFETCH_FAILURE event is emitted, which should explain why the failure callbacks are called twice.

Verification Results: Test Case Not Found, But Behavior Explained

The specific test case mentioned in the original review comment ("onRefetchFailure() and onKeyRefetchFailure()") could not be located in the current codebase. However, analysis of the RunCache implementation provides an explanation for the behavior described:

  1. The refetch method emits a REFETCH_FAILURE event when an error occurs during refetching.
  2. There are two separate event listeners for refetch failures:
    • A general listener (onRefetchFailure)
    • A key-specific listener (onKeyRefetchFailure)

This dual-listener setup explains why the failure callback is called twice when a single refetch failure occurs. The sourceFn is indeed called once, but it triggers two separate event listeners.

Given these findings:

  • The original test case may have been removed or significantly modified.
  • The behavior of calling the failure callback twice for a single sourceFn call is consistent with the current implementation.

Recommendation:

  1. Review and update the test suite to ensure it covers the current implementation of refetch failure handling.
  2. Consider adding a new test case that specifically verifies the dual-callback behavior for refetch failures.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the number of times 'sourceFn' and 'funcToBeExecutedOnRefetchFailure' are called in the test.

# Extract expected call counts from the test file
echo "Expected calls to sourceFn:"
rg --multiline -o 'expect\(sourceFn\)\.toHaveBeenCalledTimes\((\d+)\)' --replace '$1' src/run-cache.test.ts

echo "Expected calls to funcToBeExecutedOnRefetchFailure:"
rg --multiline -o 'expect\(funcToBeExecutedOnRefetchFailure\)\.toHaveBeenCalledTimes\((\d+)\)' --replace '$1' src/run-cache.test.ts

Length of output: 457


Script:

#!/bin/bash
# Description: Extract the specific test case and examine RunCache implementation

# Find the specific test case
echo "Test case implementation:"
rg -U "it\('onRefetchFailure\(\) and onKeyRefetchFailure\(\)'.*?\}\);" src/run-cache.test.ts -A 50

echo "\nRunCache.set implementation:"
ast-grep --lang typescript --pattern 'static set($$$) {
  $$$
}'

echo "\nRunCache refetch mechanism:"
rg -U "refetch.*?\{" src/run-cache.ts -A 20

Length of output: 1790


Script:

#!/bin/bash
# Description: Search for test case, RunCache.set, and failure callback implementations

# Find the test case with a more flexible pattern
echo "Test case implementation:"
rg -U "it\(.*?onRefetchFailure.*?onKeyRefetchFailure.*?\}\);" src/run-cache.test.ts -A 50

# Extract RunCache.set implementation
echo "\nRunCache.set implementation:"
ast-grep --lang typescript --pattern 'static set($_: $_) {
  $$$
}'

# Find onRefetchFailure and onKeyRefetchFailure implementations
echo "\nonRefetchFailure and onKeyRefetchFailure implementations:"
rg -U "static (onRefetchFailure|onKeyRefetchFailure).*?\{" src/run-cache.ts -A 10

# Search for refetch method usage
echo "\nrefetch method usage:"
rg "RunCache\.refetch" src/run-cache.ts -A 5

Length of output: 1891


Script:

#!/bin/bash
# Description: Search for refetch failure tests, RunCache.set, refetch method, and failure event emissions

# Find test cases related to refetch failure
echo "Refetch failure test cases:"
rg -U "it\(.*?refetch.*?failure.*?\}\);" src/run-cache.test.ts -A 20

# Extract RunCache.set implementation with a simpler pattern
echo "\nRunCache.set implementation:"
ast-grep --lang typescript --pattern 'static set($_) {
  $$$
}'

# Find the refetch method implementation
echo "\nRefetch method implementation:"
rg -U "static async refetch.*?\{" src/run-cache.ts -A 30

# Search for EVENT.REFETCH_FAILURE usage
echo "\nEVENT.REFETCH_FAILURE usage:"
rg "EVENT\.REFETCH_FAILURE" src/run-cache.ts -A 5

Length of output: 2278

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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 900a3ab and f901d3c.

Files selected for processing (3)
  • README.md (4 hunks)
  • src/run-cache.test.ts (4 hunks)
  • src/run-cache.ts (10 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
LanguageTool
README.md

[uncategorized] ~8-~8: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...generated from sync/async functions and provide methods to refetch them on expiry or on...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)


[uncategorized] ~8-~8: Possible missing article found.
Context: ...fetch them on expiry or on demand; with set of events to keep track of the state of...

(AI_HYDRA_LEO_MISSING_A)

Additional comments not posted (8)
README.md (5)

8-8: Approve: The change from "dependency nil" to "dependency-free" improves clarity.

The terminology change aligns with the suggestion from the previous review and enhances the description's clarity.

Tools
LanguageTool

[uncategorized] ~8-~8: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...generated from sync/async functions and provide methods to refetch them on expiry or on...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)


[uncategorized] ~8-~8: Possible missing article found.
Context: ...fetch them on expiry or on demand; with set of events to keep track of the state of...

(AI_HYDRA_LEO_MISSING_A)


12-16: LGTM: The added features accurately represent the library's capabilities.

The new features listed in the "Features" section, such as being dependency-free, providing in-memory caching, supporting sync/async source functions, providing events, and having an intuitive SDK, align with the library's description and the PR objectives. They provide a clear overview of the library's key selling points.


Line range hint 34-106: Excellent: The added examples effectively demonstrate the library's usage and new event-driven features.

The "Set cache" section now includes comprehensive examples of setting cache values with various options, such as TTL, source functions, and auto-refetching. The introduction of event listeners for cache expiry and refetching aligns with the PR objectives and showcases the library's new event-driven capabilities.

The examples cover different scenarios, such as registering event listeners for all expiries/refetches or for specific keys, providing developers with the flexibility to handle cache events according to their needs. The use of the EventParam type ensures type safety and clarity in the callback function parameters.

Overall, these examples significantly enhance the documentation and make it easier for developers to understand and utilize the library's features effectively.


135-135: Resolved: The addition of the article "the" improves the heading's readability.

The heading now reads "Check the existence of a specific cache," which addresses the previous suggestion and enhances the clarity of the sentence. This minor adjustment improves the overall readability of the documentation.


142-158: Excellent: The "Clear event listeners" section and examples provide a valuable feature for managing event listeners.

The addition of the "Clear event listeners" section aligns with the PR objectives and introduces a way to effectively manage event listeners in the library. The examples demonstrate how to clear all event listeners, specific event listeners (e.g., "expiry"), and specific event key listeners (e.g., "expiry" for a specific key), providing developers with the flexibility to control listener behavior.

The ability to clear event listeners is crucial for proper memory management and preventing unnecessary listener accumulation, especially in long-running applications. The examples are clear and cover different scenarios, making it easy for developers to understand and utilize the feature effectively.

Overall, this addition enhances the library's functionality and usability, and the documentation effectively communicates its usage.

src/run-cache.ts (3)

1-1: LGTM!

The import statement for EventEmitter using the 'node:' protocol is correct.


95-111: Proper exception handling in asynchronous setTimeout callback

Great job handling exceptions within the setTimeout callback by using a try-catch block. This ensures that any errors during await RunCache.refetch(key); are properly caught, preventing unhandled promise rejections.


311-321: Efficient event emission in emitEvent method

The emitEvent method correctly emits both global and key-specific events, allowing for flexible event handling.

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

Outside diff range comments (1)
README.md (1)

Line range hint 34-89: Great examples demonstrating the usage of RunCache.set!

The code segment provides clear and concise examples of how to set cache values using the RunCache.set method, covering various use cases such as setting a simple value, a value with a TTL, a value with a source function, and a value with auto-refetch enabled.

The examples also demonstrate how to use the new event handling features, such as onExpiry and onRefetch, to get notified when a cache entry expires or is refetched.

Consider adding an example that demonstrates how to handle errors that may occur during the execution of the source function. This could help users understand how to properly handle and recover from errors in their caching logic.

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between f901d3c and 8b2eab7.

Files selected for processing (3)
  • README.md (4 hunks)
  • src/run-cache.test.ts (3 hunks)
  • src/run-cache.ts (10 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (13)
README.md (8)

8-8: LGTM!

The sentence provides a clear and concise description of the RunCache library, highlighting its key features and benefits.


12-16: LGTM!

The list of features accurately describes the capabilities of the RunCache library and is well-formatted for readability.


91-94: LGTM!

The code segment demonstrates how to register an event listener for all refetch events using the RunCache.onRefetch method. The callback function logs a message indicating that a specific cache key has been refetched, which can be useful for debugging and monitoring purposes.


96-98: LGTM!

The code segment demonstrates how to register an event listener for a specific key refetch event using the RunCache.onKeyRefetch method. The callback function logs a message indicating that the specific cache key has been refetched, which can be useful for debugging and monitoring purposes.


108-112: LGTM!

The code segment demonstrates how to refetch a cache value using the RunCache.refetch method. The comment provides a clear explanation of the requirement for the refetch method to work, which is that the key must be set with a sourceFn.


Line range hint 115-122: LGTM!

The code segment demonstrates how to get a cache value using the RunCache.get method. The comment provides a clear explanation of the behavior of the get method when sourceFn is provided and autoRefetch is set to true, which is that it will automatically refetch the value.


125-132: LGTM!

The code segment demonstrates how to delete a specific cache key using the RunCache.delete method and how to delete all cache keys using the RunCache.flush method. The comments provide clear explanations of the behavior of each method, and the examples are simple and easy to understand.


135-157: LGTM!

The code segment demonstrates how to check the existence of a specific cache key using the RunCache.has method and how to clear event listeners using the RunCache.clearEventListeners method.

The comment provides a clear explanation of the behavior of the has method, including the fact that expired cache keys return false even if they are refetchable.

The examples show the different ways to clear event listeners, including clearing all listeners, clearing specific event listeners, and clearing specific event key listeners. This can be useful for managing memory and preventing unnecessary callbacks.

src/run-cache.ts (5)

1-1: Importing 'EventEmitter' using 'node:' protocol ensures proper module resolution

The import statement correctly uses the node: protocol to import EventEmitter from the Node.js built-in events module. This approach enhances clarity and ensures that the correct module and type declarations are used.


115-119: Check for empty string values explicitly

In the condition if (value === undefined && typeof sourceFn === "function"), you correctly check for undefined, allowing empty strings ('') to be valid cache values. This ensures that falsy but valid values like empty strings are handled properly.


251-257: Clear timeouts when deleting cache entries to prevent memory leaks

The delete method correctly checks for an existing timeout and clears it when deleting a cache entry. This helps prevent memory leaks due to lingering timers.


267-275: Flush method effectively clears cache and associated timeouts

The flush method iterates over all cache entries, clears any associated timeouts, and then clears the cache. This ensures that all timers are properly cleared and the cache is fully reset.


310-320: Efficiently emitting events for both global and key-specific listeners

The emitEvent method smartly emits events for both the global event name and the key-specific event name, facilitating flexible event handling for subscribers.

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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 8b2eab7 and 8edb145.

Files selected for processing (3)
  • package.json (2 hunks)
  • src/run-cache.test.ts (3 hunks)
  • src/run-cache.ts (10 hunks)
Additional context used
Learnings (2)
package.json (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: package.json:36-36
Timestamp: 2024-09-19T01:41:52.690Z
Learning: In the 'run-cache' project, the `uuid` package is only used in tests.
src/run-cache.test.ts (1)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (5)
package.json (4)

3-3: LGTM!

The version number has been updated appropriately for a minor release.


4-4: LGTM!

The updated description enhances clarity by specifying that the library is "dependency-free", emphasizing its lightweight design.


32-32: LGTM!

The addition of @types/uuid provides TypeScript type definitions for the uuid library, improving type safety when generating and manipulating UUIDs.


36-36: LGTM!

The addition of the uuid package allows for UUID generation within the project. As noted in the learnings, since uuid is only used in tests, keeping it under devDependencies is appropriate.

src/run-cache.test.ts (1)

282-282: Await 'expect' to properly handle Promise rejection

In asynchronous tests, you should await the expect assertion to correctly handle the promise rejection.

@helloscoopa
Copy link
Owner Author

@coderabbitai review

Copy link

coderabbitai bot commented Sep 19, 2024

Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 8edb145 and ea0b5c2.

Files selected for processing (1)
  • src/run-cache.ts (10 hunks)
Additional context used
Biome
src/run-cache.ts

[error] 92-92: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Additional comments not posted (13)
src/run-cache.ts (13)

1-1: Correct import statement with 'node:' protocol

The import statement correctly uses the 'node:' protocol, which is recommended for Node.js built-in modules. This ensures clarity and proper type declarations are found by TypeScript.


11-11: Adding 'timeout' to 'CacheState'

The addition of the timeout property to CacheState allows proper tracking and management of cache entry timeouts.


22-24: Defining 'EmitParam' type for event emission

The EmitParam type is appropriately defined to encapsulate the necessary properties for event emission, including cache metadata and the key.


26-30: Defining event constants using 'Object.freeze'

The EVENT constants are correctly defined and frozen to prevent modification, ensuring consistent event names throughout the codebase.


32-33: Defining 'EventName' type

The EventName type is correctly defined to be one of the event names defined in EVENT, ensuring type safety for event-related methods.


34-35: Defining 'SourceFn' and 'EventFn' types

The SourceFn and EventFn types are appropriately defined for the cache source function and event callbacks, respectively.


39-39: Initializing 'emitter' as an instance of 'EventEmitter'

The addition of the emitter property allows the cache to emit events, facilitating the new event-driven architecture.


76-77: Properly validating non-empty cache keys

Good job adding validation to ensure that the cache key is a non-empty string, preventing potential issues with empty keys.


88-93: Clearing existing timeouts when overwriting cache entries

Good practice of clearing existing timeouts when overwriting cache entries. This prevents multiple timeouts for the same key, avoiding potential duplicate events or unexpected behavior.

Tools
Biome

[error] 92-92: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


259-261: Clearing timeouts when deleting cache entries

Good job clearing any existing timeouts when deleting a cache entry. This ensures that no unnecessary timeouts remain active after the entry is removed.


272-280: Clearing all timeouts when flushing the cache

Properly clearing all active timeouts before clearing the cache ensures that there are no lingering timeouts after the cache is flushed.


315-325: Efficiently emitting events for both global and key-specific listeners

The emitEvent method correctly emits events for both the general event name and the key-specific event name, allowing listeners to handle events at different granularity levels.


406-441: Proper implementation of 'clearEventListeners' method

The clearEventListeners method effectively removes event listeners based on the provided parameters, handling cases for all listeners, specific events, and specific event-key combinations.

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

Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between ea0b5c2 and 1b07326.

Files selected for processing (2)
  • src/run-cache.test.ts (3 hunks)
  • src/run-cache.ts (9 hunks)
Additional context used
Learnings (1)
src/run-cache.test.ts (2)
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:445-446
Timestamp: 2024-09-19T11:53:52.280Z
Learning: When both `value` and `sourceFn` are provided to `RunCache.set()`, the `sourceFn` is not called during `set()`.
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:152-153
Timestamp: 2024-09-19T04:50:47.105Z
Learning: In test files, use `uuid()` for keys and values to maintain consistency across all tests, regardless of relevance.
Additional comments not posted (13)
src/run-cache.ts (2)

301-307: Verify Potential Duplicate 'expire' Events

The EVENT.EXPIRE is emitted in both the setTimeout callback and the has method when an expired entry is encountered. This might result in duplicate events for a single expiration.

Please ensure that emitting EVENT.EXPIRE in the has method does not cause unintended side effects due to duplicate events.


195-200: Preserve Original Error in Refetch Failure

Similarly, in the refetch method, the original error from sourceFn is not included when throwing a new error. Including it can aid in diagnosing issues during refetching.

Modify the error handling to include the original error message:

// Inside the catch block
throw new Error(`Source function failed for key: '${key}'. Error: ${e.message}`);

Likely invalid or redundant comment.

src/run-cache.test.ts (11)

1-2: Import Statements Are Correct

The import statements appropriately include all necessary modules for the tests:

import { EVENT, EventParam, RunCache } from "./run-cache";
import { v4 as uuid } from "uuid";

6-9: Proper Setup in beforeEach Hook

The beforeEach hook correctly sets up the test environment by using fake timers and flushing the cache:

jest.useFakeTimers();
RunCache.flush();

This ensures each test starts with a clean slate and that timing-related functionalities are consistently tested.


12-15: Effective Cleanup in afterEach Hook

The afterEach hook effectively cleans up after each test by clearing timers, restoring real timers, and clearing event listeners:

jest.clearAllTimers();
jest.useRealTimers();
RunCache.clearEventListeners();

This prevents side effects from one test affecting others.


39-49: Correct Handling of Source Function Errors

The test accurately verifies that an error is thrown when the sourceFn throws an exception during RunCache.set():

const sourceFn = jest.fn(async () => {
  throw Error("Unexpected Error");
});
await expect(
  RunCache.set({
    key,
    sourceFn,
  }),
).rejects.toThrow(`Source function failed for key: '${key}'`);

expect(sourceFn).toHaveBeenCalledTimes(1);

This ensures that error handling in RunCache.set() works as expected when the source function fails.


243-247: Correct Behavior When Refetching Without a Source Function

The test correctly asserts that RunCache.refetch() resolves to false when called on a key that has no sourceFn:

await RunCache.set({ key, value: uuid() });
await expect(RunCache.refetch(key)).resolves.toStrictEqual(false);

This behavior is appropriate, as refetching should not occur when there is no source function to provide a new value.


282-298: Accurate Testing of Concurrent Refetch Prevention

The test effectively checks that multiple concurrent calls to RunCache.refetch() on the same key do not result in multiple source function executions:

const [firstRefetch, secondRefetch, thirdRefetch] = await Promise.all([
  RunCache.refetch(key),
  RunCache.refetch(key),
  RunCache.refetch(key),
]);

expect(firstRefetch).toStrictEqual(true);
expect(secondRefetch).toStrictEqual(false);
expect(thirdRefetch).toStrictEqual(false);

expect(sourceFn).toHaveBeenCalledTimes(1);

This ensures that the cache correctly handles concurrent refetch attempts by allowing only one execution of the source function at a time.


323-349: Effective Testing of Expiry Event Handlers

The test successfully verifies that both onExpiry and onKeyExpiry event handlers are triggered after the TTL expiry:

RunCache.onExpiry(funcToBeExecutedOnExpiry);
RunCache.onKeyExpiry(key, funcToBeExecutedOnExpiry);

RunCache.set({
  key,
  value: value,
  ttl: 100,
});

jest.advanceTimersByTime(101);
await Promise.resolve(); // Flush microtasks

expect(funcToBeExecutedOnExpiry).toHaveBeenCalledTimes(2);

This confirms that the event handlers are functioning as intended and are called upon cache entry expiration.


352-383: Proper Verification of Refetch Event Handlers

The test accurately checks that onRefetch and onKeyRefetch handlers are invoked when a refetch occurs:

RunCache.onRefetch(funcToBeExecutedOnRefetch);
RunCache.onKeyRefetch(key, funcToBeExecutedOnRefetch);

await RunCache.set({
  key,
  sourceFn,
  ttl: 100,
  autoRefetch: true,
});

dynamicValue = uuid();

jest.advanceTimersByTime(101);
await Promise.resolve(); // Flush microtasks

expect(sourceFn).toHaveBeenCalledTimes(2);
expect(funcToBeExecutedOnRefetch).toHaveBeenCalledTimes(2);

This ensures that the cache correctly emits events during refetch operations and that the handlers receive the updated cache state.


386-428: Validation of Refetch Failure Event Handlers

The test properly verifies that onRefetchFailure and onKeyRefetchFailure handlers are triggered when the sourceFn fails during a refetch:

RunCache.onRefetchFailure(funcToBeExecutedOnRefetchFailure);
RunCache.onKeyRefetchFailure(key, funcToBeExecutedOnRefetchFailure);

await RunCache.set({
  key,
  value,
  sourceFn,
  ttl: 100,
  autoRefetch: true,
});

breaker = true;

jest.advanceTimersByTime(101);
await Promise.resolve(); // Flush microtasks

expect(sourceFn).toHaveBeenCalledTimes(1);
expect(funcToBeExecutedOnRefetchFailure).toHaveBeenCalledTimes(2);

This confirms that the cache correctly handles source function failures during refetch and that the appropriate event handlers are notified.


88-97: Maintain Consistency with Asynchronous sourceFn

Similar to the previous test, sourceFn here is also synchronous:

const sourceFn = jest.fn(() => value);

For consistency and to better simulate asynchronous operations, consider updating sourceFn to be asynchronous:

- const sourceFn = jest.fn(() => value);
+ const sourceFn = jest.fn(async () => value);

This adjustment helps ensure that the caching mechanism functions correctly with asynchronous source functions, which are common in real-world applications.

Skipped due to learnings
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:445-446
Timestamp: 2024-09-19T11:53:52.280Z
Learning: When both `value` and `sourceFn` are provided to `RunCache.set()`, the `sourceFn` is not called during `set()`.

74-80: Ensure Consistent Asynchronous Behavior in sourceFn

In this test, sourceFn is defined as a synchronous function:

const sourceFn = jest.fn(() => value);

To simulate real-world scenarios where sourceFn may involve asynchronous operations (e.g., fetching data from a database or an API), consider defining sourceFn as an asynchronous function:

- const sourceFn = jest.fn(() => value);
+ const sourceFn = jest.fn(async () => value);

This change will make the test more representative of actual usage and ensure that asynchronous behavior is consistently handled across all tests.

Skipped due to learnings
Learnt from: helloscoopa
PR: helloscoopa/run-cache#16
File: src/run-cache.test.ts:445-446
Timestamp: 2024-09-19T11:53:52.280Z
Learning: When both `value` and `sourceFn` are provided to `RunCache.set()`, the `sourceFn` is not called during `set()`.

@helloscoopa helloscoopa merged commit 35b7c08 into main Sep 19, 2024
3 checks passed
@helloscoopa helloscoopa deleted the scoopa/event-emitter-n-onspot-expiry branch September 19, 2024 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request tests Test related change included
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: Convert event callbacks into event emits feat: Improve onExpiry trigger
1 participant