Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 18x 18x 18x 18x 3x 3x 3x 18x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x | import { CustomError } from 'ts-custom-error'; import { catchError, firstValueFrom, switchMap, throwError } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; export type BlockfrostClientConfig = { projectId?: string; baseUrl: string; apiVersion?: string; }; export type RateLimiter = { schedule: <T>(task: () => Promise<T>) => Promise<T>; }; export type BlockfrostClientDependencies = { /** * Rate limiter from npm: https://www.npmjs.com/package/bottleneck * * new Bottleneck({ * reservoir: DEFAULT_BLOCKFROST_RATE_LIMIT_CONFIG.size, * reservoirIncreaseAmount: DEFAULT_BLOCKFROST_RATE_LIMIT_CONFIG.increaseAmount, * reservoirIncreaseInterval: DEFAULT_BLOCKFROST_RATE_LIMIT_CONFIG.increaseInterval, * reservoirIncreaseMaximum: DEFAULT_BLOCKFROST_RATE_LIMIT_CONFIG.size * }) */ rateLimiter: RateLimiter; }; export class BlockfrostError extends CustomError { constructor(public status?: number, public body?: string, public innerError?: unknown) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const message: string | null = body || (innerError as any)?.message; super(`Blockfrost error with status '${status}': ${message}`); } } export class BlockfrostClient { private rateLimiter: RateLimiter; private baseUrl: string; private requestInit: RequestInit; constructor( { apiVersion, projectId, baseUrl }: BlockfrostClientConfig, { rateLimiter }: BlockfrostClientDependencies ) { this.rateLimiter = rateLimiter; this.requestInit = projectId ? { headers: { project_id: projectId } } : {}; this.baseUrl = apiVersion ? `${baseUrl}/api/${apiVersion}` : `${baseUrl}`; } /** * @param endpoint e.g. 'blocks/latest' * @param requestInit request options * @throws {BlockfrostError} */ public request<T>(endpoint: string, requestInit?: RequestInit): Promise<T> { return this.rateLimiter.schedule(() => firstValueFrom( fromFetch(`${this.baseUrl}/${endpoint}`, { ...this.requestInit, ...requestInit, headers: requestInit?.headers ? { ...this.requestInit.headers, ...requestInit.headers } : this.requestInit.headers }).pipe( switchMap(async (response): Promise<T> => { if (response.ok) { try { return await response.json(); } catch { throw new BlockfrostError(response.status, 'Failed to parse json'); } } try { const responseBody = await response.text(); throw new BlockfrostError(response.status, responseBody); } catch { throw new BlockfrostError(response.status); } }), catchError((err) => { if (err instanceof BlockfrostError) { return throwError(() => err); } return throwError(() => new BlockfrostError(undefined, undefined, err)); }) ) ) ); } } |