@secondlayer/sdk 9.0.1 → 10.0.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.
@@ -85,8 +85,8 @@ class ApiError extends SecondLayerError {
85
85
 
86
86
  class AuthError extends ApiError {
87
87
  status = 401;
88
- constructor(message = "API key invalid or expired.") {
89
- super(401, message);
88
+ constructor(message = "API key invalid or expired.", body, code) {
89
+ super(401, message, body, code);
90
90
  this.name = "AuthError";
91
91
  }
92
92
  }
@@ -94,8 +94,8 @@ class AuthError extends ApiError {
94
94
  class RateLimitError extends ApiError {
95
95
  retryAfter;
96
96
  status = 429;
97
- constructor(message = "Rate limited. Try again later.", retryAfter) {
98
- super(429, message, undefined, undefined, {
97
+ constructor(message = "Rate limited. Try again later.", retryAfter, body, code) {
98
+ super(429, message, body, code, {
99
99
  retryable: true,
100
100
  retryAfterSeconds: parseRetryAfter(retryAfter)
101
101
  });
@@ -105,8 +105,8 @@ class RateLimitError extends ApiError {
105
105
  }
106
106
 
107
107
  class ValidationError extends ApiError {
108
- constructor(message, status, body) {
109
- super(status, message, body, undefined, { retryable: false });
108
+ constructor(message, status, body, code) {
109
+ super(status, message, body, code, { retryable: false });
110
110
  this.name = "ValidationError";
111
111
  }
112
112
  }
@@ -122,18 +122,22 @@ function parseRetryAfter(value) {
122
122
  return;
123
123
  }
124
124
 
125
- class VersionConflictError extends ApiError {
126
- currentVersion;
127
- expectedVersion;
128
- constructor(currentVersion, expectedVersion, message = `Version conflict: expected ${expectedVersion}, current ${currentVersion}`) {
129
- super(409, message, { currentVersion, expectedVersion });
130
- this.currentVersion = currentVersion;
131
- this.expectedVersion = expectedVersion;
132
- this.name = "VersionConflictError";
133
- }
134
- }
135
-
136
125
  // src/base.ts
126
+ var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
127
+ function abortError() {
128
+ return new DOMException("This operation was aborted", "AbortError");
129
+ }
130
+ function raceAbort(promise, signal) {
131
+ if (signal.aborted)
132
+ return Promise.reject(signal.reason ?? abortError());
133
+ return new Promise((resolve, reject) => {
134
+ const onAbort = () => reject(signal.reason ?? abortError());
135
+ signal.addEventListener("abort", onAbort, { once: true });
136
+ promise.then(resolve, reject).finally(() => {
137
+ signal.removeEventListener("abort", onAbort);
138
+ });
139
+ });
140
+ }
137
141
  var LOCAL_API_URL = "http://127.0.0.1:3800";
138
142
  function resolveBaseUrl(explicit) {
139
143
  if (explicit && explicit.length > 0)
@@ -163,6 +167,9 @@ function resolveApiKey(apiKey) {
163
167
  }
164
168
  return instanceToken ?? legacyKey;
165
169
  }
170
+ function seg(value) {
171
+ return encodeURIComponent(String(value));
172
+ }
166
173
  function buildQuery(params) {
167
174
  const search = new URLSearchParams;
168
175
  for (const [name, value] of Object.entries(params)) {
@@ -176,16 +183,39 @@ function buildQuery(params) {
176
183
  const query = search.toString();
177
184
  return query ? `?${query}` : "";
178
185
  }
186
+ function parseErrorEnvelope(text) {
187
+ if (text.length === 0)
188
+ return { body: undefined };
189
+ try {
190
+ const json = JSON.parse(text);
191
+ let message;
192
+ let code;
193
+ if (json && typeof json === "object") {
194
+ const err = json.error ?? json.message;
195
+ if (typeof err === "string" && err.length > 0)
196
+ message = err;
197
+ else if (err && typeof err === "object")
198
+ message = JSON.stringify(err);
199
+ if (typeof json.code === "string")
200
+ code = json.code;
201
+ }
202
+ return { message, code, body: json };
203
+ } catch {
204
+ return { message: text, body: text };
205
+ }
206
+ }
179
207
 
180
208
  class BaseClient {
181
209
  baseUrl;
182
210
  apiKey;
183
211
  origin;
184
212
  fetchImpl;
213
+ requestTimeoutMs;
185
214
  constructor(options = {}) {
186
215
  this.baseUrl = resolveBaseUrl(options.baseUrl);
187
216
  this.apiKey = resolveApiKey(options.apiKey);
188
217
  this.origin = options.origin ?? "cli";
218
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
189
219
  this.fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
190
220
  }
191
221
  static authHeaders(apiKey) {
@@ -197,27 +227,56 @@ class BaseClient {
197
227
  }
198
228
  return headers;
199
229
  }
200
- async request(method, path, body) {
201
- const response = await this.fetchResponse(method, path, body);
202
- if (response.status === 204) {
203
- return;
204
- }
205
- return response.json();
230
+ async request(method, path, body, opts = {}) {
231
+ return this.withRequestBudget(opts.signal, async (signal) => {
232
+ const response = await this.fetchResponse(method, path, body, signal);
233
+ if (response.status === 204) {
234
+ return;
235
+ }
236
+ return await raceAbort(response.json(), signal);
237
+ });
206
238
  }
207
- async requestOrNull(method, path, body) {
239
+ async requestOrNull(method, path, body, opts = {}) {
208
240
  try {
209
- return await this.request(method, path, body);
241
+ return await this.request(method, path, body, opts);
210
242
  } catch (err) {
211
243
  if (err instanceof ApiError && err.status === 404)
212
244
  return null;
213
245
  throw err;
214
246
  }
215
247
  }
216
- async requestText(method, path, body) {
217
- const response = await this.fetchResponse(method, path, body);
218
- return response.text();
248
+ async requestText(method, path, body, opts = {}) {
249
+ return this.withRequestBudget(opts.signal, async (signal) => {
250
+ const response = await this.fetchResponse(method, path, body, signal);
251
+ return raceAbort(response.text(), signal);
252
+ });
253
+ }
254
+ async withRequestBudget(callerSignal, run) {
255
+ const controller = new AbortController;
256
+ let timedOut = false;
257
+ const forward = () => controller.abort(callerSignal?.reason ?? abortError());
258
+ if (callerSignal?.aborted)
259
+ forward();
260
+ else
261
+ callerSignal?.addEventListener("abort", forward, { once: true });
262
+ const timer = this.requestTimeoutMs > 0 ? setTimeout(() => {
263
+ timedOut = true;
264
+ controller.abort(abortError());
265
+ }, this.requestTimeoutMs) : undefined;
266
+ try {
267
+ return await run(controller.signal);
268
+ } catch (err) {
269
+ if (timedOut && !callerSignal?.aborted) {
270
+ throw new ApiError(0, `No response from ${this.baseUrl} within ${this.requestTimeoutMs}ms. Retryable. Raise requestTimeoutMs for slow filtered reads.`, undefined, "REQUEST_TIMEOUT", { retryable: true, cause: err instanceof Error ? err : undefined });
271
+ }
272
+ throw err;
273
+ } finally {
274
+ if (timer !== undefined)
275
+ clearTimeout(timer);
276
+ callerSignal?.removeEventListener("abort", forward);
277
+ }
219
278
  }
220
- async fetchResponse(method, path, body) {
279
+ async fetchResponse(method, path, body, signal) {
221
280
  const url = `${this.baseUrl}${path}`;
222
281
  const headers = BaseClient.authHeaders(this.apiKey);
223
282
  headers["x-sl-origin"] = this.origin;
@@ -232,50 +291,254 @@ class BaseClient {
232
291
  }
233
292
  let response;
234
293
  try {
235
- response = await this.fetchImpl(url, {
294
+ const pending = this.fetchImpl(url, {
236
295
  method,
237
296
  headers,
238
- body: serializedBody
297
+ body: serializedBody,
298
+ signal
239
299
  });
300
+ response = signal ? await raceAbort(pending, signal) : await pending;
240
301
  } catch (err) {
302
+ if (signal?.aborted)
303
+ throw err;
241
304
  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 });
242
305
  }
243
306
  if (!response.ok) {
307
+ const errorBody = signal ? await raceAbort(response.text(), signal) : await response.text();
308
+ const envelope = parseErrorEnvelope(errorBody);
309
+ const retryAfter = response.headers.get("Retry-After") ?? undefined;
244
310
  if (response.status === 401) {
245
- throw new AuthError;
311
+ throw new AuthError(envelope.message ?? "API key invalid or expired.", envelope.body, envelope.code);
246
312
  }
247
313
  if (response.status === 429) {
248
- const retryAfter = response.headers.get("Retry-After");
249
- const msg = retryAfter ? `Rate limited. Wait ${retryAfter} seconds.` : "Rate limited. Try again later.";
250
- throw new RateLimitError(msg, retryAfter ?? undefined);
314
+ const fallback = retryAfter ? `Rate limited. Wait ${retryAfter} seconds.` : "Rate limited. Try again later.";
315
+ throw new RateLimitError(envelope.message ?? fallback, retryAfter, envelope.body, envelope.code);
251
316
  }
252
317
  if (response.status >= 500) {
253
- throw new ApiError(response.status, `Server error. Try again or check status at ${this.baseUrl}/health`);
318
+ const retryAfterSeconds = parseRetryAfter(retryAfter);
319
+ throw new ApiError(response.status, envelope.message ?? `Server error. Try again or check status at ${this.baseUrl}/health`, envelope.body, envelope.code, retryAfterSeconds !== undefined ? { retryAfterSeconds } : {});
254
320
  }
255
- const errorBody = await response.text();
256
- let message = `HTTP ${response.status}`;
257
- let parsedBody = errorBody;
258
- let code;
321
+ throw new ApiError(response.status, envelope.message ?? `HTTP ${response.status}`, envelope.body, envelope.code);
322
+ }
323
+ return response;
324
+ }
325
+ }
326
+
327
+ // src/streams/subscribe.ts
328
+ import { ed25519 } from "@secondlayer/shared";
329
+
330
+ // src/streams/errors.ts
331
+ class StreamsServerError extends ApiError {
332
+ constructor(message, status, body, code) {
333
+ super(status, message, body, code, { retryable: true });
334
+ this.name = "StreamsServerError";
335
+ }
336
+ }
337
+
338
+ class StreamsSignatureError extends SecondLayerError {
339
+ constructor(message = "Streams response signature verification failed.") {
340
+ super(message);
341
+ this.name = "StreamsSignatureError";
342
+ }
343
+ }
344
+ async function mapStreamsError(response) {
345
+ const { message, code, body } = parseErrorEnvelope(await response.text());
346
+ if (response.status === 401) {
347
+ throw new AuthError(message ?? "API key invalid or expired.", body, code);
348
+ }
349
+ if (response.status === 429) {
350
+ const retryAfter = response.headers.get("Retry-After") ?? undefined;
351
+ throw new RateLimitError(message ?? "Rate limited. Try again later.", retryAfter, body, code);
352
+ }
353
+ if (response.status >= 500) {
354
+ throw new StreamsServerError(message ?? `Streams server returned ${response.status}.`, response.status, body, code);
355
+ }
356
+ throw new ValidationError(message ?? `Streams request returned ${response.status}.`, response.status, body, code);
357
+ }
358
+
359
+ // src/streams/subscribe.ts
360
+ var SUBSCRIBE_MAX_RECONNECT_DELAY_MS = 30000;
361
+ var SUBSCRIBE_DEFAULT_STALE_AFTER_MS = 60000;
362
+ function reconnectDelay(attempt, baseMs, retryAfterSeconds, random = Math.random) {
363
+ const capped = Math.min(baseMs * 2 ** Math.max(0, attempt), SUBSCRIBE_MAX_RECONNECT_DELAY_MS);
364
+ const jittered = capped * (0.5 + random() * 0.5);
365
+ const floor = (retryAfterSeconds ?? 0) * 1000;
366
+ return Math.max(jittered, floor);
367
+ }
368
+ function isTerminal(err) {
369
+ if (err instanceof StreamsSignatureError)
370
+ return true;
371
+ return err?.retryable === false;
372
+ }
373
+ function subscribeStreamsEvents(opts) {
374
+ const { params } = opts;
375
+ const controller = new AbortController;
376
+ const external = params.signal;
377
+ if (external) {
378
+ if (external.aborted)
379
+ controller.abort();
380
+ else
381
+ external.addEventListener("abort", () => controller.abort(), {
382
+ once: true
383
+ });
384
+ }
385
+ let cursor = params.fromCursor ?? null;
386
+ const baseDelayMs = opts.reconnectDelayMs ?? params.reconnectDelayMs ?? 1000;
387
+ const staleAfterMs = opts.staleAfterMs ?? params.staleAfterMs ?? SUBSCRIBE_DEFAULT_STALE_AFTER_MS;
388
+ let attempt = 0;
389
+ const run = async () => {
390
+ while (!controller.signal.aborted) {
259
391
  try {
260
- const json = JSON.parse(errorBody);
261
- parsedBody = json;
262
- const err = json.error ?? json.message;
263
- if (typeof err === "string") {
264
- message = err;
265
- } else if (err && typeof err === "object") {
266
- message = JSON.stringify(err);
267
- }
268
- if (typeof json.code === "string") {
269
- code = json.code;
270
- }
271
- } catch {
272
- if (errorBody)
273
- message = errorBody;
392
+ const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
393
+ from_cursor: cursor ?? undefined,
394
+ types: params.types,
395
+ not_types: params.notTypes,
396
+ contract_id: params.contractId,
397
+ sender: params.sender,
398
+ recipient: params.recipient,
399
+ asset_identifier: params.assetIdentifier,
400
+ filters: params.filters ? JSON.stringify(params.filters) : undefined
401
+ })}`;
402
+ await readSse({
403
+ url,
404
+ headers: opts.headers ?? {},
405
+ signal: controller.signal,
406
+ fetchImpl: opts.fetchImpl,
407
+ staleAfterMs,
408
+ mapError: mapStreamsError,
409
+ onFrame: async (frame) => {
410
+ attempt = 0;
411
+ if (frame.event === "ping" || !frame.data)
412
+ return;
413
+ let parsed;
414
+ try {
415
+ parsed = JSON.parse(frame.data);
416
+ } catch {
417
+ return;
418
+ }
419
+ if (!parsed.event)
420
+ return;
421
+ if (opts.verify !== "off") {
422
+ if (!parsed.sig) {
423
+ if (opts.verify === "strict") {
424
+ throw new StreamsSignatureError("Streams SSE frame signature is missing.");
425
+ }
426
+ } else {
427
+ const key = await opts.loadKey(parsed.key_id);
428
+ if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
429
+ throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
430
+ }
431
+ }
432
+ }
433
+ await params.onEvent(parsed.event);
434
+ cursor = parsed.event.cursor ?? cursor;
435
+ }
436
+ });
437
+ await sleep(reconnectDelay(attempt, baseDelayMs, undefined, opts.random), controller.signal);
438
+ attempt += 1;
439
+ } catch (err) {
440
+ if (controller.signal.aborted)
441
+ return;
442
+ params.onError?.(err);
443
+ if (isTerminal(err))
444
+ throw err;
445
+ const retryAfterSeconds = err.retryAfterSeconds;
446
+ await sleep(reconnectDelay(attempt, baseDelayMs, retryAfterSeconds, opts.random), controller.signal);
447
+ attempt += 1;
274
448
  }
275
- throw new ApiError(response.status, message, parsedBody, code);
276
449
  }
277
- return response;
450
+ };
451
+ const done = run();
452
+ done.catch(() => {});
453
+ const unsubscribe = () => controller.abort();
454
+ return Object.assign(unsubscribe, { done });
455
+ }
456
+ async function readSse(opts) {
457
+ const res = await opts.fetchImpl(opts.url, {
458
+ headers: { ...opts.headers ?? {}, Accept: "text/event-stream" },
459
+ signal: opts.signal
460
+ });
461
+ if (!res.ok) {
462
+ if (opts.mapError)
463
+ await opts.mapError(res);
464
+ throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
465
+ }
466
+ if (!res.body) {
467
+ throw new StreamsServerError("Streams SSE response has no body.", 0);
278
468
  }
469
+ for await (const frame of parseSseFrames(res.body, opts.signal, opts.staleAfterMs)) {
470
+ await opts.onFrame(frame);
471
+ }
472
+ }
473
+ function sleep(ms, signal) {
474
+ return new Promise((resolve) => {
475
+ if (signal.aborted)
476
+ return resolve();
477
+ const onAbort = () => {
478
+ clearTimeout(timer);
479
+ resolve();
480
+ };
481
+ const timer = setTimeout(() => {
482
+ signal.removeEventListener("abort", onAbort);
483
+ resolve();
484
+ }, ms);
485
+ signal.addEventListener("abort", onAbort, { once: true });
486
+ });
487
+ }
488
+ async function readWithStaleTimer(reader, staleAfterMs) {
489
+ if (staleAfterMs === undefined || !Number.isFinite(staleAfterMs)) {
490
+ return reader.read();
491
+ }
492
+ let timer;
493
+ const stale = new Promise((_, reject) => {
494
+ timer = setTimeout(() => reject(new StreamsServerError(`Streams SSE went quiet for ${staleAfterMs} ms; reconnecting.`, 0, undefined, "STREAM_STALE")), staleAfterMs);
495
+ });
496
+ try {
497
+ return await Promise.race([reader.read(), stale]);
498
+ } finally {
499
+ clearTimeout(timer);
500
+ }
501
+ }
502
+ async function* parseSseFrames(body, signal, staleAfterMs) {
503
+ const reader = body.getReader();
504
+ const decoder = new TextDecoder;
505
+ let buffer = "";
506
+ try {
507
+ while (!signal.aborted) {
508
+ const { value, done } = await readWithStaleTimer(reader, staleAfterMs);
509
+ if (done)
510
+ break;
511
+ buffer += decoder.decode(value, { stream: true });
512
+ let sep = buffer.indexOf(`
513
+
514
+ `);
515
+ while (sep !== -1) {
516
+ yield parseFrame(buffer.slice(0, sep));
517
+ buffer = buffer.slice(sep + 2);
518
+ sep = buffer.indexOf(`
519
+
520
+ `);
521
+ }
522
+ }
523
+ } finally {
524
+ try {
525
+ await reader.cancel();
526
+ } catch {}
527
+ }
528
+ }
529
+ function parseFrame(raw) {
530
+ let event;
531
+ const data = [];
532
+ for (const line of raw.split(`
533
+ `)) {
534
+ if (line.startsWith("data:")) {
535
+ data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
536
+ } else if (line.startsWith("event:")) {
537
+ event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
538
+ }
539
+ }
540
+ return { event, data: data.length > 0 ? data.join(`
541
+ `) : undefined };
279
542
  }
280
543
 
281
544
  // src/subgraphs/serialize.ts
@@ -283,21 +546,28 @@ var SYSTEM_COLUMN_MAP = {
283
546
  _blockHeight: "_block_height",
284
547
  _txId: "_tx_id",
285
548
  _createdAt: "_created_at",
286
- _id: "_id",
549
+ _id: "_id"
550
+ };
551
+ var SYSTEM_COLUMN_ALIASES = {
287
552
  blockHeight: "_block_height",
288
553
  txId: "_tx_id",
289
554
  createdAt: "_created_at",
290
555
  id: "_id"
291
556
  };
292
- function resolveColumn(col) {
293
- return SYSTEM_COLUMN_MAP[col] ?? col;
557
+ function resolveColumn(col, columns) {
558
+ const canonical = SYSTEM_COLUMN_MAP[col];
559
+ if (canonical)
560
+ return canonical;
561
+ if (columns?.has(col))
562
+ return col;
563
+ return SYSTEM_COLUMN_ALIASES[col] ?? col;
294
564
  }
295
- function serializeWhere(where) {
565
+ function serializeWhere(where, columns) {
296
566
  const filters = {};
297
567
  for (const [column, value] of Object.entries(where)) {
298
568
  if (value === null || value === undefined)
299
569
  continue;
300
- const col = resolveColumn(column);
570
+ const col = resolveColumn(column, columns);
301
571
  if (typeof value === "object" && !Array.isArray(value)) {
302
572
  const ops = value;
303
573
  for (const [op, opValue] of Object.entries(ops)) {
@@ -318,11 +588,43 @@ function serializeWhere(where) {
318
588
  }
319
589
  return filters;
320
590
  }
321
- function resolveOrderByColumn(col) {
322
- return resolveColumn(col);
591
+ function resolveOrderByColumn(col, columns) {
592
+ return resolveColumn(col, columns);
323
593
  }
324
594
 
325
595
  // src/subgraphs/client.ts
596
+ var SUBSCRIBE_RECONNECT_DELAY_MS = 1000;
597
+ function declaredColumns(table) {
598
+ if (!table || typeof table !== "object")
599
+ return;
600
+ const columns = table.columns;
601
+ if (!columns || typeof columns !== "object")
602
+ return;
603
+ return new Set(Object.keys(columns));
604
+ }
605
+ function rowBlockHeight(row) {
606
+ const r = row;
607
+ const raw = r._block_height ?? r._blockHeight;
608
+ if (typeof raw !== "number" && typeof raw !== "string")
609
+ return;
610
+ const height = Number(raw);
611
+ return Number.isFinite(height) ? height : undefined;
612
+ }
613
+ function sleep2(ms, signal) {
614
+ return new Promise((resolve) => {
615
+ if (signal.aborted)
616
+ return resolve();
617
+ const onAbort = () => {
618
+ clearTimeout(timer);
619
+ resolve();
620
+ };
621
+ const timer = setTimeout(() => {
622
+ signal.removeEventListener("abort", onAbort);
623
+ resolve();
624
+ }, ms);
625
+ signal.addEventListener("abort", onAbort, { once: true });
626
+ });
627
+ }
326
628
  function buildSubgraphQueryString(params) {
327
629
  return buildQuery({
328
630
  _sort: params.sort,
@@ -352,25 +654,25 @@ class Subgraphs extends BaseClient {
352
654
  return this.request("GET", "/api/subgraphs");
353
655
  }
354
656
  async status(name) {
355
- return this.request("GET", `/api/subgraphs/${name}`);
657
+ return this.request("GET", `/api/subgraphs/${seg(name)}`);
356
658
  }
357
659
  async openapi(name, options) {
358
- return this.request("GET", `/api/subgraphs/${name}/openapi.json${buildSpecQueryString(options)}`);
660
+ return this.request("GET", `/api/subgraphs/${seg(name)}/openapi.json${buildSpecQueryString(options)}`);
359
661
  }
360
662
  async schema(name, options) {
361
- return this.request("GET", `/api/subgraphs/${name}/schema.json${buildSpecQueryString(options)}`);
663
+ return this.request("GET", `/api/subgraphs/${seg(name)}/schema.json${buildSpecQueryString(options)}`);
362
664
  }
363
665
  async markdown(name, options) {
364
- return this.requestText("GET", `/api/subgraphs/${name}/docs.md${buildSpecQueryString(options)}`);
666
+ return this.requestText("GET", `/api/subgraphs/${seg(name)}/docs.md${buildSpecQueryString(options)}`);
365
667
  }
366
668
  async reindex(name) {
367
- return this.request("POST", `/api/subgraphs/${name}/reindex`);
669
+ return this.request("POST", `/api/subgraphs/${seg(name)}/reindex`);
368
670
  }
369
671
  async stop(name) {
370
- return this.request("POST", `/api/subgraphs/${name}/stop`);
672
+ return this.request("POST", `/api/subgraphs/${seg(name)}/stop`);
371
673
  }
372
674
  async backfill(name, options) {
373
- return this.request("POST", `/api/subgraphs/${name}/backfill`, options);
675
+ return this.request("POST", `/api/subgraphs/${seg(name)}/backfill`, options);
374
676
  }
375
677
  async gaps(name, opts) {
376
678
  const qs = buildQuery({
@@ -378,62 +680,62 @@ class Subgraphs extends BaseClient {
378
680
  _offset: opts?.offset,
379
681
  resolved: opts?.resolved
380
682
  });
381
- return this.request("GET", `/api/subgraphs/${name}/gaps${qs}`);
683
+ return this.request("GET", `/api/subgraphs/${seg(name)}/gaps${qs}`);
382
684
  }
383
685
  async delete(name, options) {
384
686
  const qs = buildQuery({ force: options?.force ? true : undefined });
385
- return this.request("DELETE", `/api/subgraphs/${name}${qs}`);
687
+ return this.request("DELETE", `/api/subgraphs/${seg(name)}${qs}`);
386
688
  }
387
689
  async rows(name, table, params = {}) {
388
690
  const { cursor, ...rest } = params;
389
691
  const qs = buildSubgraphQueryString(rest);
390
692
  const sep = qs ? "&" : "?";
391
693
  const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
392
- return this.request("GET", `/v1/subgraphs/${name}/${table}${qs}${cursorQs}`);
694
+ return this.request("GET", `/v1/subgraphs/${seg(name)}/${seg(table)}${qs}${cursorQs}`);
393
695
  }
394
696
  async operations(name) {
395
- return this.request("GET", `/api/subgraphs/${name}/operations`);
697
+ return this.request("GET", `/api/subgraphs/${seg(name)}/operations`);
396
698
  }
397
699
  async getOperation(name, operationId) {
398
- return this.request("GET", `/api/subgraphs/${name}/operations/${operationId}`);
700
+ return this.request("GET", `/api/subgraphs/${seg(name)}/operations/${seg(operationId)}`);
399
701
  }
400
702
  async deploy(data) {
401
703
  return this.request("POST", "/api/subgraphs", data);
402
704
  }
403
705
  async getSource(name) {
404
- return this.request("GET", `/api/subgraphs/${name}/source`);
706
+ return this.request("GET", `/api/subgraphs/${seg(name)}/source`);
405
707
  }
406
708
  async bundle(data) {
407
709
  return this.request("POST", "/api/subgraphs/bundle", data);
408
710
  }
409
711
  async queryTable(name, table, params = {}) {
410
- const result = await this.request("GET", `/api/subgraphs/${name}/${table}${buildSubgraphQueryString(params)}`);
712
+ const result = await this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}${buildSubgraphQueryString(params)}`);
411
713
  return Array.isArray(result) ? result : result.data;
412
714
  }
413
715
  async queryTableCount(name, table, params = {}) {
414
- return this.request("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
716
+ return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/count${buildSubgraphQueryString(params)}`);
415
717
  }
416
718
  async queryTableAggregate(name, table, params = {}) {
417
- return this.request("GET", `/api/subgraphs/${name}/${table}/aggregate${buildAggregateQueryString(params)}`);
719
+ return this.request("GET", `/api/subgraphs/${seg(name)}/${seg(table)}/aggregate${buildAggregateQueryString(params)}`);
418
720
  }
419
721
  typed(def) {
420
722
  const result = {};
421
- for (const tableName of Object.keys(def.schema)) {
422
- result[tableName] = this.createTableClient(def.name, tableName);
723
+ for (const [tableName, table] of Object.entries(def.schema)) {
724
+ result[tableName] = this.createTableClient(def.name, tableName, declaredColumns(table));
423
725
  }
424
726
  return result;
425
727
  }
426
- createTableClient(subgraphName, tableName) {
728
+ createTableClient(subgraphName, tableName, columns) {
427
729
  const self = this;
428
730
  return {
429
731
  async findMany(options = {}) {
430
- const filters = options.where ? serializeWhere(options.where) : undefined;
732
+ const filters = options.where ? serializeWhere(options.where, columns) : undefined;
431
733
  let sort;
432
734
  let order;
433
735
  if (options.orderBy) {
434
736
  const entries = Array.isArray(options.orderBy) ? options.orderBy : Object.entries(options.orderBy);
435
737
  if (entries.length > 0) {
436
- sort = entries.map(([col]) => resolveOrderByColumn(col)).join(",");
738
+ sort = entries.map(([col]) => resolveOrderByColumn(col, columns)).join(",");
437
739
  order = entries.map(([, dir]) => dir ?? "asc").join(",");
438
740
  }
439
741
  }
@@ -448,14 +750,14 @@ class Subgraphs extends BaseClient {
448
750
  return self.queryTable(subgraphName, tableName, params);
449
751
  },
450
752
  async count(where) {
451
- const filters = where ? serializeWhere(where) : undefined;
753
+ const filters = where ? serializeWhere(where, columns) : undefined;
452
754
  const result = await self.queryTableCount(subgraphName, tableName, {
453
755
  filters
454
756
  });
455
757
  return result.count;
456
758
  },
457
759
  async aggregate(spec) {
458
- const filters = spec.where ? serializeWhere(spec.where) : undefined;
760
+ const filters = spec.where ? serializeWhere(spec.where, columns) : undefined;
459
761
  const result = await self.queryTableAggregate(subgraphName, tableName, {
460
762
  filters,
461
763
  count: spec.count,
@@ -467,27 +769,47 @@ class Subgraphs extends BaseClient {
467
769
  return result;
468
770
  },
469
771
  subscribe(onRow, options = {}) {
470
- const filters = options.where ? serializeWhere(options.where) : {};
471
- const qs = buildQuery({
472
- ...filters,
473
- since: options.since ?? undefined
474
- });
475
- const url = `${self.baseUrl}/api/subgraphs/${subgraphName}/${tableName}/stream${qs}`;
476
- const ES = globalThis.EventSource;
477
- if (!ES) {
478
- throw new Error("subscribe() needs a global EventSource (available in browsers and Node >= 22).");
479
- }
480
- const es = new ES(url);
481
- es.onmessage = (ev) => {
482
- try {
483
- onRow(JSON.parse(ev.data));
484
- } catch {}
772
+ const filters = options.where ? serializeWhere(options.where, columns) : {};
773
+ const controller = new AbortController;
774
+ let since = options.since ?? undefined;
775
+ const run = async () => {
776
+ while (!controller.signal.aborted) {
777
+ try {
778
+ const qs = buildQuery({ ...filters, since });
779
+ await readSse({
780
+ url: `${self.baseUrl}/v1/subgraphs/${seg(subgraphName)}/${seg(tableName)}/stream${qs}`,
781
+ headers: {
782
+ ...BaseClient.authHeaders(self.apiKey),
783
+ "x-sl-origin": self.origin
784
+ },
785
+ signal: controller.signal,
786
+ fetchImpl: self.fetchImpl,
787
+ onFrame: (frame) => {
788
+ if (frame.event === "ping" || !frame.data)
789
+ return;
790
+ let row;
791
+ try {
792
+ row = JSON.parse(frame.data);
793
+ } catch {
794
+ return;
795
+ }
796
+ const height = rowBlockHeight(row);
797
+ if (height !== undefined)
798
+ since = height;
799
+ onRow(row);
800
+ }
801
+ });
802
+ await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
803
+ } catch (err) {
804
+ if (controller.signal.aborted)
805
+ return;
806
+ options.onError?.(err);
807
+ await sleep2(SUBSCRIBE_RECONNECT_DELAY_MS, controller.signal);
808
+ }
809
+ }
485
810
  };
486
- if (options.onError) {
487
- const handler = options.onError;
488
- es.onerror = (ev) => handler(ev);
489
- }
490
- return () => es.close();
811
+ run();
812
+ return () => controller.abort();
491
813
  }
492
814
  };
493
815
  }
@@ -512,37 +834,28 @@ class Contracts extends BaseClient {
512
834
  }
513
835
  }
514
836
 
515
- // src/streams/errors.ts
516
- class StreamsServerError extends ApiError {
517
- constructor(message, status, body) {
518
- super(status, message, body, undefined, { retryable: true });
519
- this.name = "StreamsServerError";
520
- }
521
- }
522
-
523
- class StreamsSignatureError extends SecondLayerError {
524
- constructor(message = "Streams response signature verification failed.") {
525
- super(message);
526
- this.name = "StreamsSignatureError";
527
- }
528
- }
529
-
530
837
  // src/streams/cursor.ts
531
- var REWIND_FOOT_INDEX_SENTINEL = 2147483647;
838
+ import {
839
+ blockEndCursor,
840
+ decodeStreamsCursor,
841
+ encodeStreamsCursor
842
+ } from "@secondlayer/shared";
532
843
  var Cursor = {
533
844
  atHeight(height) {
534
845
  if (height <= 0)
535
- return "0:0";
536
- return `${height - 1}:${REWIND_FOOT_INDEX_SENTINEL}`;
846
+ return null;
847
+ return encodeStreamsCursor(blockEndCursor(height - 1));
537
848
  },
538
849
  parse(cursor) {
539
- const parts = cursor.split(":");
540
- const blockHeight = Number(parts[0]);
541
- const eventIndex = Number(parts[1]);
542
- if (parts.length !== 2 || !Number.isInteger(blockHeight) || !Number.isInteger(eventIndex)) {
850
+ try {
851
+ const decoded = decodeStreamsCursor(cursor);
852
+ return {
853
+ blockHeight: decoded.block_height,
854
+ eventIndex: decoded.event_index
855
+ };
856
+ } catch {
543
857
  throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
544
858
  }
545
- return { blockHeight, eventIndex };
546
859
  }
547
860
  };
548
861
 
@@ -550,6 +863,39 @@ var Cursor = {
550
863
  function reorgKey(reorg) {
551
864
  return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
552
865
  }
866
+ var DEFAULT_MAX_ROLLBACK_DEPTH = 1000;
867
+ async function applyReorgs(opts) {
868
+ const fresh = opts.reorgs.filter((reorg) => !opts.handled.has(opts.keyOf(reorg)));
869
+ if (fresh.length === 0)
870
+ return null;
871
+ for (const reorg of fresh) {
872
+ const fork = reorg.fork_point_height;
873
+ if (!Number.isSafeInteger(fork) || fork < 0) {
874
+ throw new ValidationError(`Reorg fork_point_height ${String(fork)} is not a non-negative integer. Rollback deletes every row at or above the fork from every declared table, so a malformed fork point is refused before the sink runs.`, 400);
875
+ }
876
+ }
877
+ const position = resumeHeight(opts.cursor);
878
+ const applicable = fresh.filter((reorg) => position !== null && reorg.fork_point_height <= position).sort((a, b) => a.fork_point_height - b.fork_point_height);
879
+ for (const reorg of fresh) {
880
+ if (!applicable.includes(reorg))
881
+ opts.handled.add(opts.keyOf(reorg));
882
+ }
883
+ if (applicable.length === 0 || position === null)
884
+ return null;
885
+ const forkPoint = applicable[0]?.fork_point_height ?? position;
886
+ const depth = position - forkPoint;
887
+ const maxDepth = opts.maxRollbackDepth ?? DEFAULT_MAX_ROLLBACK_DEPTH;
888
+ if (depth > maxDepth) {
889
+ throw new ValidationError(`Reorg at fork ${forkPoint} rewinds ${depth} blocks below the checkpoint at height ${position}, past maxRollbackDepth (${maxDepth}). A rollback this deep deletes every row at or above the fork from every declared table. Confirm the source is trusted, then pass maxRollbackDepth: ${depth} or higher to allow it.`, 400);
890
+ }
891
+ const rewind = Cursor.atHeight(forkPoint);
892
+ for (const reorg of applicable) {
893
+ await opts.sink?.rollback(reorg.fork_point_height, rewind);
894
+ await opts.onReorg?.(reorg, { cursor: rewind });
895
+ opts.handled.add(opts.keyOf(reorg));
896
+ }
897
+ return { forkPoint, cursor: rewind };
898
+ }
553
899
  function assertFinalizedCheckpoint(returned, checkpoint) {
554
900
  if (returned === null || returned === undefined)
555
901
  return;
@@ -629,17 +975,19 @@ async function defaultSleep(ms, signal) {
629
975
  if (signal?.aborted)
630
976
  return;
631
977
  await new Promise((resolve) => {
632
- const timeout = setTimeout(resolve, ms);
633
- if (!signal)
634
- return;
635
- signal.addEventListener("abort", () => {
978
+ const onAbort = () => {
636
979
  clearTimeout(timeout);
637
980
  resolve();
638
- }, { once: true });
981
+ };
982
+ const timeout = setTimeout(() => {
983
+ signal?.removeEventListener("abort", onAbort);
984
+ resolve();
985
+ }, ms);
986
+ signal?.addEventListener("abort", onAbort, { once: true });
639
987
  });
640
988
  }
641
989
  async function consumeStreamsEvents(opts) {
642
- const sleep = opts.sleep ?? defaultSleep;
990
+ const sleep3 = opts.sleep ?? defaultSleep;
643
991
  const mode = opts.mode ?? "tail";
644
992
  const finalizedOnly = opts.finalizedOnly ?? false;
645
993
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
@@ -649,6 +997,7 @@ async function consumeStreamsEvents(opts) {
649
997
  const committedCursor = await opts.sink?.loadCursor() ?? null;
650
998
  let cursor = opts.fromCursor ?? committedCursor;
651
999
  const handledReorgs = new Set;
1000
+ let reorgSince = null;
652
1001
  let pages = 0;
653
1002
  let emptyPolls = 0;
654
1003
  let height = resumeHeight(cursor);
@@ -668,27 +1017,61 @@ async function consumeStreamsEvents(opts) {
668
1017
  retryCount: opts.retryCount,
669
1018
  retryDelay: opts.retryDelay,
670
1019
  onError: opts.onError,
671
- sleep,
1020
+ sleep: sleep3,
672
1021
  signal: opts.signal
673
1022
  });
674
1023
  pages++;
675
- if (!finalizedOnly && (opts.onReorg || opts.sink)) {
676
- const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorgKey(reorg))).sort((a, b) => a.fork_point_height - b.fork_point_height);
677
- if (fresh.length > 0) {
678
- const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
679
- const rewind = Cursor.atHeight(forkPoint);
680
- for (const reorg of fresh) {
681
- await opts.sink?.rollback(reorg.fork_point_height, rewind);
682
- await opts.onReorg?.(reorg, { cursor: rewind });
683
- handledReorgs.add(reorgKey(reorg));
684
- }
685
- cursor = rewind;
686
- height = forkPoint > 0 ? forkPoint - 1 : null;
1024
+ const reorgsOn = !finalizedOnly && Boolean(opts.onReorg || opts.sink);
1025
+ if (reorgsOn) {
1026
+ const rewound = await applyReorgs({
1027
+ reorgs: envelope.reorgs,
1028
+ cursor,
1029
+ handled: handledReorgs,
1030
+ keyOf: reorgKey,
1031
+ sink: opts.sink,
1032
+ onReorg: opts.onReorg,
1033
+ maxRollbackDepth: opts.maxRollbackDepth
1034
+ });
1035
+ if (rewound) {
1036
+ cursor = rewound.cursor;
1037
+ height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
687
1038
  scanned = height;
688
1039
  emptyPolls = 0;
689
1040
  continue;
690
1041
  }
691
1042
  }
1043
+ if (reorgsOn && opts.fetchReorgs && envelope.events.length === 0) {
1044
+ reorgSince ??= cursor;
1045
+ if (reorgSince !== null) {
1046
+ const since = reorgSince;
1047
+ const fetchReorgs = opts.fetchReorgs;
1048
+ const listed = await fetchPageWithRetry(() => fetchReorgs({ since }), {
1049
+ retryCount: opts.retryCount,
1050
+ retryDelay: opts.retryDelay,
1051
+ onError: opts.onError,
1052
+ sleep: sleep3,
1053
+ signal: opts.signal
1054
+ });
1055
+ if (listed.next_since !== null)
1056
+ reorgSince = listed.next_since;
1057
+ const rewound = await applyReorgs({
1058
+ reorgs: listed.reorgs,
1059
+ cursor,
1060
+ handled: handledReorgs,
1061
+ keyOf: reorgKey,
1062
+ sink: opts.sink,
1063
+ onReorg: opts.onReorg,
1064
+ maxRollbackDepth: opts.maxRollbackDepth
1065
+ });
1066
+ if (rewound) {
1067
+ cursor = rewound.cursor;
1068
+ height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
1069
+ scanned = height;
1070
+ emptyPolls = 0;
1071
+ continue;
1072
+ }
1073
+ }
1074
+ }
692
1075
  const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
693
1076
  const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
694
1077
  height = emitted.at(-1)?.block_height ?? height;
@@ -729,7 +1112,7 @@ async function consumeStreamsEvents(opts) {
729
1112
  if (mode === "bounded") {
730
1113
  return { cursor, pages, emptyPolls };
731
1114
  }
732
- await sleep(emptyBackoffMs, opts.signal);
1115
+ await sleep3(emptyBackoffMs, opts.signal);
733
1116
  continue;
734
1117
  }
735
1118
  return { cursor, pages, emptyPolls };
@@ -737,7 +1120,7 @@ async function consumeStreamsEvents(opts) {
737
1120
  return { cursor, pages, emptyPolls };
738
1121
  }
739
1122
  async function* iterateStreamsBatches(opts) {
740
- const sleep = opts.sleep ?? defaultSleep;
1123
+ const sleep3 = opts.sleep ?? defaultSleep;
741
1124
  let cursor = opts.fromCursor ?? null;
742
1125
  while (!opts.signal?.aborted) {
743
1126
  const envelope = await opts.fetchEvents({
@@ -765,12 +1148,12 @@ async function* iterateStreamsBatches(opts) {
765
1148
  if (!advanced && envelope.events.length === 0) {
766
1149
  if (opts.signal?.aborted)
767
1150
  return;
768
- await sleep(opts.intervalMs, opts.signal);
1151
+ await sleep3(opts.intervalMs, opts.signal);
769
1152
  }
770
1153
  }
771
1154
  }
772
1155
  async function* streamStreamsEvents(opts) {
773
- const sleep = opts.sleep ?? defaultSleep;
1156
+ const sleep3 = opts.sleep ?? defaultSleep;
774
1157
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
775
1158
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
776
1159
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
@@ -805,7 +1188,7 @@ async function* streamStreamsEvents(opts) {
805
1188
  emptyPolls++;
806
1189
  if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
807
1190
  return;
808
- await sleep(emptyBackoffMs, opts.signal);
1191
+ await sleep3(emptyBackoffMs, opts.signal);
809
1192
  continue;
810
1193
  }
811
1194
  return;
@@ -814,7 +1197,7 @@ async function* streamStreamsEvents(opts) {
814
1197
 
815
1198
  // src/index-api/consumer.ts
816
1199
  async function consumeIndexFeed(opts) {
817
- const sleep = opts.sleep ?? defaultSleep;
1200
+ const sleep3 = opts.sleep ?? defaultSleep;
818
1201
  const mode = opts.mode ?? "tail";
819
1202
  const finalizedOnly = opts.finalizedOnly ?? false;
820
1203
  const batchSize = opts.batchSize ?? 200;
@@ -838,22 +1221,23 @@ async function consumeIndexFeed(opts) {
838
1221
  retryCount: opts.retryCount,
839
1222
  retryDelay: opts.retryDelay,
840
1223
  onError: opts.onError,
841
- sleep,
1224
+ sleep: sleep3,
842
1225
  signal: opts.signal
843
1226
  });
844
1227
  pages++;
845
1228
  if (!finalizedOnly && (opts.onReorg || opts.sink)) {
846
- const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorg.id)).sort((a, b) => a.fork_point_height - b.fork_point_height);
847
- if (fresh.length > 0) {
848
- const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
849
- const rewind = Cursor.atHeight(forkPoint);
850
- for (const reorg of fresh) {
851
- await opts.sink?.rollback(reorg.fork_point_height, rewind);
852
- await opts.onReorg?.(reorg, { cursor: rewind });
853
- handledReorgs.add(reorg.id);
854
- }
855
- cursor = rewind;
856
- height = forkPoint > 0 ? forkPoint - 1 : null;
1229
+ const rewound = await applyReorgs({
1230
+ reorgs: envelope.reorgs,
1231
+ cursor,
1232
+ handled: handledReorgs,
1233
+ keyOf: (reorg) => reorg.id,
1234
+ sink: opts.sink,
1235
+ onReorg: opts.onReorg,
1236
+ maxRollbackDepth: opts.maxRollbackDepth
1237
+ });
1238
+ if (rewound) {
1239
+ cursor = rewound.cursor;
1240
+ height = rewound.forkPoint > 0 ? rewound.forkPoint - 1 : null;
857
1241
  scanned = height;
858
1242
  emptyPolls = 0;
859
1243
  continue;
@@ -900,7 +1284,7 @@ async function consumeIndexFeed(opts) {
900
1284
  if (mode === "bounded") {
901
1285
  return { cursor, pages, emptyPolls };
902
1286
  }
903
- await sleep(emptyBackoffMs, opts.signal);
1287
+ await sleep3(emptyBackoffMs, opts.signal);
904
1288
  continue;
905
1289
  }
906
1290
  return { cursor, pages, emptyPolls };
@@ -909,6 +1293,7 @@ async function consumeIndexFeed(opts) {
909
1293
  }
910
1294
 
911
1295
  // src/index-api/client.ts
1296
+ var INDEX_MAX_PAGE_SIZE = 1000;
912
1297
  function feedFilters(params) {
913
1298
  const { fromCursor: _checkpoint, ...filters } = params;
914
1299
  return filters;
@@ -958,7 +1343,8 @@ class Index extends BaseClient {
958
1343
  ...feedFilters(params),
959
1344
  cursor,
960
1345
  fromHeight,
961
- limit
1346
+ limit,
1347
+ signal: params.signal
962
1348
  }),
963
1349
  itemsOf: (envelope) => envelope.events
964
1350
  })
@@ -972,7 +1358,8 @@ class Index extends BaseClient {
972
1358
  ...feedFilters(params),
973
1359
  cursor,
974
1360
  fromHeight,
975
- limit
1361
+ limit,
1362
+ signal: params.signal
976
1363
  }),
977
1364
  itemsOf: (envelope) => envelope.contract_calls
978
1365
  })
@@ -1011,7 +1398,8 @@ class Index extends BaseClient {
1011
1398
  ...feedFilters(params),
1012
1399
  cursor,
1013
1400
  fromHeight,
1014
- limit
1401
+ limit,
1402
+ signal: params.signal
1015
1403
  }),
1016
1404
  itemsOf: (envelope) => envelope.deposits
1017
1405
  }),
@@ -1031,7 +1419,8 @@ class Index extends BaseClient {
1031
1419
  ...feedFilters(params),
1032
1420
  cursor,
1033
1421
  fromHeight,
1034
- limit
1422
+ limit,
1423
+ signal: params.signal
1035
1424
  }),
1036
1425
  itemsOf: (envelope) => envelope.events
1037
1426
  })
@@ -1058,7 +1447,7 @@ class Index extends BaseClient {
1058
1447
  asset_identifier: params.assetIdentifier,
1059
1448
  sender: params.sender,
1060
1449
  recipient: params.recipient
1061
- })}`);
1450
+ })}`, undefined, { signal: params.signal });
1062
1451
  }
1063
1452
  async listNftTransfers(params = {}) {
1064
1453
  return this.request("GET", `/v1/index/nft-transfers${buildQuery({
@@ -1067,29 +1456,45 @@ class Index extends BaseClient {
1067
1456
  asset_identifier: params.assetIdentifier,
1068
1457
  sender: params.sender,
1069
1458
  recipient: params.recipient
1070
- })}`);
1459
+ })}`, undefined, { signal: params.signal });
1071
1460
  }
1072
1461
  async* keysetWalk(params, list, itemsOf) {
1073
1462
  const batchSize = params.batchSize ?? 200;
1463
+ if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > INDEX_MAX_PAGE_SIZE) {
1464
+ throw new ValidationError(`walk batchSize must be an integer from 1 to ${INDEX_MAX_PAGE_SIZE} (the largest page the Index serves); got ${batchSize}.`, 400);
1465
+ }
1074
1466
  let cursor = params.cursor ?? params.fromCursor ?? null;
1075
1467
  let firstPage = true;
1076
- while (!params.signal?.aborted) {
1077
- const envelope = await list({
1468
+ const throwIfAborted = () => {
1469
+ if (params.signal?.aborted)
1470
+ throw params.signal.reason ?? abortError();
1471
+ };
1472
+ for (;; ) {
1473
+ throwIfAborted();
1474
+ const page = {
1078
1475
  limit: batchSize,
1079
1476
  cursor: firstPage ? params.cursor : cursor,
1080
1477
  fromCursor: firstPage ? params.fromCursor : undefined,
1081
- fromHeight: firstPage ? firstWalkFromHeight(params) : undefined
1478
+ fromHeight: firstPage ? firstWalkFromHeight(params) : undefined,
1479
+ signal: params.signal
1480
+ };
1481
+ const envelope = await fetchPageWithRetry(() => list(page), {
1482
+ retryCount: params.retryCount,
1483
+ retryDelay: params.retryDelay,
1484
+ onError: params.onError,
1485
+ sleep: defaultSleep,
1486
+ signal: params.signal
1082
1487
  });
1083
1488
  const items = itemsOf(envelope);
1084
1489
  for (const item of items) {
1085
- if (params.signal?.aborted)
1086
- return;
1490
+ throwIfAborted();
1087
1491
  yield item;
1088
1492
  }
1089
1493
  const nextCursor = envelope.next_cursor;
1090
- if (!nextCursor || nextCursor === cursor || items.length < batchSize) {
1494
+ if (nextCursor === null || nextCursor === undefined)
1495
+ return;
1496
+ if (nextCursor === cursor)
1091
1497
  return;
1092
- }
1093
1498
  cursor = nextCursor;
1094
1499
  firstPage = false;
1095
1500
  }
@@ -1110,7 +1515,7 @@ class Index extends BaseClient {
1110
1515
  recipient: params.recipient,
1111
1516
  trait: params.trait,
1112
1517
  tx_context: params.txContext ? "true" : undefined
1113
- })}`);
1518
+ })}`, undefined, { signal: params.signal });
1114
1519
  }
1115
1520
  walkEvents(params) {
1116
1521
  return this.keysetWalk(params, (page) => this.listEvents({ ...params, ...page }), (e) => e.events);
@@ -1122,19 +1527,19 @@ class Index extends BaseClient {
1122
1527
  function_name: params.functionName,
1123
1528
  sender: params.sender,
1124
1529
  trait: params.trait
1125
- })}`);
1530
+ })}`, undefined, { signal: params.signal });
1126
1531
  }
1127
1532
  walkContractCalls(params = {}) {
1128
1533
  return this.keysetWalk(params, (page) => this.listContractCalls({ ...params, ...page }), (e) => e.contract_calls);
1129
1534
  }
1130
1535
  async listCanonical(params = {}) {
1131
- return this.request("GET", `/v1/index/canonical${buildQuery(indexPageQuery(params))}`);
1536
+ return this.request("GET", `/v1/index/canonical${buildQuery(indexPageQuery(params))}`, undefined, { signal: params.signal });
1132
1537
  }
1133
1538
  walkCanonical(params = {}) {
1134
1539
  return this.keysetWalk(params, (page) => this.listCanonical({ ...params, ...page }), (e) => e.canonical);
1135
1540
  }
1136
1541
  async listBlocks(params = {}) {
1137
- return this.request("GET", `/v1/index/blocks${buildQuery(indexPageQuery(params))}`);
1542
+ return this.request("GET", `/v1/index/blocks${buildQuery(indexPageQuery(params))}`, undefined, { signal: params.signal });
1138
1543
  }
1139
1544
  async getBlock(ref) {
1140
1545
  return this.requestOrNull("GET", `/v1/index/blocks/${encodeURIComponent(String(ref))}`);
@@ -1148,7 +1553,7 @@ class Index extends BaseClient {
1148
1553
  type: params.type,
1149
1554
  sender: params.sender,
1150
1555
  contract_id: params.contractId
1151
- })}`);
1556
+ })}`, undefined, { signal: params.signal });
1152
1557
  }
1153
1558
  async getTransaction(txId) {
1154
1559
  return this.requestOrNull("GET", `/v1/index/transactions/${encodeURIComponent(txId)}`);
@@ -1165,7 +1570,7 @@ class Index extends BaseClient {
1165
1570
  function_name: params.functionName,
1166
1571
  stacker: params.stacker,
1167
1572
  caller: params.caller
1168
- })}`);
1573
+ })}`, undefined, { signal: params.signal });
1169
1574
  }
1170
1575
  walkStacking(params = {}) {
1171
1576
  return this.keysetWalk(params, (page) => this.listStacking({ ...params, ...page }), (e) => e.stacking);
@@ -1177,7 +1582,7 @@ class Index extends BaseClient {
1177
1582
  type: params.type,
1178
1583
  contract_id: params.contractId,
1179
1584
  function_name: params.functionName
1180
- })}`);
1585
+ })}`, undefined, { signal: params.signal });
1181
1586
  }
