Toncenter Streaming API v2 clients - #188
Conversation
📝 WalkthroughWalkthroughThis PR introduces complete streaming client functionality for the TON blockchain SDK, enabling real-time event subscriptions via WebSocket and SSE transports. It includes abstract lifecycle management with reconciliation, protocol-level message parsing, two transport implementations, comprehensive type definitions, and live tests validating functionality against mainnet and testnet. ChangesStreaming Client Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/index.ts (1)
31-38: 💤 Low valueConsider exporting
StreamingSupersededErrorfor completeness.
StreamingSupersededErroris exported from./client/streaming/index.tsbut not re-exported here. If consumers might need to distinguish superseded subscriptions from other errors (e.g., for retry logic), consider adding it to the public API.Suggested addition
export { TonWsClient, TonSseClient, StreamingClosedError, StreamingError, StreamingHandshakeError, StreamingRequestTimeoutError, + StreamingSupersededError, } from "./client/streaming";🤖 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/index.ts` around lines 31 - 38, The public re-exports omit StreamingSupersededError; add it to the exported symbols alongside TonWsClient, TonSseClient, StreamingClosedError, StreamingError, StreamingHandshakeError, and StreamingRequestTimeoutError so consumers can detect superseded-subscription errors; update the export list that currently references "./client/streaming" to include StreamingSupersededError.
🤖 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/client/streaming/protocol.ts`:
- Around line 100-107: The optional-field checks treat only undefined as
"absent" and throw when JSON uses null; update castOptionalJsonObjectRecord<T>
to treat null the same as undefined (e.g., check value == null or value === null
|| value === undefined) and likewise change the guards in parseTraceEvent for
optional fields like actions (and any similar checks for address_book/metadata)
to consider null as absent so those fields are ignored instead of passed to
expectRecord/expectArray.
In `@src/client/streaming/Streaming.transactions.live.spec.ts`:
- Around line 143-149: The failing formatter is caused by the formatting of the
process.stdout.write template literal inside the target.on("transactions")
handler; update the formatting of that call (the process.stdout.write(...) line)
so it matches the project's style (or simply run the project's formatter, e.g.,
npm run format) and ensure the surrounding handler (target.on("transactions",
...), the first flag usage, and events.push({ source }) remain unchanged.
In `@src/client/streaming/Streaming.wallet.e2e.live.spec.ts`:
- Around line 287-290: The Promise.all call formatting causes CI failure;
reformat the array containing addressStream and traceStream to match project
style (likely collapse into a single-line Promise.all([addressStream.close(),
traceStream.close()]) or run the project's formatter). Locate the
Promise.all(...) in the test file where addressStream and traceStream are closed
and either manually adjust to the project's preferred single-line style or run
the project's formatter (e.g., npm run format) to apply the correct formatting.
---
Nitpick comments:
In `@src/index.ts`:
- Around line 31-38: The public re-exports omit StreamingSupersededError; add it
to the exported symbols alongside TonWsClient, TonSseClient,
StreamingClosedError, StreamingError, StreamingHandshakeError, and
StreamingRequestTimeoutError so consumers can detect superseded-subscription
errors; update the export list that currently references "./client/streaming" to
include StreamingSupersededError.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4585cce9-807f-4c50-97e7-098051821a60
📒 Files selected for processing (15)
package.jsonsrc/client/streaming/AbstractStreamingClient.tssrc/client/streaming/SseParser.tssrc/client/streaming/Streaming.transactions.live.spec.tssrc/client/streaming/Streaming.wallet.e2e.live.spec.tssrc/client/streaming/TonSseClient.tssrc/client/streaming/TonWsClient.tssrc/client/streaming/TypedEventEmitter.tssrc/client/streaming/errors.tssrc/client/streaming/index.tssrc/client/streaming/protocol.tssrc/client/streaming/subscriptionState.tssrc/client/streaming/types.tssrc/client/streaming/utils.tssrc/index.ts
| function castOptionalJsonObjectRecord<T extends JsonObject>( | ||
| value: unknown, | ||
| fieldName: string, | ||
| ): Record<string, T> | undefined { | ||
| return value === undefined | ||
| ? undefined | ||
| : castJsonObjectRecord<T>(value, fieldName); | ||
| } |
There was a problem hiding this comment.
null values for optional payload fields throw instead of being treated as absent
castOptionalJsonObjectRecord guards with === undefined (Line 104), and parseTraceEvent guards actions with === undefined (Line 185). JSON APIs routinely send "field": null for absent optional fields. Receiving null for address_book, metadata, or actions falls through to expectRecord/expectArray, which correctly rejects null as a non-array/non-object and throws a parse error, surfacing a confusing protocol-level failure to callers instead of silently treating the field as missing.
🐛 Proposed fix — treat `null` identically to absent (`undefined`)
function castOptionalJsonObjectRecord<T extends JsonObject>(
value: unknown,
fieldName: string,
): Record<string, T> | undefined {
- return value === undefined
+ return value == null
? undefined
: castJsonObjectRecord<T>(value, fieldName);
} actions:
- payload.actions === undefined
+ payload.actions == null
? undefined
: castJsonObjectArray<StreamingAction>(
payload.actions,
"trace.actions",
),Also applies to: 184-190
🤖 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/client/streaming/protocol.ts` around lines 100 - 107, The optional-field
checks treat only undefined as "absent" and throw when JSON uses null; update
castOptionalJsonObjectRecord<T> to treat null the same as undefined (e.g., check
value == null or value === null || value === undefined) and likewise change the
guards in parseTraceEvent for optional fields like actions (and any similar
checks for address_book/metadata) to consider null as absent so those fields are
ignored instead of passed to expectRecord/expectArray.
| target.on("transactions", () => { | ||
| if (first) { | ||
| first = false; | ||
| process.stdout.write(` ✓ ${source}: first transaction received\n`); | ||
| } | ||
| events.push({ source }); | ||
| }), |
There was a problem hiding this comment.
Fix formatting to resolve CI failure.
The pipeline indicates a formatting check failure on lines 146-148. The process.stdout.write call with the template literal needs reformatting to satisfy the project's formatter.
Suggested fix
Run the project's formatter (e.g., npm run format) to automatically fix the line formatting for the process.stdout.write call.
🧰 Tools
🪛 GitHub Actions: Check formatting / 0_test.txt
[error] 146-148: Formatter (format:check) failed for this file. Process.stdout.write template call would be reformatted (would split into multi-line call).
🤖 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/client/streaming/Streaming.transactions.live.spec.ts` around lines 143 -
149, The failing formatter is caused by the formatting of the
process.stdout.write template literal inside the target.on("transactions")
handler; update the formatting of that call (the process.stdout.write(...) line)
so it matches the project's style (or simply run the project's formatter, e.g.,
npm run format) and ensure the surrounding handler (target.on("transactions",
...), the first flag usage, and events.push({ source }) remain unchanged.
| await Promise.all([ | ||
| addressStream.close(), | ||
| traceStream.close(), | ||
| ]); |
There was a problem hiding this comment.
Fix formatting to resolve CI failure.
The pipeline indicates a formatting check failure. The Promise.all array should be formatted according to the project's style (likely single-line).
Suggested fix
Run the project's formatter (e.g., npm run format) to automatically fix the Promise.all array formatting.
🧰 Tools
🪛 GitHub Actions: Check formatting / 0_test.txt
[error] 287-290: Formatter (format:check) failed for this file. Promise.all array formatting would be changed from multi-line to single-line.
🤖 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/client/streaming/Streaming.wallet.e2e.live.spec.ts` around lines 287 -
290, The Promise.all call formatting causes CI failure; reformat the array
containing addressStream and traceStream to match project style (likely collapse
into a single-line Promise.all([addressStream.close(), traceStream.close()]) or
run the project's formatter). Locate the Promise.all(...) in the test file where
addressStream and traceStream are closed and either manually adjust to the
project's preferred single-line style or run the project's formatter (e.g., npm
run format) to apply the correct formatting.
Summary
This PR adds streaming clients for
@ton/tonwith support for Toncenter Streaming API v2 over both SSE and WebSocket.The implementation covers:
pending/confirmed/finalized)transactions,actions,trace,account_state_change,jettons_change, andtrace_invalidatedWhat’s included
New streaming clients
TonSseClientTonWsClientTransport support
Subscription handling
trace_external_hash_normsmin_finality,include_address_book,include_metadata,action_types, andsupported_action_typesEvent parsing and typing
Lifecycle and reliability
close()behaviorDesign notes
The implementation follows the semantics of Streaming API v2:
POSTrequest with an immutable subscription for the lifetime of the stream.subscribereplaces the current subscription snapshot for the connection.unsubscriberemoves specific addresses or trace hashes.trace_invalidatedis handled as a first-class event.The parser and event model were also checked against real SSE output from live wallet e2e runs to make sure the client matches real payload shapes rather than only the nominal protocol description.
Compatibility
Supported provider presets cover:
The implementation also supports custom endpoints and auth parameter configuration where needed.
Summary by CodeRabbit
New Features
Tests
Chores