@secondlayer/sdk 6.48.0 → 6.49.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.d.ts CHANGED
@@ -773,12 +773,21 @@ type ConsumerBatchContext<
773
773
  > = {
774
774
  /** Checkpoint to commit alongside your rows. */
775
775
  cursor: string | null;
776
- /** Highest canonical block the sweep has reached. `null` before the first
777
- * row of a fresh consume. */
776
+ /** Highest canonical block a row was DELIVERED from. `null` before the
777
+ * first row of a fresh consume. With a sparse filter this parks at the
778
+ * last matching event — it measures event recency, not progress. */
778
779
  height: number | null;
780
+ /** Highest canonical block the sweep has VERIFIED through: an empty page
781
+ * is the server confirming nothing matches between the cursor and the
782
+ * tip, so a caught-up tail scans to the tip even when `height` is far
783
+ * below it. Rolls back with reorg rewinds. */
784
+ scannedHeight: number | null;
779
785
  /** Chain tip as of this page's read. */
780
786
  tipHeight: number;
781
- /** `tipHeight - height`, floored at 0. `null` until `height` is known. */
787
+ /** `tipHeight - scannedHeight`, floored at 0 — the consumer's actual
788
+ * backlog. `null` until a position is known. A caught-up consumer on a
789
+ * quiet contract reads ~0 here while `tipHeight - height` grows — that
790
+ * difference is event age, not lag. */
782
791
  blocksBehind: number | null;
783
792
  /**
784
793
  * The full tip for this page — `StreamsTip` or `IndexTip` depending on the
@@ -3324,7 +3333,10 @@ interface ConsumerHealth {
3324
3333
  * "are we near the tip": during a genesis backfill the loop is millions of
3325
3334
  * blocks behind and perfectly healthy, so lag can't be the signal. A wedged
3326
3335
  * loop stops reporting pages and flips to 503 — that's what platforms
3327
- * should restart on. `blocks_behind` still ships in the body for dashboards.
3336
+ * should restart on. The body carries position for dashboards:
3337
+ * `blocks_behind` is the actual backlog (`tip - scanned_height`, ~0 for a
3338
+ * caught-up tail even on a quiet contract), while `block_height` is the
3339
+ * last DELIVERED row — its distance from the tip is event age, not lag.
3328
3340
  */
3329
3341
  declare function consumerHealth(options?: {
3330
3342
  staleAfterMs?: number;
package/dist/index.js CHANGED
@@ -597,13 +597,24 @@ function assertSinkModeCompatible(sink, finalizedOnly) {
597
597
  throw new ValidationError("This sink declares capabilities.finalizedOnly — it cannot roll back reorged rows, and following the unfinalized tip would corrupt it at the first fork. Pass finalizedOnly: true to consume(), or use a sink that implements rollback.", 400);
598
598
  }
599
599
  }
600
- function batchContext(cursor, height, tip, reorgs = []) {
600
+ function resumeHeight(cursor) {
601
+ if (cursor === null)
602
+ return null;
603
+ try {
604
+ return Cursor.parse(cursor).blockHeight;
605
+ } catch {
606
+ return null;
607
+ }
608
+ }
609
+ function batchContext(cursor, height, tip, reorgs = [], scannedHeight = null) {
601
610
  const tipHeight = tip.block_height;
611
+ const position = scannedHeight ?? height;
602
612
  return {
603
613
  cursor,
604
614
  height,
615
+ scannedHeight: position,
605
616
  tipHeight,
606
- blocksBehind: height === null ? null : Math.max(0, tipHeight - height),
617
+ blocksBehind: position === null ? null : Math.max(0, tipHeight - position),
607
618
  tip,
608
619
  reorgs
609
620
  };
@@ -669,7 +680,8 @@ async function consumeStreamsEvents(opts) {
669
680
  const handledReorgs = new Set;
670
681
  let pages = 0;
671
682
  let emptyPolls = 0;
672
- let height = null;
683
+ let height = resumeHeight(cursor);
684
+ let scanned = height;
673
685
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
674
686
  const envelope = await fetchPageWithRetry(() => opts.fetchEvents({
675
687
  cursor,
@@ -701,6 +713,7 @@ async function consumeStreamsEvents(opts) {
701
713
  }
702
714
  cursor = rewind;
703
715
  height = forkPoint > 0 ? forkPoint - 1 : null;
716
+ scanned = height;
704
717
  emptyPolls = 0;
705
718
  continue;
706
719
  }
@@ -708,7 +721,14 @@ async function consumeStreamsEvents(opts) {
708
721
  const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
709
722
  const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
710
723
  height = emitted.at(-1)?.block_height ?? height;
711
- const ctx = batchContext(checkpoint, height, envelope.tip, envelope.reorgs);
724
+ if (emitted.length > 0) {
725
+ scanned = height;
726
+ } else if (checkpoint !== null && checkpoint !== cursor) {
727
+ scanned = resumeHeight(checkpoint) ?? scanned;
728
+ } else if (envelope.events.length === 0) {
729
+ scanned = envelope.tip.block_height;
730
+ }
731
+ const ctx = batchContext(checkpoint ?? cursor, height, envelope.tip, envelope.reorgs, scanned);
712
732
  opts.onProgress?.(ctx);
713
733
  let nextCursor;
714
734
  if (opts.sink) {
@@ -836,7 +856,8 @@ async function consumeIndexFeed(opts) {
836
856
  const handledReorgs = new Set;
837
857
  let pages = 0;
838
858
  let emptyPolls = 0;
839
- let height = null;
859
+ let height = resumeHeight(cursor);
860
+ let scanned = height;
840
861
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
841
862
  const envelope = await fetchPageWithRetry(() => opts.fetchPage({
842
863
  cursor,
@@ -862,6 +883,7 @@ async function consumeIndexFeed(opts) {
862
883
  }
863
884
  cursor = rewind;
864
885
  height = forkPoint > 0 ? forkPoint - 1 : null;
886
+ scanned = height;
865
887
  emptyPolls = 0;
866
888
  continue;
867
889
  }
@@ -870,7 +892,14 @@ async function consumeIndexFeed(opts) {
870
892
  const emitted = finalizedOnly ? items.filter((item) => item.block_height <= envelope.tip.finalized_height) : items;
871
893
  const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
872
894
  height = emitted.at(-1)?.block_height ?? height;
873
- const ctx = batchContext(checkpoint, height, envelope.tip, envelope.reorgs);
895
+ if (emitted.length > 0) {
896
+ scanned = height;
897
+ } else if (checkpoint !== null && checkpoint !== cursor) {
898
+ scanned = resumeHeight(checkpoint) ?? scanned;
899
+ } else {
900
+ scanned = finalizedOnly ? envelope.tip.finalized_height : envelope.tip.block_height;
901
+ }
902
+ const ctx = batchContext(checkpoint ?? cursor, height, envelope.tip, envelope.reorgs, scanned);
874
903
  opts.onProgress?.(ctx);
875
904
  let nextCursor;
876
905
  if (opts.sink) {
@@ -2659,6 +2688,7 @@ function consumerHealth(options = {}) {
2659
2688
  ok,
2660
2689
  checkpoint: lastCtx?.cursor ?? null,
2661
2690
  block_height: lastCtx?.height ?? null,
2691
+ scanned_height: lastCtx?.scannedHeight ?? null,
2662
2692
  tip_height: lastCtx?.tipHeight ?? null,
2663
2693
  blocks_behind: lastCtx?.blocksBehind ?? null,
2664
2694
  idle_s: Math.floor(idleMs / 1000),
@@ -2899,5 +2929,5 @@ export {
2899
2929
  ApiError
2900
2930
  };
2901
2931
 
2902
- //# debugId=AADEB8FAB0EE368C64756E2164756E21
2932
+ //# debugId=7E3377791B68431D64756E2164756E21
2903
2933
  //# sourceMappingURL=index.js.map