@thru/replay 0.1.36 → 0.2.0
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 +405 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -49
- package/dist/index.d.ts +137 -49
- package/dist/index.mjs +401 -74
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -17,6 +17,12 @@ var ChainClient = class {
|
|
|
17
17
|
query;
|
|
18
18
|
streaming;
|
|
19
19
|
callOptions;
|
|
20
|
+
getAccount(request) {
|
|
21
|
+
return this.query.getAccount(protobuf.create(proto.GetAccountRequestSchema, request), this.callOptions);
|
|
22
|
+
}
|
|
23
|
+
listAccounts(request) {
|
|
24
|
+
return this.query.listAccounts(protobuf.create(proto.ListAccountsRequestSchema, request), this.callOptions);
|
|
25
|
+
}
|
|
20
26
|
listBlocks(request) {
|
|
21
27
|
return this.query.listBlocks(protobuf.create(proto.ListBlocksRequestSchema, request), this.callOptions);
|
|
22
28
|
}
|
|
@@ -303,18 +309,52 @@ var LivePump = class {
|
|
|
303
309
|
}
|
|
304
310
|
};
|
|
305
311
|
|
|
312
|
+
// src/retry.ts
|
|
313
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
314
|
+
initialDelayMs: 1e3,
|
|
315
|
+
maxDelayMs: 3e4,
|
|
316
|
+
connectionTimeoutMs: 3e4
|
|
317
|
+
};
|
|
318
|
+
function calculateBackoff(attempt, config) {
|
|
319
|
+
const delay2 = config.initialDelayMs * Math.pow(2, attempt);
|
|
320
|
+
return Math.min(delay2, config.maxDelayMs);
|
|
321
|
+
}
|
|
322
|
+
function withTimeout(promise, timeoutMs) {
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
const timer = setTimeout(() => {
|
|
325
|
+
reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`));
|
|
326
|
+
}, timeoutMs);
|
|
327
|
+
promise.then((value) => {
|
|
328
|
+
clearTimeout(timer);
|
|
329
|
+
resolve(value);
|
|
330
|
+
}).catch((err) => {
|
|
331
|
+
clearTimeout(timer);
|
|
332
|
+
reject(err);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
var TimeoutError = class extends Error {
|
|
337
|
+
constructor(message) {
|
|
338
|
+
super(message);
|
|
339
|
+
this.name = "TimeoutError";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
function delay(ms) {
|
|
343
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
344
|
+
}
|
|
345
|
+
|
|
306
346
|
// src/replay-stream.ts
|
|
307
347
|
var DEFAULT_METRICS = {
|
|
308
348
|
bufferedItems: 0,
|
|
309
349
|
emittedBackfill: 0,
|
|
310
350
|
emittedLive: 0,
|
|
351
|
+
emittedReconnect: 0,
|
|
311
352
|
discardedDuplicates: 0
|
|
312
353
|
};
|
|
313
354
|
function compareBigint(a, b) {
|
|
314
355
|
if (a === b) return 0;
|
|
315
356
|
return a < b ? -1 : 1;
|
|
316
357
|
}
|
|
317
|
-
var RETRY_DELAY_MS = 1e3;
|
|
318
358
|
var ReplayStream = class {
|
|
319
359
|
config;
|
|
320
360
|
logger;
|
|
@@ -337,12 +377,15 @@ var ReplayStream = class {
|
|
|
337
377
|
extractSlot,
|
|
338
378
|
extractKey,
|
|
339
379
|
safetyMargin,
|
|
340
|
-
resubscribeOnEnd
|
|
380
|
+
resubscribeOnEnd,
|
|
381
|
+
onReconnect
|
|
341
382
|
} = this.config;
|
|
342
383
|
const shouldResubscribeOnEnd = resubscribeOnEnd ?? true;
|
|
343
384
|
const keyOf = extractKey ?? ((item) => extractSlot(item).toString());
|
|
385
|
+
let currentSubscribeLive = subscribeLive;
|
|
386
|
+
let currentFetchBackfill = fetchBackfill;
|
|
344
387
|
const createLivePump = (slot, startStreaming = false, emitFloor) => new LivePump({
|
|
345
|
-
source:
|
|
388
|
+
source: currentSubscribeLive(slot),
|
|
346
389
|
slotOf: extractSlot,
|
|
347
390
|
keyOf,
|
|
348
391
|
logger: this.logger,
|
|
@@ -428,9 +471,15 @@ var ReplayStream = class {
|
|
|
428
471
|
}
|
|
429
472
|
if (!drained.length) livePump.updateEmitFloor(currentSlot);
|
|
430
473
|
this.logger.info("replay entering STREAMING state");
|
|
474
|
+
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
475
|
+
let retryAttempt = 0;
|
|
431
476
|
while (true) {
|
|
432
477
|
try {
|
|
433
|
-
const next = await
|
|
478
|
+
const next = await withTimeout(
|
|
479
|
+
livePump.next(),
|
|
480
|
+
retryConfig.connectionTimeoutMs
|
|
481
|
+
);
|
|
482
|
+
retryAttempt = 0;
|
|
434
483
|
if (next.done) {
|
|
435
484
|
if (!shouldResubscribeOnEnd) break;
|
|
436
485
|
throw new Error("stream ended");
|
|
@@ -448,20 +497,91 @@ var ReplayStream = class {
|
|
|
448
497
|
livePump.updateEmitFloor(currentSlot);
|
|
449
498
|
} catch (err) {
|
|
450
499
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
500
|
+
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
451
501
|
this.logger.warn(
|
|
452
|
-
`live stream disconnected (${errMsg}); reconnecting in ${
|
|
502
|
+
`live stream disconnected (${errMsg}); reconnecting in ${backoffMs}ms from slot ${currentSlot} (attempt ${retryAttempt + 1})`
|
|
453
503
|
);
|
|
454
|
-
await delay(
|
|
504
|
+
await delay(backoffMs);
|
|
455
505
|
await safeClose(livePump);
|
|
506
|
+
retryAttempt++;
|
|
507
|
+
if (onReconnect) {
|
|
508
|
+
try {
|
|
509
|
+
const fresh = onReconnect();
|
|
510
|
+
currentSubscribeLive = fresh.subscribeLive;
|
|
511
|
+
if (fresh.fetchBackfill) {
|
|
512
|
+
currentFetchBackfill = fresh.fetchBackfill;
|
|
513
|
+
}
|
|
514
|
+
this.logger.info("created fresh client for reconnection");
|
|
515
|
+
} catch (factoryErr) {
|
|
516
|
+
this.logger.error(
|
|
517
|
+
`failed to create fresh client: ${factoryErr instanceof Error ? factoryErr.message : String(factoryErr)}; using existing`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (onReconnect && currentSlot > 0n) {
|
|
522
|
+
for await (const item of this.miniBackfill(
|
|
523
|
+
currentSlot,
|
|
524
|
+
currentFetchBackfill,
|
|
525
|
+
extractSlot,
|
|
526
|
+
keyOf,
|
|
527
|
+
seenItem,
|
|
528
|
+
recordEmission
|
|
529
|
+
)) {
|
|
530
|
+
const itemSlot = extractSlot(item);
|
|
531
|
+
if (itemSlot > currentSlot) {
|
|
532
|
+
currentSlot = itemSlot;
|
|
533
|
+
}
|
|
534
|
+
yield item;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
456
537
|
const resumeSlot = currentSlot > 0n ? currentSlot : 0n;
|
|
457
538
|
livePump = createLivePump(resumeSlot, true, currentSlot);
|
|
458
539
|
}
|
|
459
540
|
}
|
|
460
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Perform mini-backfill from lastProcessedSlot to catch up after reconnection.
|
|
544
|
+
* Ensures no data gaps from events that occurred during disconnection.
|
|
545
|
+
*/
|
|
546
|
+
async *miniBackfill(fromSlot, fetchBackfill, extractSlot, keyOf, seenItem, recordEmission) {
|
|
547
|
+
this.logger.info(`mini-backfill starting from slot ${fromSlot}`);
|
|
548
|
+
const MINI_BACKFILL_TIMEOUT = 3e4;
|
|
549
|
+
const startTime = Date.now();
|
|
550
|
+
let cursor;
|
|
551
|
+
let itemsYielded = 0;
|
|
552
|
+
try {
|
|
553
|
+
while (true) {
|
|
554
|
+
if (Date.now() - startTime > MINI_BACKFILL_TIMEOUT) {
|
|
555
|
+
this.logger.warn(`mini-backfill timed out after ${MINI_BACKFILL_TIMEOUT}ms`);
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
const page = await fetchBackfill({ startSlot: fromSlot, cursor });
|
|
559
|
+
const sorted = [...page.items].sort(
|
|
560
|
+
(a, b) => compareBigint(extractSlot(a), extractSlot(b))
|
|
561
|
+
);
|
|
562
|
+
for (const item of sorted) {
|
|
563
|
+
const slot = extractSlot(item);
|
|
564
|
+
const key = keyOf(item);
|
|
565
|
+
if (seenItem(slot, key)) {
|
|
566
|
+
this.metrics.discardedDuplicates += 1;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
recordEmission(slot, key);
|
|
570
|
+
itemsYielded++;
|
|
571
|
+
this.metrics.emittedReconnect += 1;
|
|
572
|
+
yield item;
|
|
573
|
+
}
|
|
574
|
+
cursor = page.cursor;
|
|
575
|
+
if (page.done || cursor === void 0) break;
|
|
576
|
+
}
|
|
577
|
+
this.logger.info(`mini-backfill complete: ${itemsYielded} items yielded`);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
this.logger.warn(
|
|
580
|
+
`mini-backfill failed: ${err instanceof Error ? err.message : String(err)}; proceeding with live stream`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
461
584
|
};
|
|
462
|
-
function delay(ms) {
|
|
463
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
464
|
-
}
|
|
465
585
|
async function safeClose(pump) {
|
|
466
586
|
try {
|
|
467
587
|
await pump.close();
|
|
@@ -623,12 +743,26 @@ function bytesToHex(bytes) {
|
|
|
623
743
|
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
624
744
|
return hex;
|
|
625
745
|
}
|
|
746
|
+
|
|
747
|
+
// src/types.ts
|
|
748
|
+
function resolveClient(opts, optionsName) {
|
|
749
|
+
if (opts.clientFactory) {
|
|
750
|
+
return opts.clientFactory();
|
|
751
|
+
}
|
|
752
|
+
if (!opts.client) {
|
|
753
|
+
throw new Error(`${optionsName} requires either client or clientFactory`);
|
|
754
|
+
}
|
|
755
|
+
return opts.client;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/replay/event-replay.ts
|
|
626
759
|
var DEFAULT_PAGE_SIZE3 = 512;
|
|
627
760
|
var DEFAULT_SAFETY_MARGIN3 = 64n;
|
|
628
761
|
var PAGE_ORDER_ASC3 = "slot asc";
|
|
629
762
|
function createEventReplay(options) {
|
|
630
763
|
const safetyMargin = options.safetyMargin ?? DEFAULT_SAFETY_MARGIN3;
|
|
631
|
-
|
|
764
|
+
let currentClient = resolveClient(options, "EventReplayOptions");
|
|
765
|
+
const createFetchBackfill = (client) => async ({
|
|
632
766
|
startSlot,
|
|
633
767
|
cursor
|
|
634
768
|
}) => {
|
|
@@ -639,7 +773,7 @@ function createEventReplay(options) {
|
|
|
639
773
|
});
|
|
640
774
|
const baseFilter = slotLiteralFilter("event.slot", startSlot);
|
|
641
775
|
const mergedFilter = combineFilters(baseFilter, options.filter);
|
|
642
|
-
const response = await
|
|
776
|
+
const response = await client.listEvents(
|
|
643
777
|
protobuf.create(proto.ListEventsRequestSchema, {
|
|
644
778
|
filter: mergedFilter,
|
|
645
779
|
page
|
|
@@ -647,25 +781,33 @@ function createEventReplay(options) {
|
|
|
647
781
|
);
|
|
648
782
|
return backfillPage(response.events, response.page);
|
|
649
783
|
};
|
|
650
|
-
const
|
|
784
|
+
const createSubscribeLive = (client) => (startSlot) => {
|
|
651
785
|
const mergedFilter = combineFilters(slotLiteralFilter("event.slot", startSlot), options.filter);
|
|
652
786
|
const request = protobuf.create(proto.StreamEventsRequestSchema, {
|
|
653
787
|
filter: mergedFilter
|
|
654
788
|
});
|
|
655
789
|
return mapAsyncIterable(
|
|
656
|
-
|
|
790
|
+
client.streamEvents(request),
|
|
657
791
|
(resp) => streamResponseToEvent(resp)
|
|
658
792
|
);
|
|
659
793
|
};
|
|
794
|
+
const onReconnect = options.clientFactory ? () => {
|
|
795
|
+
currentClient = options.clientFactory();
|
|
796
|
+
return {
|
|
797
|
+
subscribeLive: createSubscribeLive(currentClient),
|
|
798
|
+
fetchBackfill: createFetchBackfill(currentClient)
|
|
799
|
+
};
|
|
800
|
+
} : void 0;
|
|
660
801
|
return new ReplayStream({
|
|
661
802
|
startSlot: options.startSlot,
|
|
662
803
|
safetyMargin,
|
|
663
|
-
fetchBackfill,
|
|
664
|
-
subscribeLive,
|
|
804
|
+
fetchBackfill: createFetchBackfill(currentClient),
|
|
805
|
+
subscribeLive: createSubscribeLive(currentClient),
|
|
665
806
|
extractSlot: (event) => event.slot ?? 0n,
|
|
666
807
|
extractKey: eventKey,
|
|
667
808
|
logger: options.logger,
|
|
668
|
-
resubscribeOnEnd: options.resubscribeOnEnd
|
|
809
|
+
resubscribeOnEnd: options.resubscribeOnEnd,
|
|
810
|
+
onReconnect
|
|
669
811
|
});
|
|
670
812
|
}
|
|
671
813
|
function streamResponseToEvent(resp) {
|
|
@@ -859,7 +1001,7 @@ function snapshotToState(account) {
|
|
|
859
1001
|
meta: account.meta,
|
|
860
1002
|
data: account.data?.data ?? new Uint8Array(0),
|
|
861
1003
|
isDelete: account.meta.flags?.isDeleted ?? false,
|
|
862
|
-
source: "
|
|
1004
|
+
source: "stream"
|
|
863
1005
|
};
|
|
864
1006
|
}
|
|
865
1007
|
function assembledToState(assembled) {
|
|
@@ -871,9 +1013,248 @@ function assembledToState(assembled) {
|
|
|
871
1013
|
meta: assembled.meta,
|
|
872
1014
|
data: assembled.data,
|
|
873
1015
|
isDelete: assembled.isDelete,
|
|
874
|
-
source: "
|
|
1016
|
+
source: "stream"
|
|
875
1017
|
};
|
|
876
1018
|
}
|
|
1019
|
+
function getAccountToState(account) {
|
|
1020
|
+
if (!account.address?.value || !account.meta) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
address: account.address.value,
|
|
1025
|
+
addressHex: bytesToHex2(account.address.value),
|
|
1026
|
+
slot: account.meta.lastUpdatedSlot ?? account.versionContext?.slot ?? 0n,
|
|
1027
|
+
seq: BigInt(account.meta.seq.toString()),
|
|
1028
|
+
meta: account.meta,
|
|
1029
|
+
data: account.data?.data ?? new Uint8Array(0),
|
|
1030
|
+
isDelete: account.meta.flags?.isDeleted ?? false,
|
|
1031
|
+
source: "backfill"
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function buildListAccountsOwnerFilter(owner, dataSizes, minUpdatedSlot) {
|
|
1035
|
+
let expression = "account.meta.owner.value == params.owner_bytes";
|
|
1036
|
+
if (dataSizes && dataSizes.length > 0) {
|
|
1037
|
+
const sizeConditions = dataSizes.map((size) => `account.meta.data_size == uint(${size})`).join(" || ");
|
|
1038
|
+
expression = `(${expression}) && (${sizeConditions})`;
|
|
1039
|
+
}
|
|
1040
|
+
if (minUpdatedSlot !== void 0 && minUpdatedSlot > 0n) {
|
|
1041
|
+
expression = `(${expression}) && account.meta.last_updated_slot >= params.min_updated_slot`;
|
|
1042
|
+
}
|
|
1043
|
+
const params = {
|
|
1044
|
+
owner_bytes: protobuf.create(proto.FilterParamValueSchema, { kind: { case: "bytesValue", value: new Uint8Array(owner) } })
|
|
1045
|
+
};
|
|
1046
|
+
if (minUpdatedSlot !== void 0 && minUpdatedSlot > 0n) {
|
|
1047
|
+
params["min_updated_slot"] = protobuf.create(proto.FilterParamValueSchema, { kind: { case: "uintValue", value: minUpdatedSlot } });
|
|
1048
|
+
}
|
|
1049
|
+
return protobuf.create(proto.FilterSchema, { expression, params });
|
|
1050
|
+
}
|
|
1051
|
+
async function* createAccountsByOwnerReplay(options) {
|
|
1052
|
+
const {
|
|
1053
|
+
owner,
|
|
1054
|
+
view = proto.AccountView.FULL,
|
|
1055
|
+
dataSizes,
|
|
1056
|
+
minUpdatedSlot,
|
|
1057
|
+
pageSize = 100,
|
|
1058
|
+
maxRetries = 3,
|
|
1059
|
+
pageAssemblerOptions,
|
|
1060
|
+
cleanupInterval = 1e4,
|
|
1061
|
+
onBackfillComplete,
|
|
1062
|
+
clientFactory,
|
|
1063
|
+
logger = NOOP_LOGGER
|
|
1064
|
+
} = options;
|
|
1065
|
+
let client = resolveClient(options, "AccountsByOwnerReplayOptions");
|
|
1066
|
+
const seenFromStream = /* @__PURE__ */ new Set();
|
|
1067
|
+
const fetchQueue = [];
|
|
1068
|
+
let highestSlotSeen = minUpdatedSlot ?? 0n;
|
|
1069
|
+
const assembler = new PageAssembler(pageAssemblerOptions);
|
|
1070
|
+
let cleanupTimer = null;
|
|
1071
|
+
const streamBuffer = [];
|
|
1072
|
+
let streamDone = false;
|
|
1073
|
+
let streamError = null;
|
|
1074
|
+
try {
|
|
1075
|
+
cleanupTimer = setInterval(() => {
|
|
1076
|
+
assembler.cleanup();
|
|
1077
|
+
}, cleanupInterval);
|
|
1078
|
+
const streamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, minUpdatedSlot);
|
|
1079
|
+
const stream = client.streamAccountUpdates({ view, filter: streamFilter });
|
|
1080
|
+
const streamProcessor = (async () => {
|
|
1081
|
+
try {
|
|
1082
|
+
for await (const response of stream) {
|
|
1083
|
+
const event = processResponseMulti(response, assembler);
|
|
1084
|
+
if (event) {
|
|
1085
|
+
if (event.type === "account") {
|
|
1086
|
+
seenFromStream.add(event.account.addressHex);
|
|
1087
|
+
if (event.account.slot > highestSlotSeen) {
|
|
1088
|
+
highestSlotSeen = event.account.slot;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
streamBuffer.push(event);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
streamError = err;
|
|
1096
|
+
} finally {
|
|
1097
|
+
streamDone = true;
|
|
1098
|
+
}
|
|
1099
|
+
})();
|
|
1100
|
+
const yieldStreamBuffer = function* () {
|
|
1101
|
+
while (streamBuffer.length > 0) {
|
|
1102
|
+
const event = streamBuffer.shift();
|
|
1103
|
+
if (event.type === "account") {
|
|
1104
|
+
seenFromStream.add(event.account.addressHex);
|
|
1105
|
+
}
|
|
1106
|
+
yield event;
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
const backfillFilter = buildListAccountsOwnerFilter(owner, dataSizes, minUpdatedSlot);
|
|
1110
|
+
let pageToken;
|
|
1111
|
+
do {
|
|
1112
|
+
const request = {
|
|
1113
|
+
view: proto.AccountView.META_ONLY,
|
|
1114
|
+
// Address + metadata only, no data
|
|
1115
|
+
filter: backfillFilter,
|
|
1116
|
+
page: protobuf.create(proto.PageRequestSchema, {
|
|
1117
|
+
pageSize,
|
|
1118
|
+
pageToken
|
|
1119
|
+
})
|
|
1120
|
+
};
|
|
1121
|
+
const response = await client.listAccounts(request);
|
|
1122
|
+
for (const account of response.accounts) {
|
|
1123
|
+
if (account.address?.value) {
|
|
1124
|
+
fetchQueue.push(account.address.value);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
pageToken = response.page?.nextPageToken;
|
|
1128
|
+
yield* yieldStreamBuffer();
|
|
1129
|
+
} while (pageToken);
|
|
1130
|
+
for (const address of fetchQueue) {
|
|
1131
|
+
const addressHex = bytesToHex2(address);
|
|
1132
|
+
if (seenFromStream.has(addressHex)) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
yield* yieldStreamBuffer();
|
|
1136
|
+
if (seenFromStream.has(addressHex)) {
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
let account = null;
|
|
1140
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1141
|
+
try {
|
|
1142
|
+
account = await client.getAccount({
|
|
1143
|
+
address: protobuf.create(proto.PubkeySchema, { value: address }),
|
|
1144
|
+
view: proto.AccountView.FULL
|
|
1145
|
+
});
|
|
1146
|
+
break;
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
if (attempt === maxRetries - 1) {
|
|
1149
|
+
logger.error(`[backfill] failed to fetch account ${addressHex} after ${maxRetries} attempts`, { error: err });
|
|
1150
|
+
} else {
|
|
1151
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (account) {
|
|
1156
|
+
const state = getAccountToState(account);
|
|
1157
|
+
if (state) {
|
|
1158
|
+
if (state.slot > highestSlotSeen) {
|
|
1159
|
+
highestSlotSeen = state.slot;
|
|
1160
|
+
}
|
|
1161
|
+
yield { type: "account", account: state };
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (onBackfillComplete) {
|
|
1166
|
+
onBackfillComplete(highestSlotSeen);
|
|
1167
|
+
}
|
|
1168
|
+
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
1169
|
+
let retryAttempt = 0;
|
|
1170
|
+
let currentStream = stream;
|
|
1171
|
+
let currentStreamProcessor = streamProcessor;
|
|
1172
|
+
const createStreamProcessor = () => {
|
|
1173
|
+
if (clientFactory) {
|
|
1174
|
+
try {
|
|
1175
|
+
client = clientFactory();
|
|
1176
|
+
logger.info("[account-stream] created fresh client for reconnection");
|
|
1177
|
+
} catch (err) {
|
|
1178
|
+
logger.error("[account-stream] failed to create fresh client", { error: err });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const newStreamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, highestSlotSeen > 0n ? highestSlotSeen : minUpdatedSlot);
|
|
1182
|
+
const newStream = client.streamAccountUpdates({ view, filter: newStreamFilter });
|
|
1183
|
+
const newProcessor = (async () => {
|
|
1184
|
+
try {
|
|
1185
|
+
for await (const response of newStream) {
|
|
1186
|
+
retryAttempt = 0;
|
|
1187
|
+
const event = processResponseMulti(response, assembler);
|
|
1188
|
+
if (event) {
|
|
1189
|
+
if (event.type === "account") {
|
|
1190
|
+
seenFromStream.add(event.account.addressHex);
|
|
1191
|
+
if (event.account.slot > highestSlotSeen) {
|
|
1192
|
+
highestSlotSeen = event.account.slot;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
streamBuffer.push(event);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
streamError = err;
|
|
1200
|
+
} finally {
|
|
1201
|
+
streamDone = true;
|
|
1202
|
+
}
|
|
1203
|
+
})();
|
|
1204
|
+
return { stream: newStream, processor: newProcessor };
|
|
1205
|
+
};
|
|
1206
|
+
while (true) {
|
|
1207
|
+
yield* yieldStreamBuffer();
|
|
1208
|
+
if (streamDone) {
|
|
1209
|
+
if (streamError) {
|
|
1210
|
+
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
1211
|
+
logger.warn(
|
|
1212
|
+
`[account-stream] disconnected (${streamError.message}); reconnecting in ${backoffMs}ms (attempt ${retryAttempt + 1})`
|
|
1213
|
+
);
|
|
1214
|
+
await delay(backoffMs);
|
|
1215
|
+
retryAttempt++;
|
|
1216
|
+
streamDone = false;
|
|
1217
|
+
streamError = null;
|
|
1218
|
+
streamBuffer.length = 0;
|
|
1219
|
+
const { stream: newStream, processor: newProcessor } = createStreamProcessor();
|
|
1220
|
+
currentStream = newStream;
|
|
1221
|
+
currentStreamProcessor = newProcessor;
|
|
1222
|
+
continue;
|
|
1223
|
+
} else {
|
|
1224
|
+
logger.warn("[account-stream] stream ended unexpectedly; reconnecting...");
|
|
1225
|
+
streamDone = false;
|
|
1226
|
+
const { stream: newStream, processor: newProcessor } = createStreamProcessor();
|
|
1227
|
+
currentStream = newStream;
|
|
1228
|
+
currentStreamProcessor = newProcessor;
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
await delay(10);
|
|
1233
|
+
}
|
|
1234
|
+
} finally {
|
|
1235
|
+
if (cleanupTimer) {
|
|
1236
|
+
clearInterval(cleanupTimer);
|
|
1237
|
+
}
|
|
1238
|
+
assembler.clear();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
function buildOwnerFilterWithMinSlot(owner, dataSizes, minSlot) {
|
|
1242
|
+
let expression = "(has(snapshot.meta.owner) && snapshot.meta.owner.value == params.owner) || (has(account_update.meta.owner) && account_update.meta.owner.value == params.owner)";
|
|
1243
|
+
if (dataSizes && dataSizes.length > 0) {
|
|
1244
|
+
const sizeConditions = dataSizes.map((size) => `snapshot.meta.data_size == uint(${size}) || account_update.meta.data_size == uint(${size})`).join(" || ");
|
|
1245
|
+
expression = `(${expression}) && (${sizeConditions})`;
|
|
1246
|
+
}
|
|
1247
|
+
if (minSlot !== void 0 && minSlot > 0n) {
|
|
1248
|
+
expression = `(${expression}) && (has(snapshot.address) || (has(account_update.slot) && account_update.slot >= params.min_slot))`;
|
|
1249
|
+
}
|
|
1250
|
+
const params = {
|
|
1251
|
+
owner: protobuf.create(proto.FilterParamValueSchema, { kind: { case: "bytesValue", value: new Uint8Array(owner) } })
|
|
1252
|
+
};
|
|
1253
|
+
if (minSlot !== void 0 && minSlot > 0n) {
|
|
1254
|
+
params["min_slot"] = protobuf.create(proto.FilterParamValueSchema, { kind: { case: "uintValue", value: minSlot } });
|
|
1255
|
+
}
|
|
1256
|
+
return protobuf.create(proto.FilterSchema, { expression, params });
|
|
1257
|
+
}
|
|
877
1258
|
async function* createAccountReplay(options) {
|
|
878
1259
|
const {
|
|
879
1260
|
client,
|
|
@@ -975,60 +1356,6 @@ function processResponseMulti(response, assembler) {
|
|
|
975
1356
|
return null;
|
|
976
1357
|
}
|
|
977
1358
|
}
|
|
978
|
-
function buildOwnerFilter(owner, dataSizes, additionalFilter) {
|
|
979
|
-
let expression = "(has(snapshot.meta.owner) && snapshot.meta.owner.value == params.owner) || (has(account_update.meta.owner) && account_update.meta.owner.value == params.owner)";
|
|
980
|
-
if (dataSizes && dataSizes.length > 0) {
|
|
981
|
-
const sizeConditions = dataSizes.map((size) => `snapshot.meta.data_size == uint(${size}) || account_update.meta.data_size == uint(${size})`).join(" || ");
|
|
982
|
-
expression = `(${expression}) && (${sizeConditions})`;
|
|
983
|
-
}
|
|
984
|
-
if (additionalFilter?.expression) {
|
|
985
|
-
expression = `(${expression}) && (${additionalFilter.expression})`;
|
|
986
|
-
}
|
|
987
|
-
const params = {
|
|
988
|
-
owner: protobuf.create(proto.FilterParamValueSchema, { kind: { case: "bytesValue", value: new Uint8Array(owner) } })
|
|
989
|
-
};
|
|
990
|
-
if (additionalFilter?.params) {
|
|
991
|
-
for (const [key, value] of Object.entries(additionalFilter.params)) {
|
|
992
|
-
params[key] = protobuf.create(proto.FilterParamValueSchema, value);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return protobuf.create(proto.FilterSchema, { expression, params });
|
|
996
|
-
}
|
|
997
|
-
async function* createAccountsByOwnerReplay(options) {
|
|
998
|
-
const {
|
|
999
|
-
client,
|
|
1000
|
-
owner,
|
|
1001
|
-
view = proto.AccountView.FULL,
|
|
1002
|
-
dataSizes,
|
|
1003
|
-
filter,
|
|
1004
|
-
pageAssemblerOptions,
|
|
1005
|
-
cleanupInterval = 1e4
|
|
1006
|
-
} = options;
|
|
1007
|
-
const assembler = new PageAssembler(pageAssemblerOptions);
|
|
1008
|
-
let cleanupTimer = null;
|
|
1009
|
-
try {
|
|
1010
|
-
cleanupTimer = setInterval(() => {
|
|
1011
|
-
assembler.cleanup();
|
|
1012
|
-
}, cleanupInterval);
|
|
1013
|
-
const ownerFilter = buildOwnerFilter(owner, dataSizes, filter);
|
|
1014
|
-
const request = {
|
|
1015
|
-
view,
|
|
1016
|
-
filter: ownerFilter
|
|
1017
|
-
};
|
|
1018
|
-
const stream = client.streamAccountUpdates(request);
|
|
1019
|
-
for await (const response of stream) {
|
|
1020
|
-
const event = processResponseMulti(response, assembler);
|
|
1021
|
-
if (event) {
|
|
1022
|
-
yield event;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
} finally {
|
|
1026
|
-
if (cleanupTimer) {
|
|
1027
|
-
clearInterval(cleanupTimer);
|
|
1028
|
-
}
|
|
1029
|
-
assembler.clear();
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
1359
|
var AccountSeqTracker = class {
|
|
1033
1360
|
seqs = /* @__PURE__ */ new Map();
|
|
1034
1361
|
/**
|
|
@@ -1181,16 +1508,22 @@ Object.defineProperty(exports, "FilterSchema", {
|
|
|
1181
1508
|
exports.AccountSeqTracker = AccountSeqTracker;
|
|
1182
1509
|
exports.ChainClient = ChainClient;
|
|
1183
1510
|
exports.ConsoleSink = ConsoleSink;
|
|
1511
|
+
exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
|
|
1184
1512
|
exports.MultiAccountReplay = MultiAccountReplay;
|
|
1185
1513
|
exports.NOOP_LOGGER = NOOP_LOGGER;
|
|
1186
1514
|
exports.PAGE_SIZE = PAGE_SIZE;
|
|
1187
1515
|
exports.PageAssembler = PageAssembler;
|
|
1188
1516
|
exports.ReplayStream = ReplayStream;
|
|
1517
|
+
exports.TimeoutError = TimeoutError;
|
|
1518
|
+
exports.calculateBackoff = calculateBackoff;
|
|
1189
1519
|
exports.createAccountReplay = createAccountReplay;
|
|
1190
1520
|
exports.createAccountsByOwnerReplay = createAccountsByOwnerReplay;
|
|
1191
1521
|
exports.createBlockReplay = createBlockReplay;
|
|
1192
1522
|
exports.createConsoleLogger = createConsoleLogger;
|
|
1193
1523
|
exports.createEventReplay = createEventReplay;
|
|
1194
1524
|
exports.createTransactionReplay = createTransactionReplay;
|
|
1525
|
+
exports.delay = delay;
|
|
1526
|
+
exports.resolveClient = resolveClient;
|
|
1527
|
+
exports.withTimeout = withTimeout;
|
|
1195
1528
|
//# sourceMappingURL=index.cjs.map
|
|
1196
1529
|
//# sourceMappingURL=index.cjs.map
|