@secondlayer/sdk 9.0.2 → 10.1.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
@@ -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);
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
+ }
278
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;
294
- }
295
- function serializeWhere(where) {
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;
564
+ }
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
  }
@@ -518,28 +840,10 @@ import {
518
840
  decodeStreamsCursor,
519
841
  encodeStreamsCursor
520
842
  } from "@secondlayer/shared";
521
-
522
- // src/streams/errors.ts
523
- class StreamsServerError extends ApiError {
524
- constructor(message, status, body) {
525
- super(status, message, body, undefined, { retryable: true });
526
- this.name = "StreamsServerError";
527
- }
528
- }
529
-
530
- class StreamsSignatureError extends SecondLayerError {
531
- constructor(message = "Streams response signature verification failed.") {
532
- super(message);
533
- this.name = "StreamsSignatureError";
534
- }
535
- }
536
-
537
- // src/streams/cursor.ts
538
843
  var Cursor = {
539
844
  atHeight(height) {
540
- if (height <= 0) {
541
- return encodeStreamsCursor({ block_height: 0, event_index: 0 });
542
- }
845
+ if (height <= 0)
846
+ return null;
543
847
  return encodeStreamsCursor(blockEndCursor(height - 1));
544
848
  },
545
849
  parse(cursor) {
@@ -559,6 +863,39 @@ var Cursor = {
559
863
  function reorgKey(reorg) {
560
864
  return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
561
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
+ }
562
899
  function assertFinalizedCheckpoint(returned, checkpoint) {
563
900
  if (returned === null || returned === undefined)
564
901
  return;
@@ -638,17 +975,19 @@ async function defaultSleep(ms, signal) {
638
975
  if (signal?.aborted)
639
976
  return;
640
977
  await new Promise((resolve) => {
641
- const timeout = setTimeout(resolve, ms);
642
- if (!signal)
643
- return;
644
- signal.addEventListener("abort", () => {
978
+ const onAbort = () => {
645
979
  clearTimeout(timeout);
646
980
  resolve();
647
- }, { once: true });
981
+ };
982
+ const timeout = setTimeout(() => {
983
+ signal?.removeEventListener("abort", onAbort);
984
+ resolve();
985
+ }, ms);
986
+ signal?.addEventListener("abort", onAbort, { once: true });
648
987
  });
649
988
  }
650
989
  async function consumeStreamsEvents(opts) {
651
- const sleep = opts.sleep ?? defaultSleep;
990
+ const sleep3 = opts.sleep ?? defaultSleep;
652
991
  const mode = opts.mode ?? "tail";
653
992
  const finalizedOnly = opts.finalizedOnly ?? false;
654
993
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
@@ -658,6 +997,7 @@ async function consumeStreamsEvents(opts) {
658
997
  const committedCursor = await opts.sink?.loadCursor() ?? null;
659
998
  let cursor = opts.fromCursor ?? committedCursor;
660
999
  const handledReorgs = new Set;
1000
+ let reorgSince = null;
661
1001
  let pages = 0;
662
1002
  let emptyPolls = 0;
663
1003
  let height = resumeHeight(cursor);
@@ -677,27 +1017,61 @@ async function consumeStreamsEvents(opts) {
677
1017
  retryCount: opts.retryCount,
678
1018
  retryDelay: opts.retryDelay,
679
1019
  onError: opts.onError,
680
- sleep,
1020
+ sleep: sleep3,
681
1021
  signal: opts.signal
682
1022
  });
683
1023
  pages++;
684
- if (!finalizedOnly && (opts.onReorg || opts.sink)) {
685
- const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorgKey(reorg))).sort((a, b) => a.fork_point_height - b.fork_point_height);
686
- if (fresh.length > 0) {
687
- const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
688
- const rewind = Cursor.atHeight(forkPoint);
689
- for (const reorg of fresh) {
690
- await opts.sink?.rollback(reorg.fork_point_height, rewind);
691
- await opts.onReorg?.(reorg, { cursor: rewind });
692
- handledReorgs.add(reorgKey(reorg));
693
- }
694
- cursor = rewind;
695
- 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;
696
1038
  scanned = height;
697
1039
  emptyPolls = 0;
698
1040
  continue;
699
1041
  }
700
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
+ }
701
1075
  const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
702
1076
  const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
703
1077
  height = emitted.at(-1)?.block_height ?? height;
@@ -738,7 +1112,7 @@ async function consumeStreamsEvents(opts) {
738
1112
  if (mode === "bounded") {
739
1113
  return { cursor, pages, emptyPolls };
740
1114
  }
741
- await sleep(emptyBackoffMs, opts.signal);
1115
+ await sleep3(emptyBackoffMs, opts.signal);
742
1116
  continue;
743
1117
  }
744
1118
  return { cursor, pages, emptyPolls };
@@ -746,7 +1120,7 @@ async function consumeStreamsEvents(opts) {
746
1120
  return { cursor, pages, emptyPolls };
747
1121
  }
748
1122
  async function* iterateStreamsBatches(opts) {
749
- const sleep = opts.sleep ?? defaultSleep;
1123
+ const sleep3 = opts.sleep ?? defaultSleep;
750
1124
  let cursor = opts.fromCursor ?? null;
751
1125
  while (!opts.signal?.aborted) {
752
1126
  const envelope = await opts.fetchEvents({
@@ -774,12 +1148,12 @@ async function* iterateStreamsBatches(opts) {
774
1148
  if (!advanced && envelope.events.length === 0) {
775
1149
  if (opts.signal?.aborted)
776
1150
  return;
777
- await sleep(opts.intervalMs, opts.signal);
1151
+ await sleep3(opts.intervalMs, opts.signal);
778
1152
  }
779
1153
  }
780
1154
  }
781
1155
  async function* streamStreamsEvents(opts) {
782
- const sleep = opts.sleep ?? defaultSleep;
1156
+ const sleep3 = opts.sleep ?? defaultSleep;
783
1157
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
784
1158
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
785
1159
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
@@ -814,7 +1188,7 @@ async function* streamStreamsEvents(opts) {
814
1188
  emptyPolls++;
815
1189
  if (emptyPolls >= maxEmptyPolls || pages >= maxPages)
816
1190
  return;
817
- await sleep(emptyBackoffMs, opts.signal);
1191
+ await sleep3(emptyBackoffMs, opts.signal);
818
1192
  continue;
819
1193
  }
820
1194
  return;
@@ -823,7 +1197,7 @@ async function* streamStreamsEvents(opts) {
823
1197
 
824
1198
  // src/index-api/consumer.ts
825
1199
  async function consumeIndexFeed(opts) {
826
- const sleep = opts.sleep ?? defaultSleep;
1200
+ const sleep3 = opts.sleep ?? defaultSleep;
827
1201
  const mode = opts.mode ?? "tail";
828
1202
  const finalizedOnly = opts.finalizedOnly ?? false;
829
1203
  const batchSize = opts.batchSize ?? 200;
@@ -847,22 +1221,23 @@ async function consumeIndexFeed(opts) {
847
1221
  retryCount: opts.retryCount,
848
1222
  retryDelay: opts.retryDelay,
849
1223
  onError: opts.onError,
850
- sleep,
1224
+ sleep: sleep3,
851
1225
  signal: opts.signal
852
1226
  });
853
1227
  pages++;
854
1228
  if (!finalizedOnly && (opts.onReorg || opts.sink)) {
855
- const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorg.id)).sort((a, b) => a.fork_point_height - b.fork_point_height);
856
- if (fresh.length > 0) {
857
- const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
858
- const rewind = Cursor.atHeight(forkPoint);
859
- for (const reorg of fresh) {
860
- await opts.sink?.rollback(reorg.fork_point_height, rewind);
861
- await opts.onReorg?.(reorg, { cursor: rewind });
862
- handledReorgs.add(reorg.id);
863
- }
864
- cursor = rewind;
865
- 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;
866
1241
  scanned = height;
867
1242
  emptyPolls = 0;
868
1243
  continue;
@@ -909,7 +1284,7 @@ async function consumeIndexFeed(opts) {
909
1284
  if (mode === "bounded") {
910
1285
  return { cursor, pages, emptyPolls };
911
1286
  }
912
- await sleep(emptyBackoffMs, opts.signal);
1287
+ await sleep3(emptyBackoffMs, opts.signal);
913
1288
  continue;
914
1289
  }
915
1290
  return { cursor, pages, emptyPolls };
@@ -918,6 +1293,7 @@ async function consumeIndexFeed(opts) {
918
1293
  }
919
1294
 
920
1295
  // src/index-api/client.ts
1296
+ var INDEX_MAX_PAGE_SIZE = 1000;
921
1297
  function feedFilters(params) {
922
1298
  const { fromCursor: _checkpoint, ...filters } = params;
923
1299
  return filters;
@@ -967,7 +1343,8 @@ class Index extends BaseClient {
967
1343
  ...feedFilters(params),
968
1344
  cursor,
969
1345
  fromHeight,
970
- limit
1346
+ limit,
1347
+ signal: params.signal
971
1348
  }),
972
1349
  itemsOf: (envelope) => envelope.events
973
1350
  })
@@ -981,7 +1358,8 @@ class Index extends BaseClient {
981
1358
  ...feedFilters(params),
982
1359
  cursor,
983
1360
  fromHeight,
984
- limit
1361
+ limit,
1362
+ signal: params.signal
985
1363
  }),
986
1364
  itemsOf: (envelope) => envelope.contract_calls
987
1365
  })
@@ -1020,7 +1398,8 @@ class Index extends BaseClient {
1020
1398
  ...feedFilters(params),
1021
1399
  cursor,
1022
1400
  fromHeight,
1023
- limit
1401
+ limit,
1402
+ signal: params.signal
1024
1403
  }),
1025
1404
  itemsOf: (envelope) => envelope.deposits
1026
1405
  }),
