@secondlayer/sdk 10.4.3 → 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.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
 
@@ -156,6 +163,30 @@ function parseRetryAfter(value) {
156
163
  }
157
164
 
158
165
  // src/base.ts
166
+ class MemoryEtagCache {
167
+ limit;
168
+ entries = new Map;
169
+ constructor(limit = 64) {
170
+ this.limit = limit;
171
+ }
172
+ get(key) {
173
+ const entry = this.entries.get(key);
174
+ if (entry) {
175
+ this.entries.delete(key);
176
+ this.entries.set(key, entry);
177
+ }
178
+ return entry;
179
+ }
180
+ set(key, entry) {
181
+ this.entries.delete(key);
182
+ this.entries.set(key, entry);
183
+ if (this.entries.size > this.limit) {
184
+ const oldest = this.entries.keys().next().value;
185
+ if (oldest !== undefined)
186
+ this.entries.delete(oldest);
187
+ }
188
+ }
189
+ }
159
190
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
160
191
  function abortError() {
161
192
  return new DOMException("This operation was aborted", "AbortError");
@@ -266,12 +297,14 @@ class BaseClient {
266
297
  origin;
267
298
  fetchImpl;
268
299
  requestTimeoutMs;
300
+ etagCache;
269
301
  constructor(options = {}) {
270
302
  this.baseUrl = resolveBaseUrl(options.baseUrl);
271
303
  this.apiKey = resolveApiKey(options.apiKey);
272
304
  this.origin = options.origin ?? "cli";
273
305
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
274
306
  this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
307
+ this.etagCache = options.etagCache === false ? null : options.etagCache ?? new MemoryEtagCache;
275
308
  }
276
309
  static authHeaders(apiKey) {
277
310
  const headers = {
@@ -284,11 +317,25 @@ class BaseClient {
284
317
  }
285
318
  async request(method, path, body, opts = {}) {
286
319
  return this.withRequestBudget(opts.signal, async (signal) => {
287
- const response = await this.fetchResponse(method, path, body, signal);
320
+ const cacheKey = method === "GET" && this.etagCache ? `${this.baseUrl}${path}` : null;
321
+ const cached = cacheKey ? await this.etagCache?.get(cacheKey) : undefined;
322
+ const response = await this.fetchResponse(method, path, body, signal, cached ? { "If-None-Match": cached.etag } : undefined);
288
323
  if (response.status === 204) {
289
324
  return;
290
325
  }
291
- return await raceAbort(response.json(), signal);
326
+ if (response.status === 304) {
327
+ if (!cached) {
328
+ throw new ApiError(304, "Not Modified without a cached copy");
329
+ }
330
+ return JSON.parse(cached.body);
331
+ }
332
+ const etag = cacheKey ? response.headers.get("ETag") : null;
333
+ if (!cacheKey || !etag) {
334
+ return await raceAbort(response.json(), signal);
335
+ }
336
+ const text = await raceAbort(response.text(), signal);
337
+ await this.etagCache?.set(cacheKey, { etag, body: text });
338
+ return JSON.parse(text);
292
339
  });
293
340
  }
294
341
  async requestOrNull(method, path, body, opts = {}) {
@@ -331,9 +378,12 @@ class BaseClient {
331
378
  callerSignal?.removeEventListener("abort", forward);
332
379
  }
333
380
  }
334
- async fetchResponse(method, path, body, signal) {
381
+ async fetchResponse(method, path, body, signal, extraHeaders) {
335
382
  const url = `${this.baseUrl}${path}`;
336
- const headers = BaseClient.authHeaders(this.apiKey);
383
+ const headers = {
384
+ ...BaseClient.authHeaders(this.apiKey),
385
+ ...extraHeaders
386
+ };
337
387
  headers["x-sl-origin"] = this.origin;
338
388
  let serializedBody;
339
389
  if (body !== undefined && body !== null) {
@@ -358,6 +408,8 @@ class BaseClient {
358
408
  throw err;
359
409
  throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`, undefined, undefined, { retryable: true, cause: err instanceof Error ? err : undefined });
360
410
  }
411
+ if (response.status === 304)
412
+ return response;
361
413
  if (!response.ok) {
362
414
  const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
363
415
  const envelope = parseErrorEnvelope(errorBody);
@@ -379,604 +431,109 @@ class BaseClient {
379
431
  }
380
432
  }
381
433
 
382
- // src/streams/subscribe.ts
383
- import { ed25519 } from "@secondlayer/shared";
384
-
385
- // src/streams/errors.ts
386
- class StreamsServerError extends ApiError {
387
- constructor(message, status, body, code) {
388
- super(status, message, body, code, { retryable: true });
389
- this.name = "StreamsServerError";
390
- }
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
+ };
391
451
  }
392
-
393
- class StreamsSignatureError extends SecondLayerError {
394
- constructor(message = "Streams response signature verification failed.") {
395
- super(message);
396
- this.name = "StreamsSignatureError";
397
- }
452
+ function stripSlash(url) {
453
+ return url.replace(/\/+$/, "");
398
454
  }
399
- async function mapStreamsError(response) {
400
- const { message, code, body } = parseErrorEnvelope(await response.text());
401
- if (response.status === 401) {
402
- throw new AuthError(message ?? "API key invalid or expired.", body, code);
403
- }
404
- if (response.status === 429) {
405
- const retryAfter = response.headers.get("Retry-After") ?? undefined;
406
- throw new RateLimitError(message ?? "Rate limited. Try again later.", retryAfter, body, code);
407
- }
408
- if (response.status >= 500) {
409
- throw new StreamsServerError(message ?? `Streams server returned ${response.status}.`, response.status, body, code);
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;
410
485
  }
411
- throw new ValidationError(message ?? `Streams request returned ${response.status}.`, response.status, body, code);
412
- }
413
-
414
- // src/streams/subscribe.ts
415
- var SUBSCRIBE_MAX_RECONNECT_DELAY_MS = 30000;
416
- var SUBSCRIBE_DEFAULT_STALE_AFTER_MS = 60000;
417
- function reconnectDelay(attempt, baseMs, retryAfterSeconds, random = Math.random) {
418
- const capped = Math.min(baseMs * 2 ** Math.max(0, attempt), SUBSCRIBE_MAX_RECONNECT_DELAY_MS);
419
- const jittered = capped * (0.5 + random() * 0.5);
420
- const floor = (retryAfterSeconds ?? 0) * 1000;
421
- return Math.max(jittered, floor);
422
- }
423
- function isTerminal(err) {
424
- if (err instanceof StreamsSignatureError)
425
- return true;
426
- return err?.retryable === false;
427
- }
428
- function subscribeStreamsEvents(opts) {
429
- const { params } = opts;
430
- const controller = new AbortController;
431
- const external = params.signal;
432
- if (external) {
433
- if (external.aborted)
434
- controller.abort();
435
- else
436
- external.addEventListener("abort", () => controller.abort(), {
437
- once: true
438
- });
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);
439
499
  }
440
- let cursor = params.fromCursor ?? null;
441
- const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
442
- const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
443
- let attempt = 0;
444
- const run = async () => {
445
- while (!controller.signal.aborted) {
446
- try {
447
- const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
448
- from_cursor: cursor ?? undefined,
449
- types: params.types,
450
- not_types: params.notTypes,
451
- contract_id: params.contractId,
452
- sender: params.sender,
453
- recipient: params.recipient,
454
- asset_identifier: params.assetIdentifier,
455
- filters: params.filters ? JSON.stringify(params.filters) : undefined
456
- })}`;
457
- await readSse({
458
- url,
459
- headers: opts.headers ?? {},
460
- signal: controller.signal,
461
- fetchImpl: opts.fetchImpl,
462
- staleAfterMs,
463
- mapError: mapStreamsError,
464
- onFrame: async (frame) => {
465
- attempt = 0;
466
- if (frame.event === "ping" || !frame.data)
467
- return;
468
- let parsed;
469
- try {
470
- parsed = JSON.parse(frame.data);
471
- } catch {
472
- return;
473
- }
474
- if (!parsed.event)
475
- return;
476
- if (opts.verify !== "off") {
477
- if (!parsed.sig) {
478
- if (opts.verify === "strict") {
479
- throw new StreamsSignatureError("Streams SSE frame signature is missing.");
480
- }
481
- } else {
482
- const key = await opts.loadKey(parsed.key_id);
483
- if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
484
- throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
485
- }
486
- }
487
- }
488
- await params.onEvent(parsed.event);
489
- cursor = parsed.event.cursor ?? cursor;
490
- }
491
- });
492
- await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
493
- attempt += 1;
494
- } catch (err) {
495
- if (controller.signal.aborted)
496
- return;
497
- params.onError?.(err);
498
- if (isTerminal(err))
499
- throw err;
500
- const retryAfterSeconds = err.retryAfterSeconds;
501
- await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
502
- 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");
503
507
  }
508
+ headers.Authorization = `Bearer ${accountKey}`;
504
509
  }
505
- };
506
- const done = run();
507
- done.catch(() => {});
508
- const unsubscribe = () => controller.abort();
509
- return Object.assign(unsubscribe, { done });
510
- }
511
- async function readSse(opts) {
512
- const res = await opts.fetchImpl(opts.url, {
513
- headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
514
- signal: opts.signal
515
- });
516
- if (!res.ok) {
517
- if (opts.mapError)
518
- await opts.mapError(res);
519
- 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();
520
518
  }
521
- if (!res.body) {
522
- throw new StreamsServerError("Streams SSE response has no body.", 0);
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 };
523
534
  }
524
- for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
525
- await opts.onFrame(frame);
526
- }
527
- }
528
- function sleep(ms, signal) {
529
- return new Promise((resolve) => {
530
- if (signal.aborted)
531
- return resolve();
532
- const onAbort = () => {
533
- clearTimeout(timer);
534
- resolve();
535
- };
536
- const timer = setTimeout(() => {
537
- signal.removeEventListener("abort", onAbort);
538
- resolve();
539
- }, ms);
540
- signal.addEventListener("abort", onAbort, { once: true });
541
- });
542
- }
543
- async function readWithStaleTimer(reader, staleAfterMs) {
544
- if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
545
- return reader.read();
546
- }
547
- let timer;
548
- const stale = new Promise((_, reject) => {
549
- timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
550
- });
551
- try {
552
- return await Promise.race([reader.read(), stale]);
553
- } finally {
554
- clearTimeout(timer);
555
- }
556
- }
557
- async function* parseSseFrames(body, signal, staleAfterMs) {
558
- const reader = body.getReader();
559
- const decoder = new TextDecoder;
560
- let buffer = "";
561
- try {
562
- while (!signal.aborted) {
563
- const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
564
- if (done)
565
- break;
566
- buffer += decoder.decode(value, { stream: true });
567
- let sep = buffer.indexOf(`
568
-
569
- `);
570
- while (sep !== -1) {
571
- yield parseFrame(buffer.slice(0, sep));
572
- buffer = buffer.slice(sep + 2);
573
- sep = buffer.indexOf(`
574
-
575
- `);
576
- }
577
- }
578
- } finally {
579
- try {
580
- await reader.cancel();
581
- } catch {}
582
- }
583
- }
584
- function parseFrame(raw) {
585
- let event;
586
- const data = [];
587
- for (const line of raw.split(`
588
- `)) {
589
- if (line.startsWith("data:")) {
590
- data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
591
- } else if (line.startsWith("event:")) {
592
- event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
593
- }
594
- }
595
- return { event, data: data.length > 0 ? data.join(`
596
- `) : undefined };
597
- }
598
-
599
- // src/subgraphs/serialize.ts
600
- var SYSTEM_COLUMN_MAP = {
601
- _blockHeight: "_block_height",
602
- _txId: "_tx_id",
603
- _createdAt: "_created_at",
604
- _id: "_id"
605
- };
606
- var SYSTEM_COLUMN_ALIASES = {
607
- blockHeight: "_block_height",
608
- txId: "_tx_id",
609
- createdAt: "_created_at",
610
- id: "_id"
611
- };
612
- function resolveColumn(col, columns) {
613
- const canonical = SYSTEM_COLUMN_MAP[col];
614
- if (canonical)
615
- return canonical;
616
- if (columns?.has(col))
617
- return col;
618
- return SYSTEM_COLUMN_ALIASES[col] ?? col;
619
- }
620
- function serializeWhere(where, columns) {
621
- const filters = {};
622
- for (const [column, value] of Object.entries(where)) {
623
- if (value === null || value === undefined)
624
- continue;
625
- const col = resolveColumn(column, columns);
626
- if (typeof value === "object" && !Array.isArray(value)) {
627
- const ops = value;
628
- for (const [op, opValue] of Object.entries(ops)) {
629
- if (opValue === null || opValue === undefined)
630
- continue;
631
- if (op === "eq") {
632
- filters[col] = String(opValue);
633
- } else if (op === "in" || op === "notIn") {
634
- const arr = Array.isArray(opValue) ? opValue : [opValue];
635
- filters[`${col}.${op}`] = arr.map((v) => String(v)).join(",");
636
- } else if (["neq", "gt", "gte", "lt", "lte", "like"].includes(op)) {
637
- filters[`${col}.${op}`] = String(opValue);
638
- }
639
- }
640
- } else {
641
- filters[col] = String(value);
642
- }
643
- }
644
- return filters;
645
- }
646
- function resolveOrderByColumn(col, columns) {
647
- return resolveColumn(col, columns);
648
- }
649
-
650
- // src/subgraphs/client.ts
651
- var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
652
- function declaredColumns(table) {
653
- if (!table || typeof table !== "object")
654
- return;
655
- const columns = table.columns;
656
- if (!columns || typeof columns !== "object")
657
- return;
658
- return new Set(Object.keys(columns));
659
- }
660
- function rowBlockHeight(row) {
661
- const r = row;
662
- const raw = r._block_height ?? r._blockHeight;
663
- if (typeof raw !== "number" && typeof raw !== "string")
664
- return;
665
- const height = Number(raw);
666
- return Number.isFinite(height) ? height : undefined;
667
- }
668
- function sleep2(ms, signal) {
669
- return new Promise((resolve) => {
670
- if (signal.aborted)
671
- return resolve();
672
- const onAbort = () => {
673
- clearTimeout(timer);
674
- resolve();
675
- };
676
- const timer = setTimeout(() => {
677
- signal.removeEventListener("abort", onAbort);
678
- resolve();
679
- }, ms);
680
- signal.addEventListener("abort", onAbort, { once: true });
681
- });
682
- }
683
- function buildSubgraphQueryString(params) {
684
- return buildQuery({
685
- _sort: params.sort,
686
- _order: params.order,
687
- _limit: params.limit,
688
- _offset: params.offset,
689
- _fields: params.fields,
690
- ...params.filters
691
- });
692
- }
693
- function buildAggregateQueryString(params) {
694
- return buildQuery({
695
- ...params.filters,
696
- _count: params.count ? "true" : undefined,
697
- _countDistinct: params.countDistinct,
698
- _sum: params.sum,
699
- _min: params.min,
700
- _max: params.max
701
- });
702
- }
703
- function buildSpecQueryString(options) {
704
- return buildQuery({ server: options?.serverUrl });
705
- }
706
-
707
- class Subgraphs extends BaseClient {
708
- async list() {
709
- return this.request("GET", "/api/subgraphs");
710
- }
711
- async status(name) {
712
- return this.request("GET", `/api/subgraphs/${seg(name)}`);
713
- }
714
- async openapi(name, options) {
715
- return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
716
- }
717
- async schema(name, options) {
718
- return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
719
- }
720
- async markdown(name, options) {
721
- return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
722
- }
723
- async reindex(name) {
724
- return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
725
- }
726
- async stop(name) {
727
- return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
728
- }
729
- async backfill(name, options) {
730
- return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
731
- }
732
- async gaps(name, opts) {
733
- const qs = buildQuery({
734
- _limit: opts?.limit,
735
- _offset: opts?.offset,
736
- resolved: opts?.resolved
737
- });
738
- return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
739
- }
740
- async delete(name, options) {
741
- const qs = buildQuery({ force: options?.force ? true : undefined });
742
- return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
743
- }
744
- async rows(name, table, params = {}) {
745
- const { cursor, ...rest } = params;
746
- const qs = buildSubgraphQueryString(rest);
747
- const sep = qs ? "&" : "?";
748
- const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
749
- return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
750
- }
751
- async operations(name) {
752
- return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
753
- }
754
- async getOperation(name, operationId) {
755
- return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
756
- }
757
- async deploy(data) {
758
- return this.request("POST", "/api/subgraphs", data);
759
- }
760
- async getSource(name) {
761
- return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
762
- }
763
- async bundle(data) {
764
- return this.request("POST", "/api/subgraphs/bundle", data);
765
- }
766
- async queryTable(name, table, params = {}) {
767
- const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
768
- return Array.isArray(result) ? result : result.data;
769
- }
770
- async queryTableCount(name, table, params = {}) {
771
- return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
772
- }
773
- async queryTableAggregate(name, table, params = {}) {
774
- return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
775
- }
776
- typed(def) {
777
- const result = {};
778
- for (const [tableName, table] of Object.entries(def.schema)) {
779
- result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
780
- }
781
- return result;
782
- }
783
- createTableClient(subgraphName, tableName, columns) {
784
- const self = this;
785
- return {
786
- async findMany(options = {}) {
787
- const filters = options.where ? serializeWhere(options.where, columns) : undefined;
788
- let sort;
789
- let order;
790
- if (options.orderBy) {
791
- const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
792
- if (entries.length > 0) {
793
- sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
794
- order = entries.map(([, dir]) => dir ?? "asc").join(",");
795
- }
796
- }
797
- const params = {
798
- sort,
799
- order,
800
- limit: options.limit,
801
- offset: options.offset,
802
- fields: options.fields?.join(","),
803
- filters
804
- };
805
- return self.queryTable(subgraphName, tableName, params);
806
- },
807
- async count(where) {
808
- const filters = where ? serializeWhere(where, columns) : undefined;
809
- const result = await self.queryTableCount(subgraphName, tableName, {
810
- filters
811
- });
812
- return result.count;
813
- },
814
- async aggregate(spec) {
815
- const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
816
- const result = await self.queryTableAggregate(subgraphName, tableName, {
817
- filters,
818
- count: spec.count,
819
- countDistinct: spec.countDistinct,
820
- sum: spec.sum,
821
- min: spec.min,
822
- max: spec.max
823
- });
824
- return result;
825
- },
826
- subscribe(onRow, options = {}) {
827
- const filters = options.where ? serializeWhere(options.where, columns) : {};
828
- const controller = new AbortController;
829
- let since = options.since ?? undefined;
830
- const run = async () => {
831
- while (!controller.signal.aborted) {
832
- try {
833
- const qs = buildQuery({ ...filters, since });
834
- await readSse({
835
- url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
836
- headers: {
837
- ...BaseClient.authHeaders(self.apiKey),
838
- "x-sl-origin": self.origin
839
- },
840
- signal: controller.signal,
841
- fetchImpl: self.fetchImpl,
842
- onFrame: (frame) => {
843
- if (frame.event === "ping" || !frame.data)
844
- return;
845
- let row;
846
- try {
847
- row = JSON.parse(frame.data);
848
- } catch {
849
- return;
850
- }
851
- const height = rowBlockHeight(row);
852
- if (height !== undefined)
853
- since = height;
854
- onRow(row);
855
- }
856
- });
857
- await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
858
- } catch (err) {
859
- if (controller.signal.aborted)
860
- return;
861
- options.onError?.(err);
862
- await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
863
- }
864
- }
865
- };
866
- run();
867
- return () => controller.abort();
868
- }
869
- };
870
- }
871
- }
872
- // src/archive/client.ts
873
- import { createHash } from "node:crypto";
874
- import {
875
- checkSignature,
876
- loadReference
877
- } from "@secondlayer/shared/archive/reference";
878
- var OFFICIAL_ARCHIVE_HOST = "archive.secondlayer.tools";
879
- var DEFAULT_ARCHIVE_BASE_URL = "https://archive.secondlayer.tools";
880
- var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
881
- var MAX_FETCH_BATCH = 64;
882
- var ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
883
- function quoteFromBody(body) {
884
- return {
885
- partitions: body.partitions,
886
- bundles: body.bundles,
887
- usdMicros: body.usd_micros,
888
- usd: body.usd,
889
- freeAllowanceAppliedMicros: body.free_allowance_applied_micros,
890
- allowanceRemainingBundles: body.allowance_remaining_bundles,
891
- balanceUsdMicros: body.balance_usd_micros,
892
- sufficient: body.sufficient
893
- };
894
- }
895
- function stripSlash(url) {
896
- return url.replace(/\/+$/, "");
897
- }
898
- function createArchiveClient(options = {}) {
899
- const accountKey = resolveAccountKey(options.accountKey ?? options.apiKey);
900
- const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
901
- const archiveBaseUrl = stripSlash(options.archiveBaseUrl ?? DEFAULT_ARCHIVE_BASE_URL);
902
- const archiveOpsUrl = stripSlash(options.archiveOpsUrl ?? DEFAULT_ARCHIVE_OPS_URL);
903
- const verifyManifest = options.verifyManifest ?? true;
904
- const pinnedPem = options.publicKeyPem;
905
- let keyPromise = null;
906
- async function fetchSigningKey() {
907
- if (pinnedPem)
908
- return pinnedPem;
909
- if (keyPromise)
910
- return keyPromise;
911
- const pending = (async () => {
912
- const res = await fetchImpl(`${archiveOpsUrl}/public/streams/signing-key`);
913
- if (!res.ok) {
914
- throw new ArchiveSignatureError(`Could not fetch archive signing key (${res.status}).`);
915
- }
916
- const body = await res.json();
917
- if (typeof body.public_key_pem !== "string") {
918
- throw new ArchiveSignatureError("Signing key response missing key.");
919
- }
920
- return body.public_key_pem;
921
- })();
922
- keyPromise = pending;
923
- pending.catch(() => {
924
- if (keyPromise === pending)
925
- keyPromise = null;
926
- });
927
- return pending;
928
- }
929
- async function throwOpsError(response) {
930
- const envelope = parseErrorEnvelope(await response.text());
931
- if (response.status === 401) {
932
- throw new ArchiveAuthError(envelope.message ?? "Archive ops request was not authenticated.");
933
- }
934
- if (response.status === 402) {
935
- const raw = envelope.body && typeof envelope.body === "object" && "shortfall_usd_micros" in envelope.body ? Number(envelope.body.shortfall_usd_micros) : undefined;
936
- throw new InsufficientArchiveCreditsError(envelope.message ?? "Insufficient archive credits.", Number.isFinite(raw) ? raw : undefined);
937
- }
938
- if (response.status === 503) {
939
- throw new ArchiveGateNotConfiguredError(envelope.message ?? "The archive gate is not configured on the server.");
940
- }
941
- throw new ApiError(response.status, envelope.message ?? `Archive ops returned ${response.status}.`, envelope.body, envelope.code);
942
- }
943
- async function opsRequest(method, path, body, authed = true) {
944
- const headers = {
945
- "Content-Type": "application/json"
946
- };
947
- if (authed && accountKey) {
948
- if (!ARCHIVE_CREDENTIAL_RE.test(accountKey)) {
949
- throw new ArchiveAuthError("archive ops need SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN");
950
- }
951
- headers.Authorization = `Bearer ${accountKey}`;
952
- }
953
- const response = await fetchImpl(`${archiveOpsUrl}${path}`, {
954
- method,
955
- headers,
956
- body: body !== undefined ? JSON.stringify(body) : undefined
957
- });
958
- if (!response.ok)
959
- await throwOpsError(response);
960
- return await response.json();
961
- }
962
- async function load(against, opts = {}) {
963
- if (!/^https?:\/\//i.test(against)) {
964
- throw new ValidationError("archive.load() accepts only http(s) URLs; local paths stay on the CLI.", 400);
965
- }
966
- const insecure = opts.insecure === true;
967
- const publicKeyPem = pinnedPem ?? (verifyManifest && !insecure ? await fetchSigningKey() : undefined);
968
- const ref = await loadReference(against, {
969
- publicKeyPem,
970
- fetchImpl
971
- });
972
- const signature = checkSignature(ref.manifest, publicKeyPem, insecure);
973
- if (verifyManifest && !insecure && !signature.verified) {
974
- throw new ArchiveSignatureError(signature.reason ?? "Archive manifest signature is missing or invalid.");
975
- }
976
- return { ...ref, signature };
977
- }
978
- async function latest(against, opts = {}) {
979
- return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
535
+ async function latest(against, opts = {}) {
536
+ return load(against ?? `${archiveBaseUrl}/latest.json`, opts);
980
537
  }
981
538
  async function status() {
982
539
  const response = await fetchImpl(`${archiveBaseUrl}/status.json`);
@@ -1133,6 +690,37 @@ import {
1133
690
  decodeStreamsCursor,
1134
691
  encodeStreamsCursor
1135
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
1136
724
  var Cursor = {
1137
725
  atHeight(height) {
1138
726
  if (height <= 0)
@@ -1280,7 +868,7 @@ async function defaultSleep(ms, signal) {
1280
868
  });
1281
869
  }
1282
870
  async function consumeStreamsEvents(opts) {
1283
- const sleep3 = opts.sleep ?? defaultSleep;
871
+ const sleep = opts.sleep ?? defaultSleep;
1284
872
  const mode = opts.mode ?? "tail";
1285
873
  const finalizedOnly = opts.finalizedOnly ?? false;
1286
874
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
@@ -1310,7 +898,7 @@ async function consumeStreamsEvents(opts) {
1310
898
  retryCount: opts.retryCount,
1311
899
  retryDelay: opts.retryDelay,
1312
900
  onError: opts.onError,
1313
- sleep: sleep3,
901
+ sleep,
1314
902
  signal: opts.signal
1315
903
  });
1316
904
  pages++;
@@ -1342,7 +930,7 @@ async function consumeStreamsEvents(opts) {
1342
930
  retryCount: opts.retryCount,
1343
931
  retryDelay: opts.retryDelay,
1344
932
  onError: opts.onError,
1345
- sleep: sleep3,
933
+ sleep,
1346
934
  signal: opts.signal
1347
935
  });
1348
936
  if (listed.next_since !== null)
@@ -1405,7 +993,7 @@ async function consumeStreamsEvents(opts) {
1405
993
  if (mode === "bounded") {
1406
994
  return { cursor, pages, emptyPolls };
1407
995
  }
1408
- await sleep3(emptyBackoffMs, opts.signal);
996
+ await sleep(emptyBackoffMs, opts.signal);
1409
997
  continue;
1410
998
  }
1411
999
  return { cursor, pages, emptyPolls };
@@ -1413,7 +1001,7 @@ async function consumeStreamsEvents(opts) {
1413
1001
  return { cursor, pages, emptyPolls };
1414
1002
  }
1415
1003
  async function* iterateStreamsBatches(opts) {
1416
- const sleep3 = opts.sleep ?? defaultSleep;
1004
+ const sleep = opts.sleep ?? defaultSleep;
1417
1005
  let cursor = opts.fromCursor ?? null;
1418
1006
  while (!opts.signal?.aborted) {
1419
1007
  const envelope = await opts.fetchEvents({
@@ -1441,12 +1029,12 @@ async function* iterateStreamsBatches(opts) {
1441
1029
  if (!advanced && envelope.events.length === 0) {
1442
1030
  if (opts.signal?.aborted)
1443
1031
  return;
1444
- await sleep3(opts.intervalMs, opts.signal);
1032
+ await sleep(opts.intervalMs, opts.signal);
1445
1033
  }
1446
1034
  }
1447
1035
  }
1448
1036
  async function* streamStreamsEvents(opts) {
1449
- const sleep3 = opts.sleep ?? defaultSleep;
1037
+ const sleep = opts.sleep ?? defaultSleep;
1450
1038
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
1451
1039
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
1452
1040
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
@@ -1481,7 +1069,7 @@ async function* streamStreamsEvents(opts) {
1481
1069
  emptyPolls++;
1482
1070
  if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
1483
1071
  return;
1484
- await sleep3(emptyBackoffMs, opts.signal);
1072
+ await sleep(emptyBackoffMs, opts.signal);
1485
1073
  continue;
1486
1074
  }
1487
1075
  return;
@@ -1490,7 +1078,7 @@ async function* streamStreamsEvents(opts) {
1490
1078
 
1491
1079
  // src/index-api/consumer.ts
1492
1080
  async function consumeIndexFeed(opts) {
1493
- const sleep3 = opts.sleep ?? defaultSleep;
1081
+ const sleep = opts.sleep ?? defaultSleep;
1494
1082
  const mode = opts.mode ?? "tail";
1495
1083
  const finalizedOnly = opts.finalizedOnly ?? false;
1496
1084
  const batchSize = opts.batchSize ?? 200;
@@ -1514,7 +1102,7 @@ async function consumeIndexFeed(opts) {
1514
1102
  retryCount: opts.retryCount,
1515
1103
  retryDelay: opts.retryDelay,
1516
1104
  onError: opts.onError,
1517
- sleep: sleep3,
1105
+ sleep,
1518
1106
  signal: opts.signal
1519
1107
  });
1520
1108
  pages++;
@@ -1577,7 +1165,7 @@ async function consumeIndexFeed(opts) {
1577
1165
  if (mode === "bounded") {
1578
1166
  return { cursor, pages, emptyPolls };
1579
1167
  }
1580
- await sleep3(emptyBackoffMs, opts.signal);
1168
+ await sleep(emptyBackoffMs, opts.signal);
1581
1169
  continue;
1582
1170
  }
1583
1171
  return { cursor, pages, emptyPolls };
@@ -2164,9 +1752,195 @@ function createStreamsDumps(opts) {
2164
1752
  bytes.set(chunk, offset);
2165
1753
  offset += chunk.byteLength;
2166
1754
  }
2167
- 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 {}
2168
1929
  }
2169
- return { list, fileUrl, download };
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 };
2170
1944
  }
2171
1945
 
2172
1946
  // src/streams/client.ts
@@ -2437,21 +2211,295 @@ function createStreamsClient(options) {
2437
2211
  signal: params.signal
2438
2212
  });
2439
2213
  }
2440
- },
2441
- blocks: {
2442
- events(heightOrHash) {
2443
- return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
2444
- }
2445
- },
2446
- reorgs: { list: listReorgs },
2447
- dumps,
2448
- canonical(height) {
2449
- return request(`/v1/streams/canonical/${height}`);
2450
- },
2451
- tip() {
2452
- return request("/v1/streams/tip");
2453
- }
2454
- };
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
+ }
2455
2503
  }
