UNPKG

@naturalcycles/db-lib

Version:

Lowest Common Denominator API to supported Databases

252 lines (251 loc) 12 kB
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>;