Skip to content

feat: jrpc v2 readiness#383

Open
lwin-kyaw wants to merge 7 commits intomasterfrom
feat/jrpc-v2-readiness
Open

feat: jrpc v2 readiness#383
lwin-kyaw wants to merge 7 commits intomasterfrom
feat/jrpc-v2-readiness

Conversation

@lwin-kyaw
Copy link
Contributor

@lwin-kyaw lwin-kyaw commented Feb 26, 2026

Motivation and Context

Jira Link:

Description

JRPC V2 Readiness

Adds the remaining V2 integration layer (provider utilities, duplex message stream, compatibility helpers) and refactors the underlying stream infrastructure to prepare for full JRPC Engine V2 adoption.

Changes

JRPC V2 Provider & Stream Utilities

  • providerUtils.ts — New V2 provider utilities: providerFromEngine, providerFromMiddleware, and providerAsMiddleware that work with JRPCEngineV2. Unlike V1, the V2 engine throws errors directly rather than wrapping them in response objects.
  • messageStream.ts — New createEngineStreamV2 that creates a Duplex object stream backed by a JRPCEngineV2. Decouples notification forwarding from the engine via an optional SafeEventEmitter, so the engine no longer needs to be an EventEmitter.
  • compatibility-utils.ts — Added propagateToMutableRequest to deep-clone frozen request objects and propagate context properties onto the mutable clone, avoiding mutation errors in middleware chains.
  • v2/index.ts — Exported all new V2 utilities (createEngineStreamV2, providerFromEngineV2, providerFromMiddlewareV2, providerAsMiddlewareV2, compatibility helpers).

Stream Layer Refactoring

  • BasePostMessageStream — Refactored into an abstract class with proper encapsulation (private fields), removed BRK protocol, added pluggable logging via _setLogger, and made _postMessage abstract for subclass implementation.
  • PostMessageStream — Moved message handling, constructor logic, and _destroy out of the base class; uses MessageEvent prototype getters for secure origin/source validation.
  • ObjectMultiplex — Replaced pump/end-of-stream with pipeline/finished from readable-stream; added stream state validation (destroyed/ended checks); removed window.console in favor of console for non-browser compatibility.
  • Substream — Properly typed _parent as ObjectMultiplex (removed any); accepts DuplexOptions for pass-through stream configuration.

Stack Upgrades & Cleanup

  • Removed buffer, bn.js, pump, end-of-stream dependencies in favor of native/lighter alternatives.
  • Removed starkey module and browserStorage utilities.
  • Upgraded toolchain: @toruslabs/torus-scripts v8, @toruslabs/config v4, @toruslabs/eslint-config-typescript v5, @types/node v25.
  • Replaced Buffer.from() usages with @toruslabs/metadata-helpers (utf8ToBytes, toBufferLike).
  • Migrated React example from CRA to Vite; updated Vue example dependencies and chain config.

Tests

  • providerUtils.test.ts — Comprehensive tests for providerFromEngine, providerFromMiddleware, and providerAsMiddleware covering happy paths, error propagation, send/sendAsync/request interfaces, context forwarding, and round-trip composition.
  • messageStream.test.ts — Tests for createEngineStreamV2 covering request/notification handling, error serialization, stream push behavior, and notification emitter lifecycle.
  • compatibility-utils.test.ts — Added tests for propagateToMutableRequest verifying immutability of the original request, deep mutability of the clone, and correct context propagation.
  • Removed starkey.test.ts.

How has this been tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My code follows the code style of this project. (run lint)
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My code requires a db migration.

Note

Medium Risk
Medium risk because it introduces new JRPC v2 bridging primitives (providerUtils, createEngineStreamV2) and changes how context is propagated onto requests (via deep-cloned mutable copies), which could affect consumers that adopt these exports.

Overview
Completes the JRPC v2 integration surface by adding providerUtils (providerFromEngine, providerFromMiddleware, providerAsMiddleware) and a new createEngineStreamV2 Duplex stream that wraps JRPCEngineV2 and optionally forwards notifications via a separate SafeEventEmitter.

Improves legacy-compat context propagation with propagateToMutableRequest, which deep-clones (including nested params) before applying context keys to avoid mutating/failing on frozen request objects, and re-exports these new helpers from src/jrpc/v2/index.ts.

Adds new Vitest coverage for the stream and provider adapters and extends compatibility-utils tests for the new mutable-propagation behavior; package-lock.json also drops a number of `

Written by Cursor Bugbot for commit b01101b. This will update automatically on new commits. Configure here.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

});
callback(serializedError, { id: req.id, jsonrpc: "2.0", error: serializedError });
}
}
Copy link

Choose a reason for hiding this comment

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

Callback invoked twice when success callback throws

High Severity

handleWithCallback places both engine.handle() and the success callback(null, ...) call inside the same try block. If the callback itself throws after being invoked with a successful result, the catch block catches that error and calls the callback a second time with a serialized error response. This means the callback can be invoked twice — once with a success response and once with an error — violating the standard Node.js convention that callbacks are called exactly once. The V1 provider.send avoids this by passing the callback directly to the engine.

Fix in Cursor Fix in Web

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