Skip to content

feat(rpc): createCommunity include:["started"] fast path for community-list started lookups#176

Open
Rinse12 wants to merge 1 commit into
masterfrom
feat/community-include-started
Open

feat(rpc): createCommunity include:["started"] fast path for community-list started lookups#176
Rinse12 wants to merge 1 commit into
masterfrom
feat/community-include-started

Conversation

@Rinse12

@Rinse12 Rinse12 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Closes #175. Alternative to #141.

Problem

A thin RPC client that only needs each community's started flag (e.g. the CLI community list) previously instantiated every community via the full subscribe -> update() -> wait-for-first-update -> stop() cycle. That's O(N) blocking round-trips that also serialize each full community record. Measured ~10-20s for 17 communities.

The started flag needs zero DB load — the daemon already holds it in memory (findStartedCommunity).

Change

Add an optional include field-selection arg to createCommunity:

started is the only cheap in-memory field today; the include enum is extensible for future cheap fields.

Tests

  • test/node/community/include-started.community.test.ts (RPC-only): asserts started true/false across start/stop and that the fast path opens no update subscription (spies communityUpdateSubscribe), unlike the full path.
  • Local benchmark (empty communities): fast path is consistently 2.2-3.5x faster with matching started results; the production win is expected larger since it also skips full-record serialization.

Verification

  • npm run build clean; npx tsc --project test/tsconfig.json --noEmit clean.
  • New test passes; started-communities and the touched rpc.listeners server test pass (no regression).

Summary by CodeRabbit

  • New Features

    • Added a faster community creation option that can return the community’s started status immediately.
    • Community creation now supports requesting only lightweight status data instead of a full community snapshot.
  • Bug Fixes

    • Improved community creation behavior to avoid unnecessary follow-up updates when only the started state is needed.
    • Existing full community creation flow remains available and unchanged for standard use.

A thin RPC client that only needs each community's `started` flag (e.g. a
CLI `community list`) previously instantiated every community via the full
subscribe -> update() -> wait-for-first-update -> stop() cycle, which is O(N)
blocking round-trips that also serialize each full community record.

Add an optional `include` field-selection arg to createCommunity. When
`include: ["started"]`, the RPC server short-circuits to an in-memory
started lookup (findStartedCommunity) and returns { address, started } in a
single request/response - no DB load, no instantiation, no subscription.
The public createCommunity fast-paths on this and falls back to the full
cycle against an old daemon (version skew). `pkc.communities` is unchanged
(stays string[]). `include` is also honored on communityUpdateSubscribe.

Alternative to #141.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds an optional include: ["started"] field-selection argument to createCommunity (and update subscriptions), enabling a lightweight in-memory started-status lookup across schemas, RPC server handlers, RPC client wrappers, and the PKC client, with a fallback to the existing full create/subscribe flow, plus new and updated tests.

Changes

include:['started'] fast path

