Skip to content

feat(rivetkit): add ConnectionMap readonly Map wrapper for actor connections#5047

Merged
abcxff merged 1 commit into
mainfrom
05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections
May 13, 2026
Merged

feat(rivetkit): add ConnectionMap readonly Map wrapper for actor connections#5047
abcxff merged 1 commit into
mainfrom
05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections

Conversation

@abcxff
Copy link
Copy Markdown
Contributor

@abcxff abcxff commented May 12, 2026

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Code Review: ConnectionMap readonly Map wrapper for actor connections

Overview

This PR replaces the eager Map snapshot in ActorContextHandleAdapter.get conns() with a lazy NativeConnectionMap wrapper that implements ReadonlyMap. Each operation on the new wrapper calls actorConns individually rather than once per conns access.


Issues

1. Type declaration mismatch (bug)

config.ts:323 still declares conns as the mutable Map<string, Conn<...>>:

// src/actor/config.ts:323
readonly conns: Map<string, Conn<...>>;

The implementation in native.ts now returns ReadonlyMap, which is a subtype of Map — but the public interface type still exposes mutable methods (.set, .delete, .clear). The interface should be updated to ReadonlyMap in the same change to keep the declared API accurate and to prevent callers from accidentally calling mutating methods (which would fail at runtime since NativeConnectionMap does not implement them).

2. Redundant actorConns calls per operation

Every individual map method issues its own callNativeSync(() => this.#runtime.actorConns(this.#ctx)). Code like:

if (ctx.conns.has(id)) {
  const conn = ctx.conns.get(id);
}

...makes two native round-trips where the old snapshot approach made one. The two existing callsites (native.ts:3482 and 3696) use Array.from(actorCtx.conns.values()) which is a single actorConns call, so it's fine for those. But it's a non-obvious semantic change for the general case and worth a note on the class.

3. O(n) linear scan in get() and has()

Both methods fetch all connections and scan by ID:

const conn = conns.find((c) => this.#runtime.connId(c) === key);

This was also true of the old eager Map build (which iterated all connections to construct it), so it's not a regression. But conns.get(id) is now O(n) on every call. Worth noting, or considering a getConn(id) native shortcut if this becomes a hot path.

4. #connMap singleton is never invalidated

The #connMap is allocated on first access and never cleared. This is safe because NativeConnectionMap holds no state itself (it always queries the runtime), but the object lives for the actor's lifetime even for actors that access conns only once. Minor, but worth being aware of.

5. satisfies MapIterator<T> usage is unconventional

return conns.map((c) => this.#runtime.connId(c))[Symbol.iterator]() satisfies MapIterator<string>;

Array.prototype[Symbol.iterator]() returns IterableIterator<T>, which TypeScript accepts as structurally compatible with MapIterator<T>. The satisfies here is a valid compile-time assertion but unconventional — typically the structural check is implicit. Not a correctness issue, just a style note.


Positives

  • Correct semantic narrowing: ReadonlyMap is the right type for a connections view; actors should not be adding or deleting entries directly.
  • Avoids eager allocation: Actors that never call conns (or only call has/get for a single connection) no longer pay to build a full Map snapshot.
  • Live view: The new implementation always reflects the current connection set, rather than a snapshot taken at the moment conns was accessed. This is more correct for long-lived references to the context.

Suggestions

  1. Update config.ts:323 to readonly conns: ReadonlyMap<string, Conn<...>> in this PR.
  2. Add a brief comment on NativeConnectionMap noting that each method call issues a fresh actorConns query (i.e., this is a live view, not a snapshot).
  3. Consider a test that exercises the live-view property: connection added between two iterator calls shows up in the second.

Test coverage

No tests are included for NativeConnectionMap. The existing driver tests will cover the integration path, but a unit test verifying size, get, has, and iteration against a known connection set would improve confidence in the new implementation.

@abcxff abcxff marked this pull request as ready for review May 12, 2026 12:52
@abcxff abcxff force-pushed the 05-12-fix_rivetkit-core_chunk_apply_batch_puts_deletes_at_128 branch from 7920c6d to 78dd055 Compare May 12, 2026 13:21
@abcxff abcxff force-pushed the 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections branch from d056f16 to 436c4a6 Compare May 12, 2026 13:21
@abcxff abcxff force-pushed the 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections branch from 436c4a6 to b18ec51 Compare May 12, 2026 13:29
@abcxff abcxff force-pushed the 05-12-fix_rivetkit-core_chunk_apply_batch_puts_deletes_at_128 branch from 78dd055 to 741181f Compare May 12, 2026 13:29
Copy link
Copy Markdown
Contributor Author

abcxff commented May 13, 2026

Merge activity

  • May 13, 6:35 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 13, 6:46 PM UTC: Graphite rebased this pull request as part of a merge.
  • May 13, 6:47 PM UTC: @abcxff merged this pull request with Graphite.

@abcxff abcxff changed the base branch from 05-12-fix_rivetkit-core_chunk_apply_batch_puts_deletes_at_128 to graphite-base/5047 May 13, 2026 18:43
@abcxff abcxff changed the base branch from graphite-base/5047 to main May 13, 2026 18:45
@abcxff abcxff force-pushed the 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections branch from b18ec51 to 469ace5 Compare May 13, 2026 18:46
@abcxff abcxff merged commit 4fbdc66 into main May 13, 2026
8 of 12 checks passed
@abcxff abcxff deleted the 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections branch May 13, 2026 18:47
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.

1 participant