1182
1587
  async getMempoolTx(txId) {
1183
1588
  return this.requestOrNull("GET", `/v1/index/mempool/${encodeURIComponent(txId)}`);
@@ -1191,7 +1596,7 @@ class Index extends BaseClient {
1191
1596
  confirmed: params.confirmed,
1192
1597
  sender: params.sender,
1193
1598
  bitcoin_txid: params.bitcoinTxid
1194
- })}`);
1599
+ })}`, undefined, { signal: params.signal });
1195
1600
  }
1196
1601
  walkSbtcDeposits(params = {}) {
1197
1602
  return this.keysetWalk(params, (page) => this.listSbtcDeposits({ ...params, ...page }), (e) => e.deposits);
@@ -1207,7 +1612,7 @@ class Index extends BaseClient {
1207
1612
  sender: params.sender,
1208
1613
  request_id: params.requestId,
1209
1614
  settlement_confirmed: params.settlementConfirmed
1210
- })}`);
1615
+ })}`, undefined, { signal: params.signal });
1211
1616
  }
1212
1617
  walkSbtcWithdrawals(params = {}) {
1213
1618
  return this.keysetWalk(params, (page) => this.listSbtcWithdrawals({ ...params, ...page }), (e) => e.withdrawals);
@@ -1223,7 +1628,7 @@ class Index extends BaseClient {
1223
1628
  sender: params.sender,
1224
1629
  request_id: params.requestId,
1225
1630
  bitcoin_txid: params.bitcoinTxid
1226
- })}`);
1631
+ })}`, undefined, { signal: params.signal });
1227
1632
  }
1228
1633
  walkSbtcEvents(params = {}) {
1229
1634
  return this.keysetWalk(params, (page) => this.listSbtcEvents({ ...params, ...page }), (e) => e.events);
@@ -1241,7 +1646,7 @@ class Index extends BaseClient {
1241
1646
  signer_manager: params.signerManager,
1242
1647
  bond_index: params.bondIndex,
1243
1648
  reward_cycle: params.rewardCycle
1244
- })}`);
1649
+ })}`, undefined, { signal: params.signal });
1245
1650
  }
1246
1651
  walkPox5Events(params = {}) {
1247
1652
  return this.keysetWalk(params, (page) => this.listPox5Events({ ...params, ...page }), (e) => e.events);
@@ -1250,24 +1655,14 @@ class Index extends BaseClient {
1250
1655
  return this.request("GET", `/v1/index/pox/cycles${buildQuery({
1251
1656
  cursor: params.cursor,
1252
1657
  limit: params.limit
1253
- })}`);
1658
+ })}`, undefined, { signal: params.signal });
1254
1659
  }
1255
- async* walkPoxCycles(params = {}) {
1256
- const batchSize = params.batchSize ?? 100;
1257
- let cursor = params.cursor ?? null;
1258
- while (!params.signal?.aborted) {
1259
- const envelope = await this.listPoxCycles({ cursor, limit: batchSize });
1260
- for (const cycle of envelope.cycles) {
1261
- if (params.signal?.aborted)
1262
- return;
1263
- yield cycle;
1264
- }
1265
- const nextCursor = envelope.next_cursor;
1266
- if (nextCursor === null || nextCursor === cursor || envelope.cycles.length < batchSize) {
1267
- return;
1268
- }
1269
- cursor = nextCursor;
1270
- }
1660
+ walkPoxCycles(params = {}) {
1661
+ return this.keysetWalk(params, (page) => this.listPoxCycles({
1662
+ cursor: page.cursor,
1663
+ limit: page.limit,
1664
+ signal: page.signal
1665
+ }), (e) => e.cycles);
1271
1666
  }
1272
1667
  async getPoxCycle(rewardCycle) {
1273
1668
  return this.requestOrNull("GET", `/v1/index/pox/cycles/${rewardCycle}`);
@@ -1431,166 +1826,57 @@ function createStreamsDumps(opts) {
1431
1826
  return manifest;
1432
1827
  }
1433
1828
  async function download(file) {
1829
+ return fetchPageWithRetry(() => downloadOnce(file), {
1830
+ sleep: defaultSleep
1831
+ });
1832
+ }
1833
+ async function downloadOnce(file) {
1434
1834
  const res = await opts.fetchImpl(fileUrl(file));
1435
1835
  if (!res.ok) {
1436
1836
  throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
1437
1837
  }
1438
- const bytes = new Uint8Array(await res.arrayBuffer());
1439
- const digest = createHash("sha256").update(bytes).digest("hex");
1440
- if (digest !== file.sha256) {
1441
- throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
1838
+ if (!res.body) {
1839
+ throw new StreamsServerError(`Dump ${file.path} response has no body.`, 0);
1442
1840
  }
1443
- return bytes;
1444
- }
1445
- return { list, fileUrl, download };
1446
- }
1447
-
1448
- // src/streams/subscribe.ts
1449
- import { ed25519 } from "@secondlayer/shared";
1450
- function subscribeStreamsEvents(opts) {
1451
- const { params } = opts;
1452
- const controller = new AbortController;
1453
- const external = params.signal;
1454
- if (external) {
1455
- if (external.aborted)
1456
- controller.abort();
1457
- else
1458
- external.addEventListener("abort", () => controller.abort(), {
1459
- once: true
1460
- });
1461
- }
1462
- let cursor = params.fromCursor ?? null;
1463
- const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
1464
- const run = async () => {
1465
- while (!controller.signal.aborted) {
1466
- try {
1467
- const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
1468
- from_cursor: cursor ?? undefined,
1469
- types: params.types,
1470
- not_types: params.notTypes,
1471
- contract_id: params.contractId,
1472
- sender: params.sender,
1473
- recipient: params.recipient,
1474
- asset_identifier: params.assetIdentifier,
1475
- filters: params.filters ? JSON.stringify(params.filters) : undefined
1476
- })}`;
1477
- const res = await opts.fetchImpl(url, {
1478
- headers: {
1479
- ...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {},
1480
- Accept: "text/event-stream"
1481
- },
1482
- signal: controller.signal
1483
- });
1484
- if (!res.ok) {
1485
- throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
1486
- }
1487
- if (!res.body) {
1488
- throw new StreamsServerError("Streams SSE response has no body.", 0);
1489
- }
1490
- for await (const frame of parseSseFrames(res.body, controller.signal)) {
1491
- if (frame.event === "ping" || !frame.data)
1492
- continue;
1493
- let parsed;
1494
- try {
1495
- parsed = JSON.parse(frame.data);
1496
- } catch {
1497
- continue;
1498
- }
1499
- if (!parsed.event)
1500
- continue;
1501
- if (opts.verify !== "off") {
1502
- if (!parsed.sig) {
1503
- if (opts.verify === "strict") {
1504
- throw new StreamsSignatureError("Streams SSE frame signature is missing.");
1505
- }
1506
- } else {
1507
- const key = await opts.loadKey();
1508
- if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
1509
- throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
1510
- }
1511
- }
1512
- }
1513
- cursor = parsed.event.cursor ?? cursor;
1514
- await params.onEvent(parsed.event);
1515
- }
1516
- } catch (err) {
1517
- if (controller.signal.aborted)
1518
- return;
1519
- params.onError?.(err);
1520
- await sleep(reconnectDelayMs, controller.signal);
1841
+ const hash = createHash("sha256");
1842
+ const chunks = [];
1843
+ let total = 0;
1844
+ const reader = res.body.getReader();
1845
+ try {
1846
+ while (true) {
1847
+ const { value, done } = await reader.read();
1848
+ if (done)
1849
+ break;
1850
+ hash.update(value);
1851
+ chunks.push(value);
1852
+ total += value.byteLength;
1521
1853
  }
1854
+ } catch {
1855
+ throw new StreamsServerError(`Download of dump ${file.path} was interrupted after ${total} bytes.`, 0);
1522
1856
  }
1523
- };
1524
- run();
1525
- return () => controller.abort();
1526
- }
1527
- function sleep(ms, signal) {
1528
- return new Promise((resolve) => {
1529
- if (signal.aborted)
1530
- return resolve();
1531
- const onAbort = () => {
1532
- clearTimeout(timer);
1533
- resolve();
1534
- };
1535
- const timer = setTimeout(() => {
1536
- signal.removeEventListener("abort", onAbort);
1537
- resolve();
1538
- }, ms);
1539
- signal.addEventListener("abort", onAbort, { once: true });
1540
- });
1541
- }
1542
- async function* parseSseFrames(body, signal) {
1543
- const reader = body.getReader();
1544
- const decoder = new TextDecoder;
1545
- let buffer = "";
1546
- try {
1547
- while (!signal.aborted) {
1548
- const { value, done } = await reader.read();
1549
- if (done)
1550
- break;
1551
- buffer += decoder.decode(value, { stream: true });
1552
- let sep = buffer.indexOf(`
1553
-
1554
- `);
1555
- while (sep !== -1) {
1556
- yield parseFrame(buffer.slice(0, sep));
1557
- buffer = buffer.slice(sep + 2);
1558
- sep = buffer.indexOf(`
1559
-
1560
- `);
1561
- }
1857
+ const digest = hash.digest("hex");
1858
+ if (digest !== file.sha256) {
1859
+ throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
1562
1860
  }
1563
- } finally {
1564
- try {
1565
- await reader.cancel();
1566
- } catch {}
1567
- }
1568
- }
1569
- function parseFrame(raw) {
1570
- let event;
1571
- const data = [];
1572
- for (const line of raw.split(`
1573
- `)) {
1574
- if (line.startsWith("data:")) {
1575
- data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
1576
- } else if (line.startsWith("event:")) {
1577
- event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
1861
+ if (chunks.length === 1 && chunks[0])
1862
+ return chunks[0];
1863
+ const bytes = new Uint8Array(total);
1864
+ let offset = 0;
1865
+ for (const chunk of chunks) {
1866
+ bytes.set(chunk, offset);
1867
+ offset += chunk.byteLength;
1578
1868
  }
1869
+ return bytes;
1579
1870
  }
1580
- return { event, data: data.length > 0 ? data.join(`
1581
- `) : undefined };
1871
+ return { list, fileUrl, download };
1582
1872
  }
1583
1873
 
1584
1874
  // src/streams/client.ts
1585
1875
  function cursorTuple(cursor) {
1586
1876
  if (!cursor)
1587
1877
  return [-1, -1];
1588
- const parts = cursor.split(":");
1589
- const [block, index] = parts.map(Number);
1590
- if (parts.length !== 2 || !Number.isInteger(block) || !Number.isInteger(index)) {
1591
- throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
1592
- }
1593
- return [block, index];
1878
+ const { blockHeight, eventIndex } = Cursor.parse(cursor);
1879
+ return [blockHeight, eventIndex];
1594
1880
  }
1595
1881
  function maxCursor(a, b) {
1596
1882
  const [ah, ai] = cursorTuple(a);
@@ -1611,51 +1897,33 @@ function streamsFilters(params) {
1611
1897
  function normalizeBaseUrl(baseUrl) {
1612
1898
  return baseUrl.replace(/\/+$/, "");
1613
1899
  }
1614
- async function responseBody(response) {
1615
- const text = await response.text();
1616
- if (text.length === 0)
1617
- return;
1900
+ function isLoopbackUrl(baseUrl) {
1618
1901
  try {
1619
- return JSON.parse(text);
1902
+ const { hostname } = new URL(baseUrl);
1903
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("127.");
1620
1904
  } catch {
1621
- return text;
1622
- }
1623
- }
1624
- function errorMessage(body, fallback) {
1625
- if (body && typeof body === "object") {
1626
- const record = body;
1627
- const message = record.error ?? record.message;
1628
- if (typeof message === "string" && message.length > 0)
1629
- return message;
1630
- }
1631
- if (typeof body === "string" && body.length > 0)
1632
- return body;
1633
- return fallback;
1634
- }
1635
- async function mapStreamsError(response) {
1636
- const body = await responseBody(response);
1637
- if (response.status === 401) {
1638
- throw new AuthError(errorMessage(body, "API key invalid or expired."));
1639
- }
1640
- if (response.status === 429) {
1641
- const retryAfter = response.headers.get("Retry-After") ?? undefined;
1642
- throw new RateLimitError(errorMessage(body, "Rate limited. Try again later."), retryAfter);
1643
- }
1644
- if (response.status >= 500) {
1645
- throw new StreamsServerError(errorMessage(body, `Streams server returned ${response.status}.`), response.status, body);
1905
+ return false;
1646
1906
  }
1647
- throw new ValidationError(errorMessage(body, `Streams request returned ${response.status}.`), response.status, body);
1648
1907
  }
1649
1908
  function createStreamsClient(options) {
1650
1909
  const baseUrl = normalizeBaseUrl(resolveBaseUrl(options.baseUrl));
1910
+ const apiKey = resolveApiKey(options.apiKey);
1911
+ const origin = options.origin ?? "cli";
1912
+ const authHeaders = () => ({
1913
+ "x-sl-origin": origin,
1914
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
1915
+ });
1651
1916
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
1652
1917
  const verify = options.verify;
1653
1918
  const verifyMode = verify === false ? "off" : verify === undefined ? "lenient" : "strict";
1919
+ if (verify === true && baseUrl.startsWith("http:") && !isLoopbackUrl(baseUrl)) {
1920
+ throw new ValidationError(`verify: true over plain http to ${baseUrl} fetches the signing key from the same unprotected path it verifies. Pin the key with verify: { publicKey } (see GET /public/streams/signing-key), or use https.`, 400);
1921
+ }
1654
1922
  let keyPromise = null;
1655
1923
  function loadKey() {
1656
1924
  if (keyPromise)
1657
1925
  return keyPromise;
1658
- keyPromise = (async () => {
1926
+ const pending = (async () => {
1659
1927
  if (typeof verify === "object") {
1660
1928
  return {
1661
1929
  keyId: ed255192.ed25519KeyId(verify.publicKey),
@@ -1663,8 +1931,16 @@ function createStreamsClient(options) {
1663
1931
  publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
1664
1932
  };
1665
1933
  }
1666
- const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
1934
+ let res;
1935
+ try {
1936
+ res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
1937
+ } catch {
1938
+ throw new StreamsServerError(`Could not reach the signing key at ${baseUrl}/public/streams/signing-key. Retryable.`, 0, undefined, "SIGNING_KEY_UNAVAILABLE");
1939
+ }
1667
1940
  if (!res.ok) {
1941
+ if (res.status >= 500 || res.status === 429) {
1942
+ throw new StreamsServerError(`Could not fetch signing key (${res.status}). Retryable.`, res.status, undefined, "SIGNING_KEY_UNAVAILABLE");
1943
+ }
1668
1944
  throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
1669
1945
  }
1670
1946
  const body = await res.json();
@@ -1677,7 +1953,26 @@ function createStreamsClient(options) {
1677
1953
  publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
1678
1954
  };
1679
1955
  })();
1680
- return keyPromise;
1956
+ keyPromise = pending;
1957
+ pending.catch(() => {
1958
+ if (keyPromise === pending)
1959
+ keyPromise = null;
1960
+ });
1961
+ return pending;
1962
+ }
1963
+ async function loadKeyFor(responseKeyId) {
1964
+ let key = await loadKey();
1965
+ if (!responseKeyId || responseKeyId === key.keyId)
1966
+ return key;
1967
+ if (typeof verify === "object") {
1968
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
1969
+ }
1970
+ keyPromise = null;
1971
+ key = await loadKey();
1972
+ if (responseKeyId !== key.keyId) {
1973
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
1974
+ }
1975
+ return key;
1681
1976
  }
1682
1977
  const dumps = createStreamsDumps({
1683
1978
  baseUrl: options.dumpsBaseUrl,
@@ -1687,7 +1982,7 @@ function createStreamsClient(options) {
1687
1982
  });
1688
1983
  async function request(path) {
1689
1984
  const response = await fetchImpl(`${baseUrl}${path}`, {
1690
- headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}
1985
+ headers: authHeaders()
1691
1986
  });
1692
1987
  if (!response.ok)
1693
1988
  await mapStreamsError(response);
@@ -1699,18 +1994,7 @@ function createStreamsClient(options) {
1699
1994
  throw new StreamsSignatureError("Response is missing X-Signature.");
1700
1995
  }
1701
1996
  } else {
1702
- const responseKeyId = response.headers.get("X-Signature-KeyId");
1703
- let key = await loadKey();
1704
- if (responseKeyId && responseKeyId !== key.keyId) {
1705
- if (typeof verify === "object") {
1706
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
1707
- }
1708
- keyPromise = null;
1709
- key = await loadKey();
1710
- if (responseKeyId !== key.keyId) {
1711
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
1712
- }
1713
- }
1997
+ const key = await loadKeyFor(response.headers.get("X-Signature-KeyId"));
1714
1998
  if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
1715
1999
  throw new StreamsSignatureError;
1716
2000
  }
@@ -1719,6 +2003,13 @@ function createStreamsClient(options) {
1719
2003
  return JSON.parse(text);
1720
2004
  }
1721
2005
  const fetchEvents = listEvents;
2006
+ function listReorgs(params) {
2007
+ return request(`/v1/streams/reorgs${buildQuery({
2008
+ since: params.since,
2009
+ limit: params.limit
2010
+ })}`);
2011
+ }
2012
+ const fetchReorgs = (params) => listReorgs(params);
1722
2013
  function encodeFilters(filters) {
1723
2014
  return filters ? JSON.stringify(filters) : undefined;
1724
2015
  }
@@ -1766,8 +2057,10 @@ function createStreamsClient(options) {
1766
2057
  ...streamsFilters(params),
1767
2058
  batchSize: params.batchSize ?? 100,
1768
2059
  fetchEvents,
2060
+ fetchReorgs,
1769
2061
  onBatch,
1770
2062
  onReorg: params.onReorg,
2063
+ maxRollbackDepth: params.maxRollbackDepth,
1771
2064
  emptyBackoffMs: params.emptyBackoffMs,
1772
2065
  maxPages: params.maxPages,
1773
2066
  maxEmptyPolls: params.maxEmptyPolls,
@@ -1809,10 +2102,12 @@ function createStreamsClient(options) {
1809
2102
  subscribe(params) {
1810
2103
  return subscribeStreamsEvents({
1811
2104
  baseUrl,
1812
- apiKey: options.apiKey,
2105
+ headers: authHeaders(),
1813
2106
  fetchImpl,
1814
2107
  verify: verifyMode,
1815
- loadKey,
2108
+ loadKey: loadKeyFor,
2109
+ reconnectDelayMs: params.reconnectDelayMs,
2110
+ staleAfterMs: params.staleAfterMs,
1816
2111
  params
1817
2112
  });
1818
2113
  },
@@ -1820,11 +2115,11 @@ function createStreamsClient(options) {
1820
2115
  const fromCursor = params.from === "genesis" ? null : params.from ?? null;
1821
2116
  const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
1822
2117
  const manifest = await dumps.list();
1823
- const files = manifest.files.filter((file) => file.to_block >= fromBlock).sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
2118
+ const files = manifest.files.filter((file) => file.to_block >= fromBlock).filter((file) => fromCursor === null || file.max_cursor === null || maxCursor(fromCursor, file.max_cursor) !== fromCursor).sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
1824
2119
  for (const file of files) {
1825
2120
  if (params.signal?.aborted)
1826
2121
  break;
1827
- await params.onDumpFile(file);
2122
+ await params.onDumpFile(file, { from: fromCursor });
1828
2123
  }
1829
2124
  const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
1830
2125
  return consumeStreamsEvents({
@@ -1849,14 +2144,7 @@ function createStreamsClient(options) {
1849
2144
  return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
1850
2145
  }
1851
2146
  },
1852
- reorgs: {
1853
- list(params) {
1854
- return request(`/v1/streams/reorgs${buildQuery({
1855
- since: params.since,
1856
- limit: params.limit
1857
- })}`);
1858
- }
1859
- },
2147
+ reorgs: { list: listReorgs },
1860
2148
  dumps,
1861
2149
  canonical(height) {
1862
2150
  return request(`/v1/streams/canonical/${height}`);
@@ -1875,44 +2163,69 @@ class Subscriptions extends BaseClient {
1875
2163
  return this.request("GET", "/api/subscriptions");
1876
2164
  }
1877
2165
  async get(id) {
1878
- return this.request("GET", `/api/subscriptions/${id}`);
2166
+ return this.request("GET", `/api/subscriptions/${seg(id)}`);
1879
2167
  }
1880
2168
  async create(input) {
1881
2169
  return this.request("POST", "/api/subscriptions", input);
1882
2170
  }
1883
2171
  async update(id, patch) {
1884
- return this.request("PATCH", `/api/subscriptions/${id}`, patch);
2172
+ return this.request("PATCH", `/api/subscriptions/${seg(id)}`, patch);
1885
2173
  }
1886
2174
  async pause(id) {
1887
- return this.request("POST", `/api/subscriptions/${id}/pause`);
2175
+ return this.request("POST", `/api/subscriptions/${seg(id)}/pause`);
1888
2176
  }
1889
2177
  async resume(id) {
1890
- return this.request("POST", `/api/subscriptions/${id}/resume`);
2178
+ return this.request("POST", `/api/subscriptions/${seg(id)}/resume`);
1891
2179
  }
1892
2180
  async delete(id) {
1893
- return this.request("DELETE", `/api/subscriptions/${id}`);
2181
+ return this.request("DELETE", `/api/subscriptions/${seg(id)}`);
1894
2182
  }
1895
2183
  async rotateSecret(id) {
1896
- return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
2184
+ return this.request("POST", `/api/subscriptions/${seg(id)}/rotate-secret`);
1897
2185
  }
1898
2186
  async test(id) {
1899
- return this.request("POST", `/api/subscriptions/${id}/test`);
2187
+ return this.request("POST", `/api/subscriptions/${seg(id)}/test`);
1900
2188
  }
1901
2189
  async deliveries(id) {
1902
- return this.request("GET", `/api/subscriptions/${id}/deliveries`);
2190
+ return this.request("GET", `/api/subscriptions/${seg(id)}/deliveries`);
1903
2191
  }
1904
2192
  async replay(id, range) {
1905
- return this.request("POST", `/api/subscriptions/${id}/replay`, range);
2193
+ return this.request("POST", `/api/subscriptions/${seg(id)}/replay`, range);
1906
2194
  }
1907
2195
  async dead(id) {
1908
- return this.request("GET", `/api/subscriptions/${id}/dead`);
2196
+ return this.request("GET", `/api/subscriptions/${seg(id)}/dead`);
1909
2197
  }
1910
2198
  async requeue(id, outboxId) {
1911
- return this.request("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
2199
+ return this.request("POST", `/api/subscriptions/${seg(id)}/dead/${seg(outboxId)}/requeue`);
1912
2200
  }
1913
2201
  }
1914
2202
 
1915
2203
  // src/client.ts
2204
+ async function contextField(read) {
2205
+ try {
2206
+ return { value: await read };
2207
+ } catch (err) {
2208
+ return { value: null, error: describeContextError(err) };
2209
+ }
2210
+ }
2211
+ function describeContextError(err) {
2212
+ if (err instanceof SecondLayerError) {
2213
+ return {
2214
+ message: err.shortMessage,
2215
+ ...err.code !== undefined ? { code: err.code } : {},
2216
+ ...err instanceof ApiError ? { status: err.status } : {},
2217
+ retryable: err.retryable
2218
+ };
2219
+ }
2220
+ if (err instanceof TypeError) {
2221
+ return { message: err.message, status: 0, retryable: true };
2222
+ }
2223
+ return {
2224
+ message: err instanceof Error ? err.message : String(err),
2225
+ retryable: false
2226
+ };
2227
+ }
2228
+
1916
2229
  class SecondLayer extends BaseClient {
1917
2230
  streams;
1918
2231
  index;
@@ -1922,10 +2235,13 @@ class SecondLayer extends BaseClient {
1922
2235
  constructor(options = {}) {
1923
2236
  super(options);
1924
2237
  this.streams = createStreamsClient({
1925
- apiKey: resolveApiKey(options.apiKey),
2238
+ apiKey: this.apiKey,
2239
+ origin: this.origin,
1926
2240
  baseUrl: options.baseUrl,
1927
2241
  fetchImpl: options.fetchImpl,
1928
- dumpsBaseUrl: options.dumpsBaseUrl
2242
+ dumpsBaseUrl: options.dumpsBaseUrl,
2243
+ verify: options.verify,
2244
+ verifyDumpsManifest: options.verifyDumpsManifest
1929
2245
  });
1930
2246
  this.index = new Index(options);
1931
2247
  this.contracts = new Contracts(options);
@@ -1936,28 +2252,42 @@ class SecondLayer extends BaseClient {
1936
2252
  return this.request("POST", "/v1/batch", { requests });
1937
2253
  }
1938
2254
  async context() {
1939
- const safe = (p) => p.then((v) => v).catch(() => null);
1940
2255
  const [account, streamsTip, indexEnv, subgraphsRes, subscriptionsRes] = await Promise.all([
1941
- safe(this.request("GET", "/api/accounts/me")),
1942
- safe(this.streams.tip()),
1943
- safe(this.index.canonical.list({ limit: 1 })),
1944
- safe(this.subgraphs.list()),
1945
- safe(this.subscriptions.list())
2256
+ contextField(this.request("GET", "/api/accounts/me")),
2257
+ contextField(this.streams.tip()),
2258
+ contextField(this.index.canonical.list({ limit: 1 })),
2259
+ contextField(this.subgraphs.list()),
2260
+ contextField(this.subscriptions.list())
1946
2261
  ]);
1947
- const subgraphs = subgraphsRes?.data ?? null;
1948
- let subscriptions = null;
1949
- if (subscriptionsRes) {
2262
+ const subgraphs = {
2263
+ value: subgraphsRes.value?.data ?? null,
2264
+ ...subgraphsRes.error ? { error: subgraphsRes.error } : {}
2265
+ };
2266
+ const subscriptions = {
2267
+ value: null,
2268
+ ...subscriptionsRes.error ? { error: subscriptionsRes.error } : {}
2269
+ };
2270
+ if (subscriptionsRes.value) {
1950
2271
  const byStatus = {};
1951
- for (const s of subscriptionsRes.data) {
2272
+ for (const s of subscriptionsRes.value.data) {
1952
2273
  byStatus[s.status] = (byStatus[s.status] ?? 0) + 1;
1953
2274
  }
1954
- subscriptions = { count: subscriptionsRes.data.length, byStatus };
2275
+ subscriptions.value = {
2276
+ count: subscriptionsRes.value.data.length,
2277
+ byStatus
2278
+ };
1955
2279
  }
1956
- let activeOperations = null;
1957
- if (subgraphs) {
1958
- const probed = await Promise.all(subgraphs.filter((s) => s.status === "reindexing").map(async (s) => {
1959
- const res = await safe(this.subgraphs.operations(s.name));
1960
- const op = res?.operations.find((o) => o.status === "queued" || o.status === "running");
2280
+ let activeOperations = {
2281
+ value: null,
2282
+ error: {
2283
+ message: "Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve.",
2284
+ retryable: subgraphs.error?.retryable ?? false
2285
+ }
2286
+ };
2287
+ if (subgraphs.value) {
2288
+ const probed = await Promise.all(subgraphs.value.filter((s) => s.status === "reindexing").map(async (s) => {
2289
+ const res = await contextField(this.subgraphs.operations(s.name));
2290
+ const op = res.value?.operations.find((o) => o.status === "queued" || o.status === "running");
1961
2291
  return op ? {
1962
2292
  subgraph: s.name,
1963
2293
  operationId: op.id,
@@ -1966,12 +2296,17 @@ class SecondLayer extends BaseClient {
1966
2296
  progress: op.progress
1967
2297
  } : null;
1968
2298
  }));
1969
- activeOperations = probed.filter((o) => o !== null);
2299
+ activeOperations = {
2300
+ value: probed.filter((o) => o !== null)
2301
+ };
1970
2302
  }
1971
2303
  return {
1972
2304
  account,
1973
2305
  streamsTip,
1974
- indexTip: indexEnv?.tip ?? null,
2306
+ indexTip: {
2307
+ value: indexEnv.value?.tip ?? null,
2308
+ ...indexEnv.error ? { error: indexEnv.error } : {}
2309
+ },
1975
2310
  subgraphs,
1976
2311
  subscriptions,
1977
2312
  activeOperations
@@ -1994,5 +2329,5 @@ export {
1994
2329
  Subgraphs
1995
2330
  };
1996
2331
 
1997
- //# debugId=9E199310A6B6CFB264756E2164756E21
2332
+ //# debugId=10070F0E5755447564756E2164756E21
1998
2333
  //# sourceMappingURL=index.js.map