@secondlayer/sdk 6.37.1 → 6.38.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 +177 -18
- package/dist/index.js +265 -78
- package/dist/index.js.map +11 -9
- package/dist/sinks/kysely.d.ts +108 -0
- package/dist/sinks/kysely.js +236 -0
- package/dist/sinks/kysely.js.map +11 -0
- package/dist/streams/index.d.ts +188 -6
- package/dist/streams/index.js +199 -71
- package/dist/streams/index.js.map +8 -7
- package/dist/subgraphs/index.d.ts +118 -15
- package/dist/subgraphs/index.js +222 -16
- package/dist/subgraphs/index.js.map +9 -7
- package/package.json +18 -3
|
@@ -316,6 +316,55 @@ interface TransactionProof {
|
|
|
316
316
|
reward_set: RewardSet
|
|
317
317
|
};
|
|
318
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* A consumer sink: the destination adapter a consume loop writes through.
|
|
321
|
+
*
|
|
322
|
+
* Without a sink, the user owns the three hard parts of a durable indexer —
|
|
323
|
+
* checkpoint persistence, rows+cursor atomicity, and reorg rollback — and
|
|
324
|
+
* the reasoning that makes them safe lives in doc comments. A sink owns all
|
|
325
|
+
* three: the loop loads its committed cursor, hands the handler a
|
|
326
|
+
* transaction, commits rows AND cursor atomically, and rolls back reorgs
|
|
327
|
+
* without any user code. Crucially, a sink also closes the silent-skip
|
|
328
|
+
* hazard where omitting `onReorg` meant reorgs were ignored forever: with a
|
|
329
|
+
* sink attached, rollback is unconditional.
|
|
330
|
+
*
|
|
331
|
+
* The interface is dependency-free on purpose: implementations with real
|
|
332
|
+
* database drivers live behind subpath exports (`@secondlayer/sdk/sinks/*`)
|
|
333
|
+
* so the root entry ships no DB dependency.
|
|
334
|
+
*/
|
|
335
|
+
interface ConsumerSink<Tx = unknown> {
|
|
336
|
+
/** Phantom marker carrying `Tx` in a directly-inferable position, so the
|
|
337
|
+
* consume loops' `TTx` type parameter resolves from the `sink` option
|
|
338
|
+
* (never set at runtime). */
|
|
339
|
+
readonly _tx?: Tx;
|
|
340
|
+
/** The committed checkpoint, or `null` on first run. Called once, before
|
|
341
|
+
* the first page. Implementations may create their checkpoint storage
|
|
342
|
+
* here and SHOULD validate their rollback preconditions (e.g. that every
|
|
343
|
+
* declared table carries the height column). */
|
|
344
|
+
loadCursor(): Promise<string | null>;
|
|
345
|
+
/**
|
|
346
|
+
* Apply one batch atomically: open a transaction, run `write(tx)` (the
|
|
347
|
+
* user's inserts), and commit the rows AND `cursor` together. A throw
|
|
348
|
+
* from `write` must abort the whole transaction — leaving neither rows
|
|
349
|
+
* nor cursor, so a crashed batch is simply re-read on restart.
|
|
350
|
+
*/
|
|
351
|
+
commitBatch(cursor: string, write: (tx: Tx) => Promise<void> | void): Promise<void>;
|
|
352
|
+
/**
|
|
353
|
+
* Roll the projection back to the fork: delete everything AT OR ABOVE
|
|
354
|
+
* `forkPointHeight` (inclusive `>=` — the new chain re-supplies the fork
|
|
355
|
+
* block) and commit `rewindCursor` in the SAME transaction. Deleting
|
|
356
|
+
* without the rewound cursor is the classic silent-gap bug: a crash
|
|
357
|
+
* between the two writes resumes above the fork and the deleted range is
|
|
358
|
+
* never re-read.
|
|
359
|
+
*/
|
|
360
|
+
rollback(forkPointHeight: number, rewindCursor: string): Promise<void>;
|
|
361
|
+
}
|
|
362
|
+
/** `ctx` gains `tx` exactly when a sink is attached; without one the shape
|
|
363
|
+
* is unchanged (an intersection with `unknown` is an identity, so
|
|
364
|
+
* contextual typing of `onBatch` callbacks never degrades to a union). */
|
|
365
|
+
type WithSinkTx<TTx> = [TTx] extends [never] ? unknown : {
|
|
366
|
+
tx: TTx
|
|
367
|
+
};
|
|
319
368
|
import { StreamsEventType } from "@secondlayer/shared";
|
|
320
369
|
/** A Clarity value as Streams serves it: the canonical hex string, a typed
|
|
321
370
|
* object carrying that hex (`{ hex }`), or a decoded Clarity-JSON object.
|
|
@@ -558,8 +607,28 @@ type StreamsBatchContext = ConsumerBatchContext;
|
|
|
558
607
|
type StreamsReorgContext = {
|
|
559
608
|
cursor: string
|
|
560
609
|
};
|
|
561
|
-
type StreamsEventsConsumeParams
|
|
610
|
+
type StreamsEventsConsumeParams<
|
|
611
|
+
TTx = never,
|
|
612
|
+
D extends boolean = false
|
|
613
|
+
> = {
|
|
562
614
|
fromCursor?: string | null
|
|
615
|
+
/**
|
|
616
|
+
* Deliver events pre-decoded as the flat, `event_type`-discriminated rows
|
|
617
|
+
* Index serves (`IndexEvent`), so Streams consumption reads identically
|
|
618
|
+
* to Index consumption — no guard+decode pairs in your handler.
|
|
619
|
+
*/
|
|
620
|
+
decoded?: D
|
|
621
|
+
/**
|
|
622
|
+
* Destination adapter that owns the checkpoint + rollback transaction
|
|
623
|
+
* (e.g. `kyselySink` from `@secondlayer/sdk/sinks/kysely`). With a sink:
|
|
624
|
+
* the loop resumes from the sink's committed cursor, `onBatch` receives
|
|
625
|
+
* `ctx.tx` and must write ONLY through it (rows and cursor commit in one
|
|
626
|
+
* transaction), reorg rollback is automatic, and `onBatch`'s return value
|
|
627
|
+
* is ignored.
|
|
628
|
+
*/
|
|
629
|
+
sink?: ConsumerSink<TTx>
|
|
630
|
+
/** Fires once per page, before `onBatch` and before any early return. */
|
|
631
|
+
onProgress?: (ctx: ConsumerBatchContext) => void
|
|
563
632
|
mode?: "tail" | "bounded"
|
|
564
633
|
/**
|
|
565
634
|
* Emit only finalized (immutable) events and never surface reorgs. The SDK
|
|
@@ -580,7 +649,7 @@ type StreamsEventsConsumeParams = {
|
|
|
580
649
|
* transaction as your writes. Returning a cursor overrides `ctx.cursor` as
|
|
581
650
|
* the resume point (advanced manual control); returning nothing uses it.
|
|
582
651
|
*/
|
|
583
|
-
onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>
|
|
652
|
+
onBatch: (events: D extends true ? IndexEvent[] : StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>
|
|
584
653
|
/**
|
|
585
654
|
* Roll your projection back to `reorg.fork_point_height`, persisting
|
|
586
655
|
* `ctx.cursor` in the same transaction. Called once per *new* reorg
|
|
@@ -737,7 +806,12 @@ type StreamsClient = {
|
|
|
737
806
|
* The consumer also exits when `maxPages`, `maxEmptyPolls`, or `signal`
|
|
738
807
|
* stops it.
|
|
739
808
|
*/
|
|
740
|
-
consume
|
|
809
|
+
consume<
|
|
810
|
+
TTx = never,
|
|
811
|
+
D extends boolean = false
|
|
812
|
+
>(params: StreamsEventsConsumeParams<TTx, D> & {
|
|
813
|
+
sink?: ConsumerSink<TTx>
|
|
814
|
+
}): Promise<StreamsEventsConsumeResult>
|
|
741
815
|
/**
|
|
742
816
|
* Backfill from bulk dumps, then continue live from the dump→live seam in
|
|
743
817
|
* one call. Iterates finalized dump files (via `onDumpFile`) in block
|
|
@@ -818,16 +892,18 @@ type IndexFeedEnvelope = {
|
|
|
818
892
|
};
|
|
819
893
|
/** Consumer options shared by `index.events.consume` and
|
|
820
894
|
* `index.contractCalls.consume`. Same contract as the Streams consumer:
|
|
821
|
-
* commit your writes inside `onBatch`, return the cursor you committed
|
|
895
|
+
* commit your writes inside `onBatch`, return the cursor you committed —
|
|
896
|
+
* or attach a `sink` and let it own checkpointing and rollback entirely. */
|
|
822
897
|
type IndexConsumeOptions<
|
|
823
898
|
TItem extends IndexFeedItem,
|
|
824
|
-
TEnvelope extends IndexFeedEnvelope
|
|
899
|
+
TEnvelope extends IndexFeedEnvelope,
|
|
900
|
+
TTx = never
|
|
825
901
|
> = {
|
|
826
902
|
/** Resume from a committed checkpoint. Without it (and without
|
|
827
903
|
* `fromHeight`) the API serves only the recent default window. */
|
|
828
904
|
fromCursor?: string | null
|
|
829
905
|
/** Start a fresh sweep at this height (e.g. `0` for genesis backfill).
|
|
830
|
-
* Ignored once a cursor exists. */
|
|
906
|
+
* Ignored once a cursor exists (including a sink's committed cursor). */
|
|
831
907
|
fromHeight?: number
|
|
832
908
|
/** `tail` (default) keeps polling at the tip; `bounded` returns on the
|
|
833
909
|
* first empty page. */
|
|
@@ -837,7 +913,20 @@ type IndexConsumeOptions<
|
|
|
837
913
|
* never reorgs, so `onReorg` is skipped entirely. */
|
|
838
914
|
finalizedOnly?: boolean
|
|
839
915
|
batchSize?: number
|
|
840
|
-
|
|
916
|
+
/**
|
|
917
|
+
* Destination adapter that owns the checkpoint + rollback transaction
|
|
918
|
+
* (e.g. `kyselySink` from `@secondlayer/sdk/sinks/kysely`). With a sink:
|
|
919
|
+
* the loop resumes from the sink's committed cursor, `onBatch` receives
|
|
920
|
+
* `ctx.tx` and must write ONLY through it (rows and cursor commit in one
|
|
921
|
+
* transaction — a throw aborts both), reorg rollback is automatic (no
|
|
922
|
+
* `onReorg` needed), and `onBatch`'s return value is ignored.
|
|
923
|
+
*/
|
|
924
|
+
sink?: ConsumerSink<TTx>
|
|
925
|
+
/** Fires once per page, before `onBatch` and before any early return —
|
|
926
|
+
* an empty page still proves the loop is alive. Feed it to
|
|
927
|
+
* `consumerHealth().record`. */
|
|
928
|
+
onProgress?: (ctx: ConsumerBatchContext) => void
|
|
929
|
+
onBatch: (items: TItem[], envelope: TEnvelope, ctx: ConsumerBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>
|
|
841
930
|
onReorg?: (reorg: IndexReorg, ctx: {
|
|
842
931
|
cursor: string
|
|
843
932
|
}) => Promise<void> | void
|
|
@@ -1103,7 +1192,10 @@ type EventsWalkParams<T extends IndexEventType = IndexEventType> = Omit<EventsLi
|
|
|
1103
1192
|
batchSize?: number
|
|
1104
1193
|
signal?: AbortSignal
|
|
1105
1194
|
};
|
|
1106
|
-
type EventsConsumeParams<
|
|
1195
|
+
type EventsConsumeParams<
|
|
1196
|
+
T extends IndexEventType = IndexEventType,
|
|
1197
|
+
TTx = never
|
|
1198
|
+
> = Omit<EventsListParams<T>, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexEventOf<T>, EventsEnvelope<T>, TTx>;
|
|
1107
1199
|
type IndexContractCall = {
|
|
1108
1200
|
cursor: string
|
|
1109
1201
|
block_height: number
|
|
@@ -1143,7 +1235,7 @@ type ContractCallsWalkParams = Omit<ContractCallsListParams, "limit"> & {
|
|
|
1143
1235
|
batchSize?: number
|
|
1144
1236
|
signal?: AbortSignal
|
|
1145
1237
|
};
|
|
1146
|
-
type ContractCallsConsumeParams = Omit<ContractCallsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexContractCall, ContractCallsEnvelope>;
|
|
1238
|
+
type ContractCallsConsumeParams<TTx = never> = Omit<ContractCallsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexContractCall, ContractCallsEnvelope, TTx>;
|
|
1147
1239
|
/** One canonical block in the sync map. Lean by design — block + parent hash
|
|
1148
1240
|
* for chain linkage, burn anchor for Bitcoin confirmations. Use `blocks` for
|
|
1149
1241
|
* the full block resource. */
|
|
@@ -1614,8 +1706,8 @@ type SbtcEventsWalkParams = Omit<SbtcEventsListParams, "limit"> & {
|
|
|
1614
1706
|
batchSize?: number
|
|
1615
1707
|
signal?: AbortSignal
|
|
1616
1708
|
};
|
|
1617
|
-
type SbtcEventsConsumeParams = Omit<SbtcEventsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexSbtcEvent, SbtcEventsEnvelope>;
|
|
1618
|
-
type SbtcDepositsConsumeParams = Omit<SbtcDepositsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexSbtcDeposit, SbtcDepositsEnvelope>;
|
|
1709
|
+
type SbtcEventsConsumeParams<TTx = never> = Omit<SbtcEventsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexSbtcEvent, SbtcEventsEnvelope, TTx>;
|
|
1710
|
+
type SbtcDepositsConsumeParams<TTx = never> = Omit<SbtcDepositsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexSbtcDeposit, SbtcDepositsEnvelope, TTx>;
|
|
1619
1711
|
/** `index.sbtc` — the decoded sBTC peg surface (deposits, withdrawals, raw
|
|
1620
1712
|
* events, scoreboard). The only productized decoded sBTC peg feed on Stacks. */
|
|
1621
1713
|
interface SbtcResource {
|
|
@@ -1624,7 +1716,9 @@ interface SbtcResource {
|
|
|
1624
1716
|
walk(params?: SbtcDepositsWalkParams): AsyncIterable<IndexSbtcDeposit>
|
|
1625
1717
|
/** Checkpointed sweep of completed deposits — append-only, so safe to
|
|
1626
1718
|
* mirror. See {@link IndexConsumeOptions}. */
|
|
1627
|
-
consume(params: SbtcDepositsConsumeParams
|
|
1719
|
+
consume<TTx = never>(params: SbtcDepositsConsumeParams<TTx> & {
|
|
1720
|
+
sink?: ConsumerSink<TTx>
|
|
1721
|
+
}): Promise<{
|
|
1628
1722
|
cursor: string | null
|
|
1629
1723
|
pages: number
|
|
1630
1724
|
emptyPolls: number
|
|
@@ -1643,7 +1737,9 @@ interface SbtcResource {
|
|
|
1643
1737
|
walk(params?: SbtcEventsWalkParams): AsyncIterable<IndexSbtcEvent>
|
|
1644
1738
|
/** Checkpointed sweep of the raw peg event log — append-only across all
|
|
1645
1739
|
* six topics. See {@link IndexConsumeOptions}. */
|
|
1646
|
-
consume(params: SbtcEventsConsumeParams
|
|
1740
|
+
consume<TTx = never>(params: SbtcEventsConsumeParams<TTx> & {
|
|
1741
|
+
sink?: ConsumerSink<TTx>
|
|
1742
|
+
}): Promise<{
|
|
1647
1743
|
cursor: string | null
|
|
1648
1744
|
pages: number
|
|
1649
1745
|
emptyPolls: number
|
|
@@ -1793,7 +1889,12 @@ interface IndexEventsResource {
|
|
|
1793
1889
|
<T extends IndexEventType>(params: EventsListParams<T>): Promise<EventsEnvelope<T>>;
|
|
1794
1890
|
list<T extends IndexEventType>(params: EventsListParams<T>): Promise<EventsEnvelope<T>>;
|
|
1795
1891
|
walk<T extends IndexEventType>(params: EventsWalkParams<T>): AsyncIterable<IndexEventOf<T>>;
|
|
1796
|
-
consume<
|
|
1892
|
+
consume<
|
|
1893
|
+
T extends IndexEventType,
|
|
1894
|
+
TTx = never
|
|
1895
|
+
>(params: EventsConsumeParams<T, TTx> & {
|
|
1896
|
+
sink?: ConsumerSink<TTx>
|
|
1897
|
+
}): Promise<{
|
|
1797
1898
|
cursor: string | null
|
|
1798
1899
|
pages: number
|
|
1799
1900
|
emptyPolls: number
|
|
@@ -1839,7 +1940,9 @@ declare class Index extends BaseClient {
|
|
|
1839
1940
|
readonly contractCalls: {
|
|
1840
1941
|
list: (params?: ContractCallsListParams) => Promise<ContractCallsEnvelope>
|
|
1841
1942
|
walk: (params?: ContractCallsWalkParams) => AsyncIterable<IndexContractCall>
|
|
1842
|
-
consume: (params: ContractCallsConsumeParams
|
|
1943
|
+
consume: <TTx = never>(params: ContractCallsConsumeParams<TTx> & {
|
|
1944
|
+
sink?: ConsumerSink<TTx>
|
|
1945
|
+
}) => Promise<{
|
|
1843
1946
|
cursor: string | null
|
|
1844
1947
|
pages: number
|
|
1845
1948
|
emptyPolls: number
|
package/dist/subgraphs/index.js
CHANGED
|
@@ -655,7 +655,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
655
655
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
656
656
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
657
657
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
658
|
-
let cursor = opts.fromCursor ?? null;
|
|
658
|
+
let cursor = opts.fromCursor ?? await opts.sink?.loadCursor() ?? null;
|
|
659
659
|
const handledReorgs = new Set;
|
|
660
660
|
let pages = 0;
|
|
661
661
|
let emptyPolls = 0;
|
|
@@ -678,13 +678,14 @@ async function consumeStreamsEvents(opts) {
|
|
|
678
678
|
signal: opts.signal
|
|
679
679
|
});
|
|
680
680
|
pages++;
|
|
681
|
-
if (!finalizedOnly && opts.onReorg) {
|
|
681
|
+
if (!finalizedOnly && (opts.onReorg || opts.sink)) {
|
|
682
682
|
const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorgKey(reorg))).sort((a, b) => a.fork_point_height - b.fork_point_height);
|
|
683
683
|
if (fresh.length > 0) {
|
|
684
684
|
const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
|
|
685
685
|
const rewind = Cursor.atHeight(forkPoint);
|
|
686
686
|
for (const reorg of fresh) {
|
|
687
|
-
await opts.
|
|
687
|
+
await opts.sink?.rollback(reorg.fork_point_height, rewind);
|
|
688
|
+
await opts.onReorg?.(reorg, { cursor: rewind });
|
|
688
689
|
handledReorgs.add(reorgKey(reorg));
|
|
689
690
|
}
|
|
690
691
|
cursor = rewind;
|
|
@@ -696,10 +697,26 @@ async function consumeStreamsEvents(opts) {
|
|
|
696
697
|
const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
|
|
697
698
|
const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
|
|
698
699
|
height = emitted.at(-1)?.block_height ?? height;
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
700
|
+
const ctx = batchContext(checkpoint, height, envelope.tip.block_height);
|
|
701
|
+
opts.onProgress?.(ctx);
|
|
702
|
+
let nextCursor;
|
|
703
|
+
if (opts.sink) {
|
|
704
|
+
const sink = opts.sink;
|
|
705
|
+
if (checkpoint !== null && (checkpoint !== cursor || emitted.length > 0)) {
|
|
706
|
+
await sink.commitBatch(checkpoint, async (tx) => {
|
|
707
|
+
await opts.onBatch(emitted, envelope, {
|
|
708
|
+
...ctx,
|
|
709
|
+
tx
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
nextCursor = checkpoint;
|
|
714
|
+
} else {
|
|
715
|
+
const returnedCursor = await opts.onBatch(emitted, envelope, ctx);
|
|
716
|
+
if (finalizedOnly)
|
|
717
|
+
assertFinalizedCheckpoint(returnedCursor, checkpoint);
|
|
718
|
+
nextCursor = returnedCursor ?? checkpoint;
|
|
719
|
+
}
|
|
703
720
|
if (nextCursor && nextCursor !== cursor) {
|
|
704
721
|
cursor = nextCursor;
|
|
705
722
|
emptyPolls = 0;
|
|
@@ -800,7 +817,7 @@ async function consumeIndexFeed(opts) {
|
|
|
800
817
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
801
818
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
802
819
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
803
|
-
let cursor = opts.fromCursor ?? null;
|
|
820
|
+
let cursor = opts.fromCursor ?? await opts.sink?.loadCursor() ?? null;
|
|
804
821
|
const handledReorgs = new Set;
|
|
805
822
|
let pages = 0;
|
|
806
823
|
let emptyPolls = 0;
|
|
@@ -818,13 +835,14 @@ async function consumeIndexFeed(opts) {
|
|
|
818
835
|
signal: opts.signal
|
|
819
836
|
});
|
|
820
837
|
pages++;
|
|
821
|
-
if (!finalizedOnly && opts.onReorg) {
|
|
838
|
+
if (!finalizedOnly && (opts.onReorg || opts.sink)) {
|
|
822
839
|
const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorg.id)).sort((a, b) => a.fork_point_height - b.fork_point_height);
|
|
823
840
|
if (fresh.length > 0) {
|
|
824
841
|
const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
|
|
825
842
|
const rewind = Cursor.atHeight(forkPoint);
|
|
826
843
|
for (const reorg of fresh) {
|
|
827
|
-
await opts.
|
|
844
|
+
await opts.sink?.rollback(reorg.fork_point_height, rewind);
|
|
845
|
+
await opts.onReorg?.(reorg, { cursor: rewind });
|
|
828
846
|
handledReorgs.add(reorg.id);
|
|
829
847
|
}
|
|
830
848
|
cursor = rewind;
|
|
@@ -837,10 +855,26 @@ async function consumeIndexFeed(opts) {
|
|
|
837
855
|
const emitted = finalizedOnly ? items.filter((item) => item.block_height <= envelope.tip.finalized_height) : items;
|
|
838
856
|
const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
|
|
839
857
|
height = emitted.at(-1)?.block_height ?? height;
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
858
|
+
const ctx = batchContext(checkpoint, height, envelope.tip.block_height);
|
|
859
|
+
opts.onProgress?.(ctx);
|
|
860
|
+
let nextCursor;
|
|
861
|
+
if (opts.sink) {
|
|
862
|
+
const sink = opts.sink;
|
|
863
|
+
if (checkpoint !== null && (checkpoint !== cursor || emitted.length > 0)) {
|
|
864
|
+
await sink.commitBatch(checkpoint, async (tx) => {
|
|
865
|
+
await opts.onBatch(emitted, envelope, {
|
|
866
|
+
...ctx,
|
|
867
|
+
tx
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
nextCursor = checkpoint;
|
|
872
|
+
} else {
|
|
873
|
+
const returnedCursor = await opts.onBatch(emitted, envelope, ctx);
|
|
874
|
+
if (finalizedOnly)
|
|
875
|
+
assertFinalizedCheckpoint(returnedCursor, checkpoint);
|
|
876
|
+
nextCursor = returnedCursor ?? checkpoint;
|
|
877
|
+
}
|
|
844
878
|
if (nextCursor && nextCursor !== cursor) {
|
|
845
879
|
cursor = nextCursor;
|
|
846
880
|
emptyPolls = 0;
|
|
@@ -1313,6 +1347,175 @@ class Projects extends BaseClient {
|
|
|
1313
1347
|
// src/streams/client.ts
|
|
1314
1348
|
import { ed25519 as ed255192 } from "@secondlayer/shared";
|
|
1315
1349
|
|
|
1350
|
+
// src/streams/_payload.ts
|
|
1351
|
+
function requireString(payload, field, eventType) {
|
|
1352
|
+
const value = payload[field];
|
|
1353
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1354
|
+
throw new Error(`${eventType} payload missing ${field}`);
|
|
1355
|
+
}
|
|
1356
|
+
return value;
|
|
1357
|
+
}
|
|
1358
|
+
function optionalString(value) {
|
|
1359
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1360
|
+
}
|
|
1361
|
+
function requireAmountField(payload, field, eventType) {
|
|
1362
|
+
const amount = requireString(payload, field, eventType);
|
|
1363
|
+
if (!/^(0|[1-9]\d*)$/.test(amount)) {
|
|
1364
|
+
throw new Error(`${eventType} payload has malformed ${field}`);
|
|
1365
|
+
}
|
|
1366
|
+
return amount;
|
|
1367
|
+
}
|
|
1368
|
+
function requireAmount(payload, eventType) {
|
|
1369
|
+
return requireAmountField(payload, "amount", eventType);
|
|
1370
|
+
}
|
|
1371
|
+
function parseAssetIdentifier(assetIdentifier, eventType) {
|
|
1372
|
+
const [contractId, tokenName] = assetIdentifier.split("::");
|
|
1373
|
+
if (!contractId) {
|
|
1374
|
+
throw new Error(`${eventType} payload has malformed asset_identifier`);
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
contract_id: contractId,
|
|
1378
|
+
token_name: tokenName && tokenName.length > 0 ? tokenName : null
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
function requireHexValue(payload, eventType) {
|
|
1382
|
+
const rawValue = payload.raw_value;
|
|
1383
|
+
if (typeof rawValue === "string") {
|
|
1384
|
+
if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
|
|
1385
|
+
throw new Error(`${eventType} payload has malformed value`);
|
|
1386
|
+
}
|
|
1387
|
+
return rawValue;
|
|
1388
|
+
}
|
|
1389
|
+
const value = payload.value;
|
|
1390
|
+
const hex = typeof value === "string" ? value : value && typeof value === "object" && typeof value.hex === "string" ? value.hex : null;
|
|
1391
|
+
if (!hex) {
|
|
1392
|
+
throw new Error(`${eventType} payload missing value`);
|
|
1393
|
+
}
|
|
1394
|
+
if (!/^0x[0-9a-fA-F]*$/.test(hex)) {
|
|
1395
|
+
throw new Error(`${eventType} payload has malformed value`);
|
|
1396
|
+
}
|
|
1397
|
+
return hex;
|
|
1398
|
+
}
|
|
1399
|
+
function decodedRow(event, eventType, decoded_payload) {
|
|
1400
|
+
return {
|
|
1401
|
+
cursor: event.cursor,
|
|
1402
|
+
block_height: event.block_height,
|
|
1403
|
+
tx_id: event.tx_id,
|
|
1404
|
+
tx_index: event.tx_index,
|
|
1405
|
+
event_index: event.event_index,
|
|
1406
|
+
event_type: eventType,
|
|
1407
|
+
decoded_payload,
|
|
1408
|
+
source_cursor: event.cursor
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/streams/decode.ts
|
|
1413
|
+
function decode(event) {
|
|
1414
|
+
const base = {
|
|
1415
|
+
cursor: event.cursor,
|
|
1416
|
+
block_height: event.block_height,
|
|
1417
|
+
block_time: event.ts,
|
|
1418
|
+
tx_id: event.tx_id,
|
|
1419
|
+
tx_index: event.tx_index,
|
|
1420
|
+
event_index: event.event_index,
|
|
1421
|
+
contract_id: event.contract_id
|
|
1422
|
+
};
|
|
1423
|
+
const p = event.payload;
|
|
1424
|
+
switch (event.event_type) {
|
|
1425
|
+
case "stx_transfer":
|
|
1426
|
+
return {
|
|
1427
|
+
...base,
|
|
1428
|
+
event_type: "stx_transfer",
|
|
1429
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1430
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1431
|
+
amount: requireAmountField(p, "amount", event.event_type),
|
|
1432
|
+
memo: optionalString(p.memo)
|
|
1433
|
+
};
|
|
1434
|
+
case "stx_mint":
|
|
1435
|
+
return {
|
|
1436
|
+
...base,
|
|
1437
|
+
event_type: "stx_mint",
|
|
1438
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1439
|
+
amount: requireAmountField(p, "amount", event.event_type)
|
|
1440
|
+
};
|
|
1441
|
+
case "stx_burn":
|
|
1442
|
+
return {
|
|
1443
|
+
...base,
|
|
1444
|
+
event_type: "stx_burn",
|
|
1445
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1446
|
+
amount: requireAmountField(p, "amount", event.event_type)
|
|
1447
|
+
};
|
|
1448
|
+
case "stx_lock":
|
|
1449
|
+
return {
|
|
1450
|
+
...base,
|
|
1451
|
+
event_type: "stx_lock",
|
|
1452
|
+
sender: requireString(p, "locked_address", event.event_type),
|
|
1453
|
+
amount: requireAmountField(p, "locked_amount", event.event_type),
|
|
1454
|
+
payload: { unlock_height: optionalString(p.unlock_height) }
|
|
1455
|
+
};
|
|
1456
|
+
case "ft_transfer":
|
|
1457
|
+
return {
|
|
1458
|
+
...base,
|
|
1459
|
+
event_type: "ft_transfer",
|
|
1460
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1461
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1462
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1463
|
+
amount: requireAmountField(p, "amount", event.event_type)
|
|
1464
|
+
};
|
|
1465
|
+
case "ft_mint":
|
|
1466
|
+
return {
|
|
1467
|
+
...base,
|
|
1468
|
+
event_type: "ft_mint",
|
|
1469
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1470
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1471
|
+
amount: requireAmountField(p, "amount", event.event_type)
|
|
1472
|
+
};
|
|
1473
|
+
case "ft_burn":
|
|
1474
|
+
return {
|
|
1475
|
+
...base,
|
|
1476
|
+
event_type: "ft_burn",
|
|
1477
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1478
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1479
|
+
amount: requireAmountField(p, "amount", event.event_type)
|
|
1480
|
+
};
|
|
1481
|
+
case "nft_transfer":
|
|
1482
|
+
return {
|
|
1483
|
+
...base,
|
|
1484
|
+
event_type: "nft_transfer",
|
|
1485
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1486
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1487
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1488
|
+
value: requireHexValue(p, event.event_type)
|
|
1489
|
+
};
|
|
1490
|
+
case "nft_mint":
|
|
1491
|
+
return {
|
|
1492
|
+
...base,
|
|
1493
|
+
event_type: "nft_mint",
|
|
1494
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1495
|
+
recipient: requireString(p, "recipient", event.event_type),
|
|
1496
|
+
value: requireHexValue(p, event.event_type)
|
|
1497
|
+
};
|
|
1498
|
+
case "nft_burn":
|
|
1499
|
+
return {
|
|
1500
|
+
...base,
|
|
1501
|
+
event_type: "nft_burn",
|
|
1502
|
+
asset_identifier: requireString(p, "asset_identifier", event.event_type),
|
|
1503
|
+
sender: requireString(p, "sender", event.event_type),
|
|
1504
|
+
value: requireHexValue(p, event.event_type)
|
|
1505
|
+
};
|
|
1506
|
+
case "print":
|
|
1507
|
+
return {
|
|
1508
|
+
...base,
|
|
1509
|
+
event_type: "print",
|
|
1510
|
+
payload: {
|
|
1511
|
+
topic: optionalString(p.topic),
|
|
1512
|
+
value: p.value,
|
|
1513
|
+
raw_value: optionalString(p.raw_value)
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1316
1519
|
// src/streams/dumps.ts
|
|
1317
1520
|
import { createHash } from "node:crypto";
|
|
1318
1521
|
import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
|
|
@@ -1687,8 +1890,11 @@ function createStreamsClient(options) {
|
|
|
1687
1890
|
return request(`/v1/streams/events/${encodeURIComponent(txId)}`);
|
|
1688
1891
|
},
|
|
1689
1892
|
consume(params) {
|
|
1893
|
+
const onBatch = params.decoded ? (events, envelope, ctx) => params.onBatch(events.map(decode), envelope, ctx) : params.onBatch;
|
|
1690
1894
|
return consumeStreamsEvents({
|
|
1691
1895
|
fromCursor: params.fromCursor,
|
|
1896
|
+
sink: params.sink,
|
|
1897
|
+
onProgress: params.onProgress,
|
|
1692
1898
|
mode: params.mode,
|
|
1693
1899
|
finalizedOnly: params.finalizedOnly,
|
|
1694
1900
|
types: params.types,
|
|
@@ -1699,7 +1905,7 @@ function createStreamsClient(options) {
|
|
|
1699
1905
|
assetIdentifier: params.assetIdentifier,
|
|
1700
1906
|
batchSize: params.batchSize ?? 100,
|
|
1701
1907
|
fetchEvents,
|
|
1702
|
-
onBatch
|
|
1908
|
+
onBatch,
|
|
1703
1909
|
onReorg: params.onReorg,
|
|
1704
1910
|
emptyBackoffMs: params.emptyBackoffMs,
|
|
1705
1911
|
maxPages: params.maxPages,
|
|
@@ -1942,5 +2148,5 @@ export {
|
|
|
1942
2148
|
Subgraphs
|
|
1943
2149
|
};
|
|
1944
2150
|
|
|
1945
|
-
//# debugId=
|
|
2151
|
+
//# debugId=E16E22A43378758864756E2164756E21
|
|
1946
2152
|
//# sourceMappingURL=index.js.map
|