Layer / File(s) Summary
Schema and type contracts
src/community/schema.ts, src/community/types.ts, src/clients/rpc-client/schema.ts, src/clients/rpc-client/types.ts, src/clients/rpc-client/rpc-schema-util.ts
Adds CommunityIncludeFieldsSchema/CommunityIncludeFields, extends CreateRemoteCommunityOptionsSchema and RpcCommunityIdentifierParamSchema with optional include, and adds RpcCommunityStartedResultSchema/RpcCommunityStartedResult plus a parseRpcCommunityStartedResult parser.
RPC server short-circuit handlers
src/rpc/src/index.ts
createCommunity and _bindCommunityUpdateSubscription gain fast paths that detect include: ["started"], read the in-memory started registry, and return/notify { address, started } without full community instantiation or DB work.
RPC client createCommunity overloads
src/clients/rpc-client/pkc-rpc-client.ts
createCommunity gains overloads accepting include, returning a parsed RpcCommunityStartedResult on the fast path, falling back to the existing local-community creation flow otherwise.
PKC client fast path with fallback
src/pkc/pkc-with-rpc-client.ts
Adds a conditional fast path for local RPC communities requesting only started, issuing a single RPC call and hydrating community.started, catching failures to fall back to the full subscribe/update/stop cycle; also updates an existing RPC call type cast.
Tests
src/rpc/test/node/rpc.listeners.test.ts, test/node/community/include-started.community.test.ts
Updates existing listener tests to type-cast createCommunity results, and adds a new test suite verifying started-only fast path behavior (no subscription opened) versus the full subscription path.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant PKCWithRpcClient
    participant PKCRpcClient
    participant RpcServer

    Caller->>PKCWithRpcClient: createCommunity({ address, include: ["started"] })
    PKCWithRpcClient->>PKCRpcClient: createCommunity({ address, include: ["started"] })
    PKCRpcClient->>RpcServer: RPC createCommunity(include=["started"])
    RpcServer->>RpcServer: lookup started registry (in-memory)
    RpcServer-->>PKCRpcClient: { address, started }
    PKCRpcClient-->>PKCWithRpcClient: RpcCommunityStartedResult
    PKCWithRpcClient->>PKCWithRpcClient: set community.started
    PKCWithRpcClient-->>Caller: RpcLocalCommunity (started only, no subscription)

    alt fast path fails
        PKCWithRpcClient->>PKCRpcClient: createCommunity() (full path)
        PKCRpcClient->>RpcServer: RPC createCommunity + communityUpdateSubscribe
        RpcServer-->>PKCRpcClient: update notification
        PKCRpcClient-->>PKCWithRpcClient: full community record
        PKCWithRpcClient-->>Caller: RpcLocalCommunity (fully subscribed)
    end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The RPC fast path, schema, and fallback are implemented, but the issue also requires the community-list CLI to pass include:["started"], which is not shown here. Add the community list caller change so it requests include:["started"] when probing started state.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the RPC createCommunity started-only fast path.
