@thru/replay 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,490 @@
1
+ import { Transport, Interceptor, CallOptions } from '@connectrpc/connect';
2
+ import { ListBlocksRequest, ListBlocksResponse, StreamBlocksRequest, StreamBlocksResponse, ListTransactionsRequest, ListTransactionsResponse, StreamTransactionsRequest, StreamTransactionsResponse, ListEventsRequest, ListEventsResponse, StreamEventsRequest, StreamEventsResponse, StreamAccountUpdatesRequest, StreamAccountUpdatesResponse, GetHeightResponse, Filter, BlockView, ConsensusStatus, Block, Transaction, Event, AccountView, AccountMeta, AccountUpdate } from '@thru/proto';
3
+ export { Account, AccountFlags, AccountMeta, AccountPage, AccountUpdate, AccountView, BlockFinished, Event, Filter, FilterParamValue, FilterParamValueSchema, FilterSchema, Pubkey as ProtoPubkey, Signature as ProtoSignature, StreamAccountUpdatesRequest, StreamAccountUpdatesResponse } from '@thru/proto';
4
+
5
+ interface ChainClientOptions {
6
+ baseUrl?: string;
7
+ apiKey?: string;
8
+ userAgent?: string;
9
+ transport?: Transport;
10
+ interceptors?: Interceptor[];
11
+ callOptions?: CallOptions;
12
+ useBinaryFormat?: boolean;
13
+ }
14
+ interface BlockSource {
15
+ listBlocks(request: Partial<ListBlocksRequest>): Promise<ListBlocksResponse>;
16
+ streamBlocks(request: Partial<StreamBlocksRequest>): AsyncIterable<StreamBlocksResponse>;
17
+ }
18
+ interface TransactionSource {
19
+ listTransactions(request: Partial<ListTransactionsRequest>): Promise<ListTransactionsResponse>;
20
+ streamTransactions(request: Partial<StreamTransactionsRequest>): AsyncIterable<StreamTransactionsResponse>;
21
+ }
22
+ interface EventSource {
23
+ listEvents(request: Partial<ListEventsRequest>): Promise<ListEventsResponse>;
24
+ streamEvents(request: Partial<StreamEventsRequest>): AsyncIterable<StreamEventsResponse>;
25
+ }
26
+ interface AccountSource {
27
+ streamAccountUpdates(request: Partial<StreamAccountUpdatesRequest>): AsyncIterable<StreamAccountUpdatesResponse>;
28
+ }
29
+ type ReplayDataSource = BlockSource & TransactionSource & EventSource & AccountSource;
30
+ declare class ChainClient implements ReplayDataSource {
31
+ private readonly options;
32
+ private readonly query;
33
+ private readonly streaming;
34
+ private readonly callOptions?;
35
+ constructor(options: ChainClientOptions);
36
+ listBlocks(request: Partial<ListBlocksRequest>): Promise<ListBlocksResponse>;
37
+ streamBlocks(request: Partial<StreamBlocksRequest>): AsyncIterable<StreamBlocksResponse>;
38
+ listTransactions(request: Partial<ListTransactionsRequest>): Promise<ListTransactionsResponse>;
39
+ streamTransactions(request: Partial<StreamTransactionsRequest>): AsyncIterable<StreamTransactionsResponse>;
40
+ listEvents(request: Partial<ListEventsRequest>): Promise<ListEventsResponse>;
41
+ streamEvents(request: Partial<StreamEventsRequest>): AsyncIterable<StreamEventsResponse>;
42
+ private createTransport;
43
+ private createHeaderInterceptor;
44
+ streamAccountUpdates(request: Partial<StreamAccountUpdatesRequest>): AsyncIterable<StreamAccountUpdatesResponse>;
45
+ getHeight(): Promise<GetHeightResponse>;
46
+ }
47
+
48
+ type Slot = bigint;
49
+ interface BackfillPage<T, Cursor = unknown> {
50
+ items: T[];
51
+ cursor?: Cursor;
52
+ done?: boolean;
53
+ }
54
+ type BackfillFetcher<T, Cursor = unknown> = (params: {
55
+ startSlot: Slot;
56
+ cursor?: Cursor;
57
+ }) => Promise<BackfillPage<T, Cursor>>;
58
+ type LiveSubscriber<T> = (startSlot: Slot) => AsyncIterable<T>;
59
+ interface ReplayLogger {
60
+ debug(message: string, meta?: Record<string, unknown>): void;
61
+ info(message: string, meta?: Record<string, unknown>): void;
62
+ warn(message: string, meta?: Record<string, unknown>): void;
63
+ error(message: string, meta?: Record<string, unknown>): void;
64
+ }
65
+ interface ReplayMetrics {
66
+ bufferedItems: number;
67
+ emittedBackfill: number;
68
+ emittedLive: number;
69
+ discardedDuplicates: number;
70
+ }
71
+ interface ReplayConfig<T, Cursor = unknown> {
72
+ startSlot: Slot;
73
+ safetyMargin: bigint;
74
+ fetchBackfill: BackfillFetcher<T, Cursor>;
75
+ subscribeLive: LiveSubscriber<T>;
76
+ extractSlot: (item: T) => Slot;
77
+ extractKey?: (item: T) => string;
78
+ logger?: ReplayLogger;
79
+ resubscribeOnEnd?: boolean;
80
+ }
81
+
82
+ declare class ReplayStream<T, Cursor = unknown> implements AsyncIterable<T> {
83
+ private readonly config;
84
+ private readonly logger;
85
+ private readonly metrics;
86
+ constructor(config: ReplayConfig<T, Cursor>);
87
+ getMetrics(): ReplayMetrics;
88
+ [Symbol.asyncIterator](): AsyncIterator<T>;
89
+ private run;
90
+ }
91
+
92
+ interface BlockReplayOptions {
93
+ client: BlockSource;
94
+ startSlot: Slot;
95
+ safetyMargin?: bigint;
96
+ pageSize?: number;
97
+ filter?: Filter;
98
+ view?: BlockView;
99
+ minConsensus?: ConsensusStatus;
100
+ logger?: ReplayLogger;
101
+ resubscribeOnEnd?: boolean;
102
+ }
103
+ declare function createBlockReplay(options: BlockReplayOptions): ReplayStream<Block, string>;
104
+
105
+ interface TransactionReplayOptions {
106
+ client: TransactionSource;
107
+ startSlot: Slot;
108
+ safetyMargin?: bigint;
109
+ pageSize?: number;
110
+ filter?: Filter;
111
+ minConsensus?: ConsensusStatus;
112
+ returnEvents?: boolean;
113
+ logger?: ReplayLogger;
114
+ resubscribeOnEnd?: boolean;
115
+ }
116
+ declare function createTransactionReplay(options: TransactionReplayOptions): ReplayStream<Transaction, string>;
117
+
118
+ interface EventReplayOptions {
119
+ client: EventSource;
120
+ startSlot: Slot;
121
+ safetyMargin?: bigint;
122
+ pageSize?: number;
123
+ filter?: Filter;
124
+ logger?: ReplayLogger;
125
+ resubscribeOnEnd?: boolean;
126
+ }
127
+ declare function createEventReplay(options: EventReplayOptions): ReplayStream<Event, string>;
128
+
129
+ /**
130
+ * Account Replay - streaming account state with backfill reconciliation.
131
+ *
132
+ * This module provides account state streaming with:
133
+ * - Page assembly for large accounts (>4KB)
134
+ * - Sequence number tracking for ordering
135
+ * - Backfill reconciliation with live updates
136
+ * - Block boundary detection via BlockFinished messages
137
+ */
138
+
139
+ /**
140
+ * Represents a complete account state ready for processing
141
+ */
142
+ interface AccountState {
143
+ /** Account address as bytes */
144
+ address: Uint8Array;
145
+ /** Account address as hex string */
146
+ addressHex: string;
147
+ /** Slot at which this state was observed */
148
+ slot: bigint;
149
+ /** Sequence number for ordering updates */
150
+ seq: bigint;
151
+ /** Account metadata */
152
+ meta: AccountMeta;
153
+ /** Complete account data (assembled from pages) */
154
+ data: Uint8Array;
155
+ /** Whether this is a deletion */
156
+ isDelete: boolean;
157
+ /** Source: "snapshot" for initial state, "update" for live updates */
158
+ source: "snapshot" | "update";
159
+ }
160
+ /**
161
+ * Block finished event for detecting transaction boundaries
162
+ */
163
+ interface BlockFinishedEvent {
164
+ slot: bigint;
165
+ }
166
+ /**
167
+ * Union type for account replay events
168
+ */
169
+ type AccountReplayEvent = {
170
+ type: "account";
171
+ account: AccountState;
172
+ } | {
173
+ type: "blockFinished";
174
+ block: BlockFinishedEvent;
175
+ };
176
+ /**
177
+ * Options for account replay (single address)
178
+ */
179
+ interface AccountReplayOptions {
180
+ /** Account source (typically ChainClient) */
181
+ client: AccountSource;
182
+ /** Account address to stream updates for */
183
+ address: Uint8Array;
184
+ /** Account view (default: FULL for complete data) */
185
+ view?: AccountView;
186
+ /** Optional filter expression */
187
+ filter?: {
188
+ expression: string;
189
+ params?: {
190
+ [key: string]: {
191
+ kind: {
192
+ case: string;
193
+ value: unknown;
194
+ };
195
+ };
196
+ };
197
+ };
198
+ /** Page assembler options */
199
+ pageAssemblerOptions?: {
200
+ assemblyTimeout?: number;
201
+ maxPendingPerAddress?: number;
202
+ };
203
+ /** Cleanup interval for page assembler (default: 10000ms) */
204
+ cleanupInterval?: number;
205
+ }
206
+ /**
207
+ * Options for streaming all accounts owned by a specific program
208
+ */
209
+ interface AccountsByOwnerReplayOptions {
210
+ /** Account source (typically ChainClient) */
211
+ client: AccountSource;
212
+ /** Program owner address - streams all accounts owned by this program */
213
+ owner: Uint8Array;
214
+ /** Account view (default: FULL for complete data) */
215
+ view?: AccountView;
216
+ /** Optional data sizes to filter (e.g., [73, 115] for TokenAccount and MintAccount) */
217
+ dataSizes?: number[];
218
+ /** Optional additional filter expression */
219
+ filter?: {
220
+ expression: string;
221
+ params?: {
222
+ [key: string]: {
223
+ kind: {
224
+ case: string;
225
+ value: unknown;
226
+ };
227
+ };
228
+ };
229
+ };
230
+ /** Page assembler options */
231
+ pageAssemblerOptions?: {
232
+ assemblyTimeout?: number;
233
+ maxPendingPerAddress?: number;
234
+ };
235
+ /** Cleanup interval for page assembler (default: 10000ms) */
236
+ cleanupInterval?: number;
237
+ }
238
+ /**
239
+ * Create an async iterable for account replay with page assembly.
240
+ *
241
+ * This function streams account updates with proper page assembly for
242
+ * large accounts. It handles:
243
+ * - Initial snapshots
244
+ * - Live updates with page assembly
245
+ * - Block finished signals
246
+ *
247
+ * @param options - Account replay options
248
+ * @returns Async iterable of account replay events
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * const client = new ChainClient({ baseUrl: "https://grpc.thru.org" });
253
+ *
254
+ * for await (const event of createAccountReplay({
255
+ * client,
256
+ * address: accountPubkey,
257
+ * view: AccountView.FULL,
258
+ * })) {
259
+ * if (event.type === "account") {
260
+ * console.log("Account update:", event.account.seq);
261
+ * } else if (event.type === "blockFinished") {
262
+ * console.log("Block finished:", event.block.slot);
263
+ * }
264
+ * }
265
+ * ```
266
+ */
267
+ declare function createAccountReplay(options: AccountReplayOptions): AsyncGenerator<AccountReplayEvent, void, undefined>;
268
+ /**
269
+ * Create an async iterable for streaming all accounts owned by a program.
270
+ *
271
+ * This function streams account updates for ALL accounts owned by a specific
272
+ * program (identified by owner). It handles:
273
+ * - Initial snapshots for each account
274
+ * - Live updates with page assembly
275
+ * - Block finished signals
276
+ * - Optional filtering by data size (useful for specific account types)
277
+ *
278
+ * @param options - Accounts by owner replay options
279
+ * @returns Async iterable of account replay events
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const client = new ChainClient({ baseUrl: "https://grpc.thru.org" });
284
+ *
285
+ * // Stream all token accounts (73 bytes) and mint accounts (115 bytes)
286
+ * for await (const event of createAccountsByOwnerReplay({
287
+ * client,
288
+ * owner: TOKEN_PROGRAM_ID,
289
+ * dataSizes: [73, 115],
290
+ * view: AccountView.FULL,
291
+ * })) {
292
+ * if (event.type === "account") {
293
+ * console.log("Account:", event.account.addressHex, "seq:", event.account.seq);
294
+ * } else if (event.type === "blockFinished") {
295
+ * console.log("Block finished:", event.block.slot);
296
+ * }
297
+ * }
298
+ * ```
299
+ */
300
+ declare function createAccountsByOwnerReplay(options: AccountsByOwnerReplayOptions): AsyncGenerator<AccountReplayEvent, void, undefined>;
301
+ /**
302
+ * State tracker for account sequence numbers.
303
+ *
304
+ * Used to track the highest sequence number seen per account address
305
+ * to ensure updates are applied in order.
306
+ */
307
+ declare class AccountSeqTracker {
308
+ private seqs;
309
+ /**
310
+ * Get the current sequence number for an address
311
+ */
312
+ getSeq(addressHex: string): bigint | undefined;
313
+ /**
314
+ * Check if an update should be applied (seq > current)
315
+ */
316
+ shouldApply(addressHex: string, seq: bigint): boolean;
317
+ /**
318
+ * Update the sequence number for an address
319
+ * Only updates if new seq is greater than current
320
+ */
321
+ update(addressHex: string, seq: bigint): boolean;
322
+ /**
323
+ * Remove tracking for an address
324
+ */
325
+ remove(addressHex: string): void;
326
+ /**
327
+ * Clear all tracking
328
+ */
329
+ clear(): void;
330
+ /**
331
+ * Get count of tracked addresses
332
+ */
333
+ size(): number;
334
+ }
335
+ /**
336
+ * Multi-account replay manager for streaming updates from multiple accounts.
337
+ *
338
+ * This class manages multiple account streams and provides:
339
+ * - Unified event stream from multiple accounts
340
+ * - Sequence number tracking per account
341
+ * - Automatic reconnection on errors
342
+ */
343
+ declare class MultiAccountReplay {
344
+ private client;
345
+ private view;
346
+ private seqTracker;
347
+ private activeStreams;
348
+ constructor(options: {
349
+ client: AccountSource;
350
+ view?: AccountView;
351
+ });
352
+ /**
353
+ * Add an account to stream updates for
354
+ */
355
+ addAccount(address: Uint8Array): AsyncGenerator<AccountReplayEvent>;
356
+ /**
357
+ * Remove an account from streaming
358
+ */
359
+ removeAccount(address: Uint8Array): void;
360
+ /**
361
+ * Get the current sequence number for an account
362
+ */
363
+ getSeq(addressHex: string): bigint | undefined;
364
+ /**
365
+ * Stop all streams
366
+ */
367
+ stop(): void;
368
+ }
369
+
370
+ /**
371
+ * Page Assembler for multi-page account updates.
372
+ *
373
+ * Large accounts are split into multiple AccountPage messages (4KB chunks).
374
+ * This module buffers pages and emits complete account data when all pages
375
+ * for a given sequence number are received.
376
+ */
377
+
378
+ /** Standard page size for account data (4KB) */
379
+ declare const PAGE_SIZE = 4096;
380
+ /**
381
+ * Assembled account data ready for processing
382
+ */
383
+ interface AssembledAccount {
384
+ address: Uint8Array;
385
+ slot: bigint;
386
+ seq: bigint;
387
+ meta: AccountMeta;
388
+ data: Uint8Array;
389
+ isDelete: boolean;
390
+ }
391
+ /**
392
+ * Options for the PageAssembler
393
+ */
394
+ interface PageAssemblerOptions {
395
+ /**
396
+ * Timeout in milliseconds for incomplete page assemblies.
397
+ * After this duration, incomplete assemblies are discarded.
398
+ * Default: 30000 (30 seconds)
399
+ */
400
+ assemblyTimeout?: number;
401
+ /**
402
+ * Maximum number of pending assemblies per address.
403
+ * Older assemblies are evicted when limit is exceeded.
404
+ * Default: 10
405
+ */
406
+ maxPendingPerAddress?: number;
407
+ }
408
+ /**
409
+ * Assembles multi-page account updates into complete account data.
410
+ *
411
+ * Usage:
412
+ * ```typescript
413
+ * const assembler = new PageAssembler();
414
+ *
415
+ * for await (const response of client.streamAccountUpdates(request)) {
416
+ * if (response.message.case === "update") {
417
+ * const assembled = assembler.processUpdate(address, response.message.value);
418
+ * if (assembled) {
419
+ * // Complete account data is ready
420
+ * console.log("Assembled account:", assembled);
421
+ * }
422
+ * }
423
+ * }
424
+ * ```
425
+ */
426
+ declare class PageAssembler {
427
+ private readonly assemblyTimeout;
428
+ private readonly maxPendingPerAddress;
429
+ /**
430
+ * Pending updates keyed by address (hex) -> seq (string) -> PendingUpdate
431
+ */
432
+ private pending;
433
+ constructor(options?: PageAssemblerOptions);
434
+ /**
435
+ * Process an account update and return assembled account if complete.
436
+ *
437
+ * @param address - Account address bytes
438
+ * @param update - Account update from streaming response
439
+ * @returns Assembled account if all pages received, null otherwise
440
+ */
441
+ processUpdate(address: Uint8Array, update: AccountUpdate): AssembledAccount | null;
442
+ /**
443
+ * Assemble complete data from buffered pages
444
+ */
445
+ private assemblePages;
446
+ /**
447
+ * Evict old pending updates for an address if limit exceeded
448
+ */
449
+ private evictOldPending;
450
+ /**
451
+ * Clean up expired pending assemblies.
452
+ * Call this periodically to prevent memory leaks.
453
+ */
454
+ cleanup(): number;
455
+ /**
456
+ * Get current pending count for debugging
457
+ */
458
+ getPendingCount(): number;
459
+ /**
460
+ * Clear all pending assemblies
461
+ */
462
+ clear(): void;
463
+ }
464
+
465
+ declare const NOOP_LOGGER: ReplayLogger;
466
+ declare function createConsoleLogger(prefix?: string): ReplayLogger;
467
+
468
+ interface ReplaySinkContext {
469
+ slot: Slot;
470
+ phase: "backfill" | "live";
471
+ }
472
+ interface ReplaySinkMeta {
473
+ stream?: string;
474
+ label?: string;
475
+ }
476
+ interface ReplaySink<T> {
477
+ open?(meta?: ReplaySinkMeta): Promise<void> | void;
478
+ write(item: T, ctx: ReplaySinkContext): Promise<void> | void;
479
+ close?(err?: unknown): Promise<void> | void;
480
+ }
481
+
482
+ declare class ConsoleSink<T> implements ReplaySink<T> {
483
+ private readonly prefix;
484
+ constructor(prefix?: string);
485
+ open(meta?: ReplaySinkMeta): void;
486
+ write(item: T, ctx: ReplaySinkContext): void;
487
+ close(err?: unknown): void;
488
+ }
489
+
490
+ export { type AccountReplayEvent, type AccountReplayOptions, AccountSeqTracker, type AccountSource, type AccountState, type AccountsByOwnerReplayOptions, type AssembledAccount, type BlockFinishedEvent, type BlockReplayOptions, type BlockSource, ChainClient, type ChainClientOptions, ConsoleSink, type EventReplayOptions, type EventSource, MultiAccountReplay, NOOP_LOGGER, PAGE_SIZE, PageAssembler, type PageAssemblerOptions, type ReplayConfig, type ReplayDataSource, type ReplayLogger, type ReplayMetrics, type ReplaySink, type ReplaySinkContext, type ReplaySinkMeta, ReplayStream, type Slot, type TransactionReplayOptions, type TransactionSource, createAccountReplay, createAccountsByOwnerReplay, createBlockReplay, createConsoleLogger, createEventReplay, createTransactionReplay };