Skip to content

feat(durable-iterator): add refreshTokenDelayInSeconds option to DurableIteratorLinkPlugin#1026

Merged
dinwwwh merged 4 commits intomainfrom
feat/durable-iterator/refreshTokenDebounceDelay
Sep 26, 2025
Merged

feat(durable-iterator): add refreshTokenDelayInSeconds option to DurableIteratorLinkPlugin#1026
dinwwwh merged 4 commits intomainfrom
feat/durable-iterator/refreshTokenDebounceDelay

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented Sep 24, 2025

Summary by CodeRabbit

  • New Features

    • Added a configurable delay between token refresh attempts (default 2s) to improve refresh/reconnect timing.
  • Tests

    • Expanded tests to simulate token refresh flows with controlled timers, covering delayed retries, multiple refresh cycles, timer lifecycle, and cleanup.
  • Documentation

    • Minor cleanup in DurableIterator options docs.

@vercel
Copy link
Copy Markdown

vercel Bot commented Sep 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Sep 26, 2025 8:36am

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Sep 24, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 24, 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 new configurable option, refreshTokenDelayInSeconds, to DurableIteratorLinkPlugin and integrates it into token refresh and retry scheduling, replacing a fixed 2000ms retry. Updates tests to use vitest fake timers and exercise additional delay-driven refresh cycles. Also performs a minor docs cleanup in DurableIteratorOptions.

Changes

Cohort / File(s) Summary
Plugin option and timing
packages/durable-iterator/src/client/plugin.ts
Introduces public option refreshTokenDelayInSeconds on DurableIteratorLinkPluginOptions and a private field on the plugin; defaults to 2s. Replaces fixed 2000ms retry with a computed delayMilliseconds derived from the option and uses max(delayMilliseconds, timeToExpiry) to schedule refresh/retry. Constructor signature updated to accept the new option.
Tests: token refresh timing & cycles
packages/durable-iterator/src/client/plugin.test.ts
Adds vitest fake timers and a realSetTimeout helper, wires a mock refreshTokenDelayInSeconds, advances fake timers to trigger extra refresh/retry cycles, updates assertions for timer counts, token/url refreshes, RPC handler invocations, and ensures timers are cleared after refreshes.
Docs tidy
packages/durable-iterator/src/iterator.ts
Minor JSDoc cleanup in DurableIteratorOptions (removed an empty line and @default [] comment); no type or behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant P as DurableIteratorLinkPlugin
  participant TP as Token Provider
  participant WS as WebSocket

  C->>P: instantiate({ url, refreshTokenBeforeExpireInSeconds, refreshTokenDelayInSeconds? })
  P->>TP: request initial token
  TP-->>P: return token
  P->>WS: connect(url, token)
  P->>P: scheduleRefresh(timeout = max(delayMs, timeToExpiry))

  alt Proactive refresh (timeToExpiry >= delayMs)
    P->>TP: refresh token
    TP-->>P: new token
    P->>WS: update/reconnect with new token
  else Retry path (delay-driven)
    Note over P: wait delayMs
    P->>TP: retry refresh token
    TP-->>P: token or failure
    opt Success
      P->>WS: update/reconnect with new token
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • unnoq/orpc#965 — Modifies the same DurableIteratorLinkPlugin token refresh logic by adding refreshTokenDelayInSeconds and updating related tests.

Poem

I twitch my ears at tokens’ play,
A pause of two, then off I sway.
I fetch, I retry, I nudge the clock,
Renew the keys — click, click, tick-tock.
A rabbit guard for links that stay. 🐇⏳

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly follows the conventional commit format by labeling this as a new feature for the durable-iterator scope and it clearly states the addition of the refreshTokenDelayInSeconds option to the DurableIteratorLinkPlugin. It directly matches the primary code changes, enabling reviewers to understand the main change at a glance. The phrasing is concise and avoids unnecessary details, focusing on the key functionality being introduced.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/durable-iterator/refreshTokenDebounceDelay

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.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @unnoq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new refreshTokenDelayInSeconds option to the DurableIteratorLinkPlugin, providing granular control over the minimum delay between token refresh attempts. This enhancement aims to improve the robustness and efficiency of the token management process by preventing rapid, consecutive refresh requests and ensuring a more controlled retry mechanism.

