feat(publisher,ratelimit,cloudflare)!: align adapter options with the lock by dinwwwh · Pull Request #2292 · middleapi/orpc · GitHub
Skip to content

feat(publisher,ratelimit,cloudflare)!: align adapter options with the lock - #2292

Open
dinwwwh wants to merge 3 commits into
mainfrom
claude/adapter-options-inconsistencies-cak51z
Open

dinwwwh wants to merge 3 commits into
mainfrom
claude/adapter-options-inconsistencies-cak51z

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Summary

The publisher, rate limit and lock adapters used different units and shapes for similar options. This PR fixes three inconsistencies:

  • The publisher's resume.seconds was in seconds, while every lock and rate-limit timing is in milliseconds.
  • Waiting was a flat timeout on the lock but blockingUntilReady: { enabled, timeout } on the rate limiter.
  • UpstashRateLimiter took an @upstash/ratelimit instance, while UpstashPublisher and UpstashLocker take a Redis client.

Breaking changes

Publisher retention is in milliseconds

resume.seconds is now resume.ttl, in milliseconds (default 300000). This applies to MemoryPublisher, RedisPublisher, UpstashPublisher and BunRedisPublisher.

// Before
new RedisPublisher(client, { resume: { enabled: true, seconds: 300 } })

// After
new RedisPublisher(client, { resume: { enabled: true, ttl: 300_000 } })

DurablePublisherObject (@orpc/cloudflare) changed the same way: seconds is now ttl, and cleanupIntervalSeconds is now cleanupInterval, both in milliseconds.

// Before
super(ctx, env, { resume: { enabled: true, seconds: 300, cleanupIntervalSeconds: 6 * 60 * 60 } })

// After
super(ctx, env, { resume: { enabled: true, ttl: 300_000, cleanupInterval: 6 * 60 * 60 * 1000 } })

Rate limiters take a flat timeout

blockingUntilReady: { enabled, timeout } is now timeout, matching the lock. The default is 0 (don't wait), so default behavior is unchanged. This applies to MemoryRateLimiter, RedisRateLimiter, UpstashRateLimiter and BunRedisRateLimiter.

// Before
new MemoryRateLimiter({ maxRequests: 10, window: 60_000, blockingUntilReady: { enabled: true, timeout: 5000 } })

// After
new MemoryRateLimiter({ maxRequests: 10, window: 60_000, timeout: 5000 })

As before, a request gives up right away when its window will not reset before the timeout.

UpstashRateLimiter takes a Redis client

UpstashRateLimiter now extends BaseRedisRateLimiter, like RedisRateLimiter and BunRedisRateLimiter, and shares counters with them.

// Before
const ratelimit = new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(10, '60 s') })
new UpstashRateLimiter(ratelimit, { blockingUntilReady: { enabled: true, timeout: 5000 } })

// After
new UpstashRateLimiter(Redis.fromEnv(), { maxRequests: 10, window: 60_000, timeout: 5000 })
  • Removed: the @upstash/ratelimit peer dependency (@upstash/redis replaces it) and the waitUntil option.
  • Lost features: only a fixed window is supported now. Upstash's other algorithms (sliding window, token bucket), multi-region support and analytics are gone.
  • Workaround: users who need those can wrap a Ratelimit instance in a small custom RateLimiter.

Other changes

  • Rate limiter internals: the memory and Redis rate limiters now share one internal wait loop and weight check (packages/ratelimit/src/utils.ts).
  • Shared resume type: publishers share one exported PublisherResumeOptions type, which DurablePublisherObjectResumeOptions extends.
  • Redis stream expiry: Redis-backed publishers now set the stream expiry with PEXPIRE (ms) instead of EXPIRE (s).
  • Durable Object storage: the SQLite stored_at column stays in seconds, and the code converts ttl at cleanup. Existing tables keep their old column default, so switching the column to milliseconds would delete every stored event.
  • | undefined convention: new optional fields are declared as X | undefined, following fix: accept explicit undefined on optional option fields #2278.
  • Docs: the publisher and rate limit pages use the new options. The v1 migration guide now maps resumeRetentionSeconds/retentionSeconds to ttl, blockingUntilReady to timeout, and shows a before/after for the Upstash rate limiter.

Verification

  • Static checks: pnpm lint, pnpm type:check and pnpm docs:validate pass.
  • Redis integration tests: the publisher, rate limit and lock tests pass against a local Redis, including the stream-expiry test that now uses PEXPIRE.
  • Bun and Cloudflare: bun test in packages/bun passes against local Redis (136 pass, 6 skip). The Cloudflare workerd tests pass (74).
  • Not run: the Upstash tests need Upstash credentials. I rewrote the Upstash rate limiter test against a Redis client. Separately, I ran UpstashRateLimiter against local Redis through a stand-in for the Upstash client's eval. It shared counters with RedisRateLimiter and waited and gave up as expected. That does not cover a real Upstash server.

Generated by Claude Code

Note

