UNPKG

@substreams/node

Version:

Substreams for Node.js & Web

179 lines (157 loc) 5.71 kB
// import { IMessageTypeRegistry } from "@bufbuild/protobuf"; import { AnyMessage, JsonObject, Message } from "@bufbuild/protobuf"; import { type CallOptions, type ConnectError, type Transport, createCallbackClient } from "@connectrpc/connect"; import { isEmptyMessage, unpackMapOutput } from "@substreams/core"; import { BlockScopedData, BlockUndoSignal, Clock, Error as FatalError, InitialSnapshotComplete, InitialSnapshotData, ModulesProgress, Request, Response, SessionInit, Stream, } from "@substreams/core/proto"; import { EventEmitter } from "eventemitter3"; export type CancelFn = () => void; export class TypedEventEmitter<Events extends Record<string, any>> { private emitter = new EventEmitter(); emit<EventName extends keyof Events & string>(eventName: EventName, ...eventArg: Events[EventName]) { return this.emitter.emit(eventName, ...(eventArg as [])); } on<EventName extends keyof Events & string>(eventName: EventName, handler: (...eventArg: Events[EventName]) => void) { return this.emitter.on(eventName, handler as any); } once<EventName extends keyof Events & string>(eventName: EventName, handler: (...eventArg: Events[EventName]) => void) { return this.emitter.once(eventName, handler as any); } removeListener<EventName extends keyof Events & string>(eventName: EventName, handler: (...eventArg: Events[EventName]) => void) { return this.emitter.removeListener(eventName, handler as any); } removeAllListeners<EventName extends keyof Events & string>(eventName?: EventName) { if (eventName) { return this.emitter.removeAllListeners(eventName); } return this.emitter.removeAllListeners(); } eventNames() { return this.emitter.eventNames(); } listenerCount<EventName extends keyof Events & string>(eventName: EventName) { return this.emitter.listenerCount(eventName); } off<EventName extends keyof Events & string>(eventName: EventName, handler: (...eventArg: Events[EventName]) => void) { return this.emitter.off(eventName, handler as any); } } /** * A map of event names to argument tuples */ export type LocalEventTypes = { // block block: [block: BlockScopedData]; session: [session: SessionInit]; progress: [progress: ModulesProgress]; undo: [undo: BlockUndoSignal]; // debug (only available in development mode) debugSnapshotData: [undo: InitialSnapshotData]; debugSnapshotComplete: [undo: InitialSnapshotComplete]; // response response: [response: Response]; cursor: [cursor: string, clock: Clock]; clock: [clock: Clock]; output: [message: Message<AnyMessage>, cursor: string, clock: Clock]; anyMessage: [message: JsonObject, cursor: string, clock: Clock]; // error close: [error?: ConnectError]; fatalError: [error: FatalError]; }; export class BlockEmitter extends TypedEventEmitter<LocalEventTypes> { public transport: Transport; public request: any; // Request public registry: any; // IMessageTypeRegistry public options?: CallOptions; public cancelFn?: CancelFn; constructor(transport: Transport, request: any /* Request */, registry: any /* IMessageTypeRegistry */, options?: CallOptions) { super(); this.transport = transport; this.request = request; this.registry = registry; this.options = options; } /** * Stop streaming blocks */ public stop(): void { if (this.cancelFn) { this.cancelFn(); } else { throw new Error("BlockEmitter.stop() called before BlockEmitter.start()"); } } /** * Start streaming blocks */ public start(): CancelFn { const closeCallback = (error?: ConnectError) => { this.emit("close", error); this.cancelFn = undefined; }; const messageCallback = (response: Response) => { this.emit("response", response); switch (response.message.case) { case "blockScopedData": { const block = response.message.value; const isNotTyped = block.output?.mapOutput?.typeUrl == ''; const isEmpty = block.output?.mapOutput?.value.byteLength === 0; this.emit("block", block); if (block.clock && !isEmpty && !isNotTyped) { const output = unpackMapOutput(response, this.registry); if (output) { this.emit("output", output, block.cursor, block.clock); if (!isEmptyMessage(output)) { const message = output.toJson({ typeRegistry: this.registry }); this.emit("anyMessage", message as JsonObject, block.cursor, block.clock); } } } if (block.clock) { this.emit("clock", block.clock); this.emit("cursor", block.cursor, block.clock); } break; } case "progress": { this.emit("progress", response.message.value); break; } case "session": { this.emit("session", response.message.value); break; } case "blockUndoSignal": { this.emit("undo", response.message.value); break; } case "debugSnapshotData": { this.emit("debugSnapshotData", response.message.value); break; } case "debugSnapshotComplete": { this.emit("debugSnapshotComplete", response.message.value); break; } case "fatalError": { this.emit("fatalError", response.message.value); break; } } }; const client = createCallbackClient(Stream, this.transport); this.cancelFn = client.blocks(this.request, messageCallback, closeCallback, this.options); return this.cancelFn; } }