@@ -1040,7 +1419,8 @@ class Index extends BaseClient {
1040
1419
  ...feedFilters(params),
1041
1420
  cursor,
1042
1421
  fromHeight,
1043
- limit
1422
+ limit,
1423
+ signal: params.signal
1044
1424
  }),
1045
1425
  itemsOf: (envelope) => envelope.events
1046
1426
  })
@@ -1067,7 +1447,7 @@ class Index extends BaseClient {
1067
1447
  asset_identifier: params.assetIdentifier,
1068
1448
  sender: params.sender,
1069
1449
  recipient: params.recipient
1070
- })}`);
1450
+ })}`, undefined, { signal: params.signal });
1071
1451
  }
1072
1452
  async listNftTransfers(params = {}) {
1073
1453
  return this.request("GET", `/v1/index/nft-transfers${buildQuery({
@@ -1076,29 +1456,45 @@ class Index extends BaseClient {
1076
1456
  asset_identifier: params.assetIdentifier,
1077
1457
  sender: params.sender,
1078
1458
  recipient: params.recipient
1079
- })}`);
1459
+ })}`, undefined, { signal: params.signal });
1080
1460
  }
1081
1461
  async* keysetWalk(params, list, itemsOf) {
1082
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
+ }
1083
1466
  let cursor = params.cursor ?? params.fromCursor ?? null;
1084
1467
  let firstPage = true;
1085
- while (!params.signal?.aborted) {
1086
- 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 = {
1087
1475
  limit: batchSize,
1088
1476
  cursor: firstPage ? params.cursor : cursor,
1089
1477
  fromCursor: firstPage ? params.fromCursor : undefined,
1090
- 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
1091
1487
  });
1092
1488
  const items = itemsOf(envelope);
1093
1489
  for (const item of items) {
1094
- if (params.signal?.aborted)
1095
- return;
1490
+ throwIfAborted();
1096
1491
  yield item;
1097
1492
  }
1098
1493
  const nextCursor = envelope.next_cursor;
1099
- if (!nextCursor || nextCursor === cursor || items.length < batchSize) {
1494
+ if (nextCursor === null || nextCursor === undefined)
1495
+ return;
1496
+ if (nextCursor === cursor)
1100
1497
  return;
1101
- }
1102
1498
  cursor = nextCursor;
1103
1499
  firstPage = false;
1104
1500
  }
@@ -1119,7 +1515,7 @@ class Index extends BaseClient {
1119
1515
  recipient: params.recipient,
1120
1516
  trait: params.trait,
1121
1517
  tx_context: params.txContext ? "true" : undefined
1122
- })}`);
1518
+ })}`, undefined, { signal: params.signal });
1123
1519
  }
1124
1520
  walkEvents(params) {
1125
1521
  return this.keysetWalk(params, (page) => this.listEvents({ ...params, ...page }), (e) => e.events);
@@ -1131,19 +1527,19 @@ class Index extends BaseClient {
1131
1527
  function_name: params.functionName,
1132
1528
  sender: params.sender,
1133
1529
  trait: params.trait
1134
- })}`);
1530
+ })}`, undefined, { signal: params.signal });
1135
1531
  }
1136
1532
  walkContractCalls(params = {}) {
1137
1533
  return this.keysetWalk(params, (page) => this.listContractCalls({ ...params, ...page }), (e) => e.contract_calls);
1138
1534
  }
1139
1535
  async listCanonical(params = {}) {
1140
- 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 });
1141
1537
  }
1142
1538
  walkCanonical(params = {}) {
1143
1539
  return this.keysetWalk(params, (page) => this.listCanonical({ ...params, ...page }), (e) => e.canonical);
1144
1540
  }
1145
1541
  async listBlocks(params = {}) {
1146
- 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 });
1147
1543
  }
1148
1544
  async getBlock(ref) {
1149
1545
  return this.requestOrNull("GET", `/v1/index/blocks/${encodeURIComponent(String(ref))}`);
@@ -1157,7 +1553,7 @@ class Index extends BaseClient {
1157
1553
  type: params.type,
1158
1554
  sender: params.sender,
1159
1555
  contract_id: params.contractId
1160
- })}`);
1556
+ })}`, undefined, { signal: params.signal });
1161
1557
  }
1162
1558
  async getTransaction(txId) {
1163
1559
  return this.requestOrNull("GET", `/v1/index/transactions/${encodeURIComponent(txId)}`);
@@ -1174,7 +1570,7 @@ class Index extends BaseClient {
1174
1570
  function_name: params.functionName,
1175
1571
  stacker: params.stacker,
1176
1572
  caller: params.caller
1177
- })}`);
1573
+ })}`, undefined, { signal: params.signal });
1178
1574
  }
1179
1575
  walkStacking(params = {}) {
1180
1576
  return this.keysetWalk(params, (page) => this.listStacking({ ...params, ...page }), (e) => e.stacking);
@@ -1186,7 +1582,7 @@ class Index extends BaseClient {
1186
1582
  type: params.type,
1187
1583
  contract_id: params.contractId,
1188
1584
  function_name: params.functionName
1189
- })}`);
1585
+ })}`, undefined, { signal: params.signal });
1190
1586
  }
1191
1587
  async getMempoolTx(txId) {
1192
1588
  return this.requestOrNull("GET", `/v1/index/mempool/${encodeURIComponent(txId)}`);
@@ -1200,7 +1596,7 @@ class Index extends BaseClient {
1200
1596
  confirmed: params.confirmed,
1201
1597
  sender: params.sender,
1202
1598
  bitcoin_txid: params.bitcoinTxid
1203
- })}`);
1599
+ })}`, undefined, { signal: params.signal });
1204
1600
  }
1205
1601
  walkSbtcDeposits(params = {}) {
1206
1602
  return this.keysetWalk(params, (page) => this.listSbtcDeposits({ ...params, ...page }), (e) => e.deposits);
@@ -1216,7 +1612,7 @@ class Index extends BaseClient {
1216
1612
  sender: params.sender,
1217
1613
  request_id: params.requestId,
1218
1614
  settlement_confirmed: params.settlementConfirmed
1219
- })}`);
1615
+ })}`, undefined, { signal: params.signal });
1220
1616
  }
1221
1617
  walkSbtcWithdrawals(params = {}) {
1222
1618
  return this.keysetWalk(params, (page) => this.listSbtcWithdrawals({ ...params, ...page }), (e) => e.withdrawals);
@@ -1232,7 +1628,7 @@ class Index extends BaseClient {
1232
1628
  sender: params.sender,
1233
1629
  request_id: params.requestId,
1234
1630
  bitcoin_txid: params.bitcoinTxid
1235
- })}`);
1631
+ })}`, undefined, { signal: params.signal });
1236
1632
  }
1237
1633
  walkSbtcEvents(params = {}) {
1238
1634
  return this.keysetWalk(params, (page) => this.listSbtcEvents({ ...params, ...page }), (e) => e.events);
@@ -1250,7 +1646,7 @@ class Index extends BaseClient {
1250
1646
  signer_manager: params.signerManager,
1251
1647
  bond_index: params.bondIndex,
1252
1648
  reward_cycle: params.rewardCycle
1253
- })}`);
1649
+ })}`, undefined, { signal: params.signal });
1254
1650
  }
1255
1651
  walkPox5Events(params = {}) {
1256
1652
  return this.keysetWalk(params, (page) => this.listPox5Events({ ...params, ...page }), (e) => e.events);
@@ -1259,24 +1655,14 @@ class Index extends BaseClient {
1259
1655
  return this.request("GET", `/v1/index/pox/cycles${buildQuery({
1260
1656
  cursor: params.cursor,
1261
1657
  limit: params.limit
1262
- })}`);
1658
+ })}`, undefined, { signal: params.signal });
1263
1659
  }
1264
- async* walkPoxCycles(params = {}) {
1265
- const batchSize = params.batchSize ?? 100;
1266
- let cursor = params.cursor ?? null;
1267
- while (!params.signal?.aborted) {
1268
- const envelope = await this.listPoxCycles({ cursor, limit: batchSize });
1269
- for (const cycle of envelope.cycles) {
1270
- if (params.signal?.aborted)
1271
- return;
1272
- yield cycle;
1273
- }
1274
- const nextCursor = envelope.next_cursor;
1275
- if (nextCursor === null || nextCursor === cursor || envelope.cycles.length < batchSize) {
1276
- return;
1277
- }
1278
- cursor = nextCursor;
1279
- }
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);
1280
1666
  }
1281
1667
  async getPoxCycle(rewardCycle) {
1282
1668
  return this.requestOrNull("GET", `/v1/index/pox/cycles/${rewardCycle}`);
@@ -1440,154 +1826,49 @@ function createStreamsDumps(opts) {
1440
1826
  return manifest;
1441
1827
  }
1442
1828
  async function download(file) {
1829
+ return fetchPageWithRetry(() => downloadOnce(file), {
1830
+ sleep: defaultSleep
1831
+ });
1832
+ }
1833
+ async function downloadOnce(file) {
1443
1834
  const res = await opts.fetchImpl(fileUrl(file));
1444
1835
  if (!res.ok) {
1445
1836
  throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
1446
1837
  }
1447
- const bytes = new Uint8Array(await res.arrayBuffer());
1448
- const digest = createHash("sha256").update(bytes).digest("hex");
1449
- if (digest !== file.sha256) {
1450
- 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);
1451
1840
  }
1452
- return bytes;
1453
- }
1454
- return { list, fileUrl, download };
1455
- }
1456
-
1457
- // src/streams/subscribe.ts
1458
- import { ed25519 } from "@secondlayer/shared";
1459
- function subscribeStreamsEvents(opts) {
1460
- const { params } = opts;
1461
- const controller = new AbortController;
1462
- const external = params.signal;
1463
- if (external) {
1464
- if (external.aborted)
1465
- controller.abort();
1466
- else
1467
- external.addEventListener("abort", () => controller.abort(), {
1468
- once: true
1469
- });
1470
- }
1471
- let cursor = params.fromCursor ?? null;
1472
- const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
1473
- const run = async () => {
1474
- while (!controller.signal.aborted) {
1475
- try {
1476
- const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
1477
- from_cursor: cursor ?? undefined,
1478
- types: params.types,
1479
- not_types: params.notTypes,
1480
- contract_id: params.contractId,
1481
- sender: params.sender,
1482
- recipient: params.recipient,
1483
- asset_identifier: params.assetIdentifier,
1484
- filters: params.filters ? JSON.stringify(params.filters) : undefined
1485
- })}`;
1486
- const res = await opts.fetchImpl(url, {
1487
- headers: {
1488
- ...opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {},
1489
- Accept: "text/event-stream"
1490
- },
1491
- signal: controller.signal
1492
- });
1493
- if (!res.ok) {
1494
- throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
1495
- }
1496
- if (!res.body) {
1497
- throw new StreamsServerError("Streams SSE response has no body.", 0);
1498
- }
1499
- for await (const frame of parseSseFrames(res.body, controller.signal)) {
1500
- if (frame.event === "ping" || !frame.data)
1501
- continue;
1502
- let parsed;
1503
- try {
1504
- parsed = JSON.parse(frame.data);
1505
- } catch {
1506
- continue;
1507
- }
1508
- if (!parsed.event)
1509
- continue;
1510
- if (opts.verify !== "off") {
1511
- if (!parsed.sig) {
1512
- if (opts.verify === "strict") {
1513
- throw new StreamsSignatureError("Streams SSE frame signature is missing.");
1514
- }
1515
- } else {
1516
- const key = await opts.loadKey();
1517
- if (!ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
1518
- throw new StreamsSignatureError("Streams SSE frame signature is invalid.");
1519
- }
1520
- }
1521
- }
1522
- cursor = parsed.event.cursor ?? cursor;
1523
- await params.onEvent(parsed.event);
1524
- }
1525
- } catch (err) {
1526
- if (controller.signal.aborted)
1527
- return;
1528
- params.onError?.(err);
1529
- 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;
1530
1853
  }
1854
+ } catch {
1855
+ throw new StreamsServerError(`Download of dump ${file.path} was interrupted after ${total} bytes.`, 0);
1531
1856
  }
1532
- };
1533
- run();
1534
- return () => controller.abort();
1535
- }
1536
- function sleep(ms, signal) {
1537
- return new Promise((resolve) => {
1538
- if (signal.aborted)
1539
- return resolve();
1540
- const onAbort = () => {
1541
- clearTimeout(timer);
1542
- resolve();
1543
- };
1544
- const timer = setTimeout(() => {
1545
- signal.removeEventListener("abort", onAbort);
1546
- resolve();
1547
- }, ms);
1548
- signal.addEventListener("abort", onAbort, { once: true });
1549
- });
1550
- }
1551
- async function* parseSseFrames(body, signal) {
1552
- const reader = body.getReader();
1553
- const decoder = new TextDecoder;
1554
- let buffer = "";
1555
- try {
1556
- while (!signal.aborted) {
1557
- const { value, done } = await reader.read();
1558
- if (done)
1559
- break;
1560
- buffer += decoder.decode(value, { stream: true });
1561
- let sep = buffer.indexOf(`
1562
-
1563
- `);
1564
- while (sep !== -1) {
1565
- yield parseFrame(buffer.slice(0, sep));
1566
- buffer = buffer.slice(sep + 2);
1567
- sep = buffer.indexOf(`
1568
-
1569
- `);
1570
- }
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}).`);
1571
1860
  }
1572
- } finally {
1573
- try {
1574
- await reader.cancel();
1575
- } catch {}
1576
- }
1577
- }
1578
- function parseFrame(raw) {
1579
- let event;
1580
- const data = [];
1581
- for (const line of raw.split(`
1582
- `)) {
1583
- if (line.startsWith("data:")) {
1584
- data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
1585
- } else if (line.startsWith("event:")) {
1586
- 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;
1587
1868
  }
1869
+ return bytes;
1588
1870
  }
1589
- return { event, data: data.length > 0 ? data.join(`
1590
- `) : undefined };
1871
+ return { list, fileUrl, download };
1591
1872
  }
1592
1873
 
1593
1874
  // src/streams/client.ts
@@ -1616,51 +1897,33 @@ function streamsFilters(params) {
1616
1897
  function normalizeBaseUrl(baseUrl) {
1617
1898
  return baseUrl.replace(/\/+$/, "");
1618
1899
  }
1619
- async function responseBody(response) {
1620
- const text = await response.text();
1621
- if (text.length === 0)
1622
- return;
1900
+ function isLoopbackUrl(baseUrl) {
1623
1901
  try {
1624
- 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.");
1625
1904
  } catch {
1626
- return text;
1627
- }
1628
- }
1629
- function errorMessage(body, fallback) {
1630
- if (body && typeof body === "object") {
1631
- const record = body;
1632
- const message = record.error ?? record.message;
1633
- if (typeof message === "string" && message.length > 0)
1634
- return message;
1635
- }
1636
- if (typeof body === "string" && body.length > 0)
1637
- return body;
1638
- return fallback;
1639
- }
1640
- async function mapStreamsError(response) {
1641
- const body = await responseBody(response);
1642
- if (response.status === 401) {
1643
- throw new AuthError(errorMessage(body, "API key invalid or expired."));
1644
- }
1645
- if (response.status === 429) {
1646
- const retryAfter = response.headers.get("Retry-After") ?? undefined;
1647
- throw new RateLimitError(errorMessage(body, "Rate limited. Try again later."), retryAfter);
1648
- }
1649
- if (response.status >= 500) {
1650
- throw new StreamsServerError(errorMessage(body, `Streams server returned ${response.status}.`), response.status, body);
1905
+ return false;
1651
1906
  }
1652
- throw new ValidationError(errorMessage(body, `Streams request returned ${response.status}.`), response.status, body);
1653
1907
  }
1654
1908
  function createStreamsClient(options) {
1655
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
+ });
1656
1916
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
1657
1917
  const verify = options.verify;
1658
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
+ }
1659
1922
  let keyPromise = null;
1660
1923
  function loadKey() {
1661
1924
  if (keyPromise)
1662
1925
  return keyPromise;
1663
- keyPromise = (async () => {
1926
+ const pending = (async () => {
1664
1927
  if (typeof verify === "object") {
1665
1928
  return {
1666
1929
  keyId: ed255192.ed25519KeyId(verify.publicKey),
@@ -1668,8 +1931,16 @@ function createStreamsClient(options) {
1668
1931
  publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
1669
1932
  };
1670
1933
  }
1671
- 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
+ }
1672
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
+ }
1673
1944
  throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
1674
1945
  }
1675
1946
  const body = await res.json();
@@ -1682,7 +1953,26 @@ function createStreamsClient(options) {
1682
1953
  publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
1683
1954
  };
1684
1955
  })();
1685
- 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;
1686
1976
  }
1687
1977
  const dumps = createStreamsDumps({
1688
1978
  baseUrl: options.dumpsBaseUrl,
@@ -1692,7 +1982,7 @@ function createStreamsClient(options) {
1692
1982
  });
1693
1983
  async function request(path) {
1694
1984
  const response = await fetchImpl(`${baseUrl}${path}`, {
1695
- headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}
1985
+ headers: authHeaders()
1696
1986
  });
1697
1987
  if (!response.ok)
1698
1988
  await mapStreamsError(response);
@@ -1704,18 +1994,7 @@ function createStreamsClient(options) {
1704
1994
  throw new StreamsSignatureError("Response is missing X-Signature.");
1705
1995
  }
1706
1996
  } else {
1707
- const responseKeyId = response.headers.get("X-Signature-KeyId");
1708
- let key = await loadKey();
1709
- if (responseKeyId && responseKeyId !== key.keyId) {
1710
- if (typeof verify === "object") {
1711
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
1712
- }
1713
- keyPromise = null;
1714
- key = await loadKey();
1715
- if (responseKeyId !== key.keyId) {
1716
- throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
1717
- }
1718
- }
1997
+ const key = await loadKeyFor(response.headers.get("X-Signature-KeyId"));
1719
1998
  if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
1720
1999
  throw new StreamsSignatureError;
1721
2000
  }
@@ -1724,6 +2003,13 @@ function createStreamsClient(options) {
1724
2003
  return JSON.parse(text);
1725
2004
  }
1726
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);
1727
2013
  function encodeFilters(filters) {
1728
2014
  return filters ? JSON.stringify(filters) : undefined;
1729
2015
  }
@@ -1771,8 +2057,10 @@ function createStreamsClient(options) {
1771
2057
  ...streamsFilters(params),
1772
2058
  batchSize: params.batchSize ?? 100,
1773
2059
  fetchEvents,
2060
+ fetchReorgs,
1774
2061
  onBatch,
1775
2062
  onReorg: params.onReorg,
2063
+ maxRollbackDepth: params.maxRollbackDepth,
1776
2064
  emptyBackoffMs: params.emptyBackoffMs,
1777
2065
  maxPages: params.maxPages,
1778
2066
  maxEmptyPolls: params.maxEmptyPolls,
@@ -1814,10 +2102,12 @@ function createStreamsClient(options) {
1814
2102
  subscribe(params) {
1815
2103
  return subscribeStreamsEvents({
1816
2104
  baseUrl,
1817
- apiKey: options.apiKey,
2105
+ headers: authHeaders(),
1818
2106
  fetchImpl,
1819
2107
  verify: verifyMode,
1820
- loadKey,
2108
+ loadKey: loadKeyFor,
2109
+ reconnectDelayMs: params.reconnectDelayMs,
2110
+ staleAfterMs: params.staleAfterMs,
1821
2111
  params
1822
2112
  });
1823
2113
  },
@@ -1825,11 +2115,11 @@ function createStreamsClient(options) {
1825
2115
  const fromCursor = params.from === "genesis" ? null : params.from ?? null;
1826
2116
  const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
1827
2117
  const manifest = await dumps.list();
1828
- 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);
1829
2119
  for (const file of files) {
1830
2120
  if (params.signal?.aborted)
1831
2121
  break;
1832
- await params.onDumpFile(file);
2122
+ await params.onDumpFile(file, { from: fromCursor });
1833
2123
  }
1834
2124
  const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
1835
2125
  return consumeStreamsEvents({
@@ -1854,14 +2144,7 @@ function createStreamsClient(options) {
1854
2144
  return request(`/v1/streams/blocks/${encodeURIComponent(String(heightOrHash))}/events`);
1855
2145
  }
1856
2146
  },
1857
- reorgs: {
1858
- list(params) {
1859
- return request(`/v1/streams/reorgs${buildQuery({
1860
- since: params.since,
1861
- limit: params.limit
1862
- })}`);
1863
- }
1864
- },
2147
+ reorgs: { list: listReorgs },
1865
2148
  dumps,
1866
2149
  canonical(height) {
1867
2150
  return request(`/v1/streams/canonical/${height}`);
@@ -1880,44 +2163,69 @@ class Subscriptions extends BaseClient {
1880
2163
  return this.request("GET", "/api/subscriptions");
1881
2164
  }
1882
2165
  async get(id) {
1883
- return this.request("GET", `/api/subscriptions/${id}`);
2166
+ return this.request("GET", `/api/subscriptions/${seg(id)}`);
1884
2167
  }
1885
2168
  async create(input) {
1886
2169
  return this.request("POST", "/api/subscriptions", input);
1887
2170
  }
1888
2171
  async update(id, patch) {
1889
- return this.request("PATCH", `/api/subscriptions/${id}`, patch);
2172
+ return this.request("PATCH", `/api/subscriptions/${seg(id)}`, patch);
1890
2173
  }
1891
2174
  async pause(id) {
1892
- return this.request("POST", `/api/subscriptions/${id}/pause`);
2175
+ return this.request("POST", `/api/subscriptions/${seg(id)}/pause`);
1893
2176
  }
1894
2177
  async resume(id) {
1895
- return this.request("POST", `/api/subscriptions/${id}/resume`);
2178
+ return this.request("POST", `/api/subscriptions/${seg(id)}/resume`);
1896
2179
  }
1897
2180
  async delete(id) {
1898
- return this.request("DELETE", `/api/subscriptions/${id}`);
2181
+ return this.request("DELETE", `/api/subscriptions/${seg(id)}`);
1899
2182
  }
1900
2183
  async rotateSecret(id) {
1901
- return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
2184
+ return this.request("POST", `/api/subscriptions/${seg(id)}/rotate-secret`);
1902
2185
  }
1903
2186
  async test(id) {
1904
- return this.request("POST", `/api/subscriptions/${id}/test`);
2187
+ return this.request("POST", `/api/subscriptions/${seg(id)}/test`);
1905
2188
  }
1906
2189
  async deliveries(id) {
1907
- return this.request("GET", `/api/subscriptions/${id}/deliveries`);
2190
+ return this.request("GET", `/api/subscriptions/${seg(id)}/deliveries`);
1908
2191
  }
1909
2192
  async replay(id, range) {
1910
- return this.request("POST", `/api/subscriptions/${id}/replay`, range);
2193
+ return this.request("POST", `/api/subscriptions/${seg(id)}/replay`, range);
1911
2194
  }
1912
2195
  async dead(id) {
1913
- return this.request("GET", `/api/subscriptions/${id}/dead`);
2196
+ return this.request("GET", `/api/subscriptions/${seg(id)}/dead`);
1914
2197
  }
1915
2198
  async requeue(id, outboxId) {
1916
- return this.request("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
2199
+ return this.request("POST", `/api/subscriptions/${seg(id)}/dead/${seg(outboxId)}/requeue`);
1917
2200
  }
1918
2201
  }
1919
2202
 
1920
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
+
1921
2229
  class SecondLayer extends BaseClient {
1922
2230
  streams;
1923
2231
  index;
@@ -1927,10 +2235,13 @@ class SecondLayer extends BaseClient {
1927
2235
  constructor(options = {}) {
1928
2236
  super(options);
1929
2237
  this.streams = createStreamsClient({
1930
- apiKey: resolveApiKey(options.apiKey),
2238
+ apiKey: this.apiKey,
2239
+ origin: this.origin,
1931
2240
  baseUrl: options.baseUrl,
1932
2241
  fetchImpl: options.fetchImpl,
1933
- dumpsBaseUrl: options.dumpsBaseUrl
2242
+ dumpsBaseUrl: options.dumpsBaseUrl,
2243
+ verify: options.verify,
2244
+ verifyDumpsManifest: options.verifyDumpsManifest
1934
2245
  });
1935
2246
  this.index = new Index(options);
1936
2247
  this.contracts = new Contracts(options);
@@ -1941,28 +2252,42 @@ class SecondLayer extends BaseClient {
1941
2252
  return this.request("POST", "/v1/batch", { requests });
1942
2253
  }
1943
2254
  async context() {
1944
- const safe = (p) => p.then((v) => v).catch(() => null);
1945
2255
  const [account, streamsTip, indexEnv, subgraphsRes, subscriptionsRes] = await Promise.all([
1946
- safe(this.request("GET", "/api/accounts/me")),
1947
- safe(this.streams.tip()),
1948
- safe(this.index.canonical.list({ limit: 1 })),
1949
- safe(this.subgraphs.list()),
1950
- 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())
1951
2261
  ]);
1952
- const subgraphs = subgraphsRes?.data ?? null;
1953
- let subscriptions = null;
1954
- 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) {
1955
2271
  const byStatus = {};
1956
- for (const s of subscriptionsRes.data) {
2272
+ for (const s of subscriptionsRes.value.data) {
1957
2273
  byStatus[s.status] = (byStatus[s.status] ?? 0) + 1;
1958
2274
  }
1959
- subscriptions = { count: subscriptionsRes.data.length, byStatus };
2275
+ subscriptions.value = {
2276
+ count: subscriptionsRes.value.data.length,
2277
+ byStatus
2278
+ };
1960
2279
  }
1961
- let activeOperations = null;
1962
- if (subgraphs) {
1963
- const probed = await Promise.all(subgraphs.filter((s) => s.status === "reindexing").map(async (s) => {
1964
- const res = await safe(this.subgraphs.operations(s.name));
1965
- 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");
1966
2291
  return op ? {
1967
2292
  subgraph: s.name,
1968
2293
  operationId: op.id,
@@ -1971,12 +2296,17 @@ class SecondLayer extends BaseClient {
1971
2296
  progress: op.progress
1972
2297
  } : null;
1973
2298
  }));
1974
- activeOperations = probed.filter((o) => o !== null);
2299
+ activeOperations = {
2300
+ value: probed.filter((o) => o !== null)
2301
+ };
1975
2302
  }
1976
2303
  return {
1977
2304
  account,
1978
2305
  streamsTip,
1979
- indexTip: indexEnv?.tip ?? null,
2306
+ indexTip: {
2307
+ value: indexEnv.value?.tip ?? null,
2308
+ ...indexEnv.error ? { error: indexEnv.error } : {}
2309
+ },
1980
2310
  subgraphs,
1981
2311
  subscriptions,
1982
2312
  activeOperations
@@ -2261,9 +2591,9 @@ export {
2261
2591
  decodeChainWebhook,
2262
2592
  decode,
2263
2593
  createStreamsClient,
2594
+ contextField,
2264
2595
  consumerHealth,
2265
2596
  consumeIndexFeed,
2266
- VersionConflictError,
2267
2597
  ValidationError,
2268
2598
  Subscriptions,
2269
2599
  Subgraphs,
@@ -2274,6 +2604,8 @@ export {
2274
2604
  RateLimitError,
2275
2605
  LOCAL_API_URL,
2276
2606
  Index,
2607
+ INDEX_MAX_PAGE_SIZE,
2608
+ DEFAULT_REQUEST_TIMEOUT_MS,
2277
2609
  Cursor,
2278
2610
  Contracts,
2279
2611
  CREDENTIAL_ENV_VARS,
@@ -2281,5 +2613,5 @@ export {
2281
2613
  ApiError
2282
2614
  };
2283
2615
 
2284
- //# debugId=9A05B56B79C1360464756E2164756E21
2616
+ //# debugId=CDF488058C75012864756E2164756E21
2285
2617
  //# sourceMappingURL=index.js.map