@secondlayer/sdk 6.37.1 → 6.39.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.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.onReorg(reorg, { cursor: rewind });
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 returnedCursor = await opts.onBatch(emitted, envelope, batchContext(checkpoint, height, envelope.tip.block_height));
700
- if (finalizedOnly)
701
- assertFinalizedCheckpoint(returnedCursor, checkpoint);
702
- const nextCursor = returnedCursor ?? checkpoint;
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.onReorg(reorg, { cursor: rewind });
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 returnedCursor = await opts.onBatch(emitted, envelope, batchContext(checkpoint, height, envelope.tip.block_height));
841
- if (finalizedOnly)
842
- assertFinalizedCheckpoint(returnedCursor, checkpoint);
843
- const nextCursor = returnedCursor ?? checkpoint;
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;
@@ -1079,7 +1113,8 @@ class Index extends BaseClient {
1079
1113
  from_height: params.fromHeight,
1080
1114
  to_height: params.toHeight,
1081
1115
  trait: params.trait,
1082
- tx_context: params.txContext ? "true" : undefined
1116
+ tx_context: params.txContext ? "true" : undefined,
1117
+ fields: params.fields?.join(",")
1083
1118
  })}`);
1084
1119
  }
1085
1120
  walkEvents(params) {
@@ -1313,6 +1348,175 @@ class Projects extends BaseClient {
1313
1348
  // src/streams/client.ts
1314
1349
  import { ed25519 as ed255192 } from "@secondlayer/shared";
1315
1350
 
1351
+ // src/streams/_payload.ts
1352
+ function requireString(payload, field, eventType) {
1353
+ const value = payload[field];
1354
+ if (typeof value !== "string" || value.length === 0) {
1355
+ throw new Error(`${eventType} payload missing ${field}`);
1356
+ }
1357
+ return value;
1358
+ }
1359
+ function optionalString(value) {
1360
+ return typeof value === "string" && value.length > 0 ? value : null;
1361
+ }
1362
+ function requireAmountField(payload, field, eventType) {
1363
+ const amount = requireString(payload, field, eventType);
1364
+ if (!/^(0|[1-9]\d*)$/.test(amount)) {
1365
+ throw new Error(`${eventType} payload has malformed ${field}`);
1366
+ }
1367
+ return amount;
1368
+ }
1369
+ function requireAmount(payload, eventType) {
1370
+ return requireAmountField(payload, "amount", eventType);
1371
+ }
1372
+ function parseAssetIdentifier(assetIdentifier, eventType) {
1373
+ const [contractId, tokenName] = assetIdentifier.split("::");
1374
+ if (!contractId) {
1375
+ throw new Error(`${eventType} payload has malformed asset_identifier`);
1376
+ }
1377
+ return {
1378
+ contract_id: contractId,
1379
+ token_name: tokenName && tokenName.length > 0 ? tokenName : null
1380
+ };
1381
+ }
1382
+ function requireHexValue(payload, eventType) {
1383
+ const rawValue = payload.raw_value;
1384
+ if (typeof rawValue === "string") {
1385
+ if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
1386
+ throw new Error(`${eventType} payload has malformed value`);
1387
+ }
1388
+ return rawValue;
1389
+ }
1390
+ const value = payload.value;
1391
+ const hex = typeof value === "string" ? value : value && typeof value === "object" && typeof value.hex === "string" ? value.hex : null;
1392
+ if (!hex) {
1393
+ throw new Error(`${eventType} payload missing value`);
1394
+ }
1395
+ if (!/^0x[0-9a-fA-F]*$/.test(hex)) {
1396
+ throw new Error(`${eventType} payload has malformed value`);
1397
+ }
1398
+ return hex;
1399
+ }
1400
+ function decodedRow(event, eventType, decoded_payload) {
1401
+ return {
1402
+ cursor: event.cursor,
1403
+ block_height: event.block_height,
1404
+ tx_id: event.tx_id,
1405
+ tx_index: event.tx_index,
1406
+ event_index: event.event_index,
1407
+ event_type: eventType,
1408
+ decoded_payload,
1409
+ source_cursor: event.cursor
1410
+ };
1411
+ }
1412
+
1413
+ // src/streams/decode.ts
1414
+ function decode(event) {
1415
+ const base = {
1416
+ cursor: event.cursor,
1417
+ block_height: event.block_height,
1418
+ block_time: event.ts,
1419
+ tx_id: event.tx_id,
1420
+ tx_index: event.tx_index,
1421
+ event_index: event.event_index,
1422
+ contract_id: event.contract_id
1423
+ };
1424
+ const p = event.payload;
1425
+ switch (event.event_type) {
1426
+ case "stx_transfer":
1427
+ return {
1428
+ ...base,
1429
+ event_type: "stx_transfer",
1430
+ sender: requireString(p, "sender", event.event_type),
1431
+ recipient: requireString(p, "recipient", event.event_type),
1432
+ amount: requireAmountField(p, "amount", event.event_type),
1433
+ memo: optionalString(p.memo)
1434
+ };
1435
+ case "stx_mint":
1436
+ return {
1437
+ ...base,
1438
+ event_type: "stx_mint",
1439
+ recipient: requireString(p, "recipient", event.event_type),
1440
+ amount: requireAmountField(p, "amount", event.event_type)
1441
+ };
1442
+ case "stx_burn":
1443
+ return {
1444
+ ...base,
1445
+ event_type: "stx_burn",
1446
+ sender: requireString(p, "sender", event.event_type),
1447
+ amount: requireAmountField(p, "amount", event.event_type)
1448
+ };
1449
+ case "stx_lock":
1450
+ return {
1451
+ ...base,
1452
+ event_type: "stx_lock",
1453
+ sender: requireString(p, "locked_address", event.event_type),
1454
+ amount: requireAmountField(p, "locked_amount", event.event_type),
1455
+ payload: { unlock_height: optionalString(p.unlock_height) }
1456
+ };
1457
+ case "ft_transfer":
1458
+ return {
1459
+ ...base,
1460
+ event_type: "ft_transfer",
1461
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1462
+ sender: requireString(p, "sender", event.event_type),
1463
+ recipient: requireString(p, "recipient", event.event_type),
1464
+ amount: requireAmountField(p, "amount", event.event_type)
1465
+ };
1466
+ case "ft_mint":
1467
+ return {
1468
+ ...base,
1469
+ event_type: "ft_mint",
1470
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1471
+ recipient: requireString(p, "recipient", event.event_type),
1472
+ amount: requireAmountField(p, "amount", event.event_type)
1473
+ };
1474
+ case "ft_burn":
1475
+ return {
1476
+ ...base,
1477
+ event_type: "ft_burn",
1478
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1479
+ sender: requireString(p, "sender", event.event_type),
1480
+ amount: requireAmountField(p, "amount", event.event_type)
1481
+ };
1482
+ case "nft_transfer":
1483
+ return {
1484
+ ...base,
1485
+ event_type: "nft_transfer",
1486
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1487
+ sender: requireString(p, "sender", event.event_type),
1488
+ recipient: requireString(p, "recipient", event.event_type),
1489
+ value: requireHexValue(p, event.event_type)
1490
+ };
1491
+ case "nft_mint":
1492
+ return {
1493
+ ...base,
1494
+ event_type: "nft_mint",
1495
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1496
+ recipient: requireString(p, "recipient", event.event_type),
1497
+ value: requireHexValue(p, event.event_type)
1498
+ };
1499
+ case "nft_burn":
1500
+ return {
1501
+ ...base,
1502
+ event_type: "nft_burn",
1503
+ asset_identifier: requireString(p, "asset_identifier", event.event_type),
1504
+ sender: requireString(p, "sender", event.event_type),
1505
+ value: requireHexValue(p, event.event_type)
1506
+ };
1507
+ case "print":
1508
+ return {
1509
+ ...base,
1510
+ event_type: "print",
1511
+ payload: {
1512
+ topic: optionalString(p.topic),
1513
+ value: p.value,
1514
+ raw_value: optionalString(p.raw_value)
1515
+ }
1516
+ };
1517
+ }
1518
+ }
1519
+
1316
1520
  // src/streams/dumps.ts
1317
1521
  import { createHash } from "node:crypto";
1318
1522
  import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
@@ -1687,8 +1891,11 @@ function createStreamsClient(options) {
1687
1891
  return request(`/v1/streams/events/${encodeURIComponent(txId)}`);
1688
1892
  },
1689
1893
  consume(params) {
1894
+ const onBatch = params.decoded ? (events, envelope, ctx) => params.onBatch(events.map(decode), envelope, ctx) : params.onBatch;
1690
1895
  return consumeStreamsEvents({
1691
1896
  fromCursor: params.fromCursor,
1897
+ sink: params.sink,
1898
+ onProgress: params.onProgress,
1692
1899
  mode: params.mode,
1693
1900
  finalizedOnly: params.finalizedOnly,
1694
1901
  types: params.types,
@@ -1699,7 +1906,7 @@ function createStreamsClient(options) {
1699
1906
  assetIdentifier: params.assetIdentifier,
1700
1907
  batchSize: params.batchSize ?? 100,
1701
1908
  fetchEvents,
1702
- onBatch: params.onBatch,
1909
+ onBatch,
1703
1910
  onReorg: params.onReorg,
1704
1911
  emptyBackoffMs: params.emptyBackoffMs,
1705
1912
  maxPages: params.maxPages,
@@ -1937,68 +2144,6 @@ function getSubgraph(def, options = {}) {
1937
2144
  }
1938
2145
  return new Subgraphs(options).typed(def);
1939
2146
  }
1940
- // src/streams/_payload.ts
1941
- function requireString(payload, field, eventType) {
1942
- const value = payload[field];
1943
- if (typeof value !== "string" || value.length === 0) {
1944
- throw new Error(`${eventType} payload missing ${field}`);
1945
- }
1946
- return value;
1947
- }
1948
- function optionalString(value) {
1949
- return typeof value === "string" && value.length > 0 ? value : null;
1950
- }
1951
- function requireAmountField(payload, field, eventType) {
1952
- const amount = requireString(payload, field, eventType);
1953
- if (!/^(0|[1-9]\d*)$/.test(amount)) {
1954
- throw new Error(`${eventType} payload has malformed ${field}`);
1955
- }
1956
- return amount;
1957
- }
1958
- function requireAmount(payload, eventType) {
1959
- return requireAmountField(payload, "amount", eventType);
1960
- }
1961
- function parseAssetIdentifier(assetIdentifier, eventType) {
1962
- const [contractId, tokenName] = assetIdentifier.split("::");
1963
- if (!contractId) {
1964
- throw new Error(`${eventType} payload has malformed asset_identifier`);
1965
- }
1966
- return {
1967
- contract_id: contractId,
1968
- token_name: tokenName && tokenName.length > 0 ? tokenName : null
1969
- };
1970
- }
1971
- function requireHexValue(payload, eventType) {
1972
- const rawValue = payload.raw_value;
1973
- if (typeof rawValue === "string") {
1974
- if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
1975
- throw new Error(`${eventType} payload has malformed value`);
1976
- }
1977
- return rawValue;
1978
- }
1979
- const value = payload.value;
1980
- const hex = typeof value === "string" ? value : value && typeof value === "object" && typeof value.hex === "string" ? value.hex : null;
1981
- if (!hex) {
1982
- throw new Error(`${eventType} payload missing value`);
1983
- }
1984
- if (!/^0x[0-9a-fA-F]*$/.test(hex)) {
1985
- throw new Error(`${eventType} payload has malformed value`);
1986
- }
1987
- return hex;
1988
- }
1989
- function decodedRow(event, eventType, decoded_payload) {
1990
- return {
1991
- cursor: event.cursor,
1992
- block_height: event.block_height,
1993
- tx_id: event.tx_id,
1994
- tx_index: event.tx_index,
1995
- event_index: event.event_index,
1996
- event_type: eventType,
1997
- decoded_payload,
1998
- source_cursor: event.cursor
1999
- };
2000
- }
2001
-
2002
2147
  // src/streams/ft-transfer.ts
2003
2148
  function isFtTransfer(event) {
2004
2149
  return event.event_type === "ft_transfer";
@@ -2439,6 +2584,46 @@ async function payAndRetry(doFetch, opts) {
2439
2584
  });
2440
2585
  return doFetch({ "PAYMENT-SIGNATURE": header });
2441
2586
  }
2587
+ // src/consumer-ops.ts
2588
+ function consumerHealth(options = {}) {
2589
+ const staleAfterMs = options.staleAfterMs ?? 120000;
2590
+ const startedAt = Date.now();
2591
+ let lastCtx = null;
2592
+ let lastBatchAt = 0;
2593
+ return {
2594
+ record(ctx) {
2595
+ lastCtx = ctx;
2596
+ lastBatchAt = Date.now();
2597
+ },
2598
+ handler(req) {
2599
+ if (new URL(req.url).pathname !== "/health") {
2600
+ return new Response("not found", { status: 404 });
2601
+ }
2602
+ const idleMs = lastBatchAt ? Date.now() - lastBatchAt : 0;
2603
+ const ok = idleMs < staleAfterMs;
2604
+ return Response.json({
2605
+ ok,
2606
+ checkpoint: lastCtx?.cursor ?? null,
2607
+ block_height: lastCtx?.height ?? null,
2608
+ tip_height: lastCtx?.tipHeight ?? null,
2609
+ blocks_behind: lastCtx?.blocksBehind ?? null,
2610
+ idle_s: Math.floor(idleMs / 1000),
2611
+ uptime_s: Math.floor((Date.now() - startedAt) / 1000)
2612
+ }, { status: ok ? 200 : 503 });
2613
+ }
2614
+ };
2615
+ }
2616
+ function shutdownSignal(options = {}) {
2617
+ const controller = new AbortController;
2618
+ if (typeof process !== "undefined" && typeof process.on === "function") {
2619
+ for (const signal of options.signals ?? ["SIGTERM", "SIGINT"]) {
2620
+ process.on(signal, () => {
2621
+ controller.abort(new Error(`${signal} — finishing the current batch`));
2622
+ });
2623
+ }
2624
+ }
2625
+ return controller.signal;
2626
+ }
2442
2627
  // src/webhooks.ts
2443
2628
  import { verifySecondlayerSignatureValues } from "@secondlayer/shared/crypto/secondlayer-webhook";
2444
2629
  import {
@@ -2599,6 +2784,7 @@ export {
2599
2784
  verifySecondlayerSignature,
2600
2785
  trigger,
2601
2786
  toJsonSafe,
2787
+ shutdownSignal,
2602
2788
  selectOffer,
2603
2789
  resolveApiKey,
2604
2790
  resolveAccountNonce,
@@ -2632,8 +2818,10 @@ export {
2632
2818
  decodeFtBurn,
2633
2819
  decodeClarityValue,
2634
2820
  decodeChainWebhook,
2821
+ decode,
2635
2822
  createX402Client,
2636
2823
  createStreamsClient,
2824
+ consumerHealth,
2637
2825
  consumeIndexFeed,
2638
2826
  buildSignedX402Payment,
2639
2827
  X402SpendGuardError,
@@ -2657,5 +2845,5 @@ export {
2657
2845
  ApiError
2658
2846
  };
2659
2847
 
2660
- //# debugId=D57E218C0E8C77C464756E2164756E21
2848
+ //# debugId=8D2C49296338C5CF64756E2164756E21
2661
2849
  //# sourceMappingURL=index.js.map