Highlights

  • New Configuration Option: Introduced refreshTokenDelayInSeconds as an optional property to DurableIteratorLinkPluginOptions. This allows specifying a minimum delay between token refresh attempts.
  • Configurable Token Refresh Delay: The DurableIteratorLinkPlugin now utilizes the refreshTokenDelayInSeconds option to control the retry delay for token refresh attempts, defaulting to 2 seconds if not specified. This prevents overly aggressive refresh retries.
  • Improved Token Refresh Logic: The internal token refresh mechanism has been updated to incorporate the new delay, ensuring that subsequent refresh attempts respect the configured minimum delay, enhancing stability.
  • Test Coverage: Comprehensive tests have been added and adjusted to validate the functionality of the new refreshTokenDelayInSeconds option, including its impact on refresh timing and retry behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a refreshTokenDelayInSeconds option to the DurableIteratorLinkPlugin, allowing control over the minimum delay between token refresh attempts. The implementation correctly adds the option and uses it to schedule subsequent refresh operations. The tests have also been updated to cover the new functionality. I have one suggestion regarding the use of this new option to also control the retry delay for failed refresh attempts, which could lead to unexpected behavior.

Comment thread packages/durable-iterator/src/client/plugin.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 24, 2025

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/durable-iterator/src/client/plugin.ts 97.05% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Sep 24, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1026

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1026

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1026

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1026

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1026

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1026

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1026

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1026

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1026

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1026

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1026

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1026

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1026

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1026

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1026

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1026

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1026

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1026

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1026

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1026

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1026

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1026

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1026

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1026

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1026

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1026

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1026

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1026

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1026

commit: ad4e10e

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/durable-iterator/src/client/plugin.ts (3)

55-60: Clarify and bound the delay option (doc).

State that values must be non‑negative, and that non‑finite or negative values fall back to the default (2s).

   /**
    * Minimum delay between token refresh attempts.
    *
-   * @default 2 (seconds)
+   * Must be non-negative. Non-finite or negative values are treated as the default (2 seconds).
+   *
+   * @default 2 (seconds)
    */
   refreshTokenDelayInSeconds?: Value<Promisable<number>, [tokenPayload: DurableIteratorTokenPayload, options: StandardLinkInterceptorOptions<T>]>

138-141: Sanitize delay to avoid NaN/negative values causing tight or undefined retry loops.

Clamp to a safe default when user-supplied resolver returns non‑finite or negative.

-        const delayMilliseconds = await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options) * 1000
+        const rawDelaySeconds = await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options)
+        const delayMilliseconds = (Number.isFinite(rawDelaySeconds) && rawDelaySeconds >= 0 ? rawDelaySeconds : 2) * 1000

146-193: Avoid work after teardown: short‑circuit the refresh cycle when finished.

When the iterator is returned/aborted, the timer callback may still run and perform updateToken/reconnect. Add early returns to avoid post‑teardown side effects.

         refreshTokenBeforeExpireTimeoutId = setTimeout(
-          async () => {
+          async () => {
+            if (isFinished) return
             // retry until success or finished
             const newTokenAndPayload = await retry({ times: Number.POSITIVE_INFINITY, delay: delayMilliseconds }, async (exit) => {
               try {
                 const output = await next()
                 return this.validateToken(output, options.path)
               }
               catch (err) {
                 if (isFinished) {
                   exit(err)
                 }
 
                 throw err
               }
             })
 
             const canProactivelyUpdateToken
               = newTokenAndPayload.payload.chn === tokenAndPayload.payload.chn
                 && stringifyJSON(newTokenAndPayload.payload.tags) === stringifyJSON(tokenAndPayload.payload.tags)
 
             tokenAndPayload = newTokenAndPayload
             await refreshTokenBeforeExpire() // recursively call
+            if (isFinished) return
 
             /**
              * The next refresh cycle doesn't depend on the logic below,
              * so we place it last to avoid interfering with recursion.
              */
             if (canProactivelyUpdateToken) {
               /**
                * Proactively update the token before expiration
                * to avoid reconnecting when the old token expires.
                */
               await durableClient.updateToken({ token: tokenAndPayload.token })
             }
             else {
               /**
                * Proactive update requires the same channel and tags.
                * If they differ, we must reconnect instead to make new token effective.
                */
               websocket.reconnect()
             }
           },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7bc0b8 and 0b15351.

📒 Files selected for processing (3)
  • packages/durable-iterator/src/client/plugin.test.ts (4 hunks)
  • packages/durable-iterator/src/client/plugin.ts (3 hunks)
  • packages/durable-iterator/src/iterator.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/durable-iterator/src/iterator.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/durable-iterator/src/client/plugin.ts (4)
packages/shared/src/value.ts (3)
  • Value (1-1)
  • fallback (17-19)
  • value (3-12)
packages/durable-iterator/src/schemas.ts (1)
  • DurableIteratorTokenPayload (6-6)
packages/client/src/adapters/standard/link.ts (1)
  • StandardLinkInterceptorOptions (9-12)
packages/shared/src/json.ts (1)
  • stringifyJSON (9-12)
packages/durable-iterator/src/client/plugin.test.ts (4)
packages/shared/src/index.ts (1)
  • sleep (22-22)
packages/durable-iterator/src/client/iterator.ts (1)
  • getClientDurableIteratorToken (74-80)
packages/durable-iterator/src/schemas.ts (1)
  • parseDurableIteratorToken (52-60)
packages/durable-iterator/src/consts.ts (1)
  • DURABLE_ITERATOR_TOKEN_PARAM (1-1)
⏰ 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: publish-commit
  • GitHub Check: lint
🔇 Additional comments (4)
packages/durable-iterator/src/client/plugin.ts (1)

77-86: Constructor changes look good.

New option is threaded through with a sensible default of 2s.

packages/durable-iterator/src/client/plugin.test.ts (3)

37-66: Test wiring LGTM.

New mock and plugin option are correctly introduced and exercised.


188-201: Timing assertions align with new min‑delay semantics.

Shorter pre‑expiry waits (2×500ms) and a 2s delay for the extra cycle match the implementation.


211-220: Good coverage: asserts delay resolver is invoked.

Verifying both call count and arguments strengthens confidence in the new option.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/durable-iterator/src/client/plugin.ts (3)

55-61: Good option add; clamp to non-negative to avoid busy-loop/instant retries

If a user returns 0 or a negative value, the retry loop below will spin too fast or schedule immediately. Clamp to >= 0 when materializing milliseconds.

Apply this diff at the usage site:

-const delayMilliseconds = await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options) * 1000
+const delayMilliseconds = Math.max(
+  0,
+  (await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options)) * 1000,
+)

139-140: Materialize delay as non-negative milliseconds

Strengthen against negative returns and fractional seconds.

Apply:

-const delayMilliseconds = await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options) * 1000
+const delayMilliseconds = Math.max(
+  0,
+  (await value(this.refreshTokenDelayInSeconds, tokenAndPayload.payload, options)) * 1000,
+)

189-193: Guard against negative “time until refresh” and improve readability

Compute msUntilRefresh once and clamp to >= 0; then take max with the min spacing. This avoids negative setTimeout delays if clocks skew or payload exp is near.

Apply:

-          Math.max(
-            refreshTokenBeforeExpireTimeoutId === undefined ? 0 : delayMilliseconds,
-            ((tokenAndPayload.payload.exp - beforeSeconds) * 1000) - Date.now(),
-          ),
+          Math.max(
+            refreshTokenBeforeExpireTimeoutId === undefined ? 0 : delayMilliseconds,
+            Math.max(((tokenAndPayload.payload.exp - beforeSeconds) * 1000) - Date.now(), 0),
+          ),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b15351 and edc44fe.

📒 Files selected for processing (1)
  • packages/durable-iterator/src/client/plugin.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/durable-iterator/src/client/plugin.ts (4)
packages/shared/src/value.ts (3)
  • Value (1-1)
  • fallback (17-19)
  • value (3-12)
packages/durable-iterator/src/schemas.ts (1)
  • DurableIteratorTokenPayload (6-6)
packages/client/src/adapters/standard/link.ts (1)
  • StandardLinkInterceptorOptions (9-12)
packages/shared/src/json.ts (1)
  • stringifyJSON (9-12)
⏰ 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: lint
  • GitHub Check: publish-commit
🔇 Additional comments (2)
packages/durable-iterator/src/client/plugin.ts (2)

77-86: Wiring and defaults look correct

Private field and constructor wiring for refreshTokenDelayInSeconds with default 2s are consistent with docs.


