@secondlayer/sdk 16.2.1 → 16.3.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 +49 -3
- package/dist/index.js +178 -62
- package/dist/index.js.map +11 -10
- package/dist/streams/index.d.ts +17 -1
- package/dist/streams/index.js +171 -59
- package/dist/streams/index.js.map +9 -8
- package/dist/subgraphs/index.d.ts +12 -0
- package/dist/subgraphs/index.js +171 -59
- package/dist/subgraphs/index.js.map +9 -8
- package/dist/webhooks/doctor.d.ts +2 -2
- package/dist/webhooks/doctor.js +6 -4
- package/dist/webhooks/doctor.js.map +3 -3
- package/package.json +3 -3
|
@@ -963,6 +963,8 @@ type StreamsEventsStreamBitcoinParams = StreamsEventsStreamCommon & {
|
|
|
963
963
|
address?: string;
|
|
964
964
|
};
|
|
965
965
|
type StreamsEventsStreamParams = StreamsEventsStreamStacksParams | StreamsEventsStreamBitcoinParams;
|
|
966
|
+
/** Where a `subscribe` connection is in its lifecycle. */
|
|
967
|
+
type StreamsSubscriptionState = "connecting" | "open" | "reconnecting" | "closed";
|
|
966
968
|
type StreamsEventsSubscribeCommon = {
|
|
967
969
|
/** Resume strictly after this cursor; omit to live-tail from the tip. */
|
|
968
970
|
fromCursor?: string | null;
|
|
@@ -976,6 +978,13 @@ type StreamsEventsSubscribeCommon = {
|
|
|
976
978
|
*/
|
|
977
979
|
onError?: (err: unknown) => void;
|
|
978
980
|
/**
|
|
981
|
+
* Lifecycle observer. `connecting` before each connection attempt, `open`
|
|
982
|
+
* once the server answers, `reconnecting` while backing off after a drop
|
|
983
|
+
* or error, `closed` once, when the subscription ends (unsubscribed,
|
|
984
|
+
* aborted, or stopped by an error a retry cannot fix).
|
|
985
|
+
*/
|
|
986
|
+
onState?: (state: StreamsSubscriptionState) => void;
|
|
987
|
+
/**
|
|
979
988
|
* First reconnect pause in ms (default 1000). Doubles per consecutive
|
|
980
989
|
* failure up to 30 s with jitter, resets once a frame arrives, and never
|
|
981
990
|
* undercuts a `Retry-After`.
|
|
@@ -2472,6 +2481,9 @@ type IndexSbtcWithdrawal = {
|
|
|
2472
2481
|
btc_block_height: number | null;
|
|
2473
2482
|
/** When the sweep crossed the confirmation threshold (ISO), null otherwise. */
|
|
2474
2483
|
confirmed_at: string | null;
|
|
2484
|
+
/** Monotonic stamp taken when the sweep became confirmed; resume a scan
|
|
2485
|
+
* from it with `confirmed_after_seq`. Null while unsettled. */
|
|
2486
|
+
settlement_seq: number | null;
|
|
2475
2487
|
requested_at?: string | null;
|
|
2476
2488
|
resolved_at?: string | null;
|
|
2477
2489
|
};
|
package/dist/subgraphs/index.js
CHANGED
|
@@ -244,6 +244,22 @@ function resolveAccountKey(accountKey) {
|
|
|
244
244
|
return;
|
|
245
245
|
return process.env?.SECONDLAYER_API_KEY || undefined;
|
|
246
246
|
}
|
|
247
|
+
function resolveAccountUrl() {
|
|
248
|
+
const env = typeof process !== "undefined" ? process.env : undefined;
|
|
249
|
+
return (env?.SL_CREDITS_API_URL || env?.ARCHIVE_OPS_API_URL || HOSTED_API_URL).replace(/\/+$/, "");
|
|
250
|
+
}
|
|
251
|
+
var ACCOUNT_CREDENTIAL_RE = /^s[ks]-sl_/;
|
|
252
|
+
function resolveIdentity(target, options = {}) {
|
|
253
|
+
const url = target === "account" ? resolveAccountUrl() : resolveBaseUrl();
|
|
254
|
+
const envKey = target === "account" ? resolveAccountKey() : typeof process !== "undefined" ? process.env?.[INSTANCE_TOKEN_ENV] || undefined : undefined;
|
|
255
|
+
if (envKey && (target === "instance" || ACCOUNT_CREDENTIAL_RE.test(envKey))) {
|
|
256
|
+
return { source: "env", token: envKey, url };
|
|
257
|
+
}
|
|
258
|
+
if (options.session) {
|
|
259
|
+
return { source: "session", token: options.session, url };
|
|
260
|
+
}
|
|
261
|
+
return { source: "none", token: undefined, url };
|
|
262
|
+
}
|
|
247
263
|
function seg(value) {
|
|
248
264
|
return encodeURIComponent(String(value));
|
|
249
265
|
}
|
|
@@ -486,8 +502,14 @@ function subscribeStreamsEvents(opts) {
|
|
|
486
502
|
const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
|
|
487
503
|
const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
|
|
488
504
|
let attempt = 0;
|
|
505
|
+
const emit = (state) => {
|
|
506
|
+
try {
|
|
507
|
+
params.onState?.(state);
|
|
508
|
+
} catch {}
|
|
509
|
+
};
|
|
489
510
|
const run = async () => {
|
|
490
511
|
while (!controller.signal.aborted) {
|
|
512
|
+
emit("connecting");
|
|
491
513
|
try {
|
|
492
514
|
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
493
515
|
chain: params.chain === "bitcoin" ? "bitcoin" : undefined,
|
|
@@ -509,6 +531,7 @@ function subscribeStreamsEvents(opts) {
|
|
|
509
531
|
fetchImpl: opts.fetchImpl,
|
|
510
532
|
staleAfterMs,
|
|
511
533
|
mapError: mapStreamsError,
|
|
534
|
+
onOpen: () => emit("open"),
|
|
512
535
|
onFrame: async (frame) => {
|
|
513
536
|
attempt = 0;
|
|
514
537
|
if (frame.event === "ping" || !frame.data)
|
|
@@ -537,6 +560,8 @@ function subscribeStreamsEvents(opts) {
|
|
|
537
560
|
cursor = parsed.event.cursor ?? cursor;
|
|
538
561
|
}
|
|
539
562
|
});
|
|
563
|
+
if (!controller.signal.aborted)
|
|
564
|
+
emit("reconnecting");
|
|
540
565
|
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
541
566
|
attempt += 1;
|
|
542
567
|
} catch (err) {
|
|
@@ -545,13 +570,14 @@ function subscribeStreamsEvents(opts) {
|
|
|
545
570
|
params.onError?.(err);
|
|
546
571
|
if (isTerminal(err))
|
|
547
572
|
throw err;
|
|
573
|
+
emit("reconnecting");
|
|
548
574
|
const retryAfterSeconds = err.retryAfterSeconds;
|
|
549
575
|
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
550
576
|
attempt += 1;
|
|
551
577
|
}
|
|
552
578
|
}
|
|
553
579
|
};
|
|
554
|
-
const done = run();
|
|
580
|
+
const done = run().finally(() => emit("closed"));
|
|
555
581
|
done.catch(() => {});
|
|
556
582
|
const unsubscribe = () => controller.abort();
|
|
557
583
|
return Object.assign(unsubscribe, { done });
|
|
@@ -569,6 +595,7 @@ async function readSse(opts) {
|
|
|
569
595
|
if (!res.body) {
|
|
570
596
|
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
571
597
|
}
|
|
598
|
+
opts.onOpen?.();
|
|
572
599
|
for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
|
|
573
600
|
await opts.onFrame(frame);
|
|
574
601
|
}
|
|
@@ -1470,13 +1497,70 @@ async function consumeStreamsEvents(opts) {
|
|
|
1470
1497
|
}
|
|
1471
1498
|
return { cursor, pages, emptyPolls };
|
|
1472
1499
|
}
|
|
1500
|
+
async function* pullFromLoop(start, external) {
|
|
1501
|
+
const controller = new AbortController;
|
|
1502
|
+
if (external) {
|
|
1503
|
+
if (external.aborted)
|
|
1504
|
+
controller.abort();
|
|
1505
|
+
else
|
|
1506
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
1507
|
+
once: true
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const state = {
|
|
1511
|
+
delivered: null,
|
|
1512
|
+
held: null,
|
|
1513
|
+
finished: false,
|
|
1514
|
+
failure: null,
|
|
1515
|
+
wake: null
|
|
1516
|
+
};
|
|
1517
|
+
const notify = () => {
|
|
1518
|
+
const resolve = state.wake;
|
|
1519
|
+
state.wake = null;
|
|
1520
|
+
resolve?.();
|
|
1521
|
+
};
|
|
1522
|
+
const loop = start((value) => new Promise((release) => {
|
|
1523
|
+
state.delivered = { value, release };
|
|
1524
|
+
notify();
|
|
1525
|
+
}), controller.signal).then(() => {
|
|
1526
|
+
state.finished = true;
|
|
1527
|
+
}, (error) => {
|
|
1528
|
+
state.failure = { error };
|
|
1529
|
+
state.finished = true;
|
|
1530
|
+
}).finally(notify);
|
|
1531
|
+
try {
|
|
1532
|
+
while (true) {
|
|
1533
|
+
if (state.delivered) {
|
|
1534
|
+
const { value, release } = state.delivered;
|
|
1535
|
+
state.delivered = null;
|
|
1536
|
+
state.held = release;
|
|
1537
|
+
yield value;
|
|
1538
|
+
state.held = null;
|
|
1539
|
+
release();
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (state.finished)
|
|
1543
|
+
break;
|
|
1544
|
+
await new Promise((resolve) => {
|
|
1545
|
+
state.wake = resolve;
|
|
1546
|
+
if (state.delivered || state.finished)
|
|
1547
|
+
notify();
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
if (state.failure)
|
|
1551
|
+
throw state.failure.error;
|
|
1552
|
+
} finally {
|
|
1553
|
+
controller.abort();
|
|
1554
|
+
state.held?.();
|
|
1555
|
+
state.delivered?.release();
|
|
1556
|
+
await loop;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1473
1559
|
async function* iterateStreamsBatches(opts) {
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
cursor,
|
|
1479
|
-
limit: opts.batchSize,
|
|
1560
|
+
yield* pullFromLoop(async (deliver, signal) => {
|
|
1561
|
+
const result = await consumeStreamsEvents({
|
|
1562
|
+
fromCursor: opts.fromCursor,
|
|
1563
|
+
batchSize: opts.batchSize,
|
|
1480
1564
|
chain: opts.chain,
|
|
1481
1565
|
types: opts.types,
|
|
1482
1566
|
notTypes: opts.notTypes,
|
|
@@ -1486,38 +1570,41 @@ async function* iterateStreamsBatches(opts) {
|
|
|
1486
1570
|
assetIdentifier: opts.assetIdentifier,
|
|
1487
1571
|
rune: opts.rune,
|
|
1488
1572
|
address: opts.address,
|
|
1489
|
-
filters: opts.filters
|
|
1573
|
+
filters: opts.filters,
|
|
1574
|
+
fetchEvents: opts.fetchEvents,
|
|
1575
|
+
sleep: opts.sleep,
|
|
1576
|
+
emptyBackoffMs: opts.intervalMs,
|
|
1577
|
+
signal,
|
|
1578
|
+
onBatch: async (events, ctx) => {
|
|
1579
|
+
if (events.length === 0 && ctx.reorgs.length === 0)
|
|
1580
|
+
return;
|
|
1581
|
+
await deliver({
|
|
1582
|
+
events,
|
|
1583
|
+
cursor: ctx.cursor,
|
|
1584
|
+
tip: ctx.tip,
|
|
1585
|
+
reorgs: [...ctx.reorgs]
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1490
1588
|
});
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
if (!advanced && envelope.events.length === 0) {
|
|
1503
|
-
if (opts.signal?.aborted)
|
|
1504
|
-
return;
|
|
1505
|
-
await sleep(opts.intervalMs, opts.signal);
|
|
1506
|
-
}
|
|
1589
|
+
assertNotStalled(result, signal);
|
|
1590
|
+
}, opts.signal);
|
|
1591
|
+
}
|
|
1592
|
+
function assertNotStalled(result, signal, bounds = {}) {
|
|
1593
|
+
if (signal.aborted)
|
|
1594
|
+
return;
|
|
1595
|
+
if (bounds.maxPages !== undefined && (bounds.pages ?? 0) >= bounds.maxPages) {
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
if (bounds.maxEmptyPolls !== undefined && result.emptyPolls >= bounds.maxEmptyPolls) {
|
|
1599
|
+
return;
|
|
1507
1600
|
}
|
|
1601
|
+
throw new StreamsServerError(`Streams returned events at cursor ${result.cursor ?? "(start)"} but the cursor did not advance, so the stream stopped instead of re-reading the same page.`, 0, undefined, "CURSOR_STALLED");
|
|
1508
1602
|
}
|
|
1509
1603
|
async function* streamStreamsEvents(opts) {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
let cursor = opts.fromCursor ?? null;
|
|
1515
|
-
let pages = 0;
|
|
1516
|
-
let emptyPolls = 0;
|
|
1517
|
-
while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
|
|
1518
|
-
const envelope = await opts.fetchEvents({
|
|
1519
|
-
cursor,
|
|
1520
|
-
limit: opts.batchSize,
|
|
1604
|
+
yield* pullFromLoop(async (deliver, signal) => {
|
|
1605
|
+
const result = await consumeStreamsEvents({
|
|
1606
|
+
fromCursor: opts.fromCursor,
|
|
1607
|
+
batchSize: opts.batchSize,
|
|
1521
1608
|
chain: opts.chain,
|
|
1522
1609
|
types: opts.types,
|
|
1523
1610
|
notTypes: opts.notTypes,
|
|
@@ -1527,29 +1614,27 @@ async function* streamStreamsEvents(opts) {
|
|
|
1527
1614
|
assetIdentifier: opts.assetIdentifier,
|
|
1528
1615
|
rune: opts.rune,
|
|
1529
1616
|
address: opts.address,
|
|
1530
|
-
filters: opts.filters
|
|
1617
|
+
filters: opts.filters,
|
|
1618
|
+
fetchEvents: opts.fetchEvents,
|
|
1619
|
+
sleep: opts.sleep,
|
|
1620
|
+
emptyBackoffMs: opts.emptyBackoffMs,
|
|
1621
|
+
maxPages: opts.maxPages,
|
|
1622
|
+
maxEmptyPolls: opts.maxEmptyPolls,
|
|
1623
|
+
signal,
|
|
1624
|
+
onBatch: async (events) => {
|
|
1625
|
+
for (const event of events) {
|
|
1626
|
+
if (signal.aborted)
|
|
1627
|
+
return;
|
|
1628
|
+
await deliver(event);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1531
1631
|
});
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
const nextCursor = envelope.next_cursor;
|
|
1539
|
-
if (nextCursor && nextCursor !== cursor) {
|
|
1540
|
-
cursor = nextCursor;
|
|
1541
|
-
emptyPolls = 0;
|
|
1542
|
-
continue;
|
|
1543
|
-
}
|
|
1544
|
-
if (envelope.events.length === 0) {
|
|
1545
|
-
emptyPolls++;
|
|
1546
|
-
if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
|
|
1547
|
-
return;
|
|
1548
|
-
await sleep(emptyBackoffMs, opts.signal);
|
|
1549
|
-
continue;
|
|
1550
|
-
}
|
|
1551
|
-
return;
|
|
1552
|
-
}
|
|
1632
|
+
assertNotStalled(result, signal, {
|
|
1633
|
+
maxPages: opts.maxPages,
|
|
1634
|
+
maxEmptyPolls: opts.maxEmptyPolls,
|
|
1635
|
+
pages: result.pages
|
|
1636
|
+
});
|
|
1637
|
+
}, opts.signal);
|
|
1553
1638
|
}
|
|
1554
1639
|
|
|
1555
1640
|
// src/index-api/consumer.ts
|
|
@@ -2311,6 +2396,33 @@ function createStreamsDumps(opts) {
|
|
|
2311
2396
|
return { list, fileUrl, download };
|
|
2312
2397
|
}
|
|
2313
2398
|
|
|
2399
|
+
// src/streams/timeout-fetch.ts
|
|
2400
|
+
var STREAMS_CONNECT_TIMEOUT_MS = 30000;
|
|
2401
|
+
function withConnectTimeout(fetchImpl, timeoutMs) {
|
|
2402
|
+
if (!(timeoutMs > 0) || !Number.isFinite(timeoutMs))
|
|
2403
|
+
return fetchImpl;
|
|
2404
|
+
return async (input, init) => {
|
|
2405
|
+
const callerSignal = init?.signal ?? undefined;
|
|
2406
|
+
const timeout = new AbortController;
|
|
2407
|
+
let timedOut = false;
|
|
2408
|
+
const timer = setTimeout(() => {
|
|
2409
|
+
timedOut = true;
|
|
2410
|
+
timeout.abort();
|
|
2411
|
+
}, timeoutMs);
|
|
2412
|
+
const signal = callerSignal ? AbortSignal.any([callerSignal, timeout.signal]) : timeout.signal;
|
|
2413
|
+
try {
|
|
2414
|
+
return await fetchImpl(input, { ...init, signal });
|
|
2415
|
+
} catch (err) {
|
|
2416
|
+
if (timedOut && !callerSignal?.aborted) {
|
|
2417
|
+
throw new StreamsServerError(`No response headers within ${timeoutMs}ms. Retryable.`, 0, undefined, "REQUEST_TIMEOUT");
|
|
2418
|
+
}
|
|
2419
|
+
throw err;
|
|
2420
|
+
} finally {
|
|
2421
|
+
clearTimeout(timer);
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2314
2426
|
// src/streams/client.ts
|
|
2315
2427
|
function cursorTuple(cursor) {
|
|
2316
2428
|
if (!cursor)
|
|
@@ -2356,7 +2468,7 @@ function createStreamsClient(options) {
|
|
|
2356
2468
|
"x-sl-origin": origin,
|
|
2357
2469
|
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
2358
2470
|
});
|
|
2359
|
-
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
2471
|
+
const fetchImpl = withConnectTimeout(options.fetchImpl ?? ((input, init) => fetch(input, init)), options.connectTimeoutMs ?? STREAMS_CONNECT_TIMEOUT_MS);
|
|
2360
2472
|
const verify = options.verify;
|
|
2361
2473
|
const verifyMode = verify === false ? "off" : verify === undefined ? "lenient" : "strict";
|
|
2362
2474
|
if (verify === true && baseUrl.startsWith("http:") && !isLoopbackUrl(baseUrl)) {
|
|
@@ -2846,5 +2958,5 @@ export {
|
|
|
2846
2958
|
getSubgraph
|
|
2847
2959
|
};
|
|
2848
2960
|
|
|
2849
|
-
//# debugId=
|
|
2961
|
+
//# debugId=82C9947AF210E6E964756E2164756E21
|
|
2850
2962
|
//# sourceMappingURL=index.js.map
|