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 93 94 95 | 20x 20x 20x 20x 1x 1x 20x 2x 2x 2x 20x 2x 2x 2x 2x 2x 2x 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;
};
const tryReadResponseText = async (response: Response): Promise<string | undefined> => {
try {
return response.text();
} catch {
return undefined;
}
};
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');
}
}
throw new BlockfrostError(response.status, await tryReadResponseText(response));
}),
catchError((err) => {
if (err instanceof BlockfrostError) {
return throwError(() => err);
}
return throwError(() => new BlockfrostError(undefined, undefined, err));
})
)
)
);
}
}
|