@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.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;
@@ -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: params.onBatch,
1908
+ onBatch,
1703
1909
  onReorg: params.onReorg,
1704
1910
  emptyBackoffMs: params.emptyBackoffMs,
1705
1911
  maxPages: params.maxPages,
@@ -1937,68 +2143,6 @@ function getSubgraph(def, options = {}) {
1937
2143
  }
1938
2144
  return new Subgraphs(options).typed(def);
1939
2145
  }
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
2146
  // src/streams/ft-transfer.ts
2003
2147
  function isFtTransfer(event) {
2004
2148
  return event.event_type === "ft_transfer";
@@ -2439,6 +2583,46 @@ async function payAndRetry(doFetch, opts) {
2439
2583
  });
2440
2584
  return doFetch({ "PAYMENT-SIGNATURE": header });
2441
2585
  }
2586
+ // src/consumer-ops.ts
2587
+ function consumerHealth(options = {}) {
2588
+ const staleAfterMs = options.staleAfterMs ?? 120000;
2589
+ const startedAt = Date.now();
2590
+ let lastCtx = null;
2591
+ let lastBatchAt = 0;
2592
+ return {
2593
+ record(ctx) {
2594
+ lastCtx = ctx;
2595
+ lastBatchAt = Date.now();
2596
+ },
2597
+ handler(req) {
2598
+ if (new URL(req.url).pathname !== "/health") {
2599
+ return new Response("not found", { status: 404 });
2600
+ }
2601
+ const idleMs = lastBatchAt ? Date.now() - lastBatchAt : 0;
2602
+ const ok = idleMs < staleAfterMs;
2603
+ return Response.json({
2604
+ ok,
2605
+ checkpoint: lastCtx?.cursor ?? null,
2606
+ block_height: lastCtx?.height ?? null,
2607
+ tip_height: lastCtx?.tipHeight ?? null,
2608
+ blocks_behind: lastCtx?.blocksBehind ?? null,
2609
+ idle_s: Math.floor(idleMs / 1000),
2610
+ uptime_s: Math.floor((Date.now() - startedAt) / 1000)
2611
+ }, { status: ok ? 200 : 503 });
2612
+ }
2613
+ };
2614
+ }
2615
+ function shutdownSignal(options = {}) {
2616
+ const controller = new AbortController;
2617
+ if (typeof process !== "undefined" && typeof process.on === "function") {
2618
+ for (const signal of options.signals ?? ["SIGTERM", "SIGINT"]) {
2619
+ process.on(signal, () => {
2620
+ controller.abort(new Error(`${signal} — finishing the current batch`));
2621
+ });
2622
+ }
2623
+ }
2624
+ return controller.signal;
2625
+ }
2442
2626
  // src/webhooks.ts
2443
2627
  import { verifySecondlayerSignatureValues } from "@secondlayer/shared/crypto/secondlayer-webhook";
2444
2628
  import {
@@ -2599,6 +2783,7 @@ export {
2599
2783
  verifySecondlayerSignature,
2600
2784
  trigger,
2601
2785
  toJsonSafe,
2786
+ shutdownSignal,
2602
2787
  selectOffer,
2603
2788
  resolveApiKey,
2604
2789
  resolveAccountNonce,
@@ -2632,8 +2817,10 @@ export {
2632
2817
  decodeFtBurn,
2633
2818
  decodeClarityValue,
2634
2819
  decodeChainWebhook,
2820
+ decode,
2635
2821
  createX402Client,
2636
2822
  createStreamsClient,
2823
+ consumerHealth,
2637
2824
  consumeIndexFeed,
2638
2825
  buildSignedX402Payment,
2639
2826
  X402SpendGuardError,
@@ -2657,5 +2844,5 @@ export {
2657
2844
  ApiError
2658
2845
  };
2659
2846
 
2660
- //# debugId=D57E218C0E8C77C464756E2164756E21
2847
+ //# debugId=1D3B2F9E333F939964756E2164756E21
2661
2848
  //# sourceMappingURL=index.js.map