@thru/replay 0.2.18 → 0.2.20
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 +51 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +51 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -165,6 +165,11 @@ interface EventReplayOptions {
|
|
|
165
165
|
/** Factory to create fresh clients on reconnection. Enables robust reconnection. */
|
|
166
166
|
clientFactory?: () => EventSource;
|
|
167
167
|
startSlot: Slot;
|
|
168
|
+
/** Last fully processed event. Allows backfill to resume within a slot. */
|
|
169
|
+
resumeAfter?: {
|
|
170
|
+
slot: Slot;
|
|
171
|
+
eventId: string;
|
|
172
|
+
};
|
|
168
173
|
safetyMargin?: bigint;
|
|
169
174
|
pageSize?: number;
|
|
170
175
|
filter?: Filter;
|
package/dist/index.d.ts
CHANGED
|
@@ -165,6 +165,11 @@ interface EventReplayOptions {
|
|
|
165
165
|
/** Factory to create fresh clients on reconnection. Enables robust reconnection. */
|
|
166
166
|
clientFactory?: () => EventSource;
|
|
167
167
|
startSlot: Slot;
|
|
168
|
+
/** Last fully processed event. Allows backfill to resume within a slot. */
|
|
169
|
+
resumeAfter?: {
|
|
170
|
+
slot: Slot;
|
|
171
|
+
eventId: string;
|
|
172
|
+
};
|
|
168
173
|
safetyMargin?: bigint;
|
|
169
174
|
pageSize?: number;
|
|
170
175
|
filter?: Filter;
|
package/dist/index.mjs
CHANGED
|
@@ -796,7 +796,7 @@ function createEventReplay(options) {
|
|
|
796
796
|
orderBy: PAGE_ORDER_ASC3,
|
|
797
797
|
pageToken: cursor
|
|
798
798
|
});
|
|
799
|
-
const baseFilter =
|
|
799
|
+
const baseFilter = eventBackfillFilter(startSlot, options.resumeAfter);
|
|
800
800
|
const mergedFilter = combineFilters(baseFilter, options.filter);
|
|
801
801
|
const response = await client.listEvents(
|
|
802
802
|
create(ListEventsRequestSchema, {
|
|
@@ -807,13 +807,19 @@ function createEventReplay(options) {
|
|
|
807
807
|
return backfillPage(response.events, response.page);
|
|
808
808
|
};
|
|
809
809
|
const createSubscribeLive = (client) => (startSlot) => {
|
|
810
|
-
const mergedFilter = combineFilters(
|
|
810
|
+
const mergedFilter = combineFilters(
|
|
811
|
+
slotLiteralFilter("event.slot", startSlot),
|
|
812
|
+
options.filter
|
|
813
|
+
);
|
|
811
814
|
const request = create(StreamEventsRequestSchema, {
|
|
812
815
|
filter: mergedFilter
|
|
813
816
|
});
|
|
814
817
|
return mapAsyncIterable(
|
|
815
818
|
client.streamEvents(request),
|
|
816
|
-
(resp) =>
|
|
819
|
+
(resp) => {
|
|
820
|
+
const event = streamResponseToEvent(resp);
|
|
821
|
+
return shouldEmitLiveEvent(event, startSlot, options.resumeAfter) ? event : null;
|
|
822
|
+
}
|
|
817
823
|
);
|
|
818
824
|
};
|
|
819
825
|
const onReconnect = options.clientFactory ? () => {
|
|
@@ -851,6 +857,48 @@ function eventKey(event) {
|
|
|
851
857
|
const slotPart = event.slot?.toString() ?? "0";
|
|
852
858
|
return `${slotPart}:${event.callIdx ?? 0}`;
|
|
853
859
|
}
|
|
860
|
+
function eventBackfillFilter(startSlot, resumeAfter) {
|
|
861
|
+
const boundary = parseEventId(resumeAfter);
|
|
862
|
+
if (!boundary || startSlot > boundary.slot) {
|
|
863
|
+
return slotLiteralFilter("event.slot", startSlot);
|
|
864
|
+
}
|
|
865
|
+
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 > uint(${boundary.callIdx.toString()}))))`
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function parseEventId(resumeAfter) {
|
|
870
|
+
if (!resumeAfter?.eventId) return null;
|
|
871
|
+
return parseCanonicalEventId(resumeAfter.eventId, resumeAfter.slot);
|
|
872
|
+
}
|
|
873
|
+
function parseCanonicalEventId(eventId, expectedSlot) {
|
|
874
|
+
if (!eventId) return null;
|
|
875
|
+
const match = /^ts(\d+)_(\d+)_(\d+)$/.exec(eventId);
|
|
876
|
+
if (!match) return null;
|
|
877
|
+
const [slotPart, blockOffsetPart, callIdxPart] = match.slice(1);
|
|
878
|
+
const slot = BigInt(slotPart);
|
|
879
|
+
if (slot !== expectedSlot) return null;
|
|
880
|
+
return {
|
|
881
|
+
slot,
|
|
882
|
+
blockOffset: BigInt(blockOffsetPart),
|
|
883
|
+
callIdx: BigInt(callIdxPart)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function isAfterBoundary(event, boundary) {
|
|
887
|
+
if (event.slot !== boundary.slot) return event.slot > boundary.slot;
|
|
888
|
+
if (event.blockOffset !== boundary.blockOffset) {
|
|
889
|
+
return event.blockOffset > boundary.blockOffset;
|
|
890
|
+
}
|
|
891
|
+
return event.callIdx > boundary.callIdx;
|
|
892
|
+
}
|
|
893
|
+
function shouldEmitLiveEvent(event, startSlot, resumeAfter) {
|
|
894
|
+
const boundary = parseEventId(resumeAfter);
|
|
895
|
+
if (!boundary || startSlot > boundary.slot) return true;
|
|
896
|
+
const eventSlot = event.slot ?? 0n;
|
|
897
|
+
if (eventSlot > boundary.slot) return true;
|
|
898
|
+
if (eventSlot < boundary.slot) return false;
|
|
899
|
+
const eventPosition = parseCanonicalEventId(event.eventId, boundary.slot);
|
|
900
|
+
return eventPosition ? isAfterBoundary(eventPosition, boundary) : false;
|
|
901
|
+
}
|
|
854
902
|
|
|
855
903
|
// src/page-assembler.ts
|
|
856
904
|
var PAGE_SIZE = 4096;
|