@secondlayer/sdk 6.36.0 → 6.37.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.
@@ -1,17 +1,127 @@
1
1
  // src/errors.ts
2
- class ApiError extends Error {
2
+ var DOCS_BASE = "https://www.secondlayer.tools";
3
+
4
+ class SecondLayerError extends Error {
5
+ name = "SecondLayerError";
6
+ shortMessage;
7
+ code;
8
+ docsUrl;
9
+ metaMessages;
10
+ retryable;
11
+ retryAfterSeconds;
12
+ constructor(shortMessage, options = {}) {
13
+ const detailBlocks = [
14
+ ...options.metaMessages ?? [],
15
+ ...options.details ? [options.details] : [],
16
+ ...options.docsUrl ? [`Docs: ${options.docsUrl}`] : []
17
+ ];
18
+ const message = [
19
+ shortMessage,
20
+ detailBlocks.length > 0 ? `
21
+ ${detailBlocks.join(`
22
+ `)}` : ""
23
+ ].join("");
24
+ super(message, options.cause ? { cause: options.cause } : undefined);
25
+ this.shortMessage = shortMessage;
26
+ this.code = options.code;
27
+ this.docsUrl = options.docsUrl;
28
+ this.metaMessages = options.metaMessages;
29
+ this.retryable = options.retryable ?? false;
30
+ this.retryAfterSeconds = options.retryAfterSeconds;
31
+ }
32
+ walk(fn) {
33
+ let err = this;
34
+ let last = this;
35
+ while (err != null) {
36
+ if (fn?.(err))
37
+ return err;
38
+ last = err;
39
+ err = err.cause;
40
+ }
41
+ return fn ? null : last;
42
+ }
43
+ toJSON() {
44
+ return {
45
+ name: this.name,
46
+ message: this.message,
47
+ shortMessage: this.shortMessage,
48
+ cause: this.cause instanceof Error ? this.cause.message : undefined,
49
+ code: this.code,
50
+ docsUrl: this.docsUrl,
51
+ retryable: this.retryable,
52
+ retryAfterSeconds: this.retryAfterSeconds
53
+ };
54
+ }
55
+ }
56
+ function statusRetryable(status) {
57
+ return status === 429 || status >= 500;
58
+ }
59
+ function docsFor(status, code) {
60
+ if (code === "UPGRADE_REQUIRED" || code === "PLAN_REQUIRED" || status === 402)
61
+ return `${DOCS_BASE}/docs/authentication#pay-as-you-go-credits`;
62
+ if (status === 429 || status === 401)
63
+ return `${DOCS_BASE}/docs/authentication`;
64
+ return;
65
+ }
66
+
67
+ class ApiError extends SecondLayerError {
3
68
  status;
4
69
  body;
5
- code;
6
- constructor(status, message, body, code) {
7
- super(message);
70
+ constructor(status, message, body, code, options = {}) {
71
+ super(message, {
72
+ code,
73
+ retryable: options.retryable ?? statusRetryable(status),
74
+ docsUrl: options.docsUrl ?? docsFor(status, code),
75
+ ...options.cause ? { cause: options.cause } : {},
76
+ ...options.details ? { details: options.details } : {},
77
+ ...options.metaMessages ? { metaMessages: options.metaMessages } : {},
78
+ ...options.retryAfterSeconds !== undefined ? { retryAfterSeconds: options.retryAfterSeconds } : {}
79
+ });
8
80
  this.status = status;
9
81
  this.body = body;
10
- this.code = code;
11
82
  this.name = "ApiError";
12
83
  }
13
84
  }
14
85
 
86
+ class AuthError extends ApiError {
87
+ status = 401;
88
+ constructor(message = "API key invalid or expired.") {
89
+ super(401, message);
90
+ this.name = "AuthError";
91
+ }
92
+ }
93
+
94
+ class RateLimitError extends ApiError {
95
+ retryAfter;
96
+ status = 429;
97
+ constructor(message = "Rate limited. Try again later.", retryAfter) {
98
+ super(429, message, undefined, undefined, {
99
+ retryable: true,
100
+ retryAfterSeconds: parseRetryAfter(retryAfter)
101
+ });
102
+ this.retryAfter = retryAfter;
103
+ this.name = "RateLimitError";
104
+ }
105
+ }
106
+
107
+ class ValidationError extends ApiError {
108
+ constructor(message, status, body) {
109
+ super(status, message, body, undefined, { retryable: false });
110
+ this.name = "ValidationError";
111
+ }
112
+ }
113
+ function parseRetryAfter(value) {
114
+ if (!value)
115
+ return;
116
+ const seconds = Number(value);
117
+ if (Number.isFinite(seconds) && seconds >= 0)
118
+ return seconds;
119
+ const date = Date.parse(value);
120
+ if (!Number.isNaN(date))
121
+ return Math.max(0, (date - Date.now()) / 1000);
122
+ return;
123
+ }
124
+
15
125
  class VersionConflictError extends ApiError {
16
126
  currentVersion;
17
127
  expectedVersion;
@@ -26,7 +136,13 @@ class VersionConflictError extends ApiError {
26
136
  class ByoBreakingChangeError extends ApiError {
27
137
  details;
28
138
  constructor(message, details) {
29
- super(422, message, details, "BYO_BREAKING_CHANGE");
139
+ super(422, message, details, "BYO_BREAKING_CHANGE", {
140
+ metaMessages: [
141
+ "Review and run manually on your database:",
142
+ details.plan.dropStatement,
143
+ ...details.plan.statements
144
+ ]
145
+ });
30
146
  this.name = "ByoBreakingChangeError";
31
147
  this.details = details;
32
148
  }
@@ -112,7 +228,7 @@ class BaseClient {
112
228
  serializedBody = JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value);
113
229
  } catch (err) {
114
230
  const detail = err instanceof Error ? err.message : String(err);
115
- throw new ApiError(0, `Failed to serialize request body: ${detail}`);
231
+ throw new ApiError(0, `Failed to serialize request body: ${detail}`, undefined, undefined, { retryable: false });
116
232
  }
117
233
  }
118
234
  let response;
@@ -122,17 +238,17 @@ class BaseClient {
122
238
  headers,
123
239
  body: serializedBody
124
240
  });
125
- } catch {
126
- throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`);
241
+ } catch (err) {
242
+ 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 });
127
243
  }
128
244
  if (!response.ok) {
129
245
  if (response.status === 401) {
130
- throw new ApiError(401, "API key invalid or expired.");
246
+ throw new AuthError;
131
247
  }
132
248
  if (response.status === 429) {
133
249
  const retryAfter = response.headers.get("Retry-After");
134
250
  const msg = retryAfter ? `Rate limited. Wait ${retryAfter} seconds.` : "Rate limited. Try again later.";
135
- throw new ApiError(429, msg);
251
+ throw new RateLimitError(msg, retryAfter ?? undefined);
136
252
  }
137
253
  if (response.status >= 500) {
138
254
  throw new ApiError(response.status, `Server error. Try again or check status at ${this.baseUrl}/health`);
@@ -426,47 +542,14 @@ class Contracts extends BaseClient {
426
542
  }
427
543
 
428
544
  // src/streams/errors.ts
429
- class AuthError extends Error {
430
- status = 401;
431
- constructor(message = "API key invalid or expired.") {
432
- super(message);
433
- this.name = "AuthError";
434
- }
435
- }
436
-
437
- class RateLimitError extends Error {
438
- retryAfter;
439
- status = 429;
440
- constructor(message = "Rate limited. Try again later.", retryAfter) {
441
- super(message);
442
- this.retryAfter = retryAfter;
443
- this.name = "RateLimitError";
444
- }
445
- }
446
-
447
- class ValidationError extends Error {
448
- status;
449
- body;
450
- constructor(message, status, body) {
451
- super(message);
452
- this.status = status;
453
- this.body = body;
454
- this.name = "ValidationError";
455
- }
456
- }
457
-
458
- class StreamsServerError extends Error {
459
- status;
460
- body;
545
+ class StreamsServerError extends ApiError {
461
546
  constructor(message, status, body) {
462
- super(message);
463
- this.status = status;
464
- this.body = body;
547
+ super(status, message, body, undefined, { retryable: true });
465
548
  this.name = "StreamsServerError";
466
549
  }
467
550
  }
468
551
 
469
- class StreamsSignatureError extends Error {
552
+ class StreamsSignatureError extends SecondLayerError {
470
553
  constructor(message = "Streams response signature verification failed.") {
471
554
  super(message);
472
555
  this.name = "StreamsSignatureError";
@@ -517,6 +600,41 @@ function batchContext(cursor, height, tipHeight) {
517
600
  blocksBehind: height === null ? null : Math.max(0, tipHeight - height)
518
601
  };
519
602
  }
603
+ var MAX_RETRY_AFTER_MS = 300000;
604
+ function errRetryable(err) {
605
+ if (err && typeof err === "object" && "retryable" in err) {
606
+ return err.retryable === true;
607
+ }
608
+ return err instanceof TypeError;
609
+ }
610
+ function errRetryAfterMs(err) {
611
+ if (err && typeof err === "object" && "retryAfterSeconds" in err) {
612
+ const seconds = err.retryAfterSeconds;
613
+ if (typeof seconds === "number" && Number.isFinite(seconds)) {
614
+ return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);
615
+ }
616
+ }
617
+ return;
618
+ }
619
+ async function fetchPageWithRetry(fetchPage, opts) {
620
+ const retryCount = opts.retryCount ?? 3;
621
+ const retryDelay = opts.retryDelay ?? 1000;
622
+ for (let attempt = 0;; attempt++) {
623
+ try {
624
+ return await fetchPage();
625
+ } catch (err) {
626
+ const retriesLeft = retryCount - attempt;
627
+ if (retriesLeft <= 0 || !errRetryable(err) || opts.signal?.aborted) {
628
+ throw err;
629
+ }
630
+ const delayMs = errRetryAfterMs(err) ?? retryDelay * (attempt + 1);
631
+ opts.onError?.(err, { attempt, retriesLeft, delayMs });
632
+ await opts.sleep(delayMs, opts.signal);
633
+ if (opts.signal?.aborted)
634
+ throw err;
635
+ }
636
+ }
637
+ }
520
638
  async function defaultSleep(ms, signal) {
521
639
  if (signal?.aborted)
522
640
  return;
@@ -543,7 +661,7 @@ async function consumeStreamsEvents(opts) {
543
661
  let emptyPolls = 0;
544
662
  let height = null;
545
663
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
546
- const envelope = await opts.fetchEvents({
664
+ const envelope = await fetchPageWithRetry(() => opts.fetchEvents({
547
665
  cursor,
548
666
  limit: opts.batchSize,
549
667
  types: opts.types,
@@ -552,6 +670,12 @@ async function consumeStreamsEvents(opts) {
552
670
  sender: opts.sender,
553
671
  recipient: opts.recipient,
554
672
  assetIdentifier: opts.assetIdentifier
673
+ }), {
674
+ retryCount: opts.retryCount,
675
+ retryDelay: opts.retryDelay,
676
+ onError: opts.onError,
677
+ sleep,
678
+ signal: opts.signal
555
679
  });
556
680
  pages++;
557
681
  if (!finalizedOnly && opts.onReorg) {
@@ -682,10 +806,16 @@ async function consumeIndexFeed(opts) {
682
806
  let emptyPolls = 0;
683
807
  let height = null;
684
808
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
685
- const envelope = await opts.fetchPage({
809
+ const envelope = await fetchPageWithRetry(() => opts.fetchPage({
686
810
  cursor,
687
811
  fromHeight: cursor === null ? opts.fromHeight : undefined,
688
812
  limit: batchSize
813
+ }), {
814
+ retryCount: opts.retryCount,
815
+ retryDelay: opts.retryDelay,
816
+ onError: opts.onError,
817
+ sleep,
818
+ signal: opts.signal
689
819
  });
690
820
  pages++;
691
821
  if (!finalizedOnly && opts.onReorg) {
@@ -1574,7 +1704,10 @@ function createStreamsClient(options) {
1574
1704
  emptyBackoffMs: params.emptyBackoffMs,
1575
1705
  maxPages: params.maxPages,
1576
1706
  maxEmptyPolls: params.maxEmptyPolls,
1577
- signal: params.signal
1707
+ signal: params.signal,
1708
+ retryCount: params.retryCount,
1709
+ retryDelay: params.retryDelay,
1710
+ onError: params.onError
1578
1711
  });
1579
1712
  },
1580
1713
  stream(params = {}) {
@@ -1809,5 +1942,5 @@ export {
1809
1942
  Subgraphs
1810
1943
  };
1811
1944
 
1812
- //# debugId=13B6D7F76CAEAFE964756E2164756E21
1945
+ //# debugId=A36A74FD0514129964756E2164756E21
1813
1946
  //# sourceMappingURL=index.js.map