@secondlayer/sdk 9.0.1 → 10.0.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.
@@ -115,8 +115,11 @@ interface ConsumerSink<Tx = unknown> {
115
115
  * same `rewindCursor` (the lowest fork point) but its own
116
116
  * `forkPointHeight` — scope the undo by `forkPointHeight` only (contract
117
117
  * invariant #11), and expect re-application after a crash (#10).
118
+ *
119
+ * `rewindCursor` is `null` only for a fork at genesis: clear the
120
+ * checkpoint so the next run starts from the first event.
118
121
  */
119
- rollback(forkPointHeight: number, rewindCursor: string): Promise<void>;
122
+ rollback(forkPointHeight: number, rewindCursor: string | null): Promise<void>;
120
123
  }
121
124
  /** `ctx` gains `tx` exactly when a sink is attached; without one the shape
122
125
  * is unchanged (an intersection with `unknown` is an identity, so
@@ -356,11 +359,41 @@ type StreamsEventsSubscribeParams = {
356
359
  filters?: StreamsFilterMap;
357
360
  /** Abort to unsubscribe (the returned function does the same). */
358
361
  signal?: AbortSignal;
359
- /** Called for each pushed event, in order. */
362
+ /**
363
+ * Called for each pushed event, in order. The resume cursor advances only
364
+ * after this resolves, so a handler that throws sees the same event again
365
+ * on reconnect (at-least-once). Key durable writes by `cursor`.
366
+ */
360
367
  onEvent: (event: StreamsEvent) => void | Promise<void>;
361
- /** Called on a connection error; the subscription auto-reconnects from the
362
- * last delivered cursor unless the signal has aborted. */
368
+ /**
369
+ * Called on every failure. Transport errors (dropped socket, 5xx, 429, a
370
+ * stale connection, a throwing handler) reconnect from the last handled
371
+ * cursor with exponential backoff. Errors that a retry cannot fix (401,
372
+ * 4xx, a bad signature) end the subscription and reject its `done`.
373
+ */
363
374
  onError?: (err: unknown) => void;
375
+ /**
376
+ * First reconnect pause in ms (default 1000). Doubles per consecutive
377
+ * failure up to 30 s with jitter, resets once a frame arrives, and never
378
+ * undercuts a `Retry-After`.
379
+ */
380
+ reconnectDelayMs?: number;
381
+ /**
382
+ * Reconnect when no frame (pings included) arrives for this long (default
383
+ * 60000, three server heartbeats). Catches a half-open socket that never
384
+ * errors; set it above your instance's heartbeat interval.
385
+ */
386
+ staleAfterMs?: number;
387
+ };
388
+ /**
389
+ * Handle for a live subscription: call it to unsubscribe. `done` resolves
390
+ * after unsubscribe (or the signal aborting) and rejects with the error that
391
+ * ended the loop when a retry could not fix it, so a worker can `await`
392
+ * it instead of polling for silence.
393
+ */
394
+ type StreamsSubscription = {
395
+ (): void;
396
+ done: Promise<void>;
364
397
  };
365
398
  /**
366
399
  * The checkpoint the SDK computes for a batch. Persist `cursor` inside the same
@@ -425,8 +458,11 @@ type StreamsBatchContext = ConsumerBatchContext;
425
458
  * The checkpoint for a reorg rollback. Persist `cursor` (the rewind position)
426
459
  * inside the same transaction as your rollback so the two commit atomically.
427
460
  */
461
+ /** `cursor` is the rewind position the loop resumes from after `onReorg`:
462
+ * the foot of the fork point, or `null` for a fork at genesis. Persist it
463
+ * in the same transaction as the rollback. */
428
464
  type StreamsReorgContext = {
429
- cursor: string;
465
+ cursor: string | null;
430
466
  };
431
467
  type StreamsEventsConsumeParams<
432
468
  TTx = never,