149-161: Decouple retry delay from min refresh spacing

Using refreshTokenDelayInSeconds for network retry delay changes semantics; long spacing (e.g., 60s) would slow retries for a single failed refresh. Keep retry delay at 2000ms as before, and reserve refreshTokenDelayInSeconds only for scheduling the next refresh cycle.

Apply:

-const newTokenAndPayload = await retry({ times: Number.POSITIVE_INFINITY, delay: delayMilliseconds }, async (exit) => {
+const newTokenAndPayload = await retry({ times: Number.POSITIVE_INFINITY, delay: 2000 }, async (exit) => {

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/durable-iterator/src/client/plugin.test.ts (2)

40-41: New option is wired correctly; add a stronger assertion.
Looks good. Consider asserting the last call to refreshTokenDelayInSeconds uses the latest token (e.g., from url3) to ensure scheduling uses the refreshed payload each cycle.

Example:

expect(refreshTokenDelayInSeconds).toHaveBeenLastCalledWith(
  parseDurableIteratorToken(new URL(url3).searchParams.get(DURABLE_ITERATOR_TOKEN_PARAM)!),
  expect.objectContaining({ path: ['durableIterator'] }),
)

Also applies to: 66-69


340-345: Don’t register afterEach inside a test; use process.once or local cleanup.
Registering afterEach during test execution is brittle. Prefer process.once or a try/finally.

Apply this diff:

-const unhandledRejectionHandler = vi.fn()
-process.on('unhandledRejection', unhandledRejectionHandler)
-afterEach(() => {
-  process.off('unhandledRejection', unhandledRejectionHandler)
-})
+const unhandledRejectionHandler = vi.fn()
+process.once('unhandledRejection', unhandledRejectionHandler)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edc44fe and 0f4d7d9.

📒 Files selected for processing (1)
  • packages/durable-iterator/src/client/plugin.test.ts (13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/durable-iterator/src/client/plugin.test.ts (3)
packages/durable-iterator/src/client/iterator.ts (1)
  • getClientDurableIteratorToken (74-80)
packages/durable-iterator/src/schemas.ts (1)
  • parseDurableIteratorToken (52-60)
packages/durable-iterator/src/consts.ts (1)
  • DURABLE_ITERATOR_TOKEN_PARAM (1-1)
⏰ 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: publish-commit
  • GitHub Check: lint
  • GitHub Check: test
🔇 Additional comments (8)
packages/durable-iterator/src/client/plugin.test.ts (8)

7-7: LGTM: Vitest imports are appropriate for fake-timer driven tests.


16-17: Good call capturing the real setTimeout.
This avoids deadlocks while fake timers are active.


203-232: LGTM: Refresh flow assertions align with delay-based scheduling.
The staged advances and token/url changes validate both before-expire and delay timers.

Also applies to: 233-242


278-278: LGTM: Explicitly disabling refresh via NaN is covered.


321-336: LGTM: Retry path for invalid tokens is asserted clearly.
Counts and URL stability checks look right.


351-354: LGTM: Ensures in-flight retry rejects after return and is surfaced via unhandledRejection.
The sequencing with timers and real waits is appropriate.

Also applies to: 356-358


395-408: LGTM: Reconnect on channel mismatch is validated and timers resume.


444-461: LGTM: Reconnect on tags mismatch is validated and timers resume.

Comment thread packages/durable-iterator/src/client/plugin.test.ts
@dinwwwh dinwwwh merged commit 01f0b7a into main Sep 26, 2025
8 of 11 checks passed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/durable-iterator/src/client/plugin.test.ts (1)

340-345: Do not register afterEach inside a test; leaks unhandledRejection handler.

Defining afterEach inside an it does not clean up for the current test and leaves a process-level listener attached, risking cross-test interference.

Apply this diff to remove the nested hook:

-      const unhandledRejectionHandler = vi.fn()
-      process.on('unhandledRejection', unhandledRejectionHandler)
-      afterEach(() => {
-        process.off('unhandledRejection', unhandledRejectionHandler)
-      })
+      const unhandledRejectionHandler = vi.fn()
+      process.on('unhandledRejection', unhandledRejectionHandler)

Then, ensure explicit cleanup at the end of this test (right before it returns). Add:

process.off('unhandledRejection', unhandledRejectionHandler)

I can provide a full patch wrapping the body in try/finally if you prefer.

🧹 Nitpick comments (1)
packages/durable-iterator/src/client/plugin.test.ts (1)

16-17: Avoid saving real setTimeout; prefer Vitest timer APIs to prevent real 1s sleeps.

Capturing and using realSetTimeout slows tests and can introduce flakiness. Prefer vi.runAllTimersAsync() (or vi.advanceTimersByTimeAsync) to flush pending timers/microtasks under fake timers.

Apply this diff to drop the saved real timer:

-const realSetTimeout = globalThis.setTimeout
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f4d7d9 and ad4e10e.

📒 Files selected for processing (1)
  • packages/durable-iterator/src/client/plugin.test.ts (13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/durable-iterator/src/client/plugin.test.ts (3)
packages/durable-iterator/src/client/iterator.ts (1)
  • getClientDurableIteratorToken (74-80)
packages/durable-iterator/src/schemas.ts (1)
  • parseDurableIteratorToken (52-60)
packages/durable-iterator/src/consts.ts (1)
  • DURABLE_ITERATOR_TOKEN_PARAM (1-1)
⏰ 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: publish-commit
  • GitHub Check: lint
🔇 Additional comments (11)
packages/durable-iterator/src/client/plugin.test.ts (11)

7-7: LGTM: vitest utilities import.

Good move to centralize timer control in tests.


151-159: Resolved: real timers restored after each test.

This addresses the earlier review about leaking fake timers. Consider also flushing timers instead of sleeping (see next comment).

Apply this refinement to avoid a real 1s wait:

-  await new Promise(resolve => realSetTimeout(resolve, 1000)) // await for all promises resolved
-  expect(vi.getTimerCount()).toBe(0) // every is cleanup
+  await vi.runAllTimersAsync() // flush pending timers & microtasks under fake timers
+  expect(vi.getTimerCount()).toBe(0) // all timers cleaned up
   vi.useRealTimers()

162-162: LGTM: deterministic refresh window.

Setting refreshTokenBeforeExpireInSeconds to 9 with TTL 10s makes the first refresh fire at +1s, matching the expectations.


192-192: LGTM: asserts refresh timer is armed.

This validates initial scheduling after iterator resolution.


203-208: LGTM: staged advancement verifies no premature refresh.

Advancing half the window and asserting stable state is correct.


209-232: LGTM: refresh cycle assertions (new token + timer restart).

Good checks for URL/token rotation, handler calls, and timer lifecycle.


233-243: LGTM: validates both before-expire and delay callbacks.

Ensures new option is exercised and called with the correct payload/context.


278-278: LGTM: confirms refresh disabled when NaN.

Right behavior when opting out of scheduled refresh.


395-408: LGTM: reconnect on channel mismatch path.

Checks for reconnect() and updated URL, plus timer restart, look correct.


444-461: LGTM: reconnect on tags mismatch path.

Mirrors the channel mismatch case; assertions are solid.


65-69: refreshTokenDelayInSeconds option correctly supported in plugin Option is defined in DurableIteratorLinkPluginOptions, wired in the constructor with a default of 2s, and applied in the scheduling logic.

Comment on lines +351 to 359
vi.advanceTimersByTime(2000) // wait next retry
expect(vi.getTimerCount()).toBe(0) // refresh token executed
await new Promise(resolve => realSetTimeout(resolve, 10)) // wait for token refresh trigger
await output.return() // cleanup

await sleep(2000)
vi.advanceTimersByTime(2000) // wait handler throw
await new Promise(resolve => realSetTimeout(resolve, 10)) // wait for token refresh reject
expect(unhandledRejectionHandler).toHaveBeenCalledTimes(1)
expect(unhandledRejectionHandler.mock.calls[0]![0]).toEqual(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Ensure the unhandledRejection listener is removed.

After the final assertions, call process.off('unhandledRejection', unhandledRejectionHandler) to prevent leaking the listener to subsequent tests.

🤖 Prompt for AI Agents
In packages/durable-iterator/src/client/plugin.test.ts around lines 351 to 359,
the test adds an unhandledRejection listener but never removes it, leaking the
listener into subsequent tests; after the final assertions add a call to remove
the listener by invoking process.off('unhandledRejection',
unhandledRejectionHandler) (or process.removeListener) to unregister the handler
and prevent test leakage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant