UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

87 lines (73 loc) 2.88 kB
import type { QueryAnnotation } from "./QueryAnnotation"; const INIT_SEQUENCE = 3; // small prime, doesn't matter /** * Knows how to translate individual strongly typed requests into DB language * and how to parse the result back. */ export abstract class Runner<TInput, TOutput> { /** If true, it's a write operation. */ static readonly IS_WRITE: boolean; /** Used to build an unique default operation key. */ private sequence = INIT_SEQUENCE; /** Operation name for logging purposes. */ abstract readonly op: string; /** Maximum batch size for this type of operations. */ abstract readonly maxBatchSize: number; /** In case undefined is returned from batching, this value will be returned * instead. */ abstract readonly default: TOutput; /** * Method runSingle is to e.g. produce simple DB requests when we have only * one input to process, not many. */ abstract runSingle( input: TInput, annotations: QueryAnnotation[], ): Promise<TOutput | undefined>; /** * Typically issues complex queries with magic. If the method is not defined, * then the runner doesn't support batching, so only runSingle() will be used. */ abstract runBatch?( inputs: Map<string, TInput>, annotations: QueryAnnotation[], ): Promise<Map<string, TOutput>>; /** * If the single query's error needs to be retried (e.g. it's a deadlock * error), returns the number of milliseconds to wait before retrying. */ abstract delayForSingleQueryRetryOnError( error: unknown, ): number | "immediate_retry" | "no_retry"; /** * If this method returns true for an error object, the batch is split back * into sub-queries, they are executed individually, and then the response of * each query is delivered to each caller individually. Used mostly for e.g. * batch-deadlock errors or for FK constraint errors when it makes sense to * retry other members of the batch and not fail it entirely hurting other * innocent queries. * * We can do this, because we know that if some transaction is aborted, it's * always safe to retry it. (If we're not sure about the transaction, e.g. the * Client doesn't support transactions at all, then the method should return * false.) */ abstract shouldDebatchOnError(error: unknown): boolean; /** * Parameter `name` is typically a table name. */ constructor(public readonly name: string) {} /** * Returns a batch-dedupping key for the input. By default, no dedupping is * performed (i.e. all inputs are processed individually and not collapsed * into one input; e.g. this is needed for inserts). */ key(_input: TInput): string { const key = "k" + this.sequence; this.sequence += INIT_SEQUENCE; if (this.sequence > INIT_SEQUENCE * 10000000) { this.sequence = INIT_SEQUENCE; } return key; } }