@@ -494,12 +530,23 @@ type StreamsEventsConsumeParams<
494
530
  onBatch?: (events: D extends true ? IndexEvent[] : StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>;
495
531
  /**
496
532
  * Roll your projection back to `reorg.fork_point_height`, persisting
497
- * `ctx.cursor` in the same transaction. Called once per *new* reorg
498
- * (deduped in-memory, fork-ascending) before the SDK rewinds and re-reads the
499
- * now-canonical events. Omit it to ignore reorgs (events stay canonical, but
500
- * stale rows from an orphaned fork are left in place).
533
+ * `ctx.cursor` in the same transaction. Called once per *new* reorg at or
534
+ * below the checkpoint (deduped in-memory, fork-ascending) before the SDK
535
+ * rewinds and re-reads the now-canonical events. A reorg whose fork sits
536
+ * above the checkpoint is not a rollback (nothing past it was written) and
537
+ * is skipped. Omit it to ignore reorgs (events stay canonical, but stale
538
+ * rows from an orphaned fork are left in place).
539
+ *
540
+ * With `onReorg` or a `sink` attached, every empty page also polls
541
+ * `reorgs.list` (one extra request per idle poll) so a fork that lands
542
+ * while the consumer idles at the tip is rolled back too.
501
543
  */
502
544
  onReorg?: (reorg: StreamsReorg, ctx: StreamsReorgContext) => Promise<void> | void;
545
+ /** Deepest rewind one reorg may make below the checkpoint before the loop
546
+ * refuses with `ValidationError`. Default 1000 blocks. The fork point is
547
+ * server supplied and drives a delete on every declared table, so raise
548
+ * this only for a source you trust. */
549
+ maxRollbackDepth?: number;
503
550
  /** Page-fetch retries after the first failure (429/5xx/network only —
504
551
  * 4xx and handler throws always propagate). Default 3; `0` disables. */
505
552
  retryCount?: number;
@@ -562,8 +609,16 @@ type StreamsEventsReplayParams = {
562
609
  * Called once per finalized dump file, in block order, before live tailing.
563
610
  * Process the parquet with your own tooling (e.g. DuckDB) — the SDK does not
564
611
  * decode parquet. Use `client.dumps.download(file)` to fetch + verify bytes.
612
+ *
613
+ * Delivery is file-granular and at-least-once: a file whose range straddles
614
+ * `from` is handed over whole. `ctx.from` is that cursor (`null` from
615
+ * genesis); skip rows at or below it, or key rows by `cursor` so a re-run
616
+ * is an idempotent no-op. Files ending at or below `from` are not handed
617
+ * over at all.
565
618
  */
566
- onDumpFile: (file: StreamsDumpFile) => Promise<void> | void;
619
+ onDumpFile: (file: StreamsDumpFile, ctx: {
620
+ from: string | null;
621
+ }) => Promise<void> | void;
567
622
  /** Called per live page after the dump phase, like `consume`. */
568
623
  onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined;
569
624
  /**
@@ -699,9 +754,11 @@ type StreamsClient = {
699
754
  * Subscribe to the real-time SSE push surface. Calls `onEvent` for each new
700
755
  * canonical event as the server pushes it (chain cadence, not poll-bounded),
701
756
  * and verifies each frame's inline ed25519 signature when the client was
702
- * created with `verify`. Returns an unsubscribe function.
757
+ * created with `verify`. Returns an unsubscribe function whose `done`
758
+ * settles when the loop ends. Not reorg-aware: an event delivered then
759
+ * orphaned is never retracted. Durable writers use `consume()`.
703
760
  */
704
- subscribe(params: StreamsEventsSubscribeParams): () => void;
761
+ subscribe(params: StreamsEventsSubscribeParams): StreamsSubscription;
705
762
  };
706
763
  blocks: {
707
764
  events(heightOrHash: number | string): Promise<StreamsEventsListEnvelope>;
@@ -717,6 +774,8 @@ type StreamsClient = {
717
774
  type CreateStreamsClientOptions = {
718
775
  apiKey?: string;
719
776
  baseUrl?: string;
777
+ /** Deploy origin label sent as `x-sl-origin` (telemetry). Defaults to `cli`. */
778
+ origin?: "cli" | "mcp" | "session";
720
779
  fetchImpl?: FetchLike2;
721
780
  /**
722
781
  * Public base URL for bulk parquet dumps (the R2/CDN bucket root). Required
@@ -844,26 +903,30 @@ declare class ApiError extends SecondLayerError {
844
903
  body?: unknown;
845
904
  constructor(status: number, message: string, body?: unknown, code?: string, options?: SecondLayerErrorOptions);
846
905
  }
847
- /** Thrown on a 401 — by both the platform clients and Streams. */
906
+ /** Thrown on a 401 by both the instance clients and Streams. Carries the
907
+ * server's `{error, code}` envelope when it sent one, so a revoked token reads
908
+ * differently from a missing one. */
848
909
  declare class AuthError extends ApiError {
849
910
  readonly status: 401;
850
- constructor(message?: string);
911
+ constructor(message?: string, body?: unknown, code?: string);
851
912
  }
852
- /** Thrown on a 429 — by both the platform clients and Streams. `retryable`,
913
+ /** Thrown on a 429 by both the instance clients and Streams. `retryable`,
853
914
  * with `retryAfterSeconds` parsed from the `Retry-After` header when sent. */
854
915
  declare class RateLimitError extends ApiError {
855
916
  /** Raw `Retry-After` header value (seconds or HTTP-date). */
856
917
  readonly retryAfter?: string | undefined;
857
918
  readonly status: 429;
858
- constructor(message?: string, retryAfter?: string | undefined);
919
+ constructor(message?: string, retryAfter?: string | undefined, body?: unknown, code?: string);
859
920
  }
860
- /** Thrown on a 4xx the caller can fix (bad cursor, bad params). Never retried. */
921
+ /** Thrown on a 4xx the caller can fix (bad cursor, bad params). Never retried.
922
+ * `code` is the server's machine-readable code when the envelope carried one. */
861
923
  declare class ValidationError extends ApiError {
862
- constructor(message: string, status: number, body?: unknown);
924
+ constructor(message: string, status: number, body?: unknown, code?: string);
863
925
  }
864
- /** Thrown on a 5xx from the Streams API. `retryable`. */
926
+ /** Thrown on a 5xx from the Streams API, and on a failed signing-key fetch.
927
+ * `retryable`: the page retry policy tries again. */
865
928
  declare class StreamsServerError extends ApiError {
866
- constructor(message: string, status: number, body?: unknown);
929
+ constructor(message: string, status: number, body?: unknown, code?: string);
867
930
  }
868
931
  /** Thrown when response signature verification is enabled and fails. */
869
932
  declare class StreamsSignatureError extends SecondLayerError {
@@ -875,7 +938,9 @@ import { DecodedEventColumns, DecodedEventRow, DecodedFtBurn, DecodedFtBurnPaylo
875
938
  * Helpers for Streams cursors. A cursor is the opaque `<block>:<index>` string
876
939
  * that marks a position in the event stream; treat the format as an
877
940
  * implementation detail and go through these helpers instead of string-building
878
- * it at call sites.
941
+ * it at call sites. Encode/decode and the rewind sentinel come from the
942
+ * canonical codec in `@secondlayer/shared` so the SDK cannot accept a spelling
943
+ * the server would 400, or rewind to a different foot than Index/Streams.
879
944
  */
880
945
  declare const Cursor: {
881
946
  /**
@@ -887,18 +952,23 @@ declare const Cursor: {
887
952
  * is `height`: the new canonical block at `height` carries a fresh first
888
953
  * event at `(height, 0)` that the consumer MUST re-read.
889
954
  *
890
- * Encoded as `${height-1}:${SENTINEL}` rather than the seemingly-natural
955
+ * Encoded as `blockEndCursor(height - 1)` rather than the seemingly-natural
891
956
  * `${height}:0` — that earlier form was an off-by-one: being exclusive, it
892
957
  * skipped `(height, 0)`, silently dropping the fork block's first row on
893
958
  * every reorg. The sentinel is int4 max (the `event_index`/`tx_index` column
894
959
  * type), larger than any real index, so nothing at `height - 1` survives the
895
960
  * keyset and the next returned row is exactly `(height, 0)`.
961
+ *
962
+ * `atHeight(0)` is `null`: the pre-genesis position both consume loops
963
+ * accept as "read everything from the first event". There is no string
964
+ * cursor below `0:0`, and `0:0` itself is exclusive, so returning it here
965
+ * skipped the genesis event.
896
966
  */
897
- atHeight(height: number): string;
967
+ atHeight(height: number): string | null;
898
968
  /** Parse a `<block>:<index>` cursor. Throws `ValidationError` if malformed. */
899
969
  parse(cursor: string): {
900
970
  blockHeight: number;
901
971
  eventIndex: number;
902
972
  };
903
973
  };
904
- export { isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decode, createStreamsClient, ValidationError, StreamsTip, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorgContext, StreamsReorg, StreamsEventsSubscribeParams, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsConsumeParams, StreamsClient, StreamsCanonicalBlock, StreamsBatchContext, StreamsBatch, STREAMS_EVENT_TYPES, RateLimitError, NftTransferPayload2 as NftTransferPayload, NftTransferEvent, FtTransferPayload2 as FtTransferPayload, FtTransferEvent, FetchLike2 as FetchLike, DecodedStxTransferPayload, DecodedStxTransfer, DecodedStxMintPayload, DecodedStxMint, DecodedStxLockPayload, DecodedStxLock, DecodedStxBurnPayload, DecodedStxBurn, DecodedPrintValue, DecodedPrintPayload, DecodedPrint, DecodedNftTransferPayload, DecodedNftTransfer, DecodedNftMintPayload, DecodedNftMint, DecodedNftBurnPayload, DecodedNftBurn, DecodedFtTransferPayload, DecodedFtTransfer, DecodedFtMintPayload, DecodedFtMint, DecodedFtBurnPayload, DecodedFtBurn, DecodedEventRow, DecodedEventColumns, Cursor, ConsumerBatchContext, AuthError };
974
+ export { isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decode, createStreamsClient, ValidationError, StreamsTip, StreamsSubscription, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorgContext, StreamsReorg, StreamsEventsSubscribeParams, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsConsumeParams, StreamsClient, StreamsCanonicalBlock, StreamsBatchContext, StreamsBatch, STREAMS_EVENT_TYPES, RateLimitError, NftTransferPayload2 as NftTransferPayload, NftTransferEvent, FtTransferPayload2 as FtTransferPayload, FtTransferEvent, FetchLike2 as FetchLike, DecodedStxTransferPayload, DecodedStxTransfer, DecodedStxMintPayload, DecodedStxMint, DecodedStxLockPayload, DecodedStxLock, DecodedStxBurnPayload, DecodedStxBurn, DecodedPrintValue, DecodedPrintPayload, DecodedPrint, DecodedNftTransferPayload, DecodedNftTransfer, DecodedNftMintPayload, DecodedNftMint, DecodedNftBurnPayload, DecodedNftBurn, DecodedFtTransferPayload, DecodedFtTransfer, DecodedFtMintPayload, DecodedFtMint, DecodedFtBurnPayload, DecodedFtBurn, DecodedEventRow, DecodedEventColumns, Cursor, ConsumerBatchContext, AuthError };