@secondlayer/sdk 10.5.0 → 10.6.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 +423 -424
- package/dist/index.js +721 -719
- package/dist/index.js.map +10 -10
- package/dist/sinks/bun-sqlite.d.ts +2 -2
- package/dist/sinks/core.d.ts +1 -1
- package/dist/sinks/core.js +4 -4
- package/dist/sinks/core.js.map +1 -1
- package/dist/sinks/drizzle.d.ts +2 -2
- package/dist/sinks/drizzle.js +6 -4
- package/dist/sinks/drizzle.js.map +5 -5
- package/dist/sinks/kysely.d.ts +2 -2
- package/dist/sinks/kysely.js +4 -2
- package/dist/sinks/kysely.js.map +5 -5
- package/dist/sinks/testing.d.ts +1 -1
- package/dist/sinks/testing.js +3 -3
- package/dist/sinks/testing.js.map +2 -2
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +31 -31
- package/dist/streams/index.js.map +1 -1
- package/dist/streams/rows.d.ts +1 -1
- package/dist/streams/rows.js +22 -22
- package/dist/streams/rows.js.map +1 -1
- package/dist/subgraphs/index.d.ts +1 -2
- package/dist/subgraphs/index.js +14 -14
- package/dist/subgraphs/index.js.map +3 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// src/archive/client.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
checkSignature,
|
|
5
|
+
loadReference
|
|
6
|
+
} from "@secondlayer/shared/archive/reference";
|
|
7
|
+
|
|
1
8
|
// src/errors.ts
|
|
2
9
|
var DOCS_BASE = "https://www.secondlayer.tools";
|
|
3
10
|
|
|
@@ -424,604 +431,109 @@ class BaseClient {
|
|
|
424
431
|
}
|
|
425
432
|
}
|
|
426
433
|
|
|
427
|
-
// src/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
// src/archive/client.ts
|
|
435
|
+
var OFFICIAL_ARCHIVE_HOST = "archive.secondlayer.tools";
|
|
436
|
+
var DEFAULT_ARCHIVE_BASE_URL = "https://archive.secondlayer.tools";
|
|
437
|
+
var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
|
|
438
|
+
var MAX_FETCH_BATCH = 64;
|
|
439
|
+
var ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
|
|
440
|
+
function quoteFromBody(body) {
|
|
441
|
+
return {
|
|
442
|
+
partitions: body.partitions,
|
|
443
|
+
bundles: body.bundles,
|
|
444
|
+
usdMicros: body.usd_micros,
|
|
445
|
+
usd: body.usd,
|
|
446
|
+
freeAllowanceAppliedMicros: body.free_allowance_applied_micros,
|
|
447
|
+
allowanceRemainingBundles: body.allowance_remaining_bundles,
|
|
448
|
+
balanceUsdMicros: body.balance_usd_micros,
|
|
449
|
+
sufficient: body.sufficient
|
|
450
|
+
};
|
|
436
451
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
constructor(message = "Streams response signature verification failed.") {
|
|
440
|
-
super(message);
|
|
441
|
-
this.name = "StreamsSignatureError";
|
|
442
|
-
}
|
|
452
|
+
function stripSlash(url) {
|
|
453
|
+
return url.replace(/\/+$/, "");
|
|
443
454
|
}
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
+
function createArchiveClient(options = {}) {
|
|
456
|
+
const accountKey = resolveAccountKey(options.accountKey ?? options.apiKey);
|
|
457
|
+
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
458
|
+
const archiveBaseUrl = stripSlash(options.archiveBaseUrl ?? DEFAULT_ARCHIVE_BASE_URL);
|
|
459
|
+
const archiveOpsUrl = stripSlash(options.archiveOpsUrl ?? DEFAULT_ARCHIVE_OPS_URL);
|
|
460
|
+
const verifyManifest = options.verifyManifest ?? true;
|
|
461
|
+
const pinnedPem = options.publicKeyPem;
|
|
462
|
+
let keyPromise = null;
|
|
463
|
+
async function fetchSigningKey() {
|
|
464
|
+
if (pinnedPem)
|
|
465
|
+
return pinnedPem;
|
|
466
|
+
if (keyPromise)
|
|
467
|
+
return keyPromise;
|
|
468
|
+
const pending = (async () => {
|
|
469
|
+
const res = await fetchImpl(`${archiveOpsUrl}/public/streams/signing-key`);
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
throw new ArchiveSignatureError(`Could not fetch archive signing key (${res.status}).`);
|
|
472
|
+
}
|
|
473
|
+
const body = await res.json();
|
|
474
|
+
if (typeof body.public_key_pem !== "string") {
|
|
475
|
+
throw new ArchiveSignatureError("Signing key response missing key.");
|
|
476
|
+
}
|
|
477
|
+
return body.public_key_pem;
|
|
478
|
+
})();
|
|
479
|
+
keyPromise = pending;
|
|
480
|
+
pending.catch(() => {
|
|
481
|
+
if (keyPromise === pending)
|
|
482
|
+
keyPromise = null;
|
|
483
|
+
});
|
|
484
|
+
return pending;
|
|
455
485
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (err instanceof StreamsSignatureError)
|
|
470
|
-
return true;
|
|
471
|
-
return err?.retryable === false;
|
|
472
|
-
}
|
|
473
|
-
function subscribeStreamsEvents(opts) {
|
|
474
|
-
const { params } = opts;
|
|
475
|
-
const controller = new AbortController;
|
|
476
|
-
const external = params.signal;
|
|
477
|
-
if (external) {
|
|
478
|
-
if (external.aborted)
|
|
479
|
-
controller.abort();
|
|
480
|
-
else
|
|
481
|
-
external.addEventListener("abort", () => controller.abort(), {
|
|
482
|
-
once: true
|
|
483
|
-
});
|
|
486
|
+
async function throwOpsError(response) {
|
|
487
|
+
const envelope = parseErrorEnvelope(await response.text());
|
|
488
|
+
if (response.status === 401) {
|
|
489
|
+
throw new ArchiveAuthError(envelope.message ?? "Archive ops request was not authenticated.");
|
|
490
|
+
}
|
|
491
|
+
if (response.status === 402) {
|
|
492
|
+
const raw = envelope.body && typeof envelope.body === "object" && "shortfall_usd_micros" in envelope.body ? Number(envelope.body.shortfall_usd_micros) : undefined;
|
|
493
|
+
throw new InsufficientArchiveCreditsError(envelope.message ?? "Insufficient archive credits.", Number.isFinite(raw) ? raw : undefined);
|
|
494
|
+
}
|
|
495
|
+
if (response.status === 503) {
|
|
496
|
+
throw new ArchiveGateNotConfiguredError(envelope.message ?? "The archive gate is not configured on the server.");
|
|
497
|
+
}
|
|
498
|
+
throw new ApiError(response.status, envelope.message ?? `Archive ops returned ${response.status}.`, envelope.body, envelope.code);
|
|
484
499
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
493
|
-
from_cursor: cursor ?? undefined,
|
|
494
|
-
types: params.types,
|
|
495
|
-
not_types: params.notTypes,
|
|
496
|
-
contract_id: params.contractId,
|
|
497
|
-
sender: params.sender,
|
|
498
|
-
recipient: params.recipient,
|
|
499
|
-
asset_identifier: params.assetIdentifier,
|
|
500
|
-
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
501
|
-
})}`;
|
|
502
|
-
await readSse({
|
|
503
|
-
url,
|
|
504
|
-
headers: opts.headers ?? {},
|
|
505
|
-
signal: controller.signal,
|
|
506
|
-
fetchImpl: opts.fetchImpl,
|
|
507
|
-
staleAfterMs,
|
|
508
|
-
mapError: mapStreamsError,
|
|
509
|
-
onFrame: async (frame) => {
|
|
510
|
-
attempt = 0;
|
|
511
|
-
if (frame.event === "ping" || !frame.data)
|
|
512
|
-
return;
|
|
513
|
-
let parsed;
|
|
514
|
-
try {
|
|
515
|
-
parsed = JSON.parse(frame.data);
|
|
516
|
-
} catch {
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if (!parsed.event)
|
|
520
|
-
return;
|
|
521
|
-
if (opts.verify !== "off") {
|
|
522
|
-
if (!parsed.sig) {
|
|
523
|
-
if (opts.verify === "strict") {
|
|
524
|
-
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
525
|
-
}
|
|
526
|
-
} else {
|
|
527
|
-
const key = await opts.loadKey(parsed.key_id);
|
|
528
|
-
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
529
|
-
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
await params.onEvent(parsed.event);
|
|
534
|
-
cursor = parsed.event.cursor ?? cursor;
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
538
|
-
attempt += 1;
|
|
539
|
-
} catch (err) {
|
|
540
|
-
if (controller.signal.aborted)
|
|
541
|
-
return;
|
|
542
|
-
params.onError?.(err);
|
|
543
|
-
if (isTerminal(err))
|
|
544
|
-
throw err;
|
|
545
|
-
const retryAfterSeconds = err.retryAfterSeconds;
|
|
546
|
-
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
547
|
-
attempt += 1;
|
|
500
|
+
async function opsRequest(method, path, body, authed = true) {
|
|
501
|
+
const headers = {
|
|
502
|
+
"Content-Type": "application/json"
|
|
503
|
+
};
|
|
504
|
+
if (authed && accountKey) {
|
|
505
|
+
if (!ARCHIVE_CREDENTIAL_RE.test(accountKey)) {
|
|
506
|
+
throw new ArchiveAuthError("archive ops need SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN");
|
|
548
507
|
}
|
|
508
|
+
headers.Authorization = `Bearer ${accountKey}`;
|
|
549
509
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
|
|
559
|
-
signal: opts.signal
|
|
560
|
-
});
|
|
561
|
-
if (!res.ok) {
|
|
562
|
-
if (opts.mapError)
|
|
563
|
-
await opts.mapError(res);
|
|
564
|
-
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
510
|
+
const response = await fetchImpl(`${archiveOpsUrl}${path}`, {
|
|
511
|
+
method,
|
|
512
|
+
headers,
|
|
513
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
514
|
+
});
|
|
515
|
+
if (!response.ok)
|
|
516
|
+
await throwOpsError(response);
|
|
517
|
+
return await response.json();
|
|
565
518
|
}
|
|
566
|
-
|
|
567
|
-
|
|
519
|
+
async function load(against, opts = {}) {
|
|
520
|
+
if (!/^https?:\/\//i.test(against)) {
|
|
521
|
+
throw new ValidationError("archive.load() accepts only http(s) URLs; local paths stay on the CLI.", 400);
|
|
522
|
+
}
|
|
523
|
+
const insecure = opts.insecure === true;
|
|
524
|
+
const publicKeyPem = pinnedPem ?? (verifyManifest && !insecure ? await fetchSigningKey() : undefined);
|
|
525
|
+
const ref = await loadReference(against, {
|
|
526
|
+
publicKeyPem,
|
|
527
|
+
fetchImpl
|
|
528
|
+
});
|
|
529
|
+
const signature = checkSignature(ref.manifest, publicKeyPem, insecure);
|
|
530
|
+
if (verifyManifest && !insecure && !signature.verified) {
|
|
531
|
+
throw new ArchiveSignatureError(signature.reason ?? "Archive manifest signature is missing or invalid.");
|
|
532
|
+
}
|
|
533
|
+
return { ...ref, signature };
|
|
568
534
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
function sleep(ms, signal) {
|
|
574
|
-
return new Promise((resolve) => {
|
|
575
|
-
if (signal.aborted)
|
|
576
|
-
return resolve();
|
|
577
|
-
const onAbort = () => {
|
|
578
|
-
clearTimeout(timer);
|
|
579
|
-
resolve();
|
|
580
|
-
};
|
|
581
|
-
const timer = setTimeout(() => {
|
|
582
|
-
signal.removeEventListener("abort", onAbort);
|
|
583
|
-
resolve();
|
|
584
|
-
}, ms);
|
|
585
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
async function readWithStaleTimer(reader, staleAfterMs) {
|
|
589
|
-
if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
|
|
590
|
-
return reader.read();
|
|
591
|
-
}
|
|
592
|
-
let timer;
|
|
593
|
-
const stale = new Promise((_, reject) => {
|
|
594
|
-
timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
|
|
595
|
-
});
|
|
596
|
-
try {
|
|
597
|
-
return await Promise.race([reader.read(), stale]);
|
|
598
|
-
} finally {
|
|
599
|
-
clearTimeout(timer);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
async function* parseSseFrames(body, signal, staleAfterMs) {
|
|
603
|
-
const reader = body.getReader();
|
|
604
|
-
const decoder = new TextDecoder;
|
|
605
|
-
let buffer = "";
|
|
606
|
-
try {
|
|
607
|
-
while (!signal.aborted) {
|
|
608
|
-
const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
|
|
609
|
-
if (done)
|
|
610
|
-
break;
|
|
611
|
-
buffer += decoder.decode(value, { stream: true });
|
|
612
|
-
let sep = buffer.indexOf(`
|
|
613
|
-
|
|
614
|
-
`);
|
|
615
|
-
while (sep !== -1) {
|
|
616
|
-
yield parseFrame(buffer.slice(0, sep));
|
|
617
|
-
buffer = buffer.slice(sep + 2);
|
|
618
|
-
sep = buffer.indexOf(`
|
|
619
|
-
|
|
620
|
-
`);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
} finally {
|
|
624
|
-
try {
|
|
625
|
-
await reader.cancel();
|
|
626
|
-
} catch {}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function parseFrame(raw) {
|
|
630
|
-
let event;
|
|
631
|
-
const data = [];
|
|
632
|
-
for (const line of raw.split(`
|
|
633
|
-
`)) {
|
|
634
|
-
if (line.startsWith("data:")) {
|
|
635
|
-
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
636
|
-
} else if (line.startsWith("event:")) {
|
|
637
|
-
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return { event, data: data.length > 0 ? data.join(`
|
|
641
|
-
`) : undefined };
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// src/subgraphs/serialize.ts
|
|
645
|
-
var SYSTEM_COLUMN_MAP = {
|
|
646
|
-
_blockHeight: "_block_height",
|
|
647
|
-
_txId: "_tx_id",
|
|
648
|
-
_createdAt: "_created_at",
|
|
649
|
-
_id: "_id"
|
|
650
|
-
};
|
|
651
|
-
var SYSTEM_COLUMN_ALIASES = {
|
|
652
|
-
blockHeight: "_block_height",
|
|
653
|
-
txId: "_tx_id",
|
|
654
|
-
createdAt: "_created_at",
|
|
655
|
-
id: "_id"
|
|
656
|
-
};
|
|
657
|
-
function resolveColumn(col, columns) {
|
|
658
|
-
const canonical = SYSTEM_COLUMN_MAP[col];
|
|
659
|
-
if (canonical)
|
|
660
|
-
return canonical;
|
|
661
|
-
if (columns?.has(col))
|
|
662
|
-
return col;
|
|
663
|
-
return SYSTEM_COLUMN_ALIASES[col] ?? col;
|
|
664
|
-
}
|
|
665
|
-
function serializeWhere(where, columns) {
|
|
666
|
-
const filters = {};
|
|
667
|
-
for (const [column, value] of Object.entries(where)) {
|
|
668
|
-
if (value === null || value === undefined)
|
|
669
|
-
continue;
|
|
670
|
-
const col = resolveColumn(column, columns);
|
|
671
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
672
|
-
const ops = value;
|
|
673
|
-
for (const [op, opValue] of Object.entries(ops)) {
|
|
674
|
-
if (opValue === null || opValue === undefined)
|
|
675
|
-
continue;
|
|
676
|
-
if (op === "eq") {
|
|
677
|
-
filters[col] = String(opValue);
|
|
678
|
-
} else if (op === "in" || op === "notIn") {
|
|
679
|
-
const arr = Array.isArray(opValue) ? opValue : [opValue];
|
|
680
|
-
filters[`${col}.${op}`] = arr.map((v) => String(v)).join(",");
|
|
681
|
-
} else if (["neq", "gt", "gte", "lt", "lte", "like"].includes(op)) {
|
|
682
|
-
filters[`${col}.${op}`] = String(opValue);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
} else {
|
|
686
|
-
filters[col] = String(value);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
return filters;
|
|
690
|
-
}
|
|
691
|
-
function resolveOrderByColumn(col, columns) {
|
|
692
|
-
return resolveColumn(col, columns);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// src/subgraphs/client.ts
|
|
696
|
-
var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
|
|
697
|
-
function declaredColumns(table) {
|
|
698
|
-
if (!table || typeof table !== "object")
|
|
699
|
-
return;
|
|
700
|
-
const columns = table.columns;
|
|
701
|
-
if (!columns || typeof columns !== "object")
|
|
702
|
-
return;
|
|
703
|
-
return new Set(Object.keys(columns));
|
|
704
|
-
}
|
|
705
|
-
function rowBlockHeight(row) {
|
|
706
|
-
const r = row;
|
|
707
|
-
const raw = r._block_height ?? r._blockHeight;
|
|
708
|
-
if (typeof raw !== "number" && typeof raw !== "string")
|
|
709
|
-
return;
|
|
710
|
-
const height = Number(raw);
|
|
711
|
-
return Number.isFinite(height) ? height : undefined;
|
|
712
|
-
}
|
|
713
|
-
function sleep2(ms, signal) {
|
|
714
|
-
return new Promise((resolve) => {
|
|
715
|
-
if (signal.aborted)
|
|
716
|
-
return resolve();
|
|
717
|
-
const onAbort = () => {
|
|
718
|
-
clearTimeout(timer);
|
|
719
|
-
resolve();
|
|
720
|
-
};
|
|
721
|
-
const timer = setTimeout(() => {
|
|
722
|
-
signal.removeEventListener("abort", onAbort);
|
|
723
|
-
resolve();
|
|
724
|
-
}, ms);
|
|
725
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
function buildSubgraphQueryString(params) {
|
|
729
|
-
return buildQuery({
|
|
730
|
-
_sort: params.sort,
|
|
731
|
-
_order: params.order,
|
|
732
|
-
_limit: params.limit,
|
|
733
|
-
_offset: params.offset,
|
|
734
|
-
_fields: params.fields,
|
|
735
|
-
...params.filters
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
function buildAggregateQueryString(params) {
|
|
739
|
-
return buildQuery({
|
|
740
|
-
...params.filters,
|
|
741
|
-
_count: params.count ? "true" : undefined,
|
|
742
|
-
_countDistinct: params.countDistinct,
|
|
743
|
-
_sum: params.sum,
|
|
744
|
-
_min: params.min,
|
|
745
|
-
_max: params.max
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
function buildSpecQueryString(options) {
|
|
749
|
-
return buildQuery({ server: options?.serverUrl });
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
class Subgraphs extends BaseClient {
|
|
753
|
-
async list() {
|
|
754
|
-
return this.request("GET", "/api/subgraphs");
|
|
755
|
-
}
|
|
756
|
-
async status(name) {
|
|
757
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}`);
|
|
758
|
-
}
|
|
759
|
-
async openapi(name, options) {
|
|
760
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
|
|
761
|
-
}
|
|
762
|
-
async schema(name, options) {
|
|
763
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
|
|
764
|
-
}
|
|
765
|
-
async markdown(name, options) {
|
|
766
|
-
return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
|
|
767
|
-
}
|
|
768
|
-
async reindex(name) {
|
|
769
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
|
|
770
|
-
}
|
|
771
|
-
async stop(name) {
|
|
772
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
|
|
773
|
-
}
|
|
774
|
-
async backfill(name, options) {
|
|
775
|
-
return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
|
|
776
|
-
}
|
|
777
|
-
async gaps(name, opts) {
|
|
778
|
-
const qs = buildQuery({
|
|
779
|
-
_limit: opts?.limit,
|
|
780
|
-
_offset: opts?.offset,
|
|
781
|
-
resolved: opts?.resolved
|
|
782
|
-
});
|
|
783
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
|
|
784
|
-
}
|
|
785
|
-
async delete(name, options) {
|
|
786
|
-
const qs = buildQuery({ force: options?.force ? true : undefined });
|
|
787
|
-
return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
|
|
788
|
-
}
|
|
789
|
-
async rows(name, table, params = {}) {
|
|
790
|
-
const { cursor, ...rest } = params;
|
|
791
|
-
const qs = buildSubgraphQueryString(rest);
|
|
792
|
-
const sep = qs ? "&" : "?";
|
|
793
|
-
const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
|
|
794
|
-
return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
|
|
795
|
-
}
|
|
796
|
-
async operations(name) {
|
|
797
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
|
|
798
|
-
}
|
|
799
|
-
async getOperation(name, operationId) {
|
|
800
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
|
|
801
|
-
}
|
|
802
|
-
async deploy(data) {
|
|
803
|
-
return this.request("POST", "/api/subgraphs", data);
|
|
804
|
-
}
|
|
805
|
-
async getSource(name) {
|
|
806
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
|
|
807
|
-
}
|
|
808
|
-
async bundle(data) {
|
|
809
|
-
return this.request("POST", "/api/subgraphs/bundle", data);
|
|
810
|
-
}
|
|
811
|
-
async queryTable(name, table, params = {}) {
|
|
812
|
-
const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
|
|
813
|
-
return Array.isArray(result) ? result : result.data;
|
|
814
|
-
}
|
|
815
|
-
async queryTableCount(name, table, params = {}) {
|
|
816
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
|
|
817
|
-
}
|
|
818
|
-
async queryTableAggregate(name, table, params = {}) {
|
|
819
|
-
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
|
|
820
|
-
}
|
|
821
|
-
typed(def) {
|
|
822
|
-
const result = {};
|
|
823
|
-
for (const [tableName, table] of Object.entries(def.schema)) {
|
|
824
|
-
result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
|
|
825
|
-
}
|
|
826
|
-
return result;
|
|
827
|
-
}
|
|
828
|
-
createTableClient(subgraphName, tableName, columns) {
|
|
829
|
-
const self = this;
|
|
830
|
-
return {
|
|
831
|
-
async findMany(options = {}) {
|
|
832
|
-
const filters = options.where ? serializeWhere(options.where, columns) : undefined;
|
|
833
|
-
let sort;
|
|
834
|
-
let order;
|
|
835
|
-
if (options.orderBy) {
|
|
836
|
-
const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
|
|
837
|
-
if (entries.length > 0) {
|
|
838
|
-
sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
|
|
839
|
-
order = entries.map(([, dir]) => dir ?? "asc").join(",");
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
const params = {
|
|
843
|
-
sort,
|
|
844
|
-
order,
|
|
845
|
-
limit: options.limit,
|
|
846
|
-
offset: options.offset,
|
|
847
|
-
fields: options.fields?.join(","),
|
|
848
|
-
filters
|
|
849
|
-
};
|
|
850
|
-
return self.queryTable(subgraphName, tableName, params);
|
|
851
|
-
},
|
|
852
|
-
async count(where) {
|
|
853
|
-
const filters = where ? serializeWhere(where, columns) : undefined;
|
|
854
|
-
const result = await self.queryTableCount(subgraphName, tableName, {
|
|
855
|
-
filters
|
|
856
|
-
});
|
|
857
|
-
return result.count;
|
|
858
|
-
},
|
|
859
|
-
async aggregate(spec) {
|
|
860
|
-
const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
|
|
861
|
-
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
862
|
-
filters,
|
|
863
|
-
count: spec.count,
|
|
864
|
-
countDistinct: spec.countDistinct,
|
|
865
|
-
sum: spec.sum,
|
|
866
|
-
min: spec.min,
|
|
867
|
-
max: spec.max
|
|
868
|
-
});
|
|
869
|
-
return result;
|
|
870
|
-
},
|
|
871
|
-
subscribe(onRow, options = {}) {
|
|
872
|
-
const filters = options.where ? serializeWhere(options.where, columns) : {};
|
|
873
|
-
const controller = new AbortController;
|
|
874
|
-
let since = options.since ?? undefined;
|
|
875
|
-
const run = async () => {
|
|
876
|
-
while (!controller.signal.aborted) {
|
|
877
|
-
try {
|
|
878
|
-
const qs = buildQuery({ ...filters, since });
|
|
879
|
-
await readSse({
|
|
880
|
-
url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
|
|
881
|
-
headers: {
|
|
882
|
-
...BaseClient.authHeaders(self.apiKey),
|
|
883
|
-
"x-sl-origin": self.origin
|
|
884
|
-
},
|
|
885
|
-
signal: controller.signal,
|
|
886
|
-
fetchImpl: self.fetchImpl,
|
|
887
|
-
onFrame: (frame) => {
|
|
888
|
-
if (frame.event === "ping" || !frame.data)
|
|
889
|
-
return;
|
|
890
|
-
let row;
|
|
891
|
-
try {
|
|
892
|
-
row = JSON.parse(frame.data);
|
|
893
|
-
} catch {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const height = rowBlockHeight(row);
|
|
897
|
-
if (height !== undefined)
|
|
898
|
-
since = height;
|
|
899
|
-
onRow(row);
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
903
|
-
} catch (err) {
|
|
904
|
-
if (controller.signal.aborted)
|
|
905
|
-
return;
|
|
906
|
-
options.onError?.(err);
|
|
907
|
-
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
};
|
|
911
|
-
run();
|
|
912
|
-
return () => controller.abort();
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
// src/archive/client.ts
|
|
918
|
-
import { createHash } from "node:crypto";
|
|
919
|
-
import {
|
|
920
|
-
checkSignature,
|
|
921
|
-
loadReference
|
|
922
|
-
} from "@secondlayer/shared/archive/reference";
|
|
923
|
-
var OFFICIAL_ARCHIVE_HOST = "archive.secondlayer.tools";
|
|
924
|
-
var DEFAULT_ARCHIVE_BASE_URL = "https://archive.secondlayer.tools";
|
|
925
|
-
var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
|
|
926
|
-
var MAX_FETCH_BATCH = 64;
|
|
927
|
-
var ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
|
|
928
|
-
function quoteFromBody(body) {
|
|
929
|
-
return {
|
|
930
|
-
partitions: body.partitions,
|
|
931
|
-
bundles: body.bundles,
|
|
932
|
-
usdMicros: body.usd_micros,
|
|
933
|
-
usd: body.usd,
|
|
934
|
-
freeAllowanceAppliedMicros: body.free_allowance_applied_micros,
|
|
935
|
-
allowanceRemainingBundles: body.allowance_remaining_bundles,
|
|
936
|
-
balanceUsdMicros: body.balance_usd_micros,
|
|
937
|
-
sufficient: body.sufficient
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
function stripSlash(url) {
|
|
941
|
-
return url.replace(/\/+$/, "");
|
|
942
|
-
}
|
|
943
|
-
function createArchiveClient(options = {}) {
|
|
944
|
-
const accountKey = resolveAccountKey(options.accountKey ?? options.apiKey);
|
|
945
|
-
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
946
|
-
const archiveBaseUrl = stripSlash(options.archiveBaseUrl ?? DEFAULT_ARCHIVE_BASE_URL);
|
|
947
|
-
const archiveOpsUrl = stripSlash(options.archiveOpsUrl ?? DEFAULT_ARCHIVE_OPS_URL);
|
|
948
|
-
const verifyManifest = options.verifyManifest ?? true;
|
|
949
|
-
const pinnedPem = options.publicKeyPem;
|
|
950
|
-
let keyPromise = null;
|
|
951
|
-
async function fetchSigningKey() {
|
|
952
|
-
if (pinnedPem)
|
|
953
|
-
return pinnedPem;
|
|
954
|
-
if (keyPromise)
|
|
955
|
-
return keyPromise;
|
|
956
|
-
const pending = (async () => {
|
|
957
|
-
const res = await fetchImpl(`${archiveOpsUrl}/public/streams/signing-key`);
|
|
958
|
-
if (!res.ok) {
|
|
959
|
-
throw new ArchiveSignatureError(`Could not fetch archive signing key (${res.status}).`);
|
|
960
|
-
}
|
|
961
|
-
const body = await res.json();
|
|
962
|
-
if (typeof body.public_key_pem !== "string") {
|
|
963
|
-
throw new ArchiveSignatureError("Signing key response missing key.");
|
|
964
|
-
}
|
|
965
|
-
return body.public_key_pem;
|
|
966
|
-
})();
|
|
967
|
-
keyPromise = pending;
|
|
968
|
-
pending.catch(() => {
|
|
969
|
-
if (keyPromise === pending)
|
|
970
|
-
keyPromise = null;
|
|
971
|
-
});
|
|
972
|
-
return pending;
|
|
973
|
-
}
|
|
974
|
-
async function throwOpsError(response) {
|
|
975
|
-
const envelope = parseErrorEnvelope(await response.text());
|
|
976
|
-
if (response.status === 401) {
|
|
977
|
-
throw new ArchiveAuthError(envelope.message ?? "Archive ops request was not authenticated.");
|
|
978
|
-
}
|
|
979
|
-
if (response.status === 402) {
|
|
980
|
-
const raw = envelope.body && typeof envelope.body === "object" && "shortfall_usd_micros" in envelope.body ? Number(envelope.body.shortfall_usd_micros) : undefined;
|
|
981
|
-
throw new InsufficientArchiveCreditsError(envelope.message ?? "Insufficient archive credits.", Number.isFinite(raw) ? raw : undefined);
|
|
982
|
-
}
|
|
983
|
-
if (response.status === 503) {
|
|
984
|
-
throw new ArchiveGateNotConfiguredError(envelope.message ?? "The archive gate is not configured on the server.");
|
|
985
|
-
}
|
|
986
|
-
throw new ApiError(response.status, envelope.message ?? `Archive ops returned ${response.status}.`, envelope.body, envelope.code);
|
|
987
|
-
}
|
|
988
|
-
async function opsRequest(method, path, body, authed = true) {
|
|
989
|
-
const headers = {
|
|
990
|
-
"Content-Type": "application/json"
|
|
991
|
-
};
|
|
992
|
-
if (authed && accountKey) {
|
|
993
|
-
if (!ARCHIVE_CREDENTIAL_RE.test(accountKey)) {
|
|
994
|
-
throw new ArchiveAuthError("archive ops need SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN");
|
|
995
|
-
}
|
|
996
|
-
headers.Authorization = `Bearer ${accountKey}`;
|
|
997
|
-
}
|
|
998
|
-
const response = await fetchImpl(`${archiveOpsUrl}${path}`, {
|
|
999
|
-
method,
|
|
1000
|
-
headers,
|
|
1001
|
-
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
1002
|
-
});
|
|
1003
|
-
if (!response.ok)
|
|
1004
|
-
await throwOpsError(response);
|
|
1005
|
-
return await response.json();
|
|
1006
|
-
}
|
|
1007
|
-
async function load(against, opts = {}) {
|
|
1008
|
-
if (!/^https?:\/\//i.test(against)) {
|
|
1009
|
-
throw new ValidationError("archive.load() accepts only http(s) URLs; local paths stay on the CLI.", 400);
|
|
1010
|
-
}
|
|
1011
|
-
const insecure = opts.insecure === true;
|
|
1012
|
-
const publicKeyPem = pinnedPem ?? (verifyManifest && !insecure ? await fetchSigningKey() : undefined);
|
|
1013
|
-
const ref = await loadReference(against, {
|
|
1014
|
-
publicKeyPem,
|
|
1015
|
-
fetchImpl
|
|
1016
|
-
});
|
|
1017
|
-
const signature = checkSignature(ref.manifest, publicKeyPem, insecure);
|
|
1018
|
-
if (verifyManifest && !insecure && !signature.verified) {
|
|
1019
|
-
throw new ArchiveSignatureError(signature.reason ?? "Archive manifest signature is missing or invalid.");
|
|
1020
|
-
}
|
|
1021
|
-
return { ...ref, signature };
|
|
1022
|
-
}
|
|
1023
|
-
async function latest(against, opts = {}) {
|
|
1024
|
-
return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
|
|
535
|
+
async function latest(against, opts = {}) {
|
|
536
|
+
return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
|
|
1025
537
|
}
|
|
1026
538
|
async function status() {
|
|
1027
539
|
const response = await fetchImpl(`${archiveBaseUrl}/status.json`);
|
|
@@ -1178,6 +690,37 @@ import {
|
|
|
1178
690
|
decodeStreamsCursor,
|
|
1179
691
|
encodeStreamsCursor
|
|
1180
692
|
} from "@secondlayer/shared";
|
|
693
|
+
|
|
694
|
+
// src/streams/errors.ts
|
|
695
|
+
class StreamsServerError extends ApiError {
|
|
696
|
+
constructor(message, status, body, code) {
|
|
697
|
+
super(status, message, body, code, { retryable: true });
|
|
698
|
+
this.name = "StreamsServerError";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
class StreamsSignatureError extends SecondLayerError {
|
|
703
|
+
constructor(message = "Streams response signature verification failed.") {
|
|
704
|
+
super(message);
|
|
705
|
+
this.name = "StreamsSignatureError";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function mapStreamsError(response) {
|
|
709
|
+
const { message, code, body } = parseErrorEnvelope(await response.text());
|
|
710
|
+
if (response.status === 401) {
|
|
711
|
+
throw new AuthError(message ?? "API key invalid or expired.", body, code);
|
|
712
|
+
}
|
|
713
|
+
if (response.status === 429) {
|
|
714
|
+
const retryAfter = response.headers.get("Retry-After") ?? undefined;
|
|
715
|
+
throw new RateLimitError(message ?? "Rate limited. Try again later.", retryAfter, body, code);
|
|
716
|
+
}
|
|
717
|
+
if (response.status >= 500) {
|
|
718
|
+
throw new StreamsServerError(message ?? `Streams server returned ${response.status}.`, response.status, body, code);
|
|
719
|
+
}
|
|
720
|
+
throw new ValidationError(message ?? `Streams request returned ${response.status}.`, response.status, body, code);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/streams/cursor.ts
|
|
1181
724
|
var Cursor = {
|
|
1182
725
|
atHeight(height) {
|
|
1183
726
|
if (height <= 0)
|
|
@@ -1325,7 +868,7 @@ async function defaultSleep(ms, signal) {
|
|
|
1325
868
|
});
|
|
1326
869
|
}
|
|
1327
870
|
async function consumeStreamsEvents(opts) {
|
|
1328
|
-
const
|
|
871
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1329
872
|
const mode = opts.mode ?? "tail";
|
|
1330
873
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
1331
874
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
@@ -1355,7 +898,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1355
898
|
retryCount: opts.retryCount,
|
|
1356
899
|
retryDelay: opts.retryDelay,
|
|
1357
900
|
onError: opts.onError,
|
|
1358
|
-
sleep
|
|
901
|
+
sleep,
|
|
1359
902
|
signal: opts.signal
|
|
1360
903
|
});
|
|
1361
904
|
pages++;
|
|
@@ -1387,7 +930,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1387
930
|
retryCount: opts.retryCount,
|
|
1388
931
|
retryDelay: opts.retryDelay,
|
|
1389
932
|
onError: opts.onError,
|
|
1390
|
-
sleep
|
|
933
|
+
sleep,
|
|
1391
934
|
signal: opts.signal
|
|
1392
935
|
});
|
|
1393
936
|
if (listed.next_since !== null)
|
|
@@ -1450,7 +993,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1450
993
|
if (mode === "bounded") {
|
|
1451
994
|
return { cursor, pages, emptyPolls };
|
|
1452
995
|
}
|
|
1453
|
-
await
|
|
996
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1454
997
|
continue;
|
|
1455
998
|
}
|
|
1456
999
|
return { cursor, pages, emptyPolls };
|
|
@@ -1458,7 +1001,7 @@ async function consumeStreamsEvents(opts) {
|
|
|
1458
1001
|
return { cursor, pages, emptyPolls };
|
|
1459
1002
|
}
|
|
1460
1003
|
async function* iterateStreamsBatches(opts) {
|
|
1461
|
-
const
|
|
1004
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1462
1005
|
let cursor = opts.fromCursor ?? null;
|
|
1463
1006
|
while (!opts.signal?.aborted) {
|
|
1464
1007
|
const envelope = await opts.fetchEvents({
|
|
@@ -1486,12 +1029,12 @@ async function* iterateStreamsBatches(opts) {
|
|
|
1486
1029
|
if (!advanced && envelope.events.length === 0) {
|
|
1487
1030
|
if (opts.signal?.aborted)
|
|
1488
1031
|
return;
|
|
1489
|
-
await
|
|
1032
|
+
await sleep(opts.intervalMs, opts.signal);
|
|
1490
1033
|
}
|
|
1491
1034
|
}
|
|
1492
1035
|
}
|
|
1493
1036
|
async function* streamStreamsEvents(opts) {
|
|
1494
|
-
const
|
|
1037
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1495
1038
|
const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
|
|
1496
1039
|
const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
|
|
1497
1040
|
const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
|
|
@@ -1526,7 +1069,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1526
1069
|
emptyPolls++;
|
|
1527
1070
|
if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
|
|
1528
1071
|
return;
|
|
1529
|
-
await
|
|
1072
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1530
1073
|
continue;
|
|
1531
1074
|
}
|
|
1532
1075
|
return;
|
|
@@ -1535,7 +1078,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1535
1078
|
|
|
1536
1079
|
// src/index-api/consumer.ts
|
|
1537
1080
|
async function consumeIndexFeed(opts) {
|
|
1538
|
-
const
|
|
1081
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1539
1082
|
const mode = opts.mode ?? "tail";
|
|
1540
1083
|
const finalizedOnly = opts.finalizedOnly ?? false;
|
|
1541
1084
|
const batchSize = opts.batchSize ?? 200;
|
|
@@ -1559,7 +1102,7 @@ async function consumeIndexFeed(opts) {
|
|
|
1559
1102
|
retryCount: opts.retryCount,
|
|
1560
1103
|
retryDelay: opts.retryDelay,
|
|
1561
1104
|
onError: opts.onError,
|
|
1562
|
-
sleep
|
|
1105
|
+
sleep,
|
|
1563
1106
|
signal: opts.signal
|
|
1564
1107
|
});
|
|
1565
1108
|
pages++;
|
|
@@ -1622,7 +1165,7 @@ async function consumeIndexFeed(opts) {
|
|
|
1622
1165
|
if (mode === "bounded") {
|
|
1623
1166
|
return { cursor, pages, emptyPolls };
|
|
1624
1167
|
}
|
|
1625
|
-
await
|
|
1168
|
+
await sleep(emptyBackoffMs, opts.signal);
|
|
1626
1169
|
continue;
|
|
1627
1170
|
}
|
|
1628
1171
|
return { cursor, pages, emptyPolls };
|
|
@@ -2209,9 +1752,195 @@ function createStreamsDumps(opts) {
|
|
|
2209
1752
|
bytes.set(chunk, offset);
|
|
2210
1753
|
offset += chunk.byteLength;
|
|
2211
1754
|
}
|
|
2212
|
-
return bytes;
|
|
1755
|
+
return bytes;
|
|
1756
|
+
}
|
|
1757
|
+
return { list, fileUrl, download };
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// src/streams/subscribe.ts
|
|
1761
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
1762
|
+
var SUBSCRIBE_MAX_RECONNECT_DELAY_MS = 30000;
|
|
1763
|
+
var SUBSCRIBE_DEFAULT_STALE_AFTER_MS = 60000;
|
|
1764
|
+
function reconnectDelay(attempt, baseMs, retryAfterSeconds, random = Math.random) {
|
|
1765
|
+
const capped = Math.min(baseMs * 2 ** Math.max(0, attempt), SUBSCRIBE_MAX_RECONNECT_DELAY_MS);
|
|
1766
|
+
const jittered = capped * (0.5 + random() * 0.5);
|
|
1767
|
+
const floor = (retryAfterSeconds ?? 0) * 1000;
|
|
1768
|
+
return Math.max(jittered, floor);
|
|
1769
|
+
}
|
|
1770
|
+
function isTerminal(err) {
|
|
1771
|
+
if (err instanceof StreamsSignatureError)
|
|
1772
|
+
return true;
|
|
1773
|
+
return err?.retryable === false;
|
|
1774
|
+
}
|
|
1775
|
+
function subscribeStreamsEvents(opts) {
|
|
1776
|
+
const { params } = opts;
|
|
1777
|
+
const controller = new AbortController;
|
|
1778
|
+
const external = params.signal;
|
|
1779
|
+
if (external) {
|
|
1780
|
+
if (external.aborted)
|
|
1781
|
+
controller.abort();
|
|
1782
|
+
else
|
|
1783
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
1784
|
+
once: true
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
let cursor = params.fromCursor ?? null;
|
|
1788
|
+
const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
|
|
1789
|
+
const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
|
|
1790
|
+
let attempt = 0;
|
|
1791
|
+
const run = async () => {
|
|
1792
|
+
while (!controller.signal.aborted) {
|
|
1793
|
+
try {
|
|
1794
|
+
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1795
|
+
from_cursor: cursor ?? undefined,
|
|
1796
|
+
types: params.types,
|
|
1797
|
+
not_types: params.notTypes,
|
|
1798
|
+
contract_id: params.contractId,
|
|
1799
|
+
sender: params.sender,
|
|
1800
|
+
recipient: params.recipient,
|
|
1801
|
+
asset_identifier: params.assetIdentifier,
|
|
1802
|
+
filters: params.filters ? JSON.stringify(params.filters) : undefined
|
|
1803
|
+
})}`;
|
|
1804
|
+
await readSse({
|
|
1805
|
+
url,
|
|
1806
|
+
headers: opts.headers ?? {},
|
|
1807
|
+
signal: controller.signal,
|
|
1808
|
+
fetchImpl: opts.fetchImpl,
|
|
1809
|
+
staleAfterMs,
|
|
1810
|
+
mapError: mapStreamsError,
|
|
1811
|
+
onFrame: async (frame) => {
|
|
1812
|
+
attempt = 0;
|
|
1813
|
+
if (frame.event === "ping" || !frame.data)
|
|
1814
|
+
return;
|
|
1815
|
+
let parsed;
|
|
1816
|
+
try {
|
|
1817
|
+
parsed = JSON.parse(frame.data);
|
|
1818
|
+
} catch {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (!parsed.event)
|
|
1822
|
+
return;
|
|
1823
|
+
if (opts.verify !== "off") {
|
|
1824
|
+
if (!parsed.sig) {
|
|
1825
|
+
if (opts.verify === "strict") {
|
|
1826
|
+
throw new StreamsSignatureError("Streams SSE frame signature is missing.");
|
|
1827
|
+
}
|
|
1828
|
+
} else {
|
|
1829
|
+
const key = await opts.loadKey(parsed.key_id);
|
|
1830
|
+
if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1831
|
+
throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
await params.onEvent(parsed.event);
|
|
1836
|
+
cursor = parsed.event.cursor ?? cursor;
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
|
|
1840
|
+
attempt += 1;
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
if (controller.signal.aborted)
|
|
1843
|
+
return;
|
|
1844
|
+
params.onError?.(err);
|
|
1845
|
+
if (isTerminal(err))
|
|
1846
|
+
throw err;
|
|
1847
|
+
const retryAfterSeconds = err.retryAfterSeconds;
|
|
1848
|
+
await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
|
|
1849
|
+
attempt += 1;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
const done = run();
|
|
1854
|
+
done.catch(() => {});
|
|
1855
|
+
const unsubscribe = () => controller.abort();
|
|
1856
|
+
return Object.assign(unsubscribe, { done });
|
|
1857
|
+
}
|
|
1858
|
+
async function readSse(opts) {
|
|
1859
|
+
const res = await opts.fetchImpl(opts.url, {
|
|
1860
|
+
headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
|
|
1861
|
+
signal: opts.signal
|
|
1862
|
+
});
|
|
1863
|
+
if (!res.ok) {
|
|
1864
|
+
if (opts.mapError)
|
|
1865
|
+
await opts.mapError(res);
|
|
1866
|
+
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1867
|
+
}
|
|
1868
|
+
if (!res.body) {
|
|
1869
|
+
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1870
|
+
}
|
|
1871
|
+
for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
|
|
1872
|
+
await opts.onFrame(frame);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function sleep(ms, signal) {
|
|
1876
|
+
return new Promise((resolve) => {
|
|
1877
|
+
if (signal.aborted)
|
|
1878
|
+
return resolve();
|
|
1879
|
+
const onAbort = () => {
|
|
1880
|
+
clearTimeout(timer);
|
|
1881
|
+
resolve();
|
|
1882
|
+
};
|
|
1883
|
+
const timer = setTimeout(() => {
|
|
1884
|
+
signal.removeEventListener("abort", onAbort);
|
|
1885
|
+
resolve();
|
|
1886
|
+
}, ms);
|
|
1887
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
async function readWithStaleTimer(reader, staleAfterMs) {
|
|
1891
|
+
if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
|
|
1892
|
+
return reader.read();
|
|
1893
|
+
}
|
|
1894
|
+
let timer;
|
|
1895
|
+
const stale = new Promise((_, reject) => {
|
|
1896
|
+
timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
|
|
1897
|
+
});
|
|
1898
|
+
try {
|
|
1899
|
+
return await Promise.race([reader.read(), stale]);
|
|
1900
|
+
} finally {
|
|
1901
|
+
clearTimeout(timer);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function* parseSseFrames(body, signal, staleAfterMs) {
|
|
1905
|
+
const reader = body.getReader();
|
|
1906
|
+
const decoder = new TextDecoder;
|
|
1907
|
+
let buffer = "";
|
|
1908
|
+
try {
|
|
1909
|
+
while (!signal.aborted) {
|
|
1910
|
+
const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
|
|
1911
|
+
if (done)
|
|
1912
|
+
break;
|
|
1913
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1914
|
+
let sep = buffer.indexOf(`
|
|
1915
|
+
|
|
1916
|
+
`);
|
|
1917
|
+
while (sep !== -1) {
|
|
1918
|
+
yield parseFrame(buffer.slice(0, sep));
|
|
1919
|
+
buffer = buffer.slice(sep + 2);
|
|
1920
|
+
sep = buffer.indexOf(`
|
|
1921
|
+
|
|
1922
|
+
`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
} finally {
|
|
1926
|
+
try {
|
|
1927
|
+
await reader.cancel();
|
|
1928
|
+
} catch {}
|
|
2213
1929
|
}
|
|
2214
|
-
|
|
1930
|
+
}
|
|
1931
|
+
function parseFrame(raw) {
|
|
1932
|
+
let event;
|
|
1933
|
+
const data = [];
|
|
1934
|
+
for (const line of raw.split(`
|
|
1935
|
+
`)) {
|
|
1936
|
+
if (line.startsWith("data:")) {
|
|
1937
|
+
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1938
|
+
} else if (line.startsWith("event:")) {
|
|
1939
|
+
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return { event, data: data.length > 0 ? data.join(`
|
|
1943
|
+
`) : undefined };
|
|
2215
1944
|
}
|
|
2216
1945
|
|
|
2217
1946
|
// src/streams/client.ts
|
|
@@ -2482,21 +2211,295 @@ function createStreamsClient(options) {
|
|
|
2482
2211
|
signal: params.signal
|
|
2483
2212
|
});
|
|
2484
2213
|
}
|
|
2485
|
-
},
|
|
2486
|
-
blocks: {
|
|
2487
|
-
events(heightOrHash) {
|
|
2488
|
-
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
2489
|
-
}
|
|
2490
|
-
},
|
|
2491
|
-
reorgs: { list: listReorgs },
|
|
2492
|
-
dumps,
|
|
2493
|
-
canonical(height) {
|
|
2494
|
-
return request(`/v1/streams/canonical/${height}`);
|
|
2495
|
-
},
|
|
2496
|
-
tip() {
|
|
2497
|
-
return request("/v1/streams/tip");
|
|
2498
|
-
}
|
|
2499
|
-
};
|
|
2214
|
+
},
|
|
2215
|
+
blocks: {
|
|
2216
|
+
events(heightOrHash) {
|
|
2217
|
+
return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2220
|
+
reorgs: { list: listReorgs },
|
|
2221
|
+
dumps,
|
|
2222
|
+
canonical(height) {
|
|
2223
|
+
return request(`/v1/streams/canonical/${height}`);
|
|
2224
|
+
},
|
|
2225
|
+
tip() {
|
|
2226
|
+
return request("/v1/streams/tip");
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/subgraphs/serialize.ts
|
|
2232
|
+
var SYSTEM_COLUMN_MAP = {
|
|
2233
|
+
_blockHeight: "_block_height",
|
|
2234
|
+
_txId: "_tx_id",
|
|
2235
|
+
_createdAt: "_created_at",
|
|
2236
|
+
_id: "_id"
|
|
2237
|
+
};
|
|
2238
|
+
var SYSTEM_COLUMN_ALIASES = {
|
|
2239
|
+
blockHeight: "_block_height",
|
|
2240
|
+
txId: "_tx_id",
|
|
2241
|
+
createdAt: "_created_at",
|
|
2242
|
+
id: "_id"
|
|
2243
|
+
};
|
|
2244
|
+
function resolveColumn(col, columns) {
|
|
2245
|
+
const canonical = SYSTEM_COLUMN_MAP[col];
|
|
2246
|
+
if (canonical)
|
|
2247
|
+
return canonical;
|
|
2248
|
+
if (columns?.has(col))
|
|
2249
|
+
return col;
|
|
2250
|
+
return SYSTEM_COLUMN_ALIASES[col] ?? col;
|
|
2251
|
+
}
|
|
2252
|
+
function serializeWhere(where, columns) {
|
|
2253
|
+
const filters = {};
|
|
2254
|
+
for (const [column, value] of Object.entries(where)) {
|
|
2255
|
+
if (value === null || value === undefined)
|
|
2256
|
+
continue;
|
|
2257
|
+
const col = resolveColumn(column, columns);
|
|
2258
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
2259
|
+
const ops = value;
|
|
2260
|
+
for (const [op, opValue] of Object.entries(ops)) {
|
|
2261
|
+
if (opValue === null || opValue === undefined)
|
|
2262
|
+
continue;
|
|
2263
|
+
if (op === "eq") {
|
|
2264
|
+
filters[col] = String(opValue);
|
|
2265
|
+
} else if (op === "in" || op === "notIn") {
|
|
2266
|
+
const arr = Array.isArray(opValue) ? opValue : [opValue];
|
|
2267
|
+
filters[`${col}.${op}`] = arr.map((v) => String(v)).join(",");
|
|
2268
|
+
} else if (["neq", "gt", "gte", "lt", "lte", "like"].includes(op)) {
|
|
2269
|
+
filters[`${col}.${op}`] = String(opValue);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
} else {
|
|
2273
|
+
filters[col] = String(value);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return filters;
|
|
2277
|
+
}
|
|
2278
|
+
function resolveOrderByColumn(col, columns) {
|
|
2279
|
+
return resolveColumn(col, columns);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/subgraphs/client.ts
|
|
2283
|
+
var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
|
|
2284
|
+
function declaredColumns(table) {
|
|
2285
|
+
if (!table || typeof table !== "object")
|
|
2286
|
+
return;
|
|
2287
|
+
const columns = table.columns;
|
|
2288
|
+
if (!columns || typeof columns !== "object")
|
|
2289
|
+
return;
|
|
2290
|
+
return new Set(Object.keys(columns));
|
|
2291
|
+
}
|
|
2292
|
+
function rowBlockHeight(row) {
|
|
2293
|
+
const r = row;
|
|
2294
|
+
const raw = r._block_height ?? r._blockHeight;
|
|
2295
|
+
if (typeof raw !== "number" && typeof raw !== "string")
|
|
2296
|
+
return;
|
|
2297
|
+
const height = Number(raw);
|
|
2298
|
+
return Number.isFinite(height) ? height : undefined;
|
|
2299
|
+
}
|
|
2300
|
+
function sleep2(ms, signal) {
|
|
2301
|
+
return new Promise((resolve) => {
|
|
2302
|
+
if (signal.aborted)
|
|
2303
|
+
return resolve();
|
|
2304
|
+
const onAbort = () => {
|
|
2305
|
+
clearTimeout(timer);
|
|
2306
|
+
resolve();
|
|
2307
|
+
};
|
|
2308
|
+
const timer = setTimeout(() => {
|
|
2309
|
+
signal.removeEventListener("abort", onAbort);
|
|
2310
|
+
resolve();
|
|
2311
|
+
}, ms);
|
|
2312
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
function buildSubgraphQueryString(params) {
|
|
2316
|
+
return buildQuery({
|
|
2317
|
+
_sort: params.sort,
|
|
2318
|
+
_order: params.order,
|
|
2319
|
+
_limit: params.limit,
|
|
2320
|
+
_offset: params.offset,
|
|
2321
|
+
_fields: params.fields,
|
|
2322
|
+
...params.filters
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
function buildAggregateQueryString(params) {
|
|
2326
|
+
return buildQuery({
|
|
2327
|
+
...params.filters,
|
|
2328
|
+
_count: params.count ? "true" : undefined,
|
|
2329
|
+
_countDistinct: params.countDistinct,
|
|
2330
|
+
_sum: params.sum,
|
|
2331
|
+
_min: params.min,
|
|
2332
|
+
_max: params.max
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
function buildSpecQueryString(options) {
|
|
2336
|
+
return buildQuery({ server: options?.serverUrl });
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
class Subgraphs extends BaseClient {
|
|
2340
|
+
async list() {
|
|
2341
|
+
return this.request("GET", "/api/subgraphs");
|
|
2342
|
+
}
|
|
2343
|
+
async status(name) {
|
|
2344
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}`);
|
|
2345
|
+
}
|
|
2346
|
+
async openapi(name, options) {
|
|
2347
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
|
|
2348
|
+
}
|
|
2349
|
+
async schema(name, options) {
|
|
2350
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
|
|
2351
|
+
}
|
|
2352
|
+
async markdown(name, options) {
|
|
2353
|
+
return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
|
|
2354
|
+
}
|
|
2355
|
+
async reindex(name) {
|
|
2356
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
|
|
2357
|
+
}
|
|
2358
|
+
async stop(name) {
|
|
2359
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
|
|
2360
|
+
}
|
|
2361
|
+
async backfill(name, options) {
|
|
2362
|
+
return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
|
|
2363
|
+
}
|
|
2364
|
+
async gaps(name, opts) {
|
|
2365
|
+
const qs = buildQuery({
|
|
2366
|
+
_limit: opts?.limit,
|
|
2367
|
+
_offset: opts?.offset,
|
|
2368
|
+
resolved: opts?.resolved
|
|
2369
|
+
});
|
|
2370
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
|
|
2371
|
+
}
|
|
2372
|
+
async delete(name, options) {
|
|
2373
|
+
const qs = buildQuery({ force: options?.force ? true : undefined });
|
|
2374
|
+
return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
|
|
2375
|
+
}
|
|
2376
|
+
async rows(name, table, params = {}) {
|
|
2377
|
+
const { cursor, ...rest } = params;
|
|
2378
|
+
const qs = buildSubgraphQueryString(rest);
|
|
2379
|
+
const sep = qs ? "&" : "?";
|
|
2380
|
+
const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
|
|
2381
|
+
return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
|
|
2382
|
+
}
|
|
2383
|
+
async operations(name) {
|
|
2384
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
|
|
2385
|
+
}
|
|
2386
|
+
async getOperation(name, operationId) {
|
|
2387
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
|
|
2388
|
+
}
|
|
2389
|
+
async deploy(data) {
|
|
2390
|
+
return this.request("POST", "/api/subgraphs", data);
|
|
2391
|
+
}
|
|
2392
|
+
async getSource(name) {
|
|
2393
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
|
|
2394
|
+
}
|
|
2395
|
+
async bundle(data) {
|
|
2396
|
+
return this.request("POST", "/api/subgraphs/bundle", data);
|
|
2397
|
+
}
|
|
2398
|
+
async queryTable(name, table, params = {}) {
|
|
2399
|
+
const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
|
|
2400
|
+
return Array.isArray(result) ? result : result.data;
|
|
2401
|
+
}
|
|
2402
|
+
async queryTableCount(name, table, params = {}) {
|
|
2403
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
|
|
2404
|
+
}
|
|
2405
|
+
async queryTableAggregate(name, table, params = {}) {
|
|
2406
|
+
return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
|
|
2407
|
+
}
|
|
2408
|
+
typed(def) {
|
|
2409
|
+
const result = {};
|
|
2410
|
+
for (const [tableName, table] of Object.entries(def.schema)) {
|
|
2411
|
+
result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
|
|
2412
|
+
}
|
|
2413
|
+
return result;
|
|
2414
|
+
}
|
|
2415
|
+
createTableClient(subgraphName, tableName, columns) {
|
|
2416
|
+
const self = this;
|
|
2417
|
+
return {
|
|
2418
|
+
async findMany(options = {}) {
|
|
2419
|
+
const filters = options.where ? serializeWhere(options.where, columns) : undefined;
|
|
2420
|
+
let sort;
|
|
2421
|
+
let order;
|
|
2422
|
+
if (options.orderBy) {
|
|
2423
|
+
const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
|
|
2424
|
+
if (entries.length > 0) {
|
|
2425
|
+
sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
|
|
2426
|
+
order = entries.map(([, dir]) => dir ?? "asc").join(",");
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
const params = {
|
|
2430
|
+
sort,
|
|
2431
|
+
order,
|
|
2432
|
+
limit: options.limit,
|
|
2433
|
+
offset: options.offset,
|
|
2434
|
+
fields: options.fields?.join(","),
|
|
2435
|
+
filters
|
|
2436
|
+
};
|
|
2437
|
+
return self.queryTable(subgraphName, tableName, params);
|
|
2438
|
+
},
|
|
2439
|
+
async count(where) {
|
|
2440
|
+
const filters = where ? serializeWhere(where, columns) : undefined;
|
|
2441
|
+
const result = await self.queryTableCount(subgraphName, tableName, {
|
|
2442
|
+
filters
|
|
2443
|
+
});
|
|
2444
|
+
return result.count;
|
|
2445
|
+
},
|
|
2446
|
+
async aggregate(spec) {
|
|
2447
|
+
const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
|
|
2448
|
+
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
2449
|
+
filters,
|
|
2450
|
+
count: spec.count,
|
|
2451
|
+
countDistinct: spec.countDistinct,
|
|
2452
|
+
sum: spec.sum,
|
|
2453
|
+
min: spec.min,
|
|
2454
|
+
max: spec.max
|
|
2455
|
+
});
|
|
2456
|
+
return result;
|
|
2457
|
+
},
|
|
2458
|
+
subscribe(onRow, options = {}) {
|
|
2459
|
+
const filters = options.where ? serializeWhere(options.where, columns) : {};
|
|
2460
|
+
const controller = new AbortController;
|
|
2461
|
+
let since = options.since ?? undefined;
|
|
2462
|
+
const run = async () => {
|
|
2463
|
+
while (!controller.signal.aborted) {
|
|
2464
|
+
try {
|
|
2465
|
+
const qs = buildQuery({ ...filters, since });
|
|
2466
|
+
await readSse({
|
|
2467
|
+
url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
|
|
2468
|
+
headers: {
|
|
2469
|
+
...BaseClient.authHeaders(self.apiKey),
|
|
2470
|
+
"x-sl-origin": self.origin
|
|
2471
|
+
},
|
|
2472
|
+
signal: controller.signal,
|
|
2473
|
+
fetchImpl: self.fetchImpl,
|
|
2474
|
+
onFrame: (frame) => {
|
|
2475
|
+
if (frame.event === "ping" || !frame.data)
|
|
2476
|
+
return;
|
|
2477
|
+
let row;
|
|
2478
|
+
try {
|
|
2479
|
+
row = JSON.parse(frame.data);
|
|
2480
|
+
} catch {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
const height = rowBlockHeight(row);
|
|
2484
|
+
if (height !== undefined)
|
|
2485
|
+
since = height;
|
|
2486
|
+
onRow(row);
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
if (controller.signal.aborted)
|
|
2492
|
+
return;
|
|
2493
|
+
options.onError?.(err);
|
|
2494
|
+
await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
run();
|
|
2499
|
+
return () => controller.abort();
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2500
2503
|
}
|
|
2501
2504
|
|
|
2502
2505
|
// src/webhooks/client.ts
|
|
@@ -2672,7 +2675,6 @@ class SecondLayer extends BaseClient {
|
|
|
2672
2675
|
};
|
|
2673
2676
|
}
|
|
2674
2677
|
}
|
|
2675
|
-
|
|
2676
2678
|
// src/subgraphs/get-subgraph.ts
|
|
2677
2679
|
function getSubgraph(def, options = {}) {
|
|
2678
2680
|
if (options instanceof Subgraphs) {
|
|
@@ -2683,36 +2685,6 @@ function getSubgraph(def, options = {}) {
|
|
|
2683
2685
|
}
|
|
2684
2686
|
return new Subgraphs(options).typed(def);
|
|
2685
2687
|
}
|
|
2686
|
-
// src/streams/index.ts
|
|
2687
|
-
import {
|
|
2688
|
-
decodeFtTransfer,
|
|
2689
|
-
isFtTransfer,
|
|
2690
|
-
decodeNftTransfer,
|
|
2691
|
-
isNftTransfer,
|
|
2692
|
-
decodeStxBurn,
|
|
2693
|
-
decodeStxLock,
|
|
2694
|
-
decodeStxMint,
|
|
2695
|
-
decodeStxTransfer,
|
|
2696
|
-
isStxBurn,
|
|
2697
|
-
isStxLock,
|
|
2698
|
-
isStxMint,
|
|
2699
|
-
isStxTransfer,
|
|
2700
|
-
decodeFtBurn,
|
|
2701
|
-
decodeFtMint,
|
|
2702
|
-
decodeNftBurn,
|
|
2703
|
-
decodeNftMint,
|
|
2704
|
-
isFtBurn,
|
|
2705
|
-
isFtMint,
|
|
2706
|
-
isNftBurn,
|
|
2707
|
-
isNftMint,
|
|
2708
|
-
decodePrint,
|
|
2709
|
-
isPrint
|
|
2710
|
-
} from "@secondlayer/shared/streams-rows";
|
|
2711
|
-
|
|
2712
|
-
// src/streams/types.ts
|
|
2713
|
-
import {
|
|
2714
|
-
STREAMS_EVENT_TYPES
|
|
2715
|
-
} from "@secondlayer/shared/streams-rows";
|
|
2716
2688
|
// src/consumer-ops.ts
|
|
2717
2689
|
function consumerHealth(options = {}) {
|
|
2718
2690
|
const staleAfterMs = options.staleAfterMs ?? 120000;
|
|
@@ -2764,12 +2736,12 @@ import {
|
|
|
2764
2736
|
} from "@secondlayer/shared/crypto/standard-webhooks";
|
|
2765
2737
|
function pickHeader(headers, name) {
|
|
2766
2738
|
if (typeof headers === "function") {
|
|
2767
|
-
const
|
|
2768
|
-
return typeof
|
|
2739
|
+
const v = headers(name);
|
|
2740
|
+
return typeof v === "string" ? v : undefined;
|
|
2769
2741
|
}
|
|
2770
2742
|
if (typeof headers.get === "function" && !(name in headers)) {
|
|
2771
|
-
const
|
|
2772
|
-
return typeof
|
|
2743
|
+
const v = headers.get(name);
|
|
2744
|
+
return typeof v === "string" ? v : undefined;
|
|
2773
2745
|
}
|
|
2774
2746
|
const bag = headers;
|
|
2775
2747
|
let v = bag[name] ?? bag[name.toLowerCase()];
|
|
@@ -2906,79 +2878,109 @@ function verifyTransactionProof(proof, opts) {
|
|
|
2906
2878
|
errors
|
|
2907
2879
|
};
|
|
2908
2880
|
}
|
|
2881
|
+
// src/streams/index.ts
|
|
2882
|
+
import {
|
|
2883
|
+
decodeFtTransfer,
|
|
2884
|
+
isFtTransfer,
|
|
2885
|
+
decodeNftTransfer,
|
|
2886
|
+
isNftTransfer,
|
|
2887
|
+
decodeStxBurn,
|
|
2888
|
+
decodeStxLock,
|
|
2889
|
+
decodeStxMint,
|
|
2890
|
+
decodeStxTransfer,
|
|
2891
|
+
isStxBurn,
|
|
2892
|
+
isStxLock,
|
|
2893
|
+
isStxMint,
|
|
2894
|
+
isStxTransfer,
|
|
2895
|
+
decodeFtBurn,
|
|
2896
|
+
decodeFtMint,
|
|
2897
|
+
decodeNftBurn,
|
|
2898
|
+
decodeNftMint,
|
|
2899
|
+
isFtBurn,
|
|
2900
|
+
isFtMint,
|
|
2901
|
+
isNftBurn,
|
|
2902
|
+
isNftMint,
|
|
2903
|
+
decodePrint,
|
|
2904
|
+
isPrint
|
|
2905
|
+
} from "@secondlayer/shared/streams-rows";
|
|
2906
|
+
|
|
2907
|
+
// src/streams/types.ts
|
|
2908
|
+
import {
|
|
2909
|
+
STREAMS_EVENT_TYPES
|
|
2910
|
+
} from "@secondlayer/shared/streams-rows";
|
|
2909
2911
|
// src/clarity.ts
|
|
2910
2912
|
import {
|
|
2911
2913
|
decodeClarityValue,
|
|
2912
2914
|
toJsonSafe
|
|
2913
2915
|
} from "@secondlayer/shared/streams-rows";
|
|
2914
2916
|
export {
|
|
2915
|
-
|
|
2916
|
-
verifyTransactionProof,
|
|
2917
|
-
verify2 as verifyStandardWebhooksHeaders,
|
|
2918
|
-
verifySecondlayerSignature,
|
|
2919
|
-
trigger,
|
|
2920
|
-
toJsonSafe,
|
|
2921
|
-
shutdownSignal,
|
|
2922
|
-
resolveBaseUrl,
|
|
2923
|
-
resolveApiKey,
|
|
2924
|
-
resolveAccountKey,
|
|
2925
|
-
parseRetryAfter,
|
|
2926
|
-
isStxTransfer,
|
|
2927
|
-
isStxMint,
|
|
2928
|
-
isStxLock,
|
|
2929
|
-
isStxBurn,
|
|
2930
|
-
isPrint,
|
|
2931
|
-
isNftTransfer,
|
|
2932
|
-
isNftMint,
|
|
2933
|
-
isNftBurn,
|
|
2934
|
-
isFtTransfer,
|
|
2935
|
-
isFtMint,
|
|
2936
|
-
isFtBurn,
|
|
2937
|
-
getSubgraph,
|
|
2938
|
-
fetchRewardSet,
|
|
2939
|
-
decodeStxTransfer,
|
|
2940
|
-
decodeStxMint,
|
|
2941
|
-
decodeStxLock,
|
|
2942
|
-
decodeStxBurn,
|
|
2943
|
-
decodePrint,
|
|
2944
|
-
decodeNftTransfer,
|
|
2945
|
-
decodeNftMint,
|
|
2946
|
-
decodeNftBurn,
|
|
2947
|
-
decodeFtTransfer,
|
|
2948
|
-
decodeFtMint,
|
|
2949
|
-
decodeFtBurn,
|
|
2950
|
-
decodeClarityValue,
|
|
2951
|
-
decodeChainWebhook,
|
|
2952
|
-
decode,
|
|
2953
|
-
createStreamsClient,
|
|
2954
|
-
createArchiveClient,
|
|
2955
|
-
contextField,
|
|
2956
|
-
consumerHealth,
|
|
2957
|
-
consumeIndexFeed,
|
|
2958
|
-
Webhooks,
|
|
2959
|
-
ValidationError,
|
|
2960
|
-
Subgraphs,
|
|
2961
|
-
StreamsSignatureError,
|
|
2962
|
-
StreamsServerError,
|
|
2963
|
-
SecondLayerError,
|
|
2964
|
-
SecondLayer,
|
|
2965
|
-
RateLimitError,
|
|
2966
|
-
MemoryEtagCache,
|
|
2967
|
-
LOCAL_API_URL,
|
|
2968
|
-
InsufficientArchiveCreditsError,
|
|
2969
|
-
Index,
|
|
2970
|
-
INSTANCE_TOKEN_ENV,
|
|
2971
|
-
INDEX_MAX_PAGE_SIZE,
|
|
2972
|
-
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2973
|
-
Cursor,
|
|
2974
|
-
Contracts,
|
|
2975
|
-
AuthError,
|
|
2976
|
-
ArchiveSignatureError,
|
|
2977
|
-
ArchiveGateNotConfiguredError,
|
|
2978
|
-
ArchiveAuthError,
|
|
2917
|
+
ACCOUNT_KEY_ENV,
|
|
2979
2918
|
ApiError,
|
|
2980
|
-
|
|
2919
|
+
ArchiveAuthError,
|
|
2920
|
+
ArchiveGateNotConfiguredError,
|
|
2921
|
+
ArchiveSignatureError,
|
|
2922
|
+
AuthError,
|
|
2923
|
+
Contracts,
|
|
2924
|
+
Cursor,
|
|
2925
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
2926
|
+
INDEX_MAX_PAGE_SIZE,
|
|
2927
|
+
INSTANCE_TOKEN_ENV,
|
|
2928
|
+
Index,
|
|
2929
|
+
InsufficientArchiveCreditsError,
|
|
2930
|
+
LOCAL_API_URL,
|
|
2931
|
+
MemoryEtagCache,
|
|
2932
|
+
RateLimitError,
|
|
2933
|
+
SecondLayer,
|
|
2934
|
+
SecondLayerError,
|
|
2935
|
+
StreamsServerError,
|
|
2936
|
+
StreamsSignatureError,
|
|
2937
|
+
Subgraphs,
|
|
2938
|
+
ValidationError,
|
|
2939
|
+
Webhooks,
|
|
2940
|
+
consumeIndexFeed,
|
|
2941
|
+
consumerHealth,
|
|
2942
|
+
contextField,
|
|
2943
|
+
createArchiveClient,
|
|
2944
|
+
createStreamsClient,
|
|
2945
|
+
decode,
|
|
2946
|
+
decodeChainWebhook,
|
|
2947
|
+
decodeClarityValue,
|
|
2948
|
+
decodeFtBurn,
|
|
2949
|
+
decodeFtMint,
|
|
2950
|
+
decodeFtTransfer,
|
|
2951
|
+
decodeNftBurn,
|
|
2952
|
+
decodeNftMint,
|
|
2953
|
+
decodeNftTransfer,
|
|
2954
|
+
decodePrint,
|
|
2955
|
+
decodeStxBurn,
|
|
2956
|
+
decodeStxLock,
|
|
2957
|
+
decodeStxMint,
|
|
2958
|
+
decodeStxTransfer,
|
|
2959
|
+
fetchRewardSet,
|
|
2960
|
+
getSubgraph,
|
|
2961
|
+
isFtBurn,
|
|
2962
|
+
isFtMint,
|
|
2963
|
+
isFtTransfer,
|
|
2964
|
+
isNftBurn,
|
|
2965
|
+
isNftMint,
|
|
2966
|
+
isNftTransfer,
|
|
2967
|
+
isPrint,
|
|
2968
|
+
isStxBurn,
|
|
2969
|
+
isStxLock,
|
|
2970
|
+
isStxMint,
|
|
2971
|
+
isStxTransfer,
|
|
2972
|
+
parseRetryAfter,
|
|
2973
|
+
resolveAccountKey,
|
|
2974
|
+
resolveApiKey,
|
|
2975
|
+
resolveBaseUrl,
|
|
2976
|
+
shutdownSignal,
|
|
2977
|
+
toJsonSafe,
|
|
2978
|
+
trigger,
|
|
2979
|
+
verifySecondlayerSignature,
|
|
2980
|
+
verify2 as verifyStandardWebhooksHeaders,
|
|
2981
|
+
verifyTransactionProof,
|
|
2982
|
+
verifyWebhookSignature
|
|
2981
2983
|
};
|
|
2982
2984
|
|
|
2983
|
-
//# debugId=
|
|
2985
|
+
//# debugId=92280D2ABC3CFA6364756E2164756E21
|
|
2984
2986
|
//# sourceMappingURL=index.js.map
|