@ensnode/ensrainbow-sdk 1.10.1 → 1.11.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/client.d.ts CHANGED
@@ -1,6 +1,32 @@
1
1
  import { LabelHash, EncodedLabelHash, Label } from 'enssdk';
2
2
  import { EnsRainbowClientLabelSet, EnsRainbowPublicConfig } from '@ensnode/ensnode-sdk';
3
3
 
4
+ /**
5
+ * Error thrown by {@link EnsRainbowApiClient} methods when the ENSRainbow service responds
6
+ * with a non-2xx HTTP status code.
7
+ *
8
+ * Carries the HTTP status code as a structured property (rather than only embedding it in the
9
+ * error message) so callers can branch their retry/abort logic on the status — e.g. retry on
10
+ * `503 Service Unavailable` while ENSRainbow bootstraps, but abort immediately on `404`/`500`,
11
+ * which usually indicate a misconfigured base URL or a hard server failure.
12
+ *
13
+ * Network-level failures (DNS, ECONNREFUSED, fetch parse errors) are *not* wrapped in this
14
+ * class — they propagate as their original `Error` (typically a `TypeError` from `fetch`),
15
+ * because such failures are commonly transient during cold start and should remain retryable
16
+ * by callers.
17
+ */
18
+ declare class EnsRainbowHttpError extends Error {
19
+ readonly name = "EnsRainbowHttpError";
20
+ /**
21
+ * The HTTP status code returned by the ENSRainbow service.
22
+ */
23
+ readonly status: number;
24
+ /**
25
+ * The HTTP status text returned by the ENSRainbow service, if any.
26
+ */
27
+ readonly statusText: string;
28
+ constructor(message: string, status: number, statusText?: string);
29
+ }
4
30
  declare namespace EnsRainbow {
5
31
  export type ApiClientOptions = EnsRainbowApiClientOptions;
6
32
  export interface ApiClient {
@@ -17,6 +43,13 @@ declare namespace EnsRainbow {
17
43
  */
18
44
  heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<HealResponse>;
19
45
  health(): Promise<HealthResponse>;
46
+ /**
47
+ * Check whether the ENSRainbow service has finished bootstrapping and is ready to serve requests.
48
+ *
49
+ * Throws when the service is not ready (e.g. 503 while the database is still being downloaded
50
+ * or validated) so callers can retry.
51
+ */
52
+ ready(): Promise<ReadyResponse>;
20
53
  getOptions(): Readonly<EnsRainbowApiClientOptions>;
21
54
  }
22
55
  type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];
@@ -24,6 +57,21 @@ declare namespace EnsRainbow {
24
57
  export interface HealthResponse {
25
58
  status: "ok";
26
59
  }
60
+ /**
61
+ * Response returned by `GET /ready` when the ENSRainbow service is ready to serve requests.
62
+ */
63
+ export interface ReadyResponse {
64
+ status: "ok";
65
+ }
66
+ /**
67
+ * Generic error shape used by endpoints that return 503 Service Unavailable while the
68
+ * database is still bootstrapping (downloading, extracting, or validating).
69
+ */
70
+ export interface ServiceUnavailableError {
71
+ status: typeof StatusCode.Error;
72
+ error: string;
73
+ errorCode: typeof ErrorCode.ServiceUnavailable;
74
+ }
27
75
  export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {
28
76
  status: Status;
29
77
  label?: Label | never;
@@ -54,12 +102,18 @@ declare namespace EnsRainbow {
54
102
  error: string;
55
103
  errorCode: typeof ErrorCode.BadRequest;
56
104
  }
57
- export type HealResponse = HealSuccess | HealNotFoundError | HealServerError | HealBadRequestError;
105
+ export interface HealServiceUnavailableError extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {
106
+ status: typeof StatusCode.Error;
107
+ label?: never;
108
+ error: string;
109
+ errorCode: typeof ErrorCode.ServiceUnavailable;
110
+ }
111
+ export type HealResponse = HealSuccess | HealNotFoundError | HealServerError | HealBadRequestError | HealServiceUnavailableError;
58
112
  export type HealError = Exclude<HealResponse, HealSuccess>;
59
113
  /**
60
- * Server errors should not be cached.
114
+ * Server errors and transient bootstrap errors should not be cached.
61
115
  */
62
- export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;
116
+ export type CacheableHealResponse = Exclude<HealResponse, HealServerError | HealServiceUnavailableError>;
63
117
  export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {
64
118
  status: Status;
65
119
  count?: number | never;
@@ -83,7 +137,14 @@ declare namespace EnsRainbow {
83
137
  error: string;
84
138
  errorCode: typeof ErrorCode.ServerError;
85
139
  }
86
- export type CountResponse = CountSuccess | CountServerError;
140
+ export interface CountServiceUnavailableError extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {
141
+ status: typeof StatusCode.Error;
142
+ count?: never;
143
+ timestamp?: never;
144
+ error: string;
145
+ errorCode: typeof ErrorCode.ServiceUnavailable;
146
+ }
147
+ export type CountResponse = CountSuccess | CountServerError | CountServiceUnavailableError;
87
148
  /**
88
149
  * Complete public configuration object for ENSRainbow.
89
150
  *
@@ -105,7 +166,7 @@ interface EnsRainbowApiClientOptions {
105
166
  */
106
167
  endpointUrl: URL;
107
168
  /**
108
- * Optional label set preferences that the ENSRainbow server at endpointUrl is expected to
169
+ * Optional client label set preferences that the ENSRainbow server at endpointUrl is expected to
109
170
  * support. If provided, enables deterministic heal results across time, such that only
110
171
  * labels from label sets with versions less than or equal to this value will be returned.
111
172
  * Therefore, even if the ENSRainbow server later ingests label sets with greater versions
@@ -119,7 +180,7 @@ interface EnsRainbowApiClientOptions {
119
180
  * will be returned.
120
181
  * When `labelSetVersion` is defined, `labelSetId` must also be defined.
121
182
  */
122
- labelSet?: EnsRainbowClientLabelSet;
183
+ clientLabelSet?: EnsRainbowClientLabelSet;
123
184
  }
124
185
  /**
125
186
  * ENSRainbow API client
@@ -137,7 +198,7 @@ interface EnsRainbowApiClientOptions {
137
198
  declare class EnsRainbowApiClient implements EnsRainbow.ApiClient {
138
199
  private readonly options;
139
200
  private readonly cache;
140
- private readonly labelSetSearchParams;
201
+ private readonly clientLabelSetSearchParams;
141
202
  static readonly DEFAULT_CACHE_CAPACITY = 1000;
142
203
  /**
143
204
  * Create default client options.
@@ -229,8 +290,27 @@ declare class EnsRainbowApiClient implements EnsRainbow.ApiClient {
229
290
  * // }
230
291
  */
231
292
  health(): Promise<EnsRainbow.HealthResponse>;
293
+ /**
294
+ * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open).
295
+ *
296
+ * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon
297
+ * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished
298
+ * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should
299
+ * poll this method instead of `health()` during startup.
300
+ *
301
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown
302
+ * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case
303
+ * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures
304
+ * (e.g. `404`, `500`) and abort retries early in the latter cases.
305
+ * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error
306
+ * type and should generally remain retryable, since they are common during cold start before
307
+ * the ENSRainbow HTTP server has bound its port.
308
+ */
309
+ ready(): Promise<EnsRainbow.ReadyResponse>;
232
310
  /**
233
311
  * Get the public configuration of the ENSRainbow service.
312
+ *
313
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status.
234
314
  */
235
315
  config(): Promise<EnsRainbow.ENSRainbowPublicConfig>;
236
316
  /**
@@ -257,4 +337,4 @@ declare const isHealError: (response: EnsRainbow.HealResponse) => response is En
257
337
  */
258
338
  declare const isCacheableHealResponse: (response: EnsRainbow.HealResponse) => response is EnsRainbow.CacheableHealResponse;
259
339
 
260
- export { EnsRainbow, EnsRainbowApiClient, type EnsRainbowApiClientOptions, isCacheableHealResponse, isHealError };
340
+ export { EnsRainbow, EnsRainbowApiClient, type EnsRainbowApiClientOptions, EnsRainbowHttpError, isCacheableHealResponse, isHealError };
package/dist/client.js CHANGED
@@ -14,14 +14,31 @@ var StatusCode = {
14
14
  var ErrorCode = {
15
15
  BadRequest: 400,
16
16
  NotFound: 404,
17
- ServerError: 500
17
+ ServerError: 500,
18
+ ServiceUnavailable: 503
18
19
  };
19
20
 
20
21
  // src/client.ts
22
+ var EnsRainbowHttpError = class extends Error {
23
+ name = "EnsRainbowHttpError";
24
+ /**
25
+ * The HTTP status code returned by the ENSRainbow service.
26
+ */
27
+ status;
28
+ /**
29
+ * The HTTP status text returned by the ENSRainbow service, if any.
30
+ */
31
+ statusText;
32
+ constructor(message, status, statusText = "") {
33
+ super(message);
34
+ this.status = status;
35
+ this.statusText = statusText;
36
+ }
37
+ };
21
38
  var EnsRainbowApiClient = class _EnsRainbowApiClient {
22
39
  options;
23
40
  cache;
24
- labelSetSearchParams;
41
+ clientLabelSetSearchParams;
25
42
  static DEFAULT_CACHE_CAPACITY = 1e3;
26
43
  /**
27
44
  * Create default client options.
@@ -32,32 +49,35 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
32
49
  return {
33
50
  endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),
34
51
  cacheCapacity: _EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,
35
- labelSet: buildEnsRainbowClientLabelSet()
52
+ clientLabelSet: buildEnsRainbowClientLabelSet()
36
53
  };
37
54
  }
38
55
  constructor(options = {}) {
39
- const { labelSet: optionsLabelSet, ...rest } = options;
56
+ const { clientLabelSet: optionsClientLabelSet, ...rest } = options;
40
57
  const defaultOptions = _EnsRainbowApiClient.defaultOptions();
41
58
  const copiedLabelSet = buildEnsRainbowClientLabelSet(
42
- optionsLabelSet?.labelSetId,
43
- optionsLabelSet?.labelSetVersion
59
+ optionsClientLabelSet?.labelSetId,
60
+ optionsClientLabelSet?.labelSetVersion
44
61
  );
45
62
  this.options = {
46
63
  ...defaultOptions,
47
64
  ...rest,
48
- labelSet: copiedLabelSet
65
+ clientLabelSet: copiedLabelSet
49
66
  };
50
67
  this.cache = new LruCache(
51
68
  this.options.cacheCapacity
52
69
  );
53
- this.labelSetSearchParams = new URLSearchParams();
54
- if (this.options.labelSet?.labelSetId !== void 0) {
55
- this.labelSetSearchParams.append("label_set_id", this.options.labelSet.labelSetId);
70
+ this.clientLabelSetSearchParams = new URLSearchParams();
71
+ if (this.options.clientLabelSet?.labelSetId !== void 0) {
72
+ this.clientLabelSetSearchParams.append(
73
+ "label_set_id",
74
+ this.options.clientLabelSet.labelSetId
75
+ );
56
76
  }
57
- if (this.options.labelSet?.labelSetVersion !== void 0) {
58
- this.labelSetSearchParams.append(
77
+ if (this.options.clientLabelSet?.labelSetVersion !== void 0) {
78
+ this.clientLabelSetSearchParams.append(
59
79
  "label_set_version",
60
- this.options.labelSet.labelSetVersion.toString()
80
+ this.options.clientLabelSet.labelSetVersion.toString()
61
81
  );
62
82
  }
63
83
  }
@@ -120,7 +140,7 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
120
140
  const cachedResult = this.cache.get(normalizedLabelHash);
121
141
  if (cachedResult) return cachedResult;
122
142
  const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);
123
- this.labelSetSearchParams.forEach((value, key) => {
143
+ this.clientLabelSetSearchParams.forEach((value, key) => {
124
144
  url.searchParams.append(key, value);
125
145
  });
126
146
  const response = await fetch(url);
@@ -171,15 +191,63 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
171
191
  */
172
192
  async health() {
173
193
  const response = await fetch(new URL("/health", this.options.endpointUrl));
194
+ if (!response.ok) {
195
+ throw new EnsRainbowHttpError(
196
+ `ENSRainbow health check failed (HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""})`,
197
+ response.status,
198
+ response.statusText
199
+ );
200
+ }
201
+ return response.json();
202
+ }
203
+ /**
204
+ * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open).
205
+ *
206
+ * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon
207
+ * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished
208
+ * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should
209
+ * poll this method instead of `health()` during startup.
210
+ *
211
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown
212
+ * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case
213
+ * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures
214
+ * (e.g. `404`, `500`) and abort retries early in the latter cases.
215
+ * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error
216
+ * type and should generally remain retryable, since they are common during cold start before
217
+ * the ENSRainbow HTTP server has bound its port.
218
+ */
219
+ async ready() {
220
+ const response = await fetch(new URL("/ready", this.options.endpointUrl));
221
+ if (!response.ok) {
222
+ const statusSuffix = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
223
+ if (response.status === 503) {
224
+ throw new EnsRainbowHttpError(
225
+ `ENSRainbow readiness check: service not ready yet (${statusSuffix})`,
226
+ response.status,
227
+ response.statusText
228
+ );
229
+ }
230
+ throw new EnsRainbowHttpError(
231
+ `ENSRainbow readiness check failed (${statusSuffix}). This usually indicates a non-readiness issue (e.g. wrong base URL, misrouting, or a server error).`,
232
+ response.status,
233
+ response.statusText
234
+ );
235
+ }
174
236
  return response.json();
175
237
  }
176
238
  /**
177
239
  * Get the public configuration of the ENSRainbow service.
240
+ *
241
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status.
178
242
  */
179
243
  async config() {
180
244
  const response = await fetch(new URL("/v1/config", this.options.endpointUrl));
181
245
  if (!response.ok) {
182
- throw new Error(`Failed to fetch ENSRainbow config: ${response.statusText}`);
246
+ throw new EnsRainbowHttpError(
247
+ `Failed to fetch ENSRainbow config: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`,
248
+ response.status,
249
+ response.statusText
250
+ );
183
251
  }
184
252
  return response.json();
185
253
  }
@@ -192,7 +260,7 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
192
260
  const deepCopy = {
193
261
  cacheCapacity: this.options.cacheCapacity,
194
262
  endpointUrl: new URL(this.options.endpointUrl.href),
195
- labelSet: this.options.labelSet ? { ...this.options.labelSet } : void 0
263
+ clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : void 0
196
264
  };
197
265
  return Object.freeze(deepCopy);
198
266
  }
@@ -201,10 +269,12 @@ var isHealError = (response) => {
201
269
  return response.status === StatusCode.Error;
202
270
  };
203
271
  var isCacheableHealResponse = (response) => {
204
- return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;
272
+ if (response.status === StatusCode.Success) return true;
273
+ return response.errorCode !== ErrorCode.ServerError && response.errorCode !== ErrorCode.ServiceUnavailable;
205
274
  };
206
275
  export {
207
276
  EnsRainbowApiClient,
277
+ EnsRainbowHttpError,
208
278
  isCacheableHealResponse,
209
279
  isHealError
210
280
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/consts.ts"],"sourcesContent":["import type { EncodedLabelHash, Label, LabelHash } from \"enssdk\";\nimport { parseLabelHashOrEncodedLabelHash } from \"enssdk\";\n\nimport {\n buildEnsRainbowClientLabelSet,\n type Cache,\n type EnsRainbowClientLabelSet,\n type EnsRainbowPublicConfig,\n LruCache,\n} from \"@ensnode/ensnode-sdk\";\n\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n /**\n * Get the public configuration of the ENSRainbow service\n */\n config(): Promise<ENSRainbowPublicConfig>;\n\n /**\n * Heal a labelHash to its original label.\n * Accepts a strict `LabelHash`, an `EncodedLabelHash` (bracket-enclosed), or any string\n * that can be normalized (missing `0x` prefix, uppercase hex chars, or 63-char hex).\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n */\n heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: Label | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: Label;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a\n * non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export type CountResponse = CountSuccess | CountServerError;\n\n /**\n * Complete public configuration object for ENSRainbow.\n *\n * Contains all public configuration information about the ENSRainbow service instance,\n * including version, label set information, and record counts.\n */\n export type ENSRainbowPublicConfig = EnsRainbowPublicConfig;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n\n /**\n * Optional label set preferences that the ENSRainbow server at endpointUrl is expected to\n * support. If provided, enables deterministic heal results across time, such that only\n * labels from label sets with versions less than or equal to this value will be returned.\n * Therefore, even if the ENSRainbow server later ingests label sets with greater versions\n * than this value, the results returned across time can be deterministic. If\n * provided, heal operations with this EnsRainbowApiClient will validate the ENSRainbow\n * server manages a compatible label set. If not provided no specific labelSetId validation\n * will be performed during heal operations.\n * If `labelSetId` is provided without `labelSetVersion`, the server will use the latest\n * available version.\n * If `labelSetVersion` is defined, only labels from sets less than or equal to this value\n * will be returned.\n * When `labelSetVersion` is defined, `labelSetId` must also be defined.\n */\n labelSet?: EnsRainbowClientLabelSet;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<LabelHash, EnsRainbow.CacheableHealResponse>;\n private readonly labelSetSearchParams: URLSearchParams;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n labelSet: buildEnsRainbowClientLabelSet(),\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n const { labelSet: optionsLabelSet, ...rest } = options;\n const defaultOptions = EnsRainbowApiClient.defaultOptions();\n\n const copiedLabelSet = buildEnsRainbowClientLabelSet(\n optionsLabelSet?.labelSetId,\n optionsLabelSet?.labelSetVersion,\n );\n\n this.options = {\n ...defaultOptions,\n ...rest,\n labelSet: copiedLabelSet,\n };\n\n this.cache = new LruCache<LabelHash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n\n // Pre-compute query parameters for label set options\n this.labelSetSearchParams = new URLSearchParams();\n if (this.options.labelSet?.labelSetId !== undefined) {\n this.labelSetSearchParams.append(\"label_set_id\", this.options.labelSet.labelSetId);\n }\n if (this.options.labelSet?.labelSetVersion !== undefined) {\n this.labelSetSearchParams.append(\n \"label_set_version\",\n this.options.labelSet.labelSetVersion.toString(),\n );\n }\n }\n\n /**\n * Attempt to [heal](https://ensnode.io/ensrainbow/concepts/glossary#heal) a labelHash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelHash - A labelHash to heal, either as a strict `LabelHash`, an `EncodedLabelHash`\n * (bracket-enclosed), or any string that can be normalized (missing `0x` prefix, uppercase hex\n * chars, or 63-char hex are all accepted and normalized automatically).\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful.\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<EnsRainbow.HealResponse> {\n let normalizedLabelHash: LabelHash;\n\n try {\n normalizedLabelHash = parseLabelHashOrEncodedLabelHash(labelHash);\n } catch (error) {\n return {\n status: StatusCode.Error,\n error: error instanceof Error ? error.message : String(error),\n errorCode: ErrorCode.BadRequest,\n } as EnsRainbow.HealBadRequestError;\n }\n\n const cachedResult = this.cache.get(normalizedLabelHash);\n if (cachedResult) return cachedResult;\n\n const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);\n\n // Apply pre-computed label set query parameters\n this.labelSetSearchParams.forEach((value, key) => {\n url.searchParams.append(key, value);\n });\n\n const response = await fetch(url);\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(normalizedLabelHash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the\n * number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the\n * provided hosted instance.\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Get the public configuration of the ENSRainbow service.\n */\n async config(): Promise<EnsRainbow.ENSRainbowPublicConfig> {\n const response = await fetch(new URL(\"/v1/config\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ENSRainbow config: ${response.statusText}`);\n }\n\n return response.json() as Promise<EnsRainbow.ENSRainbowPublicConfig>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n labelSet: this.options.labelSet ? { ...this.options.labelSet } : undefined,\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n"],"mappings":";AACA,SAAS,wCAAwC;AAEjD;AAAA,EACE;AAAA,EAIA;AAAA,OACK;;;ACTA,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AACf;;;ADsKO,IAAM,sBAAN,MAAM,qBAAoD;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,OAAuB,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhD,OAAO,iBAA8C;AACnD,WAAO;AAAA,MACL,aAAa,IAAI,IAAI,sBAAsB;AAAA,MAC3C,eAAe,qBAAoB;AAAA,MACnC,UAAU,8BAA8B;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,YAAY,UAAgD,CAAC,GAAG;AAC9D,UAAM,EAAE,UAAU,iBAAiB,GAAG,KAAK,IAAI;AAC/C,UAAM,iBAAiB,qBAAoB,eAAe;AAE1D,UAAM,iBAAiB;AAAA,MACrB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,IACnB;AAEA,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAU;AAAA,IACZ;AAEA,SAAK,QAAQ,IAAI;AAAA,MACf,KAAK,QAAQ;AAAA,IACf;AAGA,SAAK,uBAAuB,IAAI,gBAAgB;AAChD,QAAI,KAAK,QAAQ,UAAU,eAAe,QAAW;AACnD,WAAK,qBAAqB,OAAO,gBAAgB,KAAK,QAAQ,SAAS,UAAU;AAAA,IACnF;AACA,QAAI,KAAK,QAAQ,UAAU,oBAAoB,QAAW;AACxD,WAAK,qBAAqB;AAAA,QACxB;AAAA,QACA,KAAK,QAAQ,SAAS,gBAAgB,SAAS;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+CA,MAAM,KAAK,WAAoF;AAC7F,QAAI;AAEJ,QAAI;AACF,4BAAsB,iCAAiC,SAAS;AAAA,IAClE,SAAS,OAAO;AACd,aAAO;AAAA,QACL,QAAQ,WAAW;AAAA,QACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC5D,WAAW,UAAU;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,eAAe,KAAK,MAAM,IAAI,mBAAmB;AACvD,QAAI,aAAc,QAAO;AAEzB,UAAM,MAAM,IAAI,IAAI,YAAY,mBAAmB,IAAI,KAAK,QAAQ,WAAW;AAG/E,SAAK,qBAAqB,QAAQ,CAAC,OAAO,QAAQ;AAChD,UAAI,aAAa,OAAO,KAAK,KAAK;AAAA,IACpC,CAAC;AAED,UAAM,WAAW,MAAM,MAAM,GAAG;AAChC,UAAM,eAAgB,MAAM,SAAS,KAAK;AAE1C,QAAI,wBAAwB,YAAY,GAAG;AACzC,WAAK,MAAM,IAAI,qBAAqB,YAAY;AAAA,IAClD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,QAA2C;AAC/C,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,oBAAoB,KAAK,QAAQ,WAAW,CAAC;AAElF,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SAA6C;AACjD,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,WAAW,KAAK,QAAQ,WAAW,CAAC;AAEzE,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAqD;AACzD,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,cAAc,KAAK,QAAQ,WAAW,CAAC;AAE5E,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,sCAAsC,SAAS,UAAU,EAAE;AAAA,IAC7E;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAmD;AAEjD,UAAM,WAAW;AAAA,MACf,eAAe,KAAK,QAAQ;AAAA,MAC5B,aAAa,IAAI,IAAI,KAAK,QAAQ,YAAY,IAAI;AAAA,MAClD,UAAU,KAAK,QAAQ,WAAW,EAAE,GAAG,KAAK,QAAQ,SAAS,IAAI;AAAA,IACnE;AAEA,WAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B;AACF;AAQO,IAAM,cAAc,CACzB,aACqC;AACrC,SAAO,SAAS,WAAW,WAAW;AACxC;AAUO,IAAM,0BAA0B,CACrC,aACiD;AACjD,SAAO,SAAS,WAAW,WAAW,WAAW,SAAS,cAAc,UAAU;AACpF;","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/consts.ts"],"sourcesContent":["import type { EncodedLabelHash, Label, LabelHash } from \"enssdk\";\nimport { parseLabelHashOrEncodedLabelHash } from \"enssdk\";\n\nimport {\n buildEnsRainbowClientLabelSet,\n type Cache,\n type EnsRainbowClientLabelSet,\n type EnsRainbowPublicConfig,\n LruCache,\n} from \"@ensnode/ensnode-sdk\";\n\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\n/**\n * Error thrown by {@link EnsRainbowApiClient} methods when the ENSRainbow service responds\n * with a non-2xx HTTP status code.\n *\n * Carries the HTTP status code as a structured property (rather than only embedding it in the\n * error message) so callers can branch their retry/abort logic on the status — e.g. retry on\n * `503 Service Unavailable` while ENSRainbow bootstraps, but abort immediately on `404`/`500`,\n * which usually indicate a misconfigured base URL or a hard server failure.\n *\n * Network-level failures (DNS, ECONNREFUSED, fetch parse errors) are *not* wrapped in this\n * class — they propagate as their original `Error` (typically a `TypeError` from `fetch`),\n * because such failures are commonly transient during cold start and should remain retryable\n * by callers.\n */\nexport class EnsRainbowHttpError extends Error {\n readonly name = \"EnsRainbowHttpError\";\n\n /**\n * The HTTP status code returned by the ENSRainbow service.\n */\n readonly status: number;\n\n /**\n * The HTTP status text returned by the ENSRainbow service, if any.\n */\n readonly statusText: string;\n\n constructor(message: string, status: number, statusText = \"\") {\n super(message);\n this.status = status;\n this.statusText = statusText;\n }\n}\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n /**\n * Get the public configuration of the ENSRainbow service\n */\n config(): Promise<ENSRainbowPublicConfig>;\n\n /**\n * Heal a labelHash to its original label.\n * Accepts a strict `LabelHash`, an `EncodedLabelHash` (bracket-enclosed), or any string\n * that can be normalized (missing `0x` prefix, uppercase hex chars, or 63-char hex).\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n */\n heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n /**\n * Check whether the ENSRainbow service has finished bootstrapping and is ready to serve requests.\n *\n * Throws when the service is not ready (e.g. 503 while the database is still being downloaded\n * or validated) so callers can retry.\n */\n ready(): Promise<ReadyResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n /**\n * Response returned by `GET /ready` when the ENSRainbow service is ready to serve requests.\n */\n export interface ReadyResponse {\n status: \"ok\";\n }\n\n /**\n * Generic error shape used by endpoints that return 503 Service Unavailable while the\n * database is still bootstrapping (downloading, extracting, or validating).\n */\n export interface ServiceUnavailableError {\n status: typeof StatusCode.Error;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: Label | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: Label;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export interface HealServiceUnavailableError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError\n | HealServiceUnavailableError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors and transient bootstrap errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<\n HealResponse,\n HealServerError | HealServiceUnavailableError\n >;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a\n * non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface CountServiceUnavailableError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export type CountResponse = CountSuccess | CountServerError | CountServiceUnavailableError;\n\n /**\n * Complete public configuration object for ENSRainbow.\n *\n * Contains all public configuration information about the ENSRainbow service instance,\n * including version, label set information, and record counts.\n */\n export type ENSRainbowPublicConfig = EnsRainbowPublicConfig;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n\n /**\n * Optional client label set preferences that the ENSRainbow server at endpointUrl is expected to\n * support. If provided, enables deterministic heal results across time, such that only\n * labels from label sets with versions less than or equal to this value will be returned.\n * Therefore, even if the ENSRainbow server later ingests label sets with greater versions\n * than this value, the results returned across time can be deterministic. If\n * provided, heal operations with this EnsRainbowApiClient will validate the ENSRainbow\n * server manages a compatible label set. If not provided no specific labelSetId validation\n * will be performed during heal operations.\n * If `labelSetId` is provided without `labelSetVersion`, the server will use the latest\n * available version.\n * If `labelSetVersion` is defined, only labels from sets less than or equal to this value\n * will be returned.\n * When `labelSetVersion` is defined, `labelSetId` must also be defined.\n */\n clientLabelSet?: EnsRainbowClientLabelSet;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<LabelHash, EnsRainbow.CacheableHealResponse>;\n private readonly clientLabelSetSearchParams: URLSearchParams;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n clientLabelSet: buildEnsRainbowClientLabelSet(),\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n const { clientLabelSet: optionsClientLabelSet, ...rest } = options;\n const defaultOptions = EnsRainbowApiClient.defaultOptions();\n\n const copiedLabelSet = buildEnsRainbowClientLabelSet(\n optionsClientLabelSet?.labelSetId,\n optionsClientLabelSet?.labelSetVersion,\n );\n\n this.options = {\n ...defaultOptions,\n ...rest,\n clientLabelSet: copiedLabelSet,\n };\n\n this.cache = new LruCache<LabelHash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n\n // Pre-compute query parameters for label set options\n this.clientLabelSetSearchParams = new URLSearchParams();\n if (this.options.clientLabelSet?.labelSetId !== undefined) {\n this.clientLabelSetSearchParams.append(\n \"label_set_id\",\n this.options.clientLabelSet.labelSetId,\n );\n }\n if (this.options.clientLabelSet?.labelSetVersion !== undefined) {\n this.clientLabelSetSearchParams.append(\n \"label_set_version\",\n this.options.clientLabelSet.labelSetVersion.toString(),\n );\n }\n }\n\n /**\n * Attempt to [heal](https://ensnode.io/ensrainbow/concepts/glossary#heal) a labelHash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelHash - A labelHash to heal, either as a strict `LabelHash`, an `EncodedLabelHash`\n * (bracket-enclosed), or any string that can be normalized (missing `0x` prefix, uppercase hex\n * chars, or 63-char hex are all accepted and normalized automatically).\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful.\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<EnsRainbow.HealResponse> {\n let normalizedLabelHash: LabelHash;\n\n try {\n normalizedLabelHash = parseLabelHashOrEncodedLabelHash(labelHash);\n } catch (error) {\n return {\n status: StatusCode.Error,\n error: error instanceof Error ? error.message : String(error),\n errorCode: ErrorCode.BadRequest,\n } as EnsRainbow.HealBadRequestError;\n }\n\n const cachedResult = this.cache.get(normalizedLabelHash);\n if (cachedResult) return cachedResult;\n\n const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);\n\n // Apply pre-computed label set query parameters\n this.clientLabelSetSearchParams.forEach((value, key) => {\n url.searchParams.append(key, value);\n });\n\n const response = await fetch(url);\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(normalizedLabelHash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the\n * number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the\n * provided hosted instance.\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new EnsRainbowHttpError(\n `ENSRainbow health check failed (HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n })`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open).\n *\n * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon\n * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished\n * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should\n * poll this method instead of `health()` during startup.\n *\n * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown\n * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case\n * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures\n * (e.g. `404`, `500`) and abort retries early in the latter cases.\n * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error\n * type and should generally remain retryable, since they are common during cold start before\n * the ENSRainbow HTTP server has bound its port.\n */\n async ready(): Promise<EnsRainbow.ReadyResponse> {\n const response = await fetch(new URL(\"/ready\", this.options.endpointUrl));\n\n if (!response.ok) {\n const statusSuffix = `HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n }`;\n\n if (response.status === 503) {\n throw new EnsRainbowHttpError(\n `ENSRainbow readiness check: service not ready yet (${statusSuffix})`,\n response.status,\n response.statusText,\n );\n }\n\n throw new EnsRainbowHttpError(\n `ENSRainbow readiness check failed (${statusSuffix}). This usually indicates a non-readiness issue (e.g. wrong base URL, misrouting, or a server error).`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.ReadyResponse>;\n }\n\n /**\n * Get the public configuration of the ENSRainbow service.\n *\n * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status.\n */\n async config(): Promise<EnsRainbow.ENSRainbowPublicConfig> {\n const response = await fetch(new URL(\"/v1/config\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new EnsRainbowHttpError(\n `Failed to fetch ENSRainbow config: HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n }`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.ENSRainbowPublicConfig>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : undefined,\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n if (response.status === StatusCode.Success) return true;\n return (\n response.errorCode !== ErrorCode.ServerError &&\n response.errorCode !== ErrorCode.ServiceUnavailable\n );\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n ServiceUnavailable: 503,\n} as const;\n"],"mappings":"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","names":[]}
package/dist/consts.d.ts CHANGED
@@ -7,6 +7,7 @@ declare const ErrorCode: {
7
7
  readonly BadRequest: 400;
8
8
  readonly NotFound: 404;
9
9
  readonly ServerError: 500;
10
+ readonly ServiceUnavailable: 503;
10
11
  };
11
12
 
12
13
  export { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode };
package/dist/consts.js CHANGED
@@ -7,7 +7,8 @@ var StatusCode = {
7
7
  var ErrorCode = {
8
8
  BadRequest: 400,
9
9
  NotFound: 404,
10
- ServerError: 500
10
+ ServerError: 500,
11
+ ServiceUnavailable: 503
11
12
  };
12
13
  export {
13
14
  DEFAULT_ENSRAINBOW_URL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/consts.ts"],"sourcesContent":["export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n"],"mappings":";AAAO,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AACf;","names":[]}
1
+ {"version":3,"sources":["../src/consts.ts"],"sourcesContent":["export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n ServiceUnavailable: 503,\n} as const;\n"],"mappings":";AAAO,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AAAA,EACb,oBAAoB;AACtB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { EnsRainbowClientLabelSet, EnsRainbowServerLabelSet, LabelSetId, LabelSetVersion, buildEnsRainbowClientLabelSet } from '@ensnode/ensnode-sdk';
2
- export { EnsRainbow, EnsRainbowApiClient, EnsRainbowApiClientOptions, isCacheableHealResponse, isHealError } from './client.js';
2
+ export { EnsRainbow, EnsRainbowApiClient, EnsRainbowApiClientOptions, EnsRainbowHttpError, isCacheableHealResponse, isHealError } from './client.js';
3
3
  export { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from './consts.js';
4
4
  import 'enssdk';
package/dist/index.js CHANGED
@@ -17,14 +17,31 @@ var StatusCode = {
17
17
  var ErrorCode = {
18
18
  BadRequest: 400,
19
19
  NotFound: 404,
20
- ServerError: 500
20
+ ServerError: 500,
21
+ ServiceUnavailable: 503
21
22
  };
22
23
 
23
24
  // src/client.ts
25
+ var EnsRainbowHttpError = class extends Error {
26
+ name = "EnsRainbowHttpError";
27
+ /**
28
+ * The HTTP status code returned by the ENSRainbow service.
29
+ */
30
+ status;
31
+ /**
32
+ * The HTTP status text returned by the ENSRainbow service, if any.
33
+ */
34
+ statusText;
35
+ constructor(message, status, statusText = "") {
36
+ super(message);
37
+ this.status = status;
38
+ this.statusText = statusText;
39
+ }
40
+ };
24
41
  var EnsRainbowApiClient = class _EnsRainbowApiClient {
25
42
  options;
26
43
  cache;
27
- labelSetSearchParams;
44
+ clientLabelSetSearchParams;
28
45
  static DEFAULT_CACHE_CAPACITY = 1e3;
29
46
  /**
30
47
  * Create default client options.
@@ -35,32 +52,35 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
35
52
  return {
36
53
  endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),
37
54
  cacheCapacity: _EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,
38
- labelSet: buildEnsRainbowClientLabelSet()
55
+ clientLabelSet: buildEnsRainbowClientLabelSet()
39
56
  };
40
57
  }
41
58
  constructor(options = {}) {
42
- const { labelSet: optionsLabelSet, ...rest } = options;
59
+ const { clientLabelSet: optionsClientLabelSet, ...rest } = options;
43
60
  const defaultOptions = _EnsRainbowApiClient.defaultOptions();
44
61
  const copiedLabelSet = buildEnsRainbowClientLabelSet(
45
- optionsLabelSet?.labelSetId,
46
- optionsLabelSet?.labelSetVersion
62
+ optionsClientLabelSet?.labelSetId,
63
+ optionsClientLabelSet?.labelSetVersion
47
64
  );
48
65
  this.options = {
49
66
  ...defaultOptions,
50
67
  ...rest,
51
- labelSet: copiedLabelSet
68
+ clientLabelSet: copiedLabelSet
52
69
  };
53
70
  this.cache = new LruCache(
54
71
  this.options.cacheCapacity
55
72
  );
56
- this.labelSetSearchParams = new URLSearchParams();
57
- if (this.options.labelSet?.labelSetId !== void 0) {
58
- this.labelSetSearchParams.append("label_set_id", this.options.labelSet.labelSetId);
73
+ this.clientLabelSetSearchParams = new URLSearchParams();
74
+ if (this.options.clientLabelSet?.labelSetId !== void 0) {
75
+ this.clientLabelSetSearchParams.append(
76
+ "label_set_id",
77
+ this.options.clientLabelSet.labelSetId
78
+ );
59
79
  }
60
- if (this.options.labelSet?.labelSetVersion !== void 0) {
61
- this.labelSetSearchParams.append(
80
+ if (this.options.clientLabelSet?.labelSetVersion !== void 0) {
81
+ this.clientLabelSetSearchParams.append(
62
82
  "label_set_version",
63
- this.options.labelSet.labelSetVersion.toString()
83
+ this.options.clientLabelSet.labelSetVersion.toString()
64
84
  );
65
85
  }
66
86
  }
@@ -123,7 +143,7 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
123
143
  const cachedResult = this.cache.get(normalizedLabelHash);
124
144
  if (cachedResult) return cachedResult;
125
145
  const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);
126
- this.labelSetSearchParams.forEach((value, key) => {
146
+ this.clientLabelSetSearchParams.forEach((value, key) => {
127
147
  url.searchParams.append(key, value);
128
148
  });
129
149
  const response = await fetch(url);
@@ -174,15 +194,63 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
174
194
  */
175
195
  async health() {
176
196
  const response = await fetch(new URL("/health", this.options.endpointUrl));
197
+ if (!response.ok) {
198
+ throw new EnsRainbowHttpError(
199
+ `ENSRainbow health check failed (HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""})`,
200
+ response.status,
201
+ response.statusText
202
+ );
203
+ }
204
+ return response.json();
205
+ }
206
+ /**
207
+ * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open).
208
+ *
209
+ * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon
210
+ * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished
211
+ * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should
212
+ * poll this method instead of `health()` during startup.
213
+ *
214
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown
215
+ * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case
216
+ * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures
217
+ * (e.g. `404`, `500`) and abort retries early in the latter cases.
218
+ * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error
219
+ * type and should generally remain retryable, since they are common during cold start before
220
+ * the ENSRainbow HTTP server has bound its port.
221
+ */
222
+ async ready() {
223
+ const response = await fetch(new URL("/ready", this.options.endpointUrl));
224
+ if (!response.ok) {
225
+ const statusSuffix = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
226
+ if (response.status === 503) {
227
+ throw new EnsRainbowHttpError(
228
+ `ENSRainbow readiness check: service not ready yet (${statusSuffix})`,
229
+ response.status,
230
+ response.statusText
231
+ );
232
+ }
233
+ throw new EnsRainbowHttpError(
234
+ `ENSRainbow readiness check failed (${statusSuffix}). This usually indicates a non-readiness issue (e.g. wrong base URL, misrouting, or a server error).`,
235
+ response.status,
236
+ response.statusText
237
+ );
238
+ }
177
239
  return response.json();
178
240
  }
179
241
  /**
180
242
  * Get the public configuration of the ENSRainbow service.
243
+ *
244
+ * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status.
181
245
  */
182
246
  async config() {
183
247
  const response = await fetch(new URL("/v1/config", this.options.endpointUrl));
184
248
  if (!response.ok) {
185
- throw new Error(`Failed to fetch ENSRainbow config: ${response.statusText}`);
249
+ throw new EnsRainbowHttpError(
250
+ `Failed to fetch ENSRainbow config: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`,
251
+ response.status,
252
+ response.statusText
253
+ );
186
254
  }
187
255
  return response.json();
188
256
  }
@@ -195,7 +263,7 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
195
263
  const deepCopy = {
196
264
  cacheCapacity: this.options.cacheCapacity,
197
265
  endpointUrl: new URL(this.options.endpointUrl.href),
198
- labelSet: this.options.labelSet ? { ...this.options.labelSet } : void 0
266
+ clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : void 0
199
267
  };
200
268
  return Object.freeze(deepCopy);
201
269
  }
@@ -204,11 +272,13 @@ var isHealError = (response) => {
204
272
  return response.status === StatusCode.Error;
205
273
  };
206
274
  var isCacheableHealResponse = (response) => {
207
- return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;
275
+ if (response.status === StatusCode.Success) return true;
276
+ return response.errorCode !== ErrorCode.ServerError && response.errorCode !== ErrorCode.ServiceUnavailable;
208
277
  };
209
278
  export {
210
279
  DEFAULT_ENSRAINBOW_URL,
211
280
  EnsRainbowApiClient,
281
+ EnsRainbowHttpError,
212
282
  ErrorCode,
213
283
  StatusCode,
214
284
  buildEnsRainbowClientLabelSet2 as buildEnsRainbowClientLabelSet,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/consts.ts"],"sourcesContent":["// Re-export types from ensnode-sdk that are needed by consumers\nexport type {\n EnsRainbowClientLabelSet,\n EnsRainbowServerLabelSet,\n LabelSetId,\n LabelSetVersion,\n} from \"@ensnode/ensnode-sdk\";\n// Re-export utility functions and classes from ensnode-sdk that are needed by consumers\nexport { buildEnsRainbowClientLabelSet } from \"@ensnode/ensnode-sdk\";\n\nexport * from \"./client\";\nexport * from \"./consts\";\n","import type { EncodedLabelHash, Label, LabelHash } from \"enssdk\";\nimport { parseLabelHashOrEncodedLabelHash } from \"enssdk\";\n\nimport {\n buildEnsRainbowClientLabelSet,\n type Cache,\n type EnsRainbowClientLabelSet,\n type EnsRainbowPublicConfig,\n LruCache,\n} from \"@ensnode/ensnode-sdk\";\n\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n /**\n * Get the public configuration of the ENSRainbow service\n */\n config(): Promise<ENSRainbowPublicConfig>;\n\n /**\n * Heal a labelHash to its original label.\n * Accepts a strict `LabelHash`, an `EncodedLabelHash` (bracket-enclosed), or any string\n * that can be normalized (missing `0x` prefix, uppercase hex chars, or 63-char hex).\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n */\n heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: Label | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: Label;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a\n * non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export type CountResponse = CountSuccess | CountServerError;\n\n /**\n * Complete public configuration object for ENSRainbow.\n *\n * Contains all public configuration information about the ENSRainbow service instance,\n * including version, label set information, and record counts.\n */\n export type ENSRainbowPublicConfig = EnsRainbowPublicConfig;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n\n /**\n * Optional label set preferences that the ENSRainbow server at endpointUrl is expected to\n * support. If provided, enables deterministic heal results across time, such that only\n * labels from label sets with versions less than or equal to this value will be returned.\n * Therefore, even if the ENSRainbow server later ingests label sets with greater versions\n * than this value, the results returned across time can be deterministic. If\n * provided, heal operations with this EnsRainbowApiClient will validate the ENSRainbow\n * server manages a compatible label set. If not provided no specific labelSetId validation\n * will be performed during heal operations.\n * If `labelSetId` is provided without `labelSetVersion`, the server will use the latest\n * available version.\n * If `labelSetVersion` is defined, only labels from sets less than or equal to this value\n * will be returned.\n * When `labelSetVersion` is defined, `labelSetId` must also be defined.\n */\n labelSet?: EnsRainbowClientLabelSet;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<LabelHash, EnsRainbow.CacheableHealResponse>;\n private readonly labelSetSearchParams: URLSearchParams;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n labelSet: buildEnsRainbowClientLabelSet(),\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n const { labelSet: optionsLabelSet, ...rest } = options;\n const defaultOptions = EnsRainbowApiClient.defaultOptions();\n\n const copiedLabelSet = buildEnsRainbowClientLabelSet(\n optionsLabelSet?.labelSetId,\n optionsLabelSet?.labelSetVersion,\n );\n\n this.options = {\n ...defaultOptions,\n ...rest,\n labelSet: copiedLabelSet,\n };\n\n this.cache = new LruCache<LabelHash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n\n // Pre-compute query parameters for label set options\n this.labelSetSearchParams = new URLSearchParams();\n if (this.options.labelSet?.labelSetId !== undefined) {\n this.labelSetSearchParams.append(\"label_set_id\", this.options.labelSet.labelSetId);\n }\n if (this.options.labelSet?.labelSetVersion !== undefined) {\n this.labelSetSearchParams.append(\n \"label_set_version\",\n this.options.labelSet.labelSetVersion.toString(),\n );\n }\n }\n\n /**\n * Attempt to [heal](https://ensnode.io/ensrainbow/concepts/glossary#heal) a labelHash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelHash - A labelHash to heal, either as a strict `LabelHash`, an `EncodedLabelHash`\n * (bracket-enclosed), or any string that can be normalized (missing `0x` prefix, uppercase hex\n * chars, or 63-char hex are all accepted and normalized automatically).\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful.\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<EnsRainbow.HealResponse> {\n let normalizedLabelHash: LabelHash;\n\n try {\n normalizedLabelHash = parseLabelHashOrEncodedLabelHash(labelHash);\n } catch (error) {\n return {\n status: StatusCode.Error,\n error: error instanceof Error ? error.message : String(error),\n errorCode: ErrorCode.BadRequest,\n } as EnsRainbow.HealBadRequestError;\n }\n\n const cachedResult = this.cache.get(normalizedLabelHash);\n if (cachedResult) return cachedResult;\n\n const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);\n\n // Apply pre-computed label set query parameters\n this.labelSetSearchParams.forEach((value, key) => {\n url.searchParams.append(key, value);\n });\n\n const response = await fetch(url);\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(normalizedLabelHash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the\n * number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the\n * provided hosted instance.\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Get the public configuration of the ENSRainbow service.\n */\n async config(): Promise<EnsRainbow.ENSRainbowPublicConfig> {\n const response = await fetch(new URL(\"/v1/config\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ENSRainbow config: ${response.statusText}`);\n }\n\n return response.json() as Promise<EnsRainbow.ENSRainbowPublicConfig>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n labelSet: this.options.labelSet ? { ...this.options.labelSet } : undefined,\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n"],"mappings":"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","names":["buildEnsRainbowClientLabelSet"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/consts.ts"],"sourcesContent":["// Re-export types from ensnode-sdk that are needed by consumers\nexport type {\n EnsRainbowClientLabelSet,\n EnsRainbowServerLabelSet,\n LabelSetId,\n LabelSetVersion,\n} from \"@ensnode/ensnode-sdk\";\n// Re-export utility functions and classes from ensnode-sdk that are needed by consumers\nexport { buildEnsRainbowClientLabelSet } from \"@ensnode/ensnode-sdk\";\n\nexport * from \"./client\";\nexport * from \"./consts\";\n","import type { EncodedLabelHash, Label, LabelHash } from \"enssdk\";\nimport { parseLabelHashOrEncodedLabelHash } from \"enssdk\";\n\nimport {\n buildEnsRainbowClientLabelSet,\n type Cache,\n type EnsRainbowClientLabelSet,\n type EnsRainbowPublicConfig,\n LruCache,\n} from \"@ensnode/ensnode-sdk\";\n\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\n/**\n * Error thrown by {@link EnsRainbowApiClient} methods when the ENSRainbow service responds\n * with a non-2xx HTTP status code.\n *\n * Carries the HTTP status code as a structured property (rather than only embedding it in the\n * error message) so callers can branch their retry/abort logic on the status — e.g. retry on\n * `503 Service Unavailable` while ENSRainbow bootstraps, but abort immediately on `404`/`500`,\n * which usually indicate a misconfigured base URL or a hard server failure.\n *\n * Network-level failures (DNS, ECONNREFUSED, fetch parse errors) are *not* wrapped in this\n * class — they propagate as their original `Error` (typically a `TypeError` from `fetch`),\n * because such failures are commonly transient during cold start and should remain retryable\n * by callers.\n */\nexport class EnsRainbowHttpError extends Error {\n readonly name = \"EnsRainbowHttpError\";\n\n /**\n * The HTTP status code returned by the ENSRainbow service.\n */\n readonly status: number;\n\n /**\n * The HTTP status text returned by the ENSRainbow service, if any.\n */\n readonly statusText: string;\n\n constructor(message: string, status: number, statusText = \"\") {\n super(message);\n this.status = status;\n this.statusText = statusText;\n }\n}\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n /**\n * Get the public configuration of the ENSRainbow service\n */\n config(): Promise<ENSRainbowPublicConfig>;\n\n /**\n * Heal a labelHash to its original label.\n * Accepts a strict `LabelHash`, an `EncodedLabelHash` (bracket-enclosed), or any string\n * that can be normalized (missing `0x` prefix, uppercase hex chars, or 63-char hex).\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n */\n heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n /**\n * Check whether the ENSRainbow service has finished bootstrapping and is ready to serve requests.\n *\n * Throws when the service is not ready (e.g. 503 while the database is still being downloaded\n * or validated) so callers can retry.\n */\n ready(): Promise<ReadyResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n /**\n * Response returned by `GET /ready` when the ENSRainbow service is ready to serve requests.\n */\n export interface ReadyResponse {\n status: \"ok\";\n }\n\n /**\n * Generic error shape used by endpoints that return 503 Service Unavailable while the\n * database is still bootstrapping (downloading, extracting, or validating).\n */\n export interface ServiceUnavailableError {\n status: typeof StatusCode.Error;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: Label | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: Label;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export interface HealServiceUnavailableError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError\n | HealServiceUnavailableError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors and transient bootstrap errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<\n HealResponse,\n HealServerError | HealServiceUnavailableError\n >;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a\n * non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface CountServiceUnavailableError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServiceUnavailable> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServiceUnavailable;\n }\n\n export type CountResponse = CountSuccess | CountServerError | CountServiceUnavailableError;\n\n /**\n * Complete public configuration object for ENSRainbow.\n *\n * Contains all public configuration information about the ENSRainbow service instance,\n * including version, label set information, and record counts.\n */\n export type ENSRainbowPublicConfig = EnsRainbowPublicConfig;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n\n /**\n * Optional client label set preferences that the ENSRainbow server at endpointUrl is expected to\n * support. If provided, enables deterministic heal results across time, such that only\n * labels from label sets with versions less than or equal to this value will be returned.\n * Therefore, even if the ENSRainbow server later ingests label sets with greater versions\n * than this value, the results returned across time can be deterministic. If\n * provided, heal operations with this EnsRainbowApiClient will validate the ENSRainbow\n * server manages a compatible label set. If not provided no specific labelSetId validation\n * will be performed during heal operations.\n * If `labelSetId` is provided without `labelSetVersion`, the server will use the latest\n * available version.\n * If `labelSetVersion` is defined, only labels from sets less than or equal to this value\n * will be returned.\n * When `labelSetVersion` is defined, `labelSetId` must also be defined.\n */\n clientLabelSet?: EnsRainbowClientLabelSet;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<LabelHash, EnsRainbow.CacheableHealResponse>;\n private readonly clientLabelSetSearchParams: URLSearchParams;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n clientLabelSet: buildEnsRainbowClientLabelSet(),\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n const { clientLabelSet: optionsClientLabelSet, ...rest } = options;\n const defaultOptions = EnsRainbowApiClient.defaultOptions();\n\n const copiedLabelSet = buildEnsRainbowClientLabelSet(\n optionsClientLabelSet?.labelSetId,\n optionsClientLabelSet?.labelSetVersion,\n );\n\n this.options = {\n ...defaultOptions,\n ...rest,\n clientLabelSet: copiedLabelSet,\n };\n\n this.cache = new LruCache<LabelHash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n\n // Pre-compute query parameters for label set options\n this.clientLabelSetSearchParams = new URLSearchParams();\n if (this.options.clientLabelSet?.labelSetId !== undefined) {\n this.clientLabelSetSearchParams.append(\n \"label_set_id\",\n this.options.clientLabelSet.labelSetId,\n );\n }\n if (this.options.clientLabelSet?.labelSetVersion !== undefined) {\n this.clientLabelSetSearchParams.append(\n \"label_set_version\",\n this.options.clientLabelSet.labelSetVersion.toString(),\n );\n }\n }\n\n /**\n * Attempt to [heal](https://ensnode.io/ensrainbow/concepts/glossary#heal) a labelHash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelHash - A labelHash to heal, either as a strict `LabelHash`, an `EncodedLabelHash`\n * (bracket-enclosed), or any string that can be normalized (missing `0x` prefix, uppercase hex\n * chars, or 63-char hex are all accepted and normalized automatically).\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful.\n * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash.\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelHash: LabelHash | EncodedLabelHash | string): Promise<EnsRainbow.HealResponse> {\n let normalizedLabelHash: LabelHash;\n\n try {\n normalizedLabelHash = parseLabelHashOrEncodedLabelHash(labelHash);\n } catch (error) {\n return {\n status: StatusCode.Error,\n error: error instanceof Error ? error.message : String(error),\n errorCode: ErrorCode.BadRequest,\n } as EnsRainbow.HealBadRequestError;\n }\n\n const cachedResult = this.cache.get(normalizedLabelHash);\n if (cachedResult) return cachedResult;\n\n const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl);\n\n // Apply pre-computed label set query parameters\n this.clientLabelSetSearchParams.forEach((value, key) => {\n url.searchParams.append(key, value);\n });\n\n const response = await fetch(url);\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(normalizedLabelHash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the\n * number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts,\n * CORS violations, or Invalid URLs\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the\n * provided hosted instance.\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new EnsRainbowHttpError(\n `ENSRainbow health check failed (HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n })`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open).\n *\n * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon\n * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished\n * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should\n * poll this method instead of `health()` during startup.\n *\n * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown\n * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case\n * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures\n * (e.g. `404`, `500`) and abort retries early in the latter cases.\n * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error\n * type and should generally remain retryable, since they are common during cold start before\n * the ENSRainbow HTTP server has bound its port.\n */\n async ready(): Promise<EnsRainbow.ReadyResponse> {\n const response = await fetch(new URL(\"/ready\", this.options.endpointUrl));\n\n if (!response.ok) {\n const statusSuffix = `HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n }`;\n\n if (response.status === 503) {\n throw new EnsRainbowHttpError(\n `ENSRainbow readiness check: service not ready yet (${statusSuffix})`,\n response.status,\n response.statusText,\n );\n }\n\n throw new EnsRainbowHttpError(\n `ENSRainbow readiness check failed (${statusSuffix}). This usually indicates a non-readiness issue (e.g. wrong base URL, misrouting, or a server error).`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.ReadyResponse>;\n }\n\n /**\n * Get the public configuration of the ENSRainbow service.\n *\n * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status.\n */\n async config(): Promise<EnsRainbow.ENSRainbowPublicConfig> {\n const response = await fetch(new URL(\"/v1/config\", this.options.endpointUrl));\n\n if (!response.ok) {\n throw new EnsRainbowHttpError(\n `Failed to fetch ENSRainbow config: HTTP ${response.status}${\n response.statusText ? ` ${response.statusText}` : \"\"\n }`,\n response.status,\n response.statusText,\n );\n }\n\n return response.json() as Promise<EnsRainbow.ENSRainbowPublicConfig>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : undefined,\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n if (response.status === StatusCode.Success) return true;\n return (\n response.errorCode !== ErrorCode.ServerError &&\n response.errorCode !== ErrorCode.ServiceUnavailable\n );\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n ServiceUnavailable: 503,\n} as const;\n"],"mappings":"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","names":["buildEnsRainbowClientLabelSet"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensnode/ensrainbow-sdk",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "description": "ENSRainbow SDK for interacting with the ENSRainbow API.",
6
6
  "license": "MIT",
@@ -39,14 +39,14 @@
39
39
  "viem": "^2.22.13"
40
40
  },
41
41
  "dependencies": {
42
- "enssdk": "1.10.1"
42
+ "enssdk": "1.11.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "tsup": "^8.3.6",
46
46
  "typescript": "^5.7.3",
47
47
  "vitest": "^4.0.2",
48
- "@ensnode/shared-configs": "1.10.1",
49
- "@ensnode/ensnode-sdk": "1.10.1"
48
+ "@ensnode/shared-configs": "1.11.0",
49
+ "@ensnode/ensnode-sdk": "1.11.0"
50
50
  },
51
51
  "scripts": {
52
52
  "prepublish": "tsup",