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