@clickup/ent-framework
Version:
A PostgreSQL graph-database-alike library with microsharding and row-level security
150 lines • 7.08 kB
TypeScript
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