@thru/replay 0.2.21 → 0.2.23
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.
- package/dist/index.cjs +389 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -4
- package/dist/index.d.ts +33 -4
- package/dist/index.mjs +391 -175
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -12
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
import { create } from '@bufbuild/protobuf';
|
|
2
2
|
import { createClient } from '@connectrpc/connect';
|
|
3
|
-
import { createGrpcTransport } from '@connectrpc/connect-node';
|
|
4
|
-
import { QueryService, StreamingService, GetAccountRequestSchema, ListAccountsRequestSchema, ListBlocksRequestSchema, StreamBlocksRequestSchema, ListTransactionsRequestSchema, StreamTransactionsRequestSchema, ListEventsRequestSchema, StreamEventsRequestSchema, StreamAccountUpdatesRequestSchema, GetHeightRequestSchema, AccountView, PageRequestSchema, PubkeySchema, FilterParamValueSchema, FilterSchema, EventSchema } from '@thru/proto';
|
|
5
|
-
export { AccountView, FilterParamValueSchema, FilterSchema } from '@thru/proto';
|
|
3
|
+
import { Http2SessionManager, createGrpcTransport } from '@connectrpc/connect-node';
|
|
4
|
+
import { QueryService, StreamingService, GetAccountRequestSchema, ListAccountsRequestSchema, ListBlocksRequestSchema, StreamBlocksRequestSchema, ListTransactionsRequestSchema, StreamTransactionsRequestSchema, ListEventsRequestSchema, StreamEventsRequestSchema, StreamAccountUpdatesRequestSchema, GetHeightRequestSchema, GetChainInfoRequestSchema, AccountView, PageRequestSchema, PubkeySchema, FilterParamValueSchema, FilterSchema, EventSchema } from '@thru/sdk/proto';
|
|
5
|
+
export { AccountView, FilterParamValueSchema, FilterSchema } from '@thru/sdk/proto';
|
|
6
6
|
|
|
7
7
|
// src/chain-client.ts
|
|
8
8
|
var ChainClient = class {
|
|
9
9
|
constructor(options) {
|
|
10
10
|
this.options = options;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (options.transport) {
|
|
12
|
+
this.sessionManager = null;
|
|
13
|
+
this.query = createClient(QueryService, options.transport);
|
|
14
|
+
this.streaming = createClient(StreamingService, options.transport);
|
|
15
|
+
} else {
|
|
16
|
+
const { transport, sessionManager } = this.createOwnedTransport();
|
|
17
|
+
this.sessionManager = sessionManager;
|
|
18
|
+
this.query = createClient(QueryService, transport);
|
|
19
|
+
this.streaming = createClient(StreamingService, transport);
|
|
20
|
+
}
|
|
14
21
|
this.callOptions = options.callOptions;
|
|
15
22
|
}
|
|
23
|
+
options;
|
|
16
24
|
query;
|
|
17
25
|
streaming;
|
|
18
26
|
callOptions;
|
|
27
|
+
/**
|
|
28
|
+
* The HTTP/2 session manager owned by this client. Only set when the client
|
|
29
|
+
* created its own gRPC transport (i.e., `options.transport` was not provided).
|
|
30
|
+
* `close()` uses this to tear down the underlying persistent connection.
|
|
31
|
+
*/
|
|
32
|
+
sessionManager;
|
|
33
|
+
closed = false;
|
|
34
|
+
/**
|
|
35
|
+
* Close the underlying HTTP/2 session, if this client owns one. Idempotent.
|
|
36
|
+
*
|
|
37
|
+
* Callers are responsible for ensuring that no in-flight RPCs or streams
|
|
38
|
+
* are still being awaited on this client — pending requests will fail.
|
|
39
|
+
*
|
|
40
|
+
* If the client was constructed with an externally-supplied `transport`,
|
|
41
|
+
* `close()` is a no-op; the caller owns the transport's lifecycle.
|
|
42
|
+
*/
|
|
43
|
+
close() {
|
|
44
|
+
if (this.closed) return;
|
|
45
|
+
this.closed = true;
|
|
46
|
+
this.sessionManager?.abort();
|
|
47
|
+
}
|
|
19
48
|
getAccount(request) {
|
|
20
49
|
return this.query.getAccount(create(GetAccountRequestSchema, request), this.callOptions);
|
|
21
50
|
}
|
|
@@ -40,7 +69,7 @@ var ChainClient = class {
|
|
|
40
69
|
streamEvents(request) {
|
|
41
70
|
return this.streaming.streamEvents(create(StreamEventsRequestSchema, request), this.callOptions);
|
|
42
71
|
}
|
|
43
|
-
|
|
72
|
+
createOwnedTransport() {
|
|
44
73
|
if (!this.options.baseUrl) {
|
|
45
74
|
throw new Error("ChainClient requires baseUrl when no transport is provided");
|
|
46
75
|
}
|
|
@@ -50,15 +79,19 @@ var ChainClient = class {
|
|
|
50
79
|
...userInterceptors,
|
|
51
80
|
...headerInterceptor ? [headerInterceptor] : []
|
|
52
81
|
];
|
|
53
|
-
|
|
54
|
-
baseUrl: this.options.baseUrl,
|
|
55
|
-
useBinaryFormat: this.options.useBinaryFormat ?? true,
|
|
56
|
-
interceptors: mergedInterceptors.length ? mergedInterceptors : void 0,
|
|
82
|
+
const sessionManager = new Http2SessionManager(this.options.baseUrl, {
|
|
57
83
|
pingIntervalMs: 3e4,
|
|
58
84
|
pingIdleConnection: true,
|
|
59
85
|
pingTimeoutMs: 1e4,
|
|
60
86
|
idleConnectionTimeoutMs: 0
|
|
61
87
|
});
|
|
88
|
+
const transport = createGrpcTransport({
|
|
89
|
+
baseUrl: this.options.baseUrl,
|
|
90
|
+
useBinaryFormat: this.options.useBinaryFormat ?? true,
|
|
91
|
+
interceptors: mergedInterceptors.length ? mergedInterceptors : void 0,
|
|
92
|
+
sessionManager
|
|
93
|
+
});
|
|
94
|
+
return { transport, sessionManager };
|
|
62
95
|
}
|
|
63
96
|
createHeaderInterceptor() {
|
|
64
97
|
const headers = {};
|
|
@@ -76,6 +109,9 @@ var ChainClient = class {
|
|
|
76
109
|
getHeight() {
|
|
77
110
|
return this.query.getHeight(create(GetHeightRequestSchema, {}), this.callOptions);
|
|
78
111
|
}
|
|
112
|
+
getChainInfo(request = {}) {
|
|
113
|
+
return this.query.getChainInfo(create(GetChainInfoRequestSchema, request), this.callOptions);
|
|
114
|
+
}
|
|
79
115
|
};
|
|
80
116
|
|
|
81
117
|
// src/async-queue.ts
|
|
@@ -238,15 +274,16 @@ var LivePump = class {
|
|
|
238
274
|
buffer;
|
|
239
275
|
slotOf;
|
|
240
276
|
keyOf;
|
|
241
|
-
|
|
277
|
+
sourceIterator;
|
|
242
278
|
logger;
|
|
243
279
|
mode;
|
|
244
280
|
minSlotSeen = null;
|
|
245
281
|
maxSlotSeen = null;
|
|
246
282
|
minEmitSlot = null;
|
|
247
283
|
pumpPromise;
|
|
284
|
+
closing = false;
|
|
248
285
|
constructor(options) {
|
|
249
|
-
this.
|
|
286
|
+
this.sourceIterator = options.source[Symbol.asyncIterator]();
|
|
250
287
|
this.slotOf = options.slotOf;
|
|
251
288
|
this.keyOf = options.keyOf ?? ((item) => options.slotOf(item).toString());
|
|
252
289
|
this.logger = options.logger ?? NOOP_LOGGER;
|
|
@@ -290,12 +327,19 @@ var LivePump = class {
|
|
|
290
327
|
return this.queue.next();
|
|
291
328
|
}
|
|
292
329
|
async close() {
|
|
330
|
+
this.closing = true;
|
|
293
331
|
this.queue.close();
|
|
294
|
-
await
|
|
332
|
+
await Promise.allSettled([
|
|
333
|
+
this.closeSourceIterator(),
|
|
334
|
+
this.pumpPromise
|
|
335
|
+
]);
|
|
295
336
|
}
|
|
296
337
|
async start() {
|
|
297
338
|
try {
|
|
298
|
-
|
|
339
|
+
while (!this.closing) {
|
|
340
|
+
const next = await this.sourceIterator.next();
|
|
341
|
+
if (next.done || this.closing) break;
|
|
342
|
+
const item = next.value;
|
|
299
343
|
const slot = this.slotOf(item);
|
|
300
344
|
if (this.minSlotSeen === null || slot < this.minSlotSeen) this.minSlotSeen = slot;
|
|
301
345
|
if (this.maxSlotSeen === null || slot > this.maxSlotSeen) this.maxSlotSeen = slot;
|
|
@@ -305,9 +349,21 @@ var LivePump = class {
|
|
|
305
349
|
this.queue.push(item);
|
|
306
350
|
}
|
|
307
351
|
}
|
|
308
|
-
this.queue.close();
|
|
309
352
|
} catch (err) {
|
|
310
|
-
this.
|
|
353
|
+
if (!this.closing) {
|
|
354
|
+
this.queue.fail(err);
|
|
355
|
+
}
|
|
356
|
+
} finally {
|
|
357
|
+
this.queue.close();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async closeSourceIterator() {
|
|
361
|
+
if (typeof this.sourceIterator.return !== "function") {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
await this.sourceIterator.return();
|
|
366
|
+
} catch {
|
|
311
367
|
}
|
|
312
368
|
}
|
|
313
369
|
};
|
|
@@ -327,6 +383,7 @@ function withTimeout(promise, timeoutMs) {
|
|
|
327
383
|
const timer = setTimeout(() => {
|
|
328
384
|
reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`));
|
|
329
385
|
}, timeoutMs);
|
|
386
|
+
timer.unref?.();
|
|
330
387
|
promise.then((value) => {
|
|
331
388
|
clearTimeout(timer);
|
|
332
389
|
resolve(value);
|
|
@@ -345,6 +402,37 @@ var TimeoutError = class extends Error {
|
|
|
345
402
|
function delay(ms) {
|
|
346
403
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
347
404
|
}
|
|
405
|
+
function abortableDelay(ms, signal) {
|
|
406
|
+
if (ms <= 0 || signal?.aborted) {
|
|
407
|
+
return Promise.resolve();
|
|
408
|
+
}
|
|
409
|
+
const abortSignal = signal;
|
|
410
|
+
return new Promise((resolve) => {
|
|
411
|
+
if (!abortSignal) {
|
|
412
|
+
const timer2 = setTimeout(resolve, ms);
|
|
413
|
+
timer2.unref?.();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const onAbort = () => {
|
|
417
|
+
clearTimeout(timer);
|
|
418
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
419
|
+
resolve();
|
|
420
|
+
};
|
|
421
|
+
const timer = setTimeout(() => {
|
|
422
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
423
|
+
resolve();
|
|
424
|
+
}, ms);
|
|
425
|
+
timer.unref?.();
|
|
426
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function isAbortError(err) {
|
|
430
|
+
if (!err || typeof err !== "object") return false;
|
|
431
|
+
const name = typeof err.name === "string" ? err.name.toLowerCase() : "";
|
|
432
|
+
const message = typeof err.message === "string" ? err.message.toLowerCase() : "";
|
|
433
|
+
const code = String(err.code ?? "").toLowerCase();
|
|
434
|
+
return name.includes("abort") || name.includes("cancel") || code === "canceled" || code === "cancelled" || message.includes("aborted") || message.includes("canceled") || message.includes("cancelled");
|
|
435
|
+
}
|
|
348
436
|
|
|
349
437
|
// src/replay-stream.ts
|
|
350
438
|
var DEFAULT_METRICS = {
|
|
@@ -402,12 +490,17 @@ var ReplayStream = class {
|
|
|
402
490
|
extractKey,
|
|
403
491
|
safetyMargin,
|
|
404
492
|
resubscribeOnEnd,
|
|
405
|
-
onReconnect
|
|
493
|
+
onReconnect,
|
|
494
|
+
signal,
|
|
495
|
+
dispose
|
|
406
496
|
} = this.config;
|
|
407
497
|
const shouldResubscribeOnEnd = resubscribeOnEnd ?? true;
|
|
408
498
|
const keyOf = extractKey ?? ((item) => extractSlot(item).toString());
|
|
499
|
+
const shouldStop = (err) => signal?.aborted === true || isAbortError(err);
|
|
409
500
|
let currentSubscribeLive = subscribeLive;
|
|
410
501
|
let currentFetchBackfill = fetchBackfill;
|
|
502
|
+
let currentDispose = dispose ?? (() => {
|
|
503
|
+
});
|
|
411
504
|
const createLivePump = (slot, startStreaming = false, emitFloor) => new LivePump({
|
|
412
505
|
source: currentSubscribeLive(slot),
|
|
413
506
|
slotOf: extractSlot,
|
|
@@ -464,92 +557,78 @@ var ReplayStream = class {
|
|
|
464
557
|
};
|
|
465
558
|
let emptyPageRetries = 0;
|
|
466
559
|
const MAX_EMPTY_PAGE_RETRIES = 10;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
560
|
+
try {
|
|
561
|
+
while (!backfillDone) {
|
|
562
|
+
if (shouldStop()) return;
|
|
563
|
+
let page;
|
|
564
|
+
try {
|
|
565
|
+
page = await currentFetchBackfill({ startSlot, cursor });
|
|
566
|
+
} catch (err) {
|
|
567
|
+
if (shouldStop(err)) return;
|
|
568
|
+
throw err;
|
|
569
|
+
}
|
|
570
|
+
if (!page.items.length && !page.cursor && !page.done) {
|
|
571
|
+
emptyPageRetries++;
|
|
572
|
+
if (emptyPageRetries > MAX_EMPTY_PAGE_RETRIES) {
|
|
573
|
+
this.logger.error(
|
|
574
|
+
`backfill returned ${MAX_EMPTY_PAGE_RETRIES} consecutive empty pages; treating as done`
|
|
575
|
+
);
|
|
576
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
577
|
+
yield item;
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
const backoffMs = calculateBackoff(emptyPageRetries - 1, DEFAULT_RETRY_CONFIG);
|
|
582
|
+
this.logger.warn(
|
|
583
|
+
`empty backfill page without cursor; retrying in ${backoffMs}ms (${emptyPageRetries}/${MAX_EMPTY_PAGE_RETRIES})`
|
|
474
584
|
);
|
|
585
|
+
await abortableDelay(backoffMs, signal);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
emptyPageRetries = 0;
|
|
589
|
+
assertBackfillPageOrder(pendingOrderedPage, page.items, extractSlot);
|
|
590
|
+
if (pendingOrderedPage !== null) {
|
|
475
591
|
for await (const item of flushPendingBackfill(this)) {
|
|
592
|
+
if (shouldStop()) return;
|
|
476
593
|
yield item;
|
|
477
594
|
}
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
const backoffMs = calculateBackoff(emptyPageRetries - 1, DEFAULT_RETRY_CONFIG);
|
|
481
|
-
this.logger.warn(
|
|
482
|
-
`empty backfill page without cursor; retrying in ${backoffMs}ms (${emptyPageRetries}/${MAX_EMPTY_PAGE_RETRIES})`
|
|
483
|
-
);
|
|
484
|
-
await delay(backoffMs);
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
emptyPageRetries = 0;
|
|
488
|
-
assertBackfillPageOrder(pendingOrderedPage, page.items, extractSlot);
|
|
489
|
-
if (pendingOrderedPage !== null) {
|
|
490
|
-
for await (const item of flushPendingBackfill(this)) {
|
|
491
|
-
yield item;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
pendingOrderedPage = [...page.items];
|
|
495
|
-
const reachedEnd = page.done || page.cursor === void 0;
|
|
496
|
-
if (reachedEnd) {
|
|
497
|
-
for await (const item of flushPendingBackfill(this)) {
|
|
498
|
-
yield item;
|
|
499
595
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
cursor = page.cursor;
|
|
504
|
-
const maxStreamSlot = livePump.maxSlot();
|
|
505
|
-
if (maxStreamSlot !== null) {
|
|
506
|
-
const catchUpSlot = maxStreamSlot > safetyMargin ? maxStreamSlot - safetyMargin : 0n;
|
|
507
|
-
if (currentSlot >= catchUpSlot) {
|
|
596
|
+
pendingOrderedPage = [...page.items];
|
|
597
|
+
const reachedEnd = page.done || page.cursor === void 0;
|
|
598
|
+
if (reachedEnd) {
|
|
508
599
|
for await (const item of flushPendingBackfill(this)) {
|
|
600
|
+
if (shouldStop()) return;
|
|
509
601
|
yield item;
|
|
510
602
|
}
|
|
511
|
-
this.logger.info(
|
|
512
|
-
`replay reached SWITCHING threshold (currentSlot=${currentSlot}, maxStreamSlot=${maxStreamSlot}, catchUpSlot=${catchUpSlot})`
|
|
513
|
-
);
|
|
514
|
-
backfillDone = true;
|
|
515
603
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
this.metrics.emittedLive += 1;
|
|
533
|
-
yield item;
|
|
534
|
-
livePump.updateEmitFloor(currentSlot);
|
|
535
|
-
}
|
|
536
|
-
if (!drained.length) livePump.updateEmitFloor(currentSlot);
|
|
537
|
-
this.logger.info("replay entering STREAMING state");
|
|
538
|
-
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
539
|
-
let retryAttempt = 0;
|
|
540
|
-
while (true) {
|
|
541
|
-
try {
|
|
542
|
-
const next = await withTimeout(
|
|
543
|
-
livePump.next(),
|
|
544
|
-
retryConfig.connectionTimeoutMs
|
|
545
|
-
);
|
|
546
|
-
retryAttempt = 0;
|
|
547
|
-
if (next.done) {
|
|
548
|
-
if (!shouldResubscribeOnEnd) break;
|
|
549
|
-
throw new Error("stream ended");
|
|
604
|
+
const duplicatesTrimmed = livePump.discardBufferedUpTo(currentSlot);
|
|
605
|
+
this.metrics.discardedDuplicates += duplicatesTrimmed;
|
|
606
|
+
cursor = page.cursor;
|
|
607
|
+
const maxStreamSlot = livePump.maxSlot();
|
|
608
|
+
if (maxStreamSlot !== null) {
|
|
609
|
+
const catchUpSlot = maxStreamSlot > safetyMargin ? maxStreamSlot - safetyMargin : 0n;
|
|
610
|
+
if (currentSlot >= catchUpSlot) {
|
|
611
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
612
|
+
if (shouldStop()) return;
|
|
613
|
+
yield item;
|
|
614
|
+
}
|
|
615
|
+
this.logger.info(
|
|
616
|
+
`replay reached SWITCHING threshold (currentSlot=${currentSlot}, maxStreamSlot=${maxStreamSlot}, catchUpSlot=${catchUpSlot})`
|
|
617
|
+
);
|
|
618
|
+
backfillDone = true;
|
|
619
|
+
}
|
|
550
620
|
}
|
|
551
|
-
|
|
552
|
-
|
|
621
|
+
if (reachedEnd) backfillDone = true;
|
|
622
|
+
}
|
|
623
|
+
if (shouldStop()) return;
|
|
624
|
+
this.logger.info(`replay entering SWITCHING state (currentSlot=${currentSlot})`);
|
|
625
|
+
const { drained, discarded } = livePump.enableStreaming(currentSlot);
|
|
626
|
+
this.metrics.bufferedItems = drained.length;
|
|
627
|
+
this.metrics.discardedDuplicates += discarded;
|
|
628
|
+
for (const item of drained) {
|
|
629
|
+
if (shouldStop()) return;
|
|
630
|
+
const slot = extractSlot(item);
|
|
631
|
+
const key = keyOf(item);
|
|
553
632
|
if (seenItem(slot, key)) {
|
|
554
633
|
this.metrics.discardedDuplicates += 1;
|
|
555
634
|
continue;
|
|
@@ -557,57 +636,97 @@ var ReplayStream = class {
|
|
|
557
636
|
currentSlot = slot;
|
|
558
637
|
recordEmission(slot, key);
|
|
559
638
|
this.metrics.emittedLive += 1;
|
|
560
|
-
yield
|
|
639
|
+
yield item;
|
|
561
640
|
livePump.updateEmitFloor(currentSlot);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
641
|
+
}
|
|
642
|
+
if (!drained.length) livePump.updateEmitFloor(currentSlot);
|
|
643
|
+
this.logger.info("replay entering STREAMING state");
|
|
644
|
+
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
645
|
+
let retryAttempt = 0;
|
|
646
|
+
while (true) {
|
|
647
|
+
if (shouldStop()) return;
|
|
648
|
+
try {
|
|
649
|
+
const next = await withTimeout(
|
|
650
|
+
livePump.next(),
|
|
651
|
+
retryConfig.connectionTimeoutMs
|
|
652
|
+
);
|
|
653
|
+
retryAttempt = 0;
|
|
654
|
+
if (next.done) {
|
|
655
|
+
if (shouldStop()) return;
|
|
656
|
+
if (!shouldResubscribeOnEnd) break;
|
|
657
|
+
throw new Error("stream ended");
|
|
658
|
+
}
|
|
659
|
+
const slot = extractSlot(next.value);
|
|
660
|
+
const key = keyOf(next.value);
|
|
661
|
+
if (seenItem(slot, key)) {
|
|
662
|
+
this.metrics.discardedDuplicates += 1;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
currentSlot = slot;
|
|
666
|
+
recordEmission(slot, key);
|
|
667
|
+
this.metrics.emittedLive += 1;
|
|
668
|
+
yield next.value;
|
|
669
|
+
livePump.updateEmitFloor(currentSlot);
|
|
670
|
+
} catch (err) {
|
|
671
|
+
if (shouldStop(err)) return;
|
|
672
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
673
|
+
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
674
|
+
this.logger.warn(
|
|
675
|
+
`live stream disconnected (${errMsg}); reconnecting in ${backoffMs}ms from slot ${currentSlot} (attempt ${retryAttempt + 1})`
|
|
676
|
+
);
|
|
677
|
+
await abortableDelay(backoffMs, signal);
|
|
678
|
+
if (shouldStop()) return;
|
|
679
|
+
currentDispose();
|
|
680
|
+
await safeClose(livePump);
|
|
681
|
+
retryAttempt++;
|
|
682
|
+
if (onReconnect) {
|
|
683
|
+
try {
|
|
684
|
+
const fresh = onReconnect();
|
|
685
|
+
currentSubscribeLive = fresh.subscribeLive;
|
|
686
|
+
if (fresh.fetchBackfill) {
|
|
687
|
+
currentFetchBackfill = fresh.fetchBackfill;
|
|
688
|
+
}
|
|
689
|
+
currentDispose = fresh.dispose ?? (() => {
|
|
690
|
+
});
|
|
691
|
+
this.logger.info("created fresh client for reconnection");
|
|
692
|
+
} catch (factoryErr) {
|
|
693
|
+
this.logger.error(
|
|
694
|
+
`failed to create fresh client: ${factoryErr instanceof Error ? factoryErr.message : String(factoryErr)}; using existing`
|
|
695
|
+
);
|
|
577
696
|
}
|
|
578
|
-
this.logger.info("created fresh client for reconnection");
|
|
579
|
-
} catch (factoryErr) {
|
|
580
|
-
this.logger.error(
|
|
581
|
-
`failed to create fresh client: ${factoryErr instanceof Error ? factoryErr.message : String(factoryErr)}; using existing`
|
|
582
|
-
);
|
|
583
697
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
currentSlot
|
|
698
|
+
if (onReconnect && currentSlot > 0n) {
|
|
699
|
+
for await (const item of this.miniBackfill(
|
|
700
|
+
currentSlot,
|
|
701
|
+
currentFetchBackfill,
|
|
702
|
+
extractSlot,
|
|
703
|
+
keyOf,
|
|
704
|
+
seenItem,
|
|
705
|
+
recordEmission,
|
|
706
|
+
signal
|
|
707
|
+
)) {
|
|
708
|
+
if (shouldStop()) return;
|
|
709
|
+
const itemSlot = extractSlot(item);
|
|
710
|
+
if (itemSlot > currentSlot) {
|
|
711
|
+
currentSlot = itemSlot;
|
|
712
|
+
}
|
|
713
|
+
yield item;
|
|
597
714
|
}
|
|
598
|
-
yield item;
|
|
599
715
|
}
|
|
716
|
+
const resumeSlot = currentSlot > 0n ? currentSlot : 0n;
|
|
717
|
+
livePump = createLivePump(resumeSlot, true, currentSlot);
|
|
600
718
|
}
|
|
601
|
-
const resumeSlot = currentSlot > 0n ? currentSlot : 0n;
|
|
602
|
-
livePump = createLivePump(resumeSlot, true, currentSlot);
|
|
603
719
|
}
|
|
720
|
+
} finally {
|
|
721
|
+
currentDispose();
|
|
722
|
+
await safeClose(livePump);
|
|
604
723
|
}
|
|
605
724
|
}
|
|
606
725
|
/**
|
|
607
726
|
* Perform mini-backfill from lastProcessedSlot to catch up after reconnection.
|
|
608
727
|
* Ensures no data gaps from events that occurred during disconnection.
|
|
609
728
|
*/
|
|
610
|
-
async *miniBackfill(fromSlot, fetchBackfill, extractSlot, keyOf, seenItem, recordEmission) {
|
|
729
|
+
async *miniBackfill(fromSlot, fetchBackfill, extractSlot, keyOf, seenItem, recordEmission, signal) {
|
|
611
730
|
this.logger.info(`mini-backfill starting from slot ${fromSlot}`);
|
|
612
731
|
const MINI_BACKFILL_TIMEOUT = 3e4;
|
|
613
732
|
let lastProgressTime = Date.now();
|
|
@@ -615,6 +734,7 @@ var ReplayStream = class {
|
|
|
615
734
|
let itemsYielded = 0;
|
|
616
735
|
try {
|
|
617
736
|
while (true) {
|
|
737
|
+
if (signal?.aborted) return;
|
|
618
738
|
if (Date.now() - lastProgressTime > MINI_BACKFILL_TIMEOUT) {
|
|
619
739
|
this.logger.warn(`mini-backfill timed out after ${MINI_BACKFILL_TIMEOUT}ms with no progress`);
|
|
620
740
|
break;
|
|
@@ -625,6 +745,7 @@ var ReplayStream = class {
|
|
|
625
745
|
);
|
|
626
746
|
let pageYielded = 0;
|
|
627
747
|
for (const item of sorted) {
|
|
748
|
+
if (signal?.aborted) return;
|
|
628
749
|
const slot = extractSlot(item);
|
|
629
750
|
const key = keyOf(item);
|
|
630
751
|
if (seenItem(slot, key)) {
|
|
@@ -643,6 +764,9 @@ var ReplayStream = class {
|
|
|
643
764
|
}
|
|
644
765
|
this.logger.info(`mini-backfill complete: ${itemsYielded} items yielded`);
|
|
645
766
|
} catch (err) {
|
|
767
|
+
if (signal?.aborted || isAbortError(err)) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
646
770
|
this.logger.warn(
|
|
647
771
|
`mini-backfill failed: ${err instanceof Error ? err.message : String(err)}; proceeding with live stream`
|
|
648
772
|
);
|
|
@@ -650,14 +774,22 @@ var ReplayStream = class {
|
|
|
650
774
|
}
|
|
651
775
|
};
|
|
652
776
|
async function safeClose(pump) {
|
|
777
|
+
let timeoutId;
|
|
653
778
|
try {
|
|
779
|
+
const timeout = new Promise((resolve) => {
|
|
780
|
+
timeoutId = setTimeout(() => resolve("timeout"), 5e3);
|
|
781
|
+
});
|
|
654
782
|
const result = await Promise.race([
|
|
655
783
|
pump.close().then(() => "closed"),
|
|
656
|
-
|
|
784
|
+
timeout
|
|
657
785
|
]);
|
|
658
786
|
if (result === "timeout") {
|
|
659
787
|
}
|
|
660
788
|
} catch {
|
|
789
|
+
} finally {
|
|
790
|
+
if (timeoutId !== void 0) {
|
|
791
|
+
clearTimeout(timeoutId);
|
|
792
|
+
}
|
|
661
793
|
}
|
|
662
794
|
}
|
|
663
795
|
function combineFilters(base, user) {
|
|
@@ -753,15 +885,38 @@ function createBlockReplay(options) {
|
|
|
753
885
|
subscribeLive,
|
|
754
886
|
extractSlot: (block) => block.header?.slot ?? 0n,
|
|
755
887
|
logger: options.logger,
|
|
756
|
-
resubscribeOnEnd: options.resubscribeOnEnd
|
|
888
|
+
resubscribeOnEnd: options.resubscribeOnEnd,
|
|
889
|
+
signal: options.signal
|
|
757
890
|
});
|
|
758
891
|
}
|
|
892
|
+
|
|
893
|
+
// src/types.ts
|
|
894
|
+
function resolveClient(opts, optionsName) {
|
|
895
|
+
if (opts.clientFactory) {
|
|
896
|
+
return opts.clientFactory();
|
|
897
|
+
}
|
|
898
|
+
if (!opts.client) {
|
|
899
|
+
throw new Error(`${optionsName} requires either client or clientFactory`);
|
|
900
|
+
}
|
|
901
|
+
return opts.client;
|
|
902
|
+
}
|
|
903
|
+
function closeIfCloseable(value) {
|
|
904
|
+
if (value && typeof value.close === "function") {
|
|
905
|
+
try {
|
|
906
|
+
value.close();
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/replay/transaction-replay.ts
|
|
759
913
|
var DEFAULT_PAGE_SIZE2 = 256;
|
|
760
914
|
var DEFAULT_SAFETY_MARGIN2 = 64n;
|
|
761
915
|
var PAGE_ORDER_ASC2 = "slot asc";
|
|
762
916
|
function createTransactionReplay(options) {
|
|
763
917
|
const safetyMargin = options.safetyMargin ?? DEFAULT_SAFETY_MARGIN2;
|
|
764
|
-
|
|
918
|
+
let currentClient = resolveClient(options, "TransactionReplayOptions");
|
|
919
|
+
const createFetchBackfill = (client) => async ({
|
|
765
920
|
startSlot,
|
|
766
921
|
cursor
|
|
767
922
|
}) => {
|
|
@@ -771,7 +926,7 @@ function createTransactionReplay(options) {
|
|
|
771
926
|
pageToken: cursor
|
|
772
927
|
});
|
|
773
928
|
const mergedFilter = combineFilters(slotLiteralFilter("transaction.slot", startSlot), options.filter);
|
|
774
|
-
const response = await
|
|
929
|
+
const response = await client.listTransactions(
|
|
775
930
|
create(ListTransactionsRequestSchema, {
|
|
776
931
|
filter: mergedFilter,
|
|
777
932
|
page,
|
|
@@ -781,26 +936,38 @@ function createTransactionReplay(options) {
|
|
|
781
936
|
);
|
|
782
937
|
return backfillPage(response.transactions, response.page);
|
|
783
938
|
};
|
|
784
|
-
const
|
|
939
|
+
const createSubscribeLive = (client) => (startSlot) => {
|
|
785
940
|
const mergedFilter = combineFilters(slotLiteralFilter("transaction.slot", startSlot), options.filter);
|
|
786
941
|
const request = create(StreamTransactionsRequestSchema, {
|
|
787
942
|
filter: mergedFilter,
|
|
788
943
|
minConsensus: options.minConsensus
|
|
789
944
|
});
|
|
790
945
|
return mapAsyncIterable(
|
|
791
|
-
|
|
946
|
+
client.streamTransactions(request),
|
|
792
947
|
(resp) => resp.transaction
|
|
793
948
|
);
|
|
794
949
|
};
|
|
950
|
+
const onReconnect = options.clientFactory ? () => {
|
|
951
|
+
const newClient = options.clientFactory();
|
|
952
|
+
currentClient = newClient;
|
|
953
|
+
return {
|
|
954
|
+
subscribeLive: createSubscribeLive(currentClient),
|
|
955
|
+
fetchBackfill: createFetchBackfill(currentClient),
|
|
956
|
+
dispose: () => closeIfCloseable(newClient)
|
|
957
|
+
};
|
|
958
|
+
} : void 0;
|
|
795
959
|
return new ReplayStream({
|
|
796
960
|
startSlot: options.startSlot,
|
|
797
961
|
safetyMargin,
|
|
798
|
-
fetchBackfill,
|
|
799
|
-
subscribeLive,
|
|
962
|
+
fetchBackfill: createFetchBackfill(currentClient),
|
|
963
|
+
subscribeLive: createSubscribeLive(currentClient),
|
|
800
964
|
extractSlot: (tx) => tx.slot ?? 0n,
|
|
801
965
|
extractKey: transactionKey,
|
|
802
966
|
logger: options.logger,
|
|
803
|
-
resubscribeOnEnd: options.resubscribeOnEnd
|
|
967
|
+
resubscribeOnEnd: options.resubscribeOnEnd,
|
|
968
|
+
signal: options.signal,
|
|
969
|
+
onReconnect,
|
|
970
|
+
dispose: options.clientFactory ? () => closeIfCloseable(currentClient) : void 0
|
|
804
971
|
});
|
|
805
972
|
}
|
|
806
973
|
function transactionKey(tx) {
|
|
@@ -815,19 +982,6 @@ function bytesToHex(bytes) {
|
|
|
815
982
|
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
816
983
|
return hex;
|
|
817
984
|
}
|
|
818
|
-
|
|
819
|
-
// src/types.ts
|
|
820
|
-
function resolveClient(opts, optionsName) {
|
|
821
|
-
if (opts.clientFactory) {
|
|
822
|
-
return opts.clientFactory();
|
|
823
|
-
}
|
|
824
|
-
if (!opts.client) {
|
|
825
|
-
throw new Error(`${optionsName} requires either client or clientFactory`);
|
|
826
|
-
}
|
|
827
|
-
return opts.client;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// src/replay/event-replay.ts
|
|
831
985
|
var DEFAULT_PAGE_SIZE3 = 512;
|
|
832
986
|
var DEFAULT_SAFETY_MARGIN3 = 64n;
|
|
833
987
|
var PAGE_ORDER_ASC3 = "slot asc";
|
|
@@ -870,10 +1024,12 @@ function createEventReplay(options) {
|
|
|
870
1024
|
);
|
|
871
1025
|
};
|
|
872
1026
|
const onReconnect = options.clientFactory ? () => {
|
|
873
|
-
|
|
1027
|
+
const newClient = options.clientFactory();
|
|
1028
|
+
currentClient = newClient;
|
|
874
1029
|
return {
|
|
875
1030
|
subscribeLive: createSubscribeLive(currentClient),
|
|
876
|
-
fetchBackfill: createFetchBackfill(currentClient)
|
|
1031
|
+
fetchBackfill: createFetchBackfill(currentClient),
|
|
1032
|
+
dispose: () => closeIfCloseable(newClient)
|
|
877
1033
|
};
|
|
878
1034
|
} : void 0;
|
|
879
1035
|
return new ReplayStream({
|
|
@@ -885,7 +1041,9 @@ function createEventReplay(options) {
|
|
|
885
1041
|
extractKey: eventKey,
|
|
886
1042
|
logger: options.logger,
|
|
887
1043
|
resubscribeOnEnd: options.resubscribeOnEnd,
|
|
888
|
-
|
|
1044
|
+
signal: options.signal,
|
|
1045
|
+
onReconnect,
|
|
1046
|
+
dispose: options.clientFactory ? () => closeIfCloseable(currentClient) : void 0
|
|
889
1047
|
});
|
|
890
1048
|
}
|
|
891
1049
|
function streamResponseToEvent(resp) {
|
|
@@ -1110,6 +1268,15 @@ var PageAssembler = class {
|
|
|
1110
1268
|
};
|
|
1111
1269
|
|
|
1112
1270
|
// src/account-replay.ts
|
|
1271
|
+
async function closeAsyncIterator(iterator) {
|
|
1272
|
+
if (!iterator || typeof iterator.return !== "function") {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
await iterator.return();
|
|
1277
|
+
} catch {
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1113
1280
|
function bytesToHex2(bytes) {
|
|
1114
1281
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1115
1282
|
}
|
|
@@ -1184,9 +1351,12 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1184
1351
|
cleanupInterval = 1e4,
|
|
1185
1352
|
onBackfillComplete,
|
|
1186
1353
|
clientFactory,
|
|
1187
|
-
logger = NOOP_LOGGER
|
|
1354
|
+
logger = NOOP_LOGGER,
|
|
1355
|
+
signal
|
|
1188
1356
|
} = options;
|
|
1357
|
+
const shouldStop = (err) => signal?.aborted === true || isAbortError(err);
|
|
1189
1358
|
let client = resolveClient(options, "AccountsByOwnerReplayOptions");
|
|
1359
|
+
const ownsClient = Boolean(clientFactory);
|
|
1190
1360
|
const seenFromStream = /* @__PURE__ */ new Set();
|
|
1191
1361
|
const fetchQueue = [];
|
|
1192
1362
|
let highestSlotSeen = minUpdatedSlot ?? 0n;
|
|
@@ -1196,15 +1366,24 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1196
1366
|
let streamDone = false;
|
|
1197
1367
|
let streamError = null;
|
|
1198
1368
|
let lastActivityTime = Date.now();
|
|
1369
|
+
let activeStreamIterator = null;
|
|
1370
|
+
let activeStreamProcessor = null;
|
|
1199
1371
|
try {
|
|
1372
|
+
if (shouldStop()) return;
|
|
1200
1373
|
cleanupTimer = setInterval(() => {
|
|
1201
1374
|
assembler.cleanup();
|
|
1202
1375
|
}, cleanupInterval);
|
|
1376
|
+
cleanupTimer.unref?.();
|
|
1203
1377
|
const streamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, minUpdatedSlot);
|
|
1204
1378
|
const stream = client.streamAccountUpdates({ view, filter: streamFilter });
|
|
1205
|
-
const
|
|
1379
|
+
const streamIterator = stream[Symbol.asyncIterator]();
|
|
1380
|
+
activeStreamIterator = streamIterator;
|
|
1381
|
+
activeStreamProcessor = (async () => {
|
|
1206
1382
|
try {
|
|
1207
|
-
|
|
1383
|
+
while (true) {
|
|
1384
|
+
const next = await streamIterator.next();
|
|
1385
|
+
if (next.done) break;
|
|
1386
|
+
const response = next.value;
|
|
1208
1387
|
lastActivityTime = Date.now();
|
|
1209
1388
|
const event = processResponseMulti(response, assembler);
|
|
1210
1389
|
if (event) {
|
|
@@ -1235,6 +1414,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1235
1414
|
const backfillFilter = buildListAccountsOwnerFilter(owner, dataSizes, minUpdatedSlot);
|
|
1236
1415
|
let pageToken;
|
|
1237
1416
|
do {
|
|
1417
|
+
if (shouldStop()) return;
|
|
1238
1418
|
const request = {
|
|
1239
1419
|
view: AccountView.META_ONLY,
|
|
1240
1420
|
// Address + metadata only, no data
|
|
@@ -1244,7 +1424,13 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1244
1424
|
pageToken
|
|
1245
1425
|
})
|
|
1246
1426
|
};
|
|
1247
|
-
|
|
1427
|
+
let response;
|
|
1428
|
+
try {
|
|
1429
|
+
response = await client.listAccounts(request);
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
if (shouldStop(err)) return;
|
|
1432
|
+
throw err;
|
|
1433
|
+
}
|
|
1248
1434
|
for (const account of response.accounts) {
|
|
1249
1435
|
if (account.address?.value) {
|
|
1250
1436
|
fetchQueue.push(account.address.value);
|
|
@@ -1254,6 +1440,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1254
1440
|
yield* yieldStreamBuffer();
|
|
1255
1441
|
} while (pageToken);
|
|
1256
1442
|
for (const address of fetchQueue) {
|
|
1443
|
+
if (shouldStop()) return;
|
|
1257
1444
|
const addressHex = bytesToHex2(address);
|
|
1258
1445
|
if (seenFromStream.has(addressHex)) {
|
|
1259
1446
|
continue;
|
|
@@ -1271,10 +1458,11 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1271
1458
|
});
|
|
1272
1459
|
break;
|
|
1273
1460
|
} catch (err) {
|
|
1461
|
+
if (shouldStop(err)) return;
|
|
1274
1462
|
if (attempt === maxRetries - 1) {
|
|
1275
1463
|
logger.error(`[backfill] failed to fetch account ${addressHex} after ${maxRetries} attempts`, { error: err });
|
|
1276
1464
|
} else {
|
|
1277
|
-
await
|
|
1465
|
+
await abortableDelay(100 * (attempt + 1), signal);
|
|
1278
1466
|
}
|
|
1279
1467
|
}
|
|
1280
1468
|
}
|
|
@@ -1293,13 +1481,13 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1293
1481
|
}
|
|
1294
1482
|
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
1295
1483
|
let retryAttempt = 0;
|
|
1296
|
-
let currentStream = stream;
|
|
1297
|
-
let currentStreamProcessor = streamProcessor;
|
|
1298
1484
|
lastActivityTime = Date.now();
|
|
1299
1485
|
const createStreamProcessor = () => {
|
|
1300
1486
|
if (clientFactory) {
|
|
1301
1487
|
try {
|
|
1302
|
-
|
|
1488
|
+
const newClient = clientFactory();
|
|
1489
|
+
closeIfCloseable(client);
|
|
1490
|
+
client = newClient;
|
|
1303
1491
|
logger.info("[account-stream] created fresh client for reconnection");
|
|
1304
1492
|
} catch (err) {
|
|
1305
1493
|
logger.error("[account-stream] failed to create fresh client", { error: err });
|
|
@@ -1307,9 +1495,14 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1307
1495
|
}
|
|
1308
1496
|
const newStreamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, highestSlotSeen > 0n ? highestSlotSeen : minUpdatedSlot);
|
|
1309
1497
|
const newStream = client.streamAccountUpdates({ view, filter: newStreamFilter });
|
|
1498
|
+
const newStreamIterator = newStream[Symbol.asyncIterator]();
|
|
1499
|
+
activeStreamIterator = newStreamIterator;
|
|
1310
1500
|
const newProcessor = (async () => {
|
|
1311
1501
|
try {
|
|
1312
|
-
|
|
1502
|
+
while (true) {
|
|
1503
|
+
const next = await newStreamIterator.next();
|
|
1504
|
+
if (next.done) break;
|
|
1505
|
+
const response = next.value;
|
|
1313
1506
|
retryAttempt = 0;
|
|
1314
1507
|
lastActivityTime = Date.now();
|
|
1315
1508
|
const event = processResponseMulti(response, assembler);
|
|
@@ -1329,9 +1522,10 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1329
1522
|
streamDone = true;
|
|
1330
1523
|
}
|
|
1331
1524
|
})();
|
|
1332
|
-
return {
|
|
1525
|
+
return { iterator: newStreamIterator, processor: newProcessor };
|
|
1333
1526
|
};
|
|
1334
1527
|
while (true) {
|
|
1528
|
+
if (shouldStop()) return;
|
|
1335
1529
|
const hadEvents = streamBuffer.length > 0;
|
|
1336
1530
|
yield* yieldStreamBuffer();
|
|
1337
1531
|
if (hadEvents) {
|
|
@@ -1346,36 +1540,56 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1346
1540
|
}
|
|
1347
1541
|
if (streamDone) {
|
|
1348
1542
|
if (streamError) {
|
|
1543
|
+
if (shouldStop(streamError)) return;
|
|
1349
1544
|
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
1350
1545
|
logger.warn(
|
|
1351
1546
|
`[account-stream] disconnected (${streamError.message}); reconnecting in ${backoffMs}ms (attempt ${retryAttempt + 1})`
|
|
1352
1547
|
);
|
|
1353
|
-
await
|
|
1548
|
+
await abortableDelay(backoffMs, signal);
|
|
1549
|
+
if (shouldStop()) return;
|
|
1354
1550
|
retryAttempt++;
|
|
1355
1551
|
streamDone = false;
|
|
1356
1552
|
streamError = null;
|
|
1357
1553
|
streamBuffer.length = 0;
|
|
1358
1554
|
lastActivityTime = Date.now();
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1555
|
+
await closeAsyncIterator(activeStreamIterator);
|
|
1556
|
+
if (activeStreamProcessor) {
|
|
1557
|
+
await Promise.allSettled([activeStreamProcessor]);
|
|
1558
|
+
}
|
|
1559
|
+
const { iterator: newIterator, processor: newProcessor } = createStreamProcessor();
|
|
1560
|
+
activeStreamIterator = newIterator;
|
|
1561
|
+
activeStreamProcessor = newProcessor;
|
|
1362
1562
|
continue;
|
|
1363
1563
|
} else {
|
|
1564
|
+
if (shouldStop()) return;
|
|
1364
1565
|
logger.warn("[account-stream] stream ended unexpectedly; reconnecting...");
|
|
1365
1566
|
streamDone = false;
|
|
1366
1567
|
lastActivityTime = Date.now();
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1568
|
+
await closeAsyncIterator(activeStreamIterator);
|
|
1569
|
+
if (activeStreamProcessor) {
|
|
1570
|
+
await Promise.allSettled([activeStreamProcessor]);
|
|
1571
|
+
}
|
|
1572
|
+
const { iterator: newIterator, processor: newProcessor } = createStreamProcessor();
|
|
1573
|
+
activeStreamIterator = newIterator;
|
|
1574
|
+
activeStreamProcessor = newProcessor;
|
|
1370
1575
|
continue;
|
|
1371
1576
|
}
|
|
1372
1577
|
}
|
|
1373
|
-
await
|
|
1578
|
+
await abortableDelay(10, signal);
|
|
1374
1579
|
}
|
|
1375
1580
|
} finally {
|
|
1376
1581
|
if (cleanupTimer) {
|
|
1377
1582
|
clearInterval(cleanupTimer);
|
|
1378
1583
|
}
|
|
1584
|
+
const closeIteratorPromise = closeAsyncIterator(activeStreamIterator);
|
|
1585
|
+
if (ownsClient) {
|
|
1586
|
+
closeIfCloseable(client);
|
|
1587
|
+
}
|
|
1588
|
+
if (activeStreamProcessor) {
|
|
1589
|
+
await Promise.allSettled([closeIteratorPromise, activeStreamProcessor]);
|
|
1590
|
+
} else {
|
|
1591
|
+
await closeIteratorPromise;
|
|
1592
|
+
}
|
|
1379
1593
|
assembler.clear();
|
|
1380
1594
|
}
|
|
1381
1595
|
}
|
|
@@ -1411,6 +1625,7 @@ async function* createAccountReplay(options) {
|
|
|
1411
1625
|
cleanupTimer = setInterval(() => {
|
|
1412
1626
|
assembler.cleanup();
|
|
1413
1627
|
}, cleanupInterval);
|
|
1628
|
+
cleanupTimer.unref?.();
|
|
1414
1629
|
const filterParams = {
|
|
1415
1630
|
address: create(FilterParamValueSchema, { kind: { case: "bytesValue", value: new Uint8Array(address) } })
|
|
1416
1631
|
};
|
|
@@ -1617,6 +1832,7 @@ var ConsoleSink = class {
|
|
|
1617
1832
|
constructor(prefix = "ReplaySink") {
|
|
1618
1833
|
this.prefix = prefix;
|
|
1619
1834
|
}
|
|
1835
|
+
prefix;
|
|
1620
1836
|
open(meta) {
|
|
1621
1837
|
const suffix = meta?.stream ? ` (${meta.stream})` : "";
|
|
1622
1838
|
console.info(`${this.prefix}${suffix} opened`, meta?.label ?? "");
|