UNPKG

@clickup/ent-framework

Version:

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

150 lines 7.08 kB
import type pg from "pg"; import type { ClientConnectionIssue, ClientOptions, ClientPingInput, ClientRole } from "../abstract/Client"; import { Client } from "../abstract/Client"; import type { ClientQueryLoggerProps } from "../abstract/Loggers"; import type { QueryAnnotation } from "../abstract/QueryAnnotation"; import { TimelineManager } from "../abstract/TimelineManager"; import type { MaybeCallable, PickPartial } from "../internal/misc"; import type { Hints, Literal } from "../types"; /** * Options for PgClient constructor. */ export interface PgClientOptions extends ClientOptions { /** Info on how to discover the shards. */ shards?: { /** Name of a PG shard schema (e.g. "sh%04d"). */ nameFormat: string; /** A SQL query which should return the names of shard schemas served by * this Client. */ discoverQuery: MaybeCallable<string>; } | null; /** PG "SET key=value" hints to run before each query. Often times we use it * to pass statement_timeout option since e.g. PGBouncer doesn't support * per-connection statement timeout in transaction pooling mode: it throws * "unsupported startup parameter" error. I.e. we may want to emit "SET * statement_timeout TO ..." before each query in multi-query mode. */ hints?: MaybeCallable<Hints> | null; /** After how many milliseconds we give up waiting for the replica to catch up * with the master. When role="replica", then this option is the only way to * "unlatch" the reads from the master node after a write. */ maxReplicationLagMs?: MaybeCallable<number>; /** Sometimes, the role of this Client is known statically, e.g. when pointing * to AWS Aurora writer and reader endpoints. If "master" or "replica" are * provided, then no attempt is made to use functions like * pg_current_wal_insert_lsn() etc. (they are barely supported in e.g. AWS * Aurora). Instead, for "replica" role, it is treated as "always lagging up * until maxReplicationLagMs after the last write". If role="unknown", then * auto-detection and automatic lag tracking is performed using * pg_current_wal_insert_lsn() and other built-in PostgreSQL functions. */ role?: ClientRole; /** Up to how often we call TimelineManager#triggerRefresh(). */ replicaTimelinePosRefreshMs?: MaybeCallable<number>; } /** * An opened low-level PostgreSQL connection. */ export interface PgClientConn extends pg.PoolClient { /** Undocumented property of node-postgres, see: * https://github.com/brianc/node-postgres/issues/2665 */ processID?: number | null; /** An additional property to the vanilla client: auto-incrementing ID of the * connection for logging purposes. */ id?: number; /** An additional property to the vanilla client: number of queries sent * within this connection. */ queriesSent?: number; /** An additional property to the vanilla client: when do we want to * hard-close that connection. */ closeAt?: number; } /** * An abstract PostgreSQL Client which doesn't know how to acquire an actual * connection and send queries; these things are up to the derived classes to * implement. * * The idea is that in each particular project, people may have they own classes * derived from PgClient, in case the codebase already has some existing * connection pooling solution. They don't have to use PgClientPool. * * Since the class is cloneable internally (using the prototype substitution * technique), the contract of this class is that ALL its derived classes may * only have readonly immediate properties. */ export declare abstract class PgClient extends Client { /** Default values for the constructor options. */ static readonly DEFAULT_OPTIONS: Required<PickPartial<PgClientOptions>>; /** Number of decimal digits in an ID allocated for shard number. Calculated * dynamically based on shards.nameFormat (e.g. for "sh%04d", it will be 4 * since it expands to "sh0012"). */ private readonly shardNoPadLen; /** This value is set after each request to reflect the actual role of the * client. The idea is that master/replica role may change online, without * reconnecting the Client, so we need to refresh it after each request and be * ready for a fallback. The expectation is that the initial value is * populated during the very first shardNos() call. */ private readonly reportedRoleAfterLastQuery; /** This value is non-null if there was an unsuccessful connection attempt * (i.e. the PG is down), and there were no successful queries since then. */ private readonly reportedConnectionIssue; /** PgClient configuration options. */ readonly options: Required<PgClientOptions>; /** Name of the shard associated to this Client. */ readonly shardName: string; /** An active TimelineManager for this particular Client. */ readonly timelineManager: TimelineManager; /** * Returns statistics about the connection pool. */ abstract poolStats(): ClientQueryLoggerProps["poolStats"]; /** * Called when the Client needs a connection to run a query against. Implies * than the caller MUST call release() method on the returned object. */ abstract acquireConn(): Promise<PgClientConn>; /** * Initializes an instance of PgClient. */ constructor(options: PgClientOptions); /** * Sends a query (internally, a multi-query). After the query finishes, we * should expect that role() returns the actual master/replica role. */ query<TRow>({ query: queryLiteral, hints, isWrite, annotations, op, table, batchFactor, }: { query: Literal; hints?: Hints; isWrite: boolean; annotations: QueryAnnotation[]; op: string; table: string; batchFactor?: number; }): Promise<TRow[]>; shardNos(): Promise<readonly number[]>; ping({ execTimeMs, isWrite, annotation, }: ClientPingInput): Promise<void>; shardNoByID(id: string): number; withShard(no: number): this; role(): ClientRole; connectionIssue(): ClientConnectionIssue | null; /** * Prepares a PG Client multi-query from the query literal and hints. */ private buildMultiQuery; /** * Sends a multi-query to PG Client. * * A good and simple explanation of the protocol is here: * https://www.postgresql.org/docs/13/protocol-flow.html. In short, we can't * use prepared-statement-based operations even theoretically, because this * mode doesn't support multi-queries. Also notice that TS typing is doomed * for multi-queries: * https://github.com/DefinitelyTyped/DefinitelyTyped/pull/33297 */ private sendMultiQuery; /** * Builds the schema name (aka "Shard name") by Shard number using * `options#shards#nameFormat`. * * E.g. nameFormat="sh%04d" generates names like "sh0042". */ private buildShardName; } //# sourceMappingURL=PgClient.d.ts.map