2456
2504
 
2457
2505
  // src/webhooks/client.ts
@@ -2627,7 +2675,6 @@ class SecondLayer extends BaseClient {
2627
2675
  };
2628
2676
  }
2629
2677
  }
2630
-
2631
2678
  // src/subgraphs/get-subgraph.ts
2632
2679
  function getSubgraph(def, options = {}) {
2633
2680
  if (options instanceof Subgraphs) {
@@ -2638,36 +2685,6 @@ function getSubgraph(def, options = {}) {
2638
2685
  }
2639
2686
  return new Subgraphs(options).typed(def);
2640
2687
  }
2641
- // src/streams/index.ts
2642
- import {
2643
- decodeFtTransfer,
2644
- isFtTransfer,
2645
- decodeNftTransfer,
2646
- isNftTransfer,
2647
- decodeStxBurn,
2648
- decodeStxLock,
2649
- decodeStxMint,
2650
- decodeStxTransfer,
2651
- isStxBurn,
2652
- isStxLock,
2653
- isStxMint,
2654
- isStxTransfer,
2655
- decodeFtBurn,
2656
- decodeFtMint,
2657
- decodeNftBurn,
2658
- decodeNftMint,
2659
- isFtBurn,
2660
- isFtMint,
2661
- isNftBurn,
2662
- isNftMint,
2663
- decodePrint,
2664
- isPrint
2665
- } from "@secondlayer/shared/streams-rows";
2666
-
2667
- // src/streams/types.ts
2668
- import {
2669
- STREAMS_EVENT_TYPES
2670
- } from "@secondlayer/shared/streams-rows";
2671
2688
  // src/consumer-ops.ts
2672
2689
  function consumerHealth(options = {}) {
2673
2690
  const staleAfterMs = options.staleAfterMs ?? 120000;
@@ -2719,12 +2736,12 @@ import {
2719
2736
  } from "@secondlayer/shared/crypto/standard-webhooks";
2720
2737
  function pickHeader(headers, name) {
2721
2738
  if (typeof headers === "function") {
2722
- const v2 = headers(name);
2723
- return typeof v2 === "string" ? v2 : undefined;
2739
+ const v = headers(name);
2740
+ return typeof v === "string" ? v : undefined;
2724
2741
  }
2725
2742
  if (typeof headers.get === "function" && !(name in headers)) {
2726
- const v2 = headers.get(name);
2727
- return typeof v2 === "string" ? v2 : undefined;
2743
+ const v = headers.get(name);
2744
+ return typeof v === "string" ? v : undefined;
2728
2745
  }
2729
2746
  const bag = headers;
2730
2747
  let v = bag[name] ?? bag[name.toLowerCase()];
@@ -2861,78 +2878,109 @@ function verifyTransactionProof(proof, opts) {
2861
2878
  errors
2862
2879
  };
2863
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";
2864
2911
  // src/clarity.ts
2865
2912
  import {
2866
2913
  decodeClarityValue,
2867
2914
  toJsonSafe
2868
2915
  } from "@secondlayer/shared/streams-rows";
2869
2916
  export {
2870
- verifyWebhookSignature,
2871
- verifyTransactionProof,
2872
- verify2 as verifyStandardWebhooksHeaders,
2873
- verifySecondlayerSignature,
2874
- trigger,
2875
- toJsonSafe,
2876
- shutdownSignal,
2877
- resolveBaseUrl,
2878
- resolveApiKey,
2879
- resolveAccountKey,
2880
- parseRetryAfter,
2881
- isStxTransfer,
2882
- isStxMint,
2883
- isStxLock,
2884
- isStxBurn,
2885
- isPrint,
2886
- isNftTransfer,
2887
- isNftMint,
2888
- isNftBurn,
2889
- isFtTransfer,
2890
- isFtMint,
2891
- isFtBurn,
2892
- getSubgraph,
2893
- fetchRewardSet,
2894
- decodeStxTransfer,
2895
- decodeStxMint,
2896
- decodeStxLock,
2897
- decodeStxBurn,
2898
- decodePrint,
2899
- decodeNftTransfer,
2900
- decodeNftMint,
2901
- decodeNftBurn,
2902
- decodeFtTransfer,
2903
- decodeFtMint,
2904
- decodeFtBurn,
2905
- decodeClarityValue,
2906
- decodeChainWebhook,
2907
- decode,
2908
- createStreamsClient,
2909
- createArchiveClient,
2910
- contextField,
2911
- consumerHealth,
2912
- consumeIndexFeed,
2913
- Webhooks,
2914
- ValidationError,
2915
- Subgraphs,
2916
- StreamsSignatureError,
2917
- StreamsServerError,
2918
- SecondLayerError,
2919
- SecondLayer,
2920
- RateLimitError,
2921
- LOCAL_API_URL,
2922
- InsufficientArchiveCreditsError,
2923
- Index,
2924
- INSTANCE_TOKEN_ENV,
2925
- INDEX_MAX_PAGE_SIZE,
2926
- DEFAULT_REQUEST_TIMEOUT_MS,
2927
- Cursor,
2928
- Contracts,
2929
- AuthError,
2930
- ArchiveSignatureError,
2931
- ArchiveGateNotConfiguredError,
2932
- ArchiveAuthError,
2917
+ ACCOUNT_KEY_ENV,
2933
2918
  ApiError,
2934
- ACCOUNT_KEY_ENV
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
2935
2983
  };
2936
2984
 
2937
- //# debugId=1A9F98DFDABD122264756E2164756E21
2985
+ //# debugId=92280D2ABC3CFA6364756E2164756E21
2938
2986
  //# sourceMappingURL=index.js.map