fix(rivetkit): cache actor queries & automatic cache invalidation#4501
Conversation
|
🚅 Deployed to the rivet-pr-4501 environment in rivet-frontend
|
|
---\n## PR Review: fix(rivetkit): cache actor queries and automatic cache invalidation\n\nThis PR introduces ActorResolutionState to cache actor ID resolution and add automatic retry-on-stale-cache behavior across all client operations (actions, queue sends, raw HTTP, raw WebSocket, and connection open). The approach is sound and well-tested.\n\n---\n\n### Whats good\n\n- Deduplication of concurrent resolve calls via pendingResolve is well-handled. Concurrent callers share the same in-flight queryActor promise, avoiding redundant round-trips.\n- Shared state between handle and connection: ActorHandleRaw.connect() passes its actorResolutionState directly to ActorConnRaw, so invalidation in one propagates to the other. This is the right design.\n- Single-retry semantics: retryOnInvalidResolvedActor uses a retried flag to guarantee at most one retry, preventing infinite loops even if the actor keeps getting destroyed.\n- Error filtering: shouldInvalidateResolvedActorId correctly limits invalidation to actor/not_found and actor/destroyed_* errors, so transient errors do not incorrectly bust the cache.\n- Test coverage: All four operation types (action, queue, HTTP fetch, raw WebSocket) plus connection open are covered with dedicated tests for both getOrCreate and get handles.\n\n---\n\n### Issues and observations\n\n1. Potential extra round-trip from actor/not_found on a never-created actor\n\nshouldInvalidateResolvedActorId treats any actor/not_found as a stale cache signal, including when the actor genuinely never existed. The behavior is safe (at most two requests), but it is worth a comment explaining callers should expect two requests when the actor does not exist.\n\n2. code.startsWith is a broad prefix match\n\nThis matches any future error code starting with destroyed_. A brief comment explaining why a prefix match is used instead of an exhaustive list would help future contributors understand the intent and avoid accidentally adding destroyed_-prefixed codes that should not trigger cache invalidation.\n\n3. shouldReconnectForStaleActor only fires during connection open, not mid-connection\n\nIf a connection is already established and the actor is then destroyed, the error is passed through to the application rather than triggering reconnect logic. This is intentional and reasonable, but a comment documenting that the retry guarantee applies only to the initial connection open would help callers understand the semantics.\n\n4. setResolvedActorId in connectWebSocket is redundant on the happy path\n\nAfter the WS init message, setResolvedActorId is called with the server-confirmed actor ID. But resolveActorId() was already called earlier in connectWebSocket, which already populated state.resolvedActorId. A comment clarifying whether this is intended as a server-confirmation safeguard would make the intent clearer.\n\n5. invalidateResolvedActorIdFromError is exported but only used internally\n\nThis function is only called by retryOnInvalidResolvedActor. Consider whether it should remain exported, or if keeping it unexported would reduce surface area and prevent misuse.\n\n6. resolveActorId silently falls through to uncached queryActor for create-type queries\n\nThis correctly skips caching for one-shot create queries, but every call to a create-backed handle re-queries. This matches pre-existing behavior and is correct. A brief comment would make the intentional bypass explicit.\n\n---\n\n### Minor nits\n\n- Formatting-only changes (whitespace, trailing newline, empty callback bodies) are fine.\n- Removed inline comments are consistent with the project guideline against documenting deltas.\n- The updated JSDoc on ActorAccessor interface methods is a nice addition.\n\n---\n\n### Summary\n\nThe core implementation is solid. The deduplication, retry semantics, and state-sharing design are all correct. The main actionable feedback:\n1. Document the two-request behavior for .get() on a non-existent actor.\n2. Add a comment on the startsWith prefix match rationale for destroyed errors.\n3. Add a comment on the mid-connection non-retry guarantee in shouldReconnectForStaleActor.\n\nNone of these are blockers.\n\nGenerated with Claude Code\n--- |
b86e608 to
3f97c6b
Compare
3f97c6b to
031ab90
Compare
031ab90 to
d85a1fe
Compare
Merge activity
|

Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
Type of change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes.
Checklist: