@naturalcycles/db-lib
Version:
Lowest Common Denominator API to supported Databases
252 lines (251 loc) • 12 kB
TypeScript
import type { BaseDBEntity, NonNegativeInteger, ObjectWithId, StringMap, Unsaved } from '@naturalcycles/js-lib/types';
import type { JsonSchema } from '@naturalcycles/nodejs-lib/ajv';
import type { Pipeline } from '@naturalcycles/nodejs-lib/stream';
import type { CommonDBTransactionOptions, RunQueryResult } from '../db.model.js';
import type { DBQuery } from '../query/dbQuery.js';
import { RunnableDBQuery } from '../query/dbQuery.js';
import type { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoReadOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model.js';
import { CommonDaoTransaction } from './commonDaoTransaction.js';
/**
* Lowest common denominator API between supported Databases.
*
* BM = Backend model (optimized for API access)
* DBM = Database model (logical representation, before compression)
* TM = Transport model (optimized to be sent over the wire)
*
* Note: When auto-compression is enabled, the physical storage format differs from DBM.
* Compression/decompression is handled transparently at the storage boundary.
*/
export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID extends string = BM['id']> {
cfg: CommonDaoCfg<BM, DBM, ID>;
constructor(cfg: CommonDaoCfg<BM, DBM, ID>);
create(part?: Partial<BM>, opt?: CommonDaoOptions): BM;
requireById(id: ID, opt?: CommonDaoReadOptions): Promise<BM>;
requireByIdAsDBM(id: ID, opt?: CommonDaoReadOptions): Promise<DBM>;
getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoReadOptions): Promise<BM>;
getById(id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
getByIdAsDBM(id?: ID | null, opt?: CommonDaoReadOptions): Promise<DBM | null>;
getByIds(ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
getByIdsAsDBM(ids: ID[], opt?: CommonDaoReadOptions): Promise<DBM[]>;
private loadByIds;
getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoReadOptions): Promise<BM[]>;
getOneBy(by: keyof DBM, value: any, opt?: CommonDaoReadOptions): Promise<BM | null>;
getAll(opt?: CommonDaoReadOptions): Promise<BM[]>;
/**
* Pass `table` to override table
*/
query(table?: string): RunnableDBQuery<BM, DBM, ID>;
runQuery(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<BM[]>;
runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<T[]>;
/**
* Convenience method that runs multiple queries in parallel and then merges their results together.
* Does deduplication by id.
* Order is not guaranteed, as queries run in parallel.
*/
runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoReadOptions): Promise<BM[]>;
runQueryExtended(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<BM>>;
runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<DBM[]>;
runQueryExtendedAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<DBM>>;
runQueryCount(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<number>;
streamQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<DBM>): Pipeline<DBM>;
streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<BM>): Pipeline<BM>;
queryIds(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<ID[]>;
streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<ID>): Pipeline<ID>;
/**
* Mutates!
*/
assignIdCreatedUpdated<T extends BaseDBEntity>(obj: Partial<T>, opt?: CommonDaoOptions): void;
/**
* Convenience method to replace 3 operations (loading+patching+saving) with one:
*
* 1. Loads the row by id.
* 1.1 Creates the row (via this.create()) if it doesn't exist
* (this will cause a validation error if Patch has not enough data for the row to be valid).
* 2. Applies the patch on top of loaded data.
* 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
*/
patchById(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
/**
* Like patchById, but runs all operations within a Transaction.
*/
patchByIdInTransaction(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
/**
* Same as patchById, but takes the whole object as input.
* This "whole object" is mutated with the patch and returned.
* Otherwise, similar behavior as patchById.
* It still loads the row from the DB.
*/
patch(bm: BM, patch: Partial<BM>, opt?: CommonDaoPatchOptions<DBM>): Promise<BM>;
/**
* Like patch, but runs all operations within a Transaction.
*/
patchInTransaction(bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM>;
/**
* Mutates with id, created, updated
*/
save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
saveAsDBM(dbm: Unsaved<DBM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<DBM>;
saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM[]>;
saveBatchAsDBM(dbms: Unsaved<DBM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM[]>;
private prepareSaveOptions;
/**
* "Streaming" is implemented by buffering incoming rows into **batches**
* (of size opt.chunkSize, which defaults to 500),
* and then executing db.saveBatch(chunk) with the concurrency
* of opt.chunkConcurrency (which defaults to 32).
*
* It takes a Pipeline as input, appends necessary saving transforms to it,
* and calls .run() on it.
*/
streamSave(p: Pipeline<BM>, opt?: CommonDaoStreamSaveOptions<DBM>): Promise<void>;
/**
* @returns number of deleted items
*/
deleteById(id?: ID | null, opt?: CommonDaoOptions): Promise<number>;
deleteByIds(ids: ID[], opt?: CommonDaoOptions): Promise<number>;
/**
* Pass `chunkSize: number` (e.g 500) option to use Streaming: it will Stream the query, chunk by 500, and execute
* `deleteByIds` for each chunk concurrently (infinite concurrency).
* This is expected to be more memory-efficient way of deleting large number of rows.
*/
deleteByQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
patchByIds(ids: ID[], patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
patchByQuery(q: DBQuery<DBM>, patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
/**
* Caveat: it doesn't update created/updated props.
*
* @experimental
*/
increment(prop: keyof DBM, id: ID, by?: number, opt?: CommonDaoOptions): Promise<number>;
/**
* Caveat: it doesn't update created/updated props.
*
* @experimental
*/
incrementBatch(prop: keyof DBM, incrementMap: StringMap<number>, opt?: CommonDaoOptions): Promise<StringMap<number>>;
dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): null;
dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): BM;
dbmsToBM(dbms: DBM[], opt?: CommonDaoOptions): BM[];
/**
* Mutates object with properties: id, created, updated.
* Returns DBM (new reference).
*/
bmToDBM(bm: undefined, opt?: CommonDaoOptions): null;
bmToDBM(bm?: BM, opt?: CommonDaoOptions): DBM;
bmsToDBM(bms: BM[], opt?: CommonDaoOptions): DBM[];
/**
* Converts a DBM to storage format, applying compression if configured.
*
* Use this when you need to write directly to the database, bypassing the DAO save methods.
* The returned value is opaque and should only be passed to db.saveBatch() or similar.
*
* @example
* const storageRow = await dao.dbmToStorageRow(dbm)
* await db.saveBatch(table, [storageRow])
*/
dbmToStorageRow(dbm: DBM): Promise<ObjectWithId>;
/**
* Converts multiple DBMs to storage rows.
*/
dbmsToStorageRows(dbms: DBM[]): Promise<ObjectWithId[]>;
/**
* Converts a storage row back to a DBM, applying decompression if needed.
*
* Use this when you need to read directly from the database, bypassing the DAO load methods.
*
* @example
* const rows = await db.getByIds(table, ids)
* const dbms = await Promise.all(rows.map(row => dao.storageRowToDBM(row)))
*/
storageRowToDBM(row: ObjectWithId): DBM;
/**
* Converts multiple storage rows to DBMs.
*/
storageRowsToDBM(rows: ObjectWithId[]): DBM[];
private compress;
/**
* Mutates `dbm`.
*/
private decompress;
anyToDBM(dbm: undefined, opt?: CommonDaoOptions): null;
anyToDBM(dbm?: any, opt?: CommonDaoOptions): DBM;
anyToDBMs(rows: DBM[], opt?: CommonDaoOptions): DBM[];
/**
* Returns *converted value* (NOT the same reference).
* Does NOT mutate the object.
* Validates (unless `skipValidation=true` passed).
*/
private validateAndConvert;
getTableSchema(): Promise<JsonSchema<DBM>>;
createTable(schema: JsonSchema<DBM>, opt?: CommonDaoCreateOptions): Promise<void>;
/**
* Proxy to this.cfg.db.ping
*/
ping(): Promise<void>;
withId(id: ID): DaoWithId<CommonDao<BM, DBM, ID>>;
withIds(ids: ID[]): DaoWithIds<CommonDao<BM, DBM, ID>>;
withRowsToSave(rows: Unsaved<BM>[]): DaoWithRows<CommonDao<BM, DBM, ID>>;
withRowToSave(row: Unsaved<BM>, opt?: DaoWithRowOptions<BM>): DaoWithRow<CommonDao<BM, DBM, ID>>;
/**
* Load rows (by their ids) from Multiple tables at once.
* An optimized way to load data, minimizing DB round-trips.
*
* @experimental
*/
static multiGet<MAP extends Record<string, DaoWithIds<AnyDao> | DaoWithId<AnyDao>>>(inputMap: MAP, opt?: CommonDaoReadOptions): Promise<{
[K in keyof MAP]: MAP[K] extends DaoWithIds<any> ? InferBM<MAP[K]['dao']>[] : InferBM<MAP[K]['dao']> | null;
}>;
private static prepareMultiGetIds;
private static multiGetMapByTableById;
private static prepareMultiGetOutput;
/**
* @experimental
*/
static multiDelete(inputs: (DaoWithId<AnyDao> | DaoWithIds<AnyDao>)[], opt?: CommonDaoOptions): Promise<NonNegativeInteger>;
static multiSave(inputs: (DaoWithRows<AnyDao> | DaoWithRow<AnyDao>)[], opt?: CommonDaoSaveBatchOptions<any>): Promise<void>;
createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction>;
runInTransaction<T = void>(fn: CommonDaoTransactionFn<T>, opt?: CommonDBTransactionOptions): Promise<T>;
private ensureRequired;
/**
* Throws if readOnly is true
*/
private requireWriteAccess;
/**
* Throws if readOnly is true
*/
private requireObjectMutability;
/**
* Throws if query uses a property that is in `excludeFromIndexes` list.
*/
private validateQueryIndexes;
}
/**
* Transaction is committed when the function returns resolved Promise (aka "returns normally").
*
* Transaction is rolled back when the function returns rejected Promise (aka "throws").
*/
export type CommonDaoTransactionFn<T = void> = (tx: CommonDaoTransaction) => Promise<T>;
export interface DaoWithIds<DAO extends AnyDao> {
dao: DAO;
ids: string[];
}
export interface DaoWithId<DAO extends AnyDao> {
dao: DAO;
id: string;
}
export interface DaoWithRows<DAO extends AnyDao, BM = InferBM<DAO>> {
dao: DAO;
rows: Unsaved<BM>[];
}
export interface DaoWithRow<DAO extends AnyDao, BM = InferBM<DAO>> {
dao: DAO;
row: Unsaved<BM>;
opt?: DaoWithRowOptions<BM>;
}
export interface DaoWithRowOptions<BM> {
skipIfEquals?: BM;
}
export type InferBM<DAO> = DAO extends CommonDao<infer BM> ? BM : never;
export type InferDBM<DAO> = DAO extends CommonDao<any, infer DBM> ? DBM : never;
export type InferID<DAO> = DAO extends CommonDao<any, any, infer ID> ? ID : never;
export type AnyDao = CommonDao<any>;