UNPKG

@clickup/ent-framework

Version:

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

257 lines 12.5 kB
import pg from "pg"; import type { ClientConnectionIssue, ClientOptions, ClientPingInput, ClientRole } from "../abstract/Client"; import { Client } from "../abstract/Client"; import type { SwallowedErrorLoggerProps } 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<TPool extends pg.Pool = pg.Pool> extends ClientOptions { /** Node-Postgres config. We can't make it MaybeCallable unfortunately, * because it's used to initialize Node-Postgres Pool. */ config: pg.PoolConfig & { min?: number | undefined; }; /** Should create an instance of Pool class compatible with node-postgres * Pool. By default, node-postgres Pool is used. */ createPool?: (config: pg.PoolConfig) => TPool; /** Close the connection after the query if it was opened long time ago. */ maxConnLifetimeMs?: MaybeCallable<number>; /** Jitter for maxConnLifetimeMs. */ maxConnLifetimeJitter?: MaybeCallable<number>; /** Add not more than this number of connections in each prewarm interval. New * connections are expensive to establish (especially when SSL is enabled). */ prewarmIntervalStep?: MaybeCallable<number>; /** How often to send bursts of prewarm queries to all Clients to keep the * minimal number of open connections. The default value is half of the * default node-postgres'es idleTimeoutMillis=10s. Together with 1..1.5x * jitter (default prewarmIntervalJitter=0.5), it is still slightly below * idleTimeoutMillis, and thus, doesn't let Ent Framework close the * connections prematurely. */ prewarmIntervalMs?: MaybeCallable<number>; /** Jitter for prewarmIntervalMs. */ prewarmIntervalJitter?: MaybeCallable<number>; /** What prewarm query to send. */ prewarmQuery?: MaybeCallable<string>; /** If true, also sends prewarm queries and keeps the min number of * connections in all sub-pools. See pool() method for details. */ prewarmSubPools?: boolean; /** 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<TPool extends pg.Pool = pg.Pool> extends pg.PoolClient { /** Pool instance that created this connection. */ pool: TPool; /** 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 | null; } /** * A named low-level Pool config used to create sub-pools. Sub-pool derives * configuration from the default PgClientOptions#config, but allow overrides. * See PgClient#pool() method for details. */ export interface PgClientSubPoolConfig extends Partial<pg.PoolConfig> { name: string; } /** * An abstract PostgreSQL Client. Includes connection pooling logic. * * Since the class is cloneable internally (using the prototype substitution * technique, see withShard()), the contract of this class is that ALL its * derived classes may only have readonly immediate properties. Use Ref helper * if you need some mutable properties. */ export declare class PgClient<TPool extends pg.Pool = pg.Pool> extends Client { /** Default values for the constructor options. */ static readonly DEFAULT_OPTIONS: Required<PickPartial<PgClientOptions<pg.Pool>>>; /** PG named connection pools to use. The default pool has `null` key.*/ private readonly pools; /** Prewarming periodic timer (if scheduled). */ private readonly prewarmTimeout; /** Whether the pool has been ended and is not usable anymore. */ private readonly ended; /** 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<TPool>>; /** Name of the shard associated to this Client. */ readonly shardName: string; /** An active TimelineManager for this particular Client. */ readonly timelineManager: TimelineManager; /** * Calls swallowedErrorLogger() doing some preliminary amendment. */ protected logSwallowedError(props: SwallowedErrorLoggerProps): void; /** * Initializes an instance of PgClient. */ constructor(options: PgClientOptions<TPool>); /** * Represents the full destination address this Client is working with. * Depending on the implementation, it may include hostname, port number, * database name, shard name etc. It is required that the address is stable * enough to be able to cache some destination database related metadata (e.g. * shardNos) based on that address. */ address(): string; /** * Gracefully closes all the connections of this Client to let the caller * destroy it. The pending queries are awaited to finish before returning. The * Client becomes unusable right after calling this method (even before the * connections are drained): you should not send queries to it. */ end(): Promise<void>; /** * Returns true if the Client is ended and can't be used anymore. */ isEnded(): boolean; /** * Returns all Shard numbers discoverable via the connection to the Client's * database. */ shardNos(): Promise<readonly number[]>; /** * Sends a read or write test query to the server. Tells the server to sit and * wait for at least the provided number of milliseconds. */ ping({ execTimeMs, isWrite, annotation, }: ClientPingInput): Promise<void>; /** * Creates a new Client which is namespaced to the provided Shard number. The * new Client will share the same connection pool with the parent's Client. */ withShard(no: number): this; /** * Returns the Client's role reported after the last successful query. Master * and replica roles may switch online unpredictably, without reconnecting, so * we only know the role after a query. */ role(): ClientRole; /** * Returns a non-nullable value if the Client couldn't connect to the server * (or it could, but the load balancer reported the remote server as not * working), so it should ideally be removed from the list of active replicas * until e.g. the next discovery query to it (or any query) succeeds. */ connectionIssue(): ClientConnectionIssue | null; /** * A convenience method to put connections prewarming logic to. The idea is to * keep the needed number of open connections and also, in each connection, * minimize the time which the very 1st query will take (e.g. pre-cache * full-text dictionaries). */ prewarm(): void; /** * Returns a default pool (when subPoolConfig is not passed), or a "sub-pool" * (a named low-level Pool implementation compatible to node-postgres). The * method is specific to the current class and is not a part of * database-agnostic Client API. * - Sub-pools are lazily created and memoized by the provided name. They may * differ by config options (like statement_timeout or max connections). * - Sub-pools inherit the properties from default PgClientOptions.config. * - It is implied (but not enforced) that all sub-pools use the same physical * database, because otherwise it makes not a lot of sense. */ pool(subPoolConfig?: PgClientSubPoolConfig): TPool; /** * Called when the Client needs a connection in the default pool (when * subPoolConfig is not passed), or in a sub-pool (see pool() method) to run a * query against. Implies than the caller MUST call release() method on the * returned object. The difference from pool().connect() is that when calling * release() on a result of acquireConn(), it additionally closes the * connection automatically if was OPENED (not queried!) more than * maxConnLifetimeMs ago (node-postgres Pool doesn't have this feature) The * method is specific to the current class and is not a part of * database-agnostic Client API. */ acquireConn(subPoolConfig?: PgClientSubPoolConfig): Promise<PgClientConn<TPool>>; /** * Sends a query (internally, a multi-query) through the default Pool (if * subPoolConfig is not passed), or through a named sub-pool (see pool() * method). After the query finishes, we should expect that role() returns the * actual master/replica role. The method is specific to the current class and * is not a part of database-agnostic Client API. */ query<TRow>({ query: queryLiteral, hints, isWrite, annotations, op, table, batchFactor, subPoolConfig, }: { query: Literal; hints?: Hints; isWrite: boolean; annotations: QueryAnnotation[]; op: string; table: string; batchFactor?: number; subPoolConfig?: PgClientSubPoolConfig; }): Promise<TRow[]>; /** * 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; } /** * For backward compatibility, exposing the old name as well. * @deprecated Use PgClient instead. * @ignore */ export declare const PgClientPool: typeof PgClient; /** * For backward compatibility, exposing the old name as well. * @deprecated Use PgClient instead. * @ignore */ export type PgClientPool = PgClient; /** * For backward compatibility, exposing the old name as well. * @deprecated Use PgClientOptions instead. * @ignore */ export type PgClientPoolOptions = PgClientOptions; //# sourceMappingURL=PgClient.d.ts.map