@ensnode/ensrainbow-sdk 1.14.0 → 1.15.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.js +9 -2
- package/dist/client.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import { parseLabelHashOrEncodedLabelHash } from "enssdk";
|
|
2
|
+
import { asLiteralLabel, labelhashLiteralLabel, parseLabelHashOrEncodedLabelHash } from "enssdk";
|
|
3
3
|
import {
|
|
4
4
|
buildEnsRainbowClientLabelSet,
|
|
5
5
|
LruCache
|
|
@@ -144,7 +144,14 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
|
|
|
144
144
|
url.searchParams.append(key, value);
|
|
145
145
|
});
|
|
146
146
|
const response = await fetch(url);
|
|
147
|
-
|
|
147
|
+
let healResponse = await response.json();
|
|
148
|
+
if (healResponse.status === StatusCode.Success && labelhashLiteralLabel(asLiteralLabel(healResponse.label)) !== normalizedLabelHash) {
|
|
149
|
+
healResponse = {
|
|
150
|
+
status: StatusCode.Error,
|
|
151
|
+
error: "Label not found",
|
|
152
|
+
errorCode: ErrorCode.NotFound
|
|
153
|
+
};
|
|
154
|
+
}
|
|
148
155
|
if (isCacheableHealResponse(healResponse)) {
|
|
149
156
|
this.cache.set(normalizedLabelHash, healResponse);
|
|
150
157
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -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\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/consts.ts"],"sourcesContent":["import type { EncodedLabelHash, Label, LabelHash } from \"enssdk\";\nimport { asLiteralLabel, labelhashLiteralLabel, 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 let healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n // Sanity Check: avoid returning malformed heals to consumers, treating as not-found\n if (\n healResponse.status === StatusCode.Success &&\n labelhashLiteralLabel(asLiteralLabel(healResponse.label)) !== normalizedLabelHash\n ) {\n healResponse = {\n status: StatusCode.Error,\n error: \"Label not found\",\n errorCode: ErrorCode.NotFound,\n } satisfies EnsRainbow.HealNotFoundError;\n }\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/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { buildEnsRainbowClientLabelSet as buildEnsRainbowClientLabelSet2 } from "@ensnode/ensnode-sdk";
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
|
-
import { parseLabelHashOrEncodedLabelHash } from "enssdk";
|
|
5
|
+
import { asLiteralLabel, labelhashLiteralLabel, parseLabelHashOrEncodedLabelHash } from "enssdk";
|
|
6
6
|
import {
|
|
7
7
|
buildEnsRainbowClientLabelSet,
|
|
8
8
|
LruCache
|
|
@@ -147,7 +147,14 @@ var EnsRainbowApiClient = class _EnsRainbowApiClient {
|
|
|
147
147
|
url.searchParams.append(key, value);
|
|
148
148
|
});
|
|
149
149
|
const response = await fetch(url);
|
|
150
|
-
|
|
150
|
+
let healResponse = await response.json();
|
|
151
|
+
if (healResponse.status === StatusCode.Success && labelhashLiteralLabel(asLiteralLabel(healResponse.label)) !== normalizedLabelHash) {
|
|
152
|
+
healResponse = {
|
|
153
|
+
status: StatusCode.Error,
|
|
154
|
+
error: "Label not found",
|
|
155
|
+
errorCode: ErrorCode.NotFound
|
|
156
|
+
};
|
|
157
|
+
}
|
|
151
158
|
if (isCacheableHealResponse(healResponse)) {
|
|
152
159
|
this.cache.set(normalizedLabelHash, healResponse);
|
|
153
160
|
}
|
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\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"]}
|
|
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 { asLiteralLabel, labelhashLiteralLabel, 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 let healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n // Sanity Check: avoid returning malformed heals to consumers, treating as not-found\n if (\n healResponse.status === StatusCode.Success &&\n labelhashLiteralLabel(asLiteralLabel(healResponse.label)) !== normalizedLabelHash\n ) {\n healResponse = {\n status: StatusCode.Error,\n error: \"Label not found\",\n errorCode: ErrorCode.NotFound,\n } satisfies EnsRainbow.HealNotFoundError;\n }\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.
|
|
3
|
+
"version": "1.15.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.50.3"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"enssdk": "1.
|
|
42
|
+
"enssdk": "1.15.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.
|
|
49
|
-
"@ensnode/ensnode-sdk": "1.
|
|
48
|
+
"@ensnode/shared-configs": "1.15.0",
|
|
49
|
+
"@ensnode/ensnode-sdk": "1.15.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"prepublish": "tsup",
|