Align publisher resume and rate-limit adapter options with the lock

  • Unifies publisher resume config behind a shared PublisherResumeOptions with millisecond ttl; Redis, Upstash, memory, and Cloudflare Durable Object adapters now use it instead of seconds-based settings.
  • Replaces the nested blockingUntilReady rate-limit option with a flat millisecond timeout, backed by shared blockUntilReady and resolveWeight utilities in utils.ts.
  • Rewrites UpstashRateLimiter as a BaseRedisRateLimiter subclass that takes an @upstash/redis client and runs the shared fixed-window Lua script, dropping the @upstash/ratelimit dependency and waitUntil.
  • Updates docs and tests, including the v1 migration guide at from-v1.mdx.
  • Risk: breaking option renames — resume.seconds → resume.ttl, cleanupIntervalSeconds → cleanupInterval, blockingUntilReady → timeout; Redis expiry now uses PEXPIRE with millisecond TTLs in base-redis.ts.

🖇️ Linked Issues

Resolves algolia#3613 and fixes algolia#3511.

Macroscope summarized bb40f3a.

Make the adapter options of the publisher, rate limit, and lock helpers
follow the same units and shapes:

- Publisher `resume.seconds` is now `resume.ttl`, in milliseconds like
  every lock and rate limit timing. `DurablePublisherObject`'s
  `cleanupIntervalSeconds` is now `cleanupInterval`, also in
  milliseconds. Redis-backed publishers set the stream expiry with
  `PEXPIRE`.
- Rate limiters take a flat `timeout` (default 0, no waiting) like the
  lock, replacing `blockingUntilReady: { enabled, timeout }`.
- `UpstashRateLimiter` now takes an Upstash `Redis` client like
  `UpstashPublisher` and `UpstashLocker`. It extends
  `BaseRedisRateLimiter`, so it shares counters with `RedisRateLimiter`
  and `BunRedisRateLimiter`. The `@upstash/ratelimit` peer dependency
  and the `waitUntil` option are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G38QumhkQEV68MTzxy2oQd
- Move the wait-until-reset loop and weight validation shared by
  `MemoryRateLimiter` and `BaseRedisRateLimiter` into one internal
  module, dropping the leftover `timeoutMs` parameter.
- Share one exported `PublisherResumeOptions` type between the memory
  and Redis publishers, and extend it in `DurablePublisherObject`.
- Say that the rate limiter gives up, rather than rejects, when the
  window will not reset before the timeout, since `limit` returns a
  failed result.
- Drop an unused `UpstashPublisher` instance from the compatibility
  test and a stale comment from the Upstash rate limiter test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G38QumhkQEV68MTzxy2oQd
…s-inconsistencies-cak51z

Keep the renamed `ttl`, `timeout`, `cleanupInterval` and shared `resume`
options, declared as `X | undefined` to follow #2278.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G38QumhkQEV68MTzxy2oQd
@macroscopeapp

macroscopeapp Bot commented Oct 21, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new

pkg-pr-new Bot commented Oct 21, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2292

@orpc/arktype

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

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2292

@orpc/client

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

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2292

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2292

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2292

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2292

@orpc/json-schema

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

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2292

@orpc/mcp

npm i https://pkg.pr.new/@orpc/mcp@2292

@orpc/msw

npm i https://pkg.pr.new/@orpc/msw@2292

@orpc/nest

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

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2292

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2292

@orpc/openapi

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

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2292

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2292

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2292

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2292

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2292

@orpc/server

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

@orpc/shared

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

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2292

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: bb40f3a

@codspeed

codspeed Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/adapter-options-inconsistencies-cak51z (bb40f3a) with main (a762db7)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Publisher resume retention moves to milliseconds — resume.seconds → resume.ttl (default 300000) behind a shared PublisherResumeOptions type, applied to memory, Redis/Upstash/Bun Redis (via BaseRedisPublisher) and the Cloudflare Durable Object, which also renames cleanupIntervalSeconds → cleanupInterval (ms).
  • Redis stream expiry uses PEXPIRE — the publish script and its trim/TTL arguments now derive from resumeTtl ms instead of seconds.
  • Rate-limiter blocking flattened to timeout — blockingUntilReady: { enabled, timeout } → flat timeout (default 0) across memory, Redis, Upstash and Bun Redis, backed by the shared blockUntilReady/resolveWeight helpers in packages/ratelimit/src/utils.ts.
  • UpstashRateLimiter reworked — now extends BaseRedisRateLimiter over an @upstash/redis client with a fixed window and shares counters with RedisRateLimiter; drops @upstash/ratelimit and waitUntil.
  • Docs and tests — publisher, rate-limit and v1 migration pages updated; adapter tests moved to the new options.

The Durable Object correctly keeps stored_at in seconds and converts ttl at cleanup (this.ttl / 1000), so existing tables are not invalidated. UpstashRateLimiter's evalScript follows the same redis.eval(script, keys, args) contract already used by UpstashLocker and UpstashPublisher. I found no stale seconds/blockingUntilReady/cleanupIntervalSeconds references outside the v1 migration guide, where they are intentional.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
orpc bb40f3a Commit Preview URL

Branch Preview URL
Oct 21 2026, 02:13 PM

@dinwwwh dinwwwh changed the title feat!: align publisher and rate limit adapter options with the lock feat(publisher,ratelimit,cloudflare)!: align adapter options with the lock Oct 21, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants