@thru/replay 0.2.20 → 0.2.21
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 +78 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +78 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -359,6 +359,27 @@ function compareBigint(a, b) {
|
|
|
359
359
|
if (a === b) return 0;
|
|
360
360
|
return a < b ? -1 : 1;
|
|
361
361
|
}
|
|
362
|
+
function isNonDecreasing(items, extractSlot) {
|
|
363
|
+
for (let idx = 1; idx < items.length; idx += 1) {
|
|
364
|
+
if (extractSlot(items[idx]) < extractSlot(items[idx - 1])) return false;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
function assertBackfillPageOrder(previousPage, currentPage, extractSlot) {
|
|
369
|
+
if (!isNonDecreasing(currentPage, extractSlot)) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"backfill source returned a page that is not ordered by ascending slot"
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (!previousPage?.length || !currentPage.length) return;
|
|
375
|
+
const previousMaxSlot = extractSlot(previousPage[previousPage.length - 1]);
|
|
376
|
+
const currentMinSlot = extractSlot(currentPage[0]);
|
|
377
|
+
if (currentMinSlot < previousMaxSlot) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`backfill source returned pages out of ascending slot order: page minimum slot ${currentMinSlot} is before previous page maximum slot ${previousMaxSlot}`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
362
383
|
var ReplayStream = class {
|
|
363
384
|
config;
|
|
364
385
|
logger;
|
|
@@ -419,6 +440,29 @@ var ReplayStream = class {
|
|
|
419
440
|
this.logger.info(
|
|
420
441
|
`replay entering BACKFILLING state (startSlot=${startSlot}, safetyMargin=${safetyMargin})`
|
|
421
442
|
);
|
|
443
|
+
let pendingOrderedPage = null;
|
|
444
|
+
const emitBackfillItems = async function* (self, items) {
|
|
445
|
+
for (const item of items) {
|
|
446
|
+
const slot = extractSlot(item);
|
|
447
|
+
const key = keyOf(item);
|
|
448
|
+
if (slot < startSlot) continue;
|
|
449
|
+
if (seenItem(slot, key)) {
|
|
450
|
+
self.metrics.discardedDuplicates += 1;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
currentSlot = slot;
|
|
454
|
+
recordEmission(slot, key);
|
|
455
|
+
self.metrics.emittedBackfill += 1;
|
|
456
|
+
yield item;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const flushPendingBackfill = async function* (self) {
|
|
460
|
+
if (!pendingOrderedPage) return;
|
|
461
|
+
for await (const item of emitBackfillItems(self, pendingOrderedPage)) {
|
|
462
|
+
yield item;
|
|
463
|
+
}
|
|
464
|
+
pendingOrderedPage = null;
|
|
465
|
+
};
|
|
422
466
|
let emptyPageRetries = 0;
|
|
423
467
|
const MAX_EMPTY_PAGE_RETRIES = 10;
|
|
424
468
|
while (!backfillDone) {
|
|
@@ -429,6 +473,9 @@ var ReplayStream = class {
|
|
|
429
473
|
this.logger.error(
|
|
430
474
|
`backfill returned ${MAX_EMPTY_PAGE_RETRIES} consecutive empty pages; treating as done`
|
|
431
475
|
);
|
|
476
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
477
|
+
yield item;
|
|
478
|
+
}
|
|
432
479
|
break;
|
|
433
480
|
}
|
|
434
481
|
const backoffMs = calculateBackoff(emptyPageRetries - 1, DEFAULT_RETRY_CONFIG);
|
|
@@ -439,21 +486,18 @@ var ReplayStream = class {
|
|
|
439
486
|
continue;
|
|
440
487
|
}
|
|
441
488
|
emptyPageRetries = 0;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
489
|
+
assertBackfillPageOrder(pendingOrderedPage, page.items, extractSlot);
|
|
490
|
+
if (pendingOrderedPage !== null) {
|
|
491
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
492
|
+
yield item;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
pendingOrderedPage = [...page.items];
|
|
496
|
+
const reachedEnd = page.done || page.cursor === void 0;
|
|
497
|
+
if (reachedEnd) {
|
|
498
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
499
|
+
yield item;
|
|
452
500
|
}
|
|
453
|
-
currentSlot = slot;
|
|
454
|
-
recordEmission(slot, key);
|
|
455
|
-
this.metrics.emittedBackfill += 1;
|
|
456
|
-
yield item;
|
|
457
501
|
}
|
|
458
502
|
const duplicatesTrimmed = livePump.discardBufferedUpTo(currentSlot);
|
|
459
503
|
this.metrics.discardedDuplicates += duplicatesTrimmed;
|
|
@@ -462,13 +506,16 @@ var ReplayStream = class {
|
|
|
462
506
|
if (maxStreamSlot !== null) {
|
|
463
507
|
const catchUpSlot = maxStreamSlot > safetyMargin ? maxStreamSlot - safetyMargin : 0n;
|
|
464
508
|
if (currentSlot >= catchUpSlot) {
|
|
509
|
+
for await (const item of flushPendingBackfill(this)) {
|
|
510
|
+
yield item;
|
|
511
|
+
}
|
|
465
512
|
this.logger.info(
|
|
466
513
|
`replay reached SWITCHING threshold (currentSlot=${currentSlot}, maxStreamSlot=${maxStreamSlot}, catchUpSlot=${catchUpSlot})`
|
|
467
514
|
);
|
|
468
515
|
backfillDone = true;
|
|
469
516
|
}
|
|
470
517
|
}
|
|
471
|
-
if (
|
|
518
|
+
if (reachedEnd) backfillDone = true;
|
|
472
519
|
}
|
|
473
520
|
this.logger.info(`replay entering SWITCHING state (currentSlot=${currentSlot})`);
|
|
474
521
|
const { drained, discarded } = livePump.enableStreaming(currentSlot);
|
|
@@ -864,7 +911,7 @@ function eventBackfillFilter(startSlot, resumeAfter) {
|
|
|
864
911
|
return slotLiteralFilter("event.slot", startSlot);
|
|
865
912
|
}
|
|
866
913
|
return protobuf.create(proto.FilterSchema, {
|
|
867
|
-
expression: `event.slot > uint(${boundary.slot.toString()}) || (event.slot == uint(${boundary.slot.toString()}) && (event.block_offset > uint(${boundary.blockOffset.toString()}) || (event.block_offset == uint(${boundary.blockOffset.toString()}) && event.call_idx
|
|
914
|
+
expression: `event.slot > uint(${boundary.slot.toString()}) || (event.slot == uint(${boundary.slot.toString()}) && (event.block_offset > uint(${boundary.blockOffset.toString()}) || (event.block_offset == uint(${boundary.blockOffset.toString()}) && event.call_idx >= uint(${boundary.callIdx.toString()}))))`
|
|
868
915
|
});
|
|
869
916
|
}
|
|
870
917
|
function parseEventId(resumeAfter) {
|
|
@@ -873,23 +920,27 @@ function parseEventId(resumeAfter) {
|
|
|
873
920
|
}
|
|
874
921
|
function parseCanonicalEventId(eventId, expectedSlot) {
|
|
875
922
|
if (!eventId) return null;
|
|
876
|
-
const
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
923
|
+
const parts = eventId.split(":");
|
|
924
|
+
if (parts.length === 5) {
|
|
925
|
+
const [, slotPart, blockOffsetPart, callIdxPart, eventIdxPart] = parts;
|
|
926
|
+
const slot = BigInt(slotPart);
|
|
927
|
+
if (slot !== expectedSlot) return null;
|
|
928
|
+
return {
|
|
929
|
+
slot,
|
|
930
|
+
blockOffset: BigInt(blockOffsetPart),
|
|
931
|
+
callIdx: BigInt(callIdxPart),
|
|
932
|
+
eventIdx: BigInt(eventIdxPart)
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
886
936
|
}
|
|
887
937
|
function isAfterBoundary(event, boundary) {
|
|
888
938
|
if (event.slot !== boundary.slot) return event.slot > boundary.slot;
|
|
889
939
|
if (event.blockOffset !== boundary.blockOffset) {
|
|
890
940
|
return event.blockOffset > boundary.blockOffset;
|
|
891
941
|
}
|
|
892
|
-
return event.callIdx > boundary.callIdx;
|
|
942
|
+
if (event.callIdx !== boundary.callIdx) return event.callIdx > boundary.callIdx;
|
|
943
|
+
return event.eventIdx > boundary.eventIdx;
|
|
893
944
|
}
|
|
894
945
|
function shouldEmitLiveEvent(event, startSlot, resumeAfter) {
|
|
895
946
|
const boundary = parseEventId(resumeAfter);
|