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