@thru/replay 0.2.19 → 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.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 = slotLiteralFilter("event.slot", startSlot);
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(slotLiteralFilter("event.slot", startSlot), options.filter);
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) => streamResponseToEvent(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;