Out of Scope Changes check ✅ Passed The changes stay focused on the started-only RPC path, schema plumbing, and tests without unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/community-include-started

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/rpc/src/index.ts`:
- Around line 629-639: The fast-path in createCommunity is returning a derived
address from parsedIdentifier instead of the canonical community.address. Update
the include/startured-only branch to use the actual community record returned by
findStartedCommunity (or otherwise fetch the started community object) and
return its immutable address field directly, rather than rebuilding it from
params. Keep the existing parseRpcCommunityIdentifierParam and _getPKCInstance
flow, but ensure the result object is populated from the canonical
community.address.
- Around line 1164-1174: The fast-path in this subscription handler is deriving
`address` from `parsedArgs.name`/`parsedArgs.publicKey`, which can diverge from
the canonical community address. Update this branch to use the actual found
community’s immutable `address` (and `started`) from the result of
`findStartedCommunity(pkc, parsedArgs)`, and avoid any fallback-derived address.
Prefer extracting a shared private helper such as
`_resolveStartedOnlyResult(lookup)` that both this path and `createCommunity`’s
fast path can call so the `sendEvent("update", ...)` payload always uses the
canonical address.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ca5143b-fc22-424e-8066-68a40e6869db

📥 Commits

Reviewing files that changed from the base of the PR and between afbbafa and e51ed7c.

📒 Files selected for processing (10)
  • src/clients/rpc-client/pkc-rpc-client.ts
  • src/clients/rpc-client/rpc-schema-util.ts
  • src/clients/rpc-client/schema.ts
  • src/clients/rpc-client/types.ts
  • src/community/schema.ts
  • src/community/types.ts
  • src/pkc/pkc-with-rpc-client.ts
  • src/rpc/src/index.ts
  • src/rpc/test/node/rpc.listeners.test.ts
  • test/node/community/include-started.community.test.ts

Comment thread src/rpc/src/index.ts
Comment on lines +629 to +639
async createCommunity(params: any): Promise<RpcInternalCommunityRecordBeforeFirstUpdateType | RpcCommunityStartedResult> {
// Fast path: a caller that only wants cheap in-memory fields (currently just `started`)
// passes `include`. We read the started flag from the in-memory registry — no DB load,
// no community instantiation, no subscription. See pkc-js issue #175 (alternative to #141).
const includeFields = params[0]?.include;
if (Array.isArray(includeFields) && includeFields.length > 0 && includeFields.every((f: unknown) => f === "started")) {
const parsedIdentifier = parseRpcCommunityIdentifierParam(params[0]);
const pkcInstance = await this._getPKCInstance();
const started = Boolean(findStartedCommunity(pkcInstance, parsedIdentifier));
return { address: parsedIdentifier.name ?? parsedIdentifier.publicKey!, started };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fast-path result uses a derived address instead of the canonical community.address.

findStartedCommunity's result is discarded except for its truthiness; address is rebuilt from the request identifier (parsedIdentifier.name ?? parsedIdentifier.publicKey) instead of the actual community's immutable .address. If the identifier used for lookup ever differs from the community's current canonical address, the fast path returns a stale/wrong address to the client.

As per coding guidelines: "author.address and community.address are immutable — never override or fall back to a derived address."

🛠️ Proposed fix using the canonical address
         const includeFields = params[0]?.include;
         if (Array.isArray(includeFields) && includeFields.length > 0 && includeFields.every((f: unknown) => f === "started")) {
             const parsedIdentifier = parseRpcCommunityIdentifierParam(params[0]);
             const pkcInstance = await this._getPKCInstance();
-            const started = Boolean(findStartedCommunity(pkcInstance, parsedIdentifier));
-            return { address: parsedIdentifier.name ?? parsedIdentifier.publicKey!, started };
+            const startedCommunity = findStartedCommunity(pkcInstance, parsedIdentifier);
+            return {
+                address: startedCommunity?.address ?? parsedIdentifier.name ?? parsedIdentifier.publicKey!,
+                started: Boolean(startedCommunity)
+            };
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async createCommunity(params: any): Promise<RpcInternalCommunityRecordBeforeFirstUpdateType | RpcCommunityStartedResult> {
// Fast path: a caller that only wants cheap in-memory fields (currently just `started`)
// passes `include`. We read the started flag from the in-memory registry — no DB load,
// no community instantiation, no subscription. See pkc-js issue #175 (alternative to #141).
const includeFields = params[0]?.include;
if (Array.isArray(includeFields) && includeFields.length > 0 && includeFields.every((f: unknown) => f === "started")) {
const parsedIdentifier = parseRpcCommunityIdentifierParam(params[0]);
const pkcInstance = await this._getPKCInstance();
const started = Boolean(findStartedCommunity(pkcInstance, parsedIdentifier));
return { address: parsedIdentifier.name ?? parsedIdentifier.publicKey!, started };
}
async createCommunity(params: any): Promise<RpcInternalCommunityRecordBeforeFirstUpdateType | RpcCommunityStartedResult> {
// Fast path: a caller that only wants cheap in-memory fields (currently just `started`)
// passes `include`. We read the started flag from the in-memory registry — no DB load,
// no community instantiation, no subscription. See pkc-js issue `#175` (alternative to `#141`).
const includeFields = params[0]?.include;
if (Array.isArray(includeFields) && includeFields.length > 0 && includeFields.every((f: unknown) => f === "started")) {
const parsedIdentifier = parseRpcCommunityIdentifierParam(params[0]);
const pkcInstance = await this._getPKCInstance();
const startedCommunity = findStartedCommunity(pkcInstance, parsedIdentifier);
return {
address: startedCommunity?.address ?? parsedIdentifier.name ?? parsedIdentifier.publicKey!,
started: Boolean(startedCommunity)
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/src/index.ts` around lines 629 - 639, The fast-path in
createCommunity is returning a derived address from parsedIdentifier instead of
the canonical community.address. Update the include/startured-only branch to use
the actual community record returned by findStartedCommunity (or otherwise fetch
the started community object) and return its immutable address field directly,
rather than rebuilding it from params. Keep the existing
parseRpcCommunityIdentifierParam and _getPKCInstance flow, but ensure the result
object is populated from the canonical community.address.

Source: Coding guidelines

Comment thread src/rpc/src/index.ts
Comment on lines +1164 to +1174

// Fast path: a subscriber that only wants the cheap in-memory `started` flag passes `include`.
// Emit an immediate started-only "update" and skip creating/binding a real community — no DB
// load, no update wait. See pkc-js issue #175 (alternative to #141).
if (Array.isArray(parsedArgs.include) && parsedArgs.include.length > 0 && parsedArgs.include.every((f) => f === "started")) {
const started = Boolean(findStartedCommunity(pkc, parsedArgs));
sendEvent("update", { address: parsedArgs.name ?? parsedArgs.publicKey!, started });
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {}; // nothing created — no-op cleanup
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Same derived-address issue as createCommunity's fast path (Lines 629-639).

Consider extracting a shared private helper (e.g. _resolveStartedOnlyResult(lookup)) that both call sites use to compute { address, started } from the actual found community, so this can't diverge again.

As per coding guidelines: "author.address and community.address are immutable — never override or fall back to a derived address."

🛠️ Proposed fix using the canonical address
         if (Array.isArray(parsedArgs.include) && parsedArgs.include.length > 0 && parsedArgs.include.every((f) => f === "started")) {
-            const started = Boolean(findStartedCommunity(pkc, parsedArgs));
-            sendEvent("update", { address: parsedArgs.name ?? parsedArgs.publicKey!, started });
+            const startedCommunity = findStartedCommunity(pkc, parsedArgs);
+            sendEvent("update", {
+                address: startedCommunity?.address ?? parsedArgs.name ?? parsedArgs.publicKey!,
+                started: Boolean(startedCommunity)
+            });
             this.subscriptionCleanups[connectionId][subscriptionId] = async () => {}; // nothing created — no-op cleanup
             return;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Fast path: a subscriber that only wants the cheap in-memory `started` flag passes `include`.
// Emit an immediate started-only "update" and skip creating/binding a real community — no DB
// load, no update wait. See pkc-js issue #175 (alternative to #141).
if (Array.isArray(parsedArgs.include) && parsedArgs.include.length > 0 && parsedArgs.include.every((f) => f === "started")) {
const started = Boolean(findStartedCommunity(pkc, parsedArgs));
sendEvent("update", { address: parsedArgs.name ?? parsedArgs.publicKey!, started });
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {}; // nothing created — no-op cleanup
return;
}
if (Array.isArray(parsedArgs.include) && parsedArgs.include.length > 0 && parsedArgs.include.every((f) => f === "started")) {
const startedCommunity = findStartedCommunity(pkc, parsedArgs);
sendEvent("update", {
address: startedCommunity?.address ?? parsedArgs.name ?? parsedArgs.publicKey!,
started: Boolean(startedCommunity)
});
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {}; // nothing created — no-op cleanup
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/src/index.ts` around lines 1164 - 1174, The fast-path in this
subscription handler is deriving `address` from
`parsedArgs.name`/`parsedArgs.publicKey`, which can diverge from the canonical
community address. Update this branch to use the actual found community’s
immutable `address` (and `started`) from the result of
`findStartedCommunity(pkc, parsedArgs)`, and avoid any fallback-derived address.
Prefer extracting a shared private helper such as
`_resolveStartedOnlyResult(lookup)` that both this path and `createCommunity`’s
fast path can call so the `sendEvent("update", ...)` payload always uses the
canonical address.

Source: Coding guidelines

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.

feat(rpc): include:["started"] field-selection arg on createCommunity for fast community-list started lookups (alternative to #141)

1 participant