@naturalcycles/db-lib
Version:
Lowest Common Denominator API to supported Databases
125 lines (113 loc) • 3.66 kB
text/typescript
import type { CommonLogger } from '@naturalcycles/js-lib/log'
import { _deepCopy } from '@naturalcycles/js-lib/object'
import type { BaseDBEntity, Unsaved } from '@naturalcycles/js-lib/types'
import type { DBTransaction } from '../db.model.js'
import type { AnyDao, CommonDao, InferID } from './common.dao.js'
import type {
CommonDaoOptions,
CommonDaoReadOptions,
CommonDaoSaveBatchOptions,
CommonDaoSaveOptions,
} from './common.dao.model.js'
/**
* Transaction context.
* Has similar API than CommonDao, but all operations are performed in the context of the transaction.
*/
export class CommonDaoTransaction {
constructor(
public tx: DBTransaction,
private logger: CommonLogger,
) {}
/**
* Commits the underlying DBTransaction.
* May throw.
*/
async commit(): Promise<void> {
await this.tx.commit()
}
/**
* Perform a graceful rollback without throwing/re-throwing any error.
* Never throws.
*/
async rollback(): Promise<void> {
try {
await this.tx.rollback()
} catch (err) {
// graceful rollback without re-throw
this.logger.error(err)
}
}
async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
dao: CommonDao<BM, DBM, ID>,
id?: ID | null,
opt?: CommonDaoReadOptions,
): Promise<BM | null> {
return await dao.getById(id, { ...opt, tx: this.tx })
}
async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
dao: CommonDao<BM, DBM, ID>,
ids: ID[],
opt?: CommonDaoReadOptions,
): Promise<BM[]> {
return await dao.getByIds(ids, { ...opt, tx: this.tx })
}
// todo: Queries inside Transaction are not supported yet
// async runQuery<BM extends PartialObjectWithId, DBM extends ObjectWithId>(
// dao: CommonDao<BM, DBM, any>,
// q: DBQuery<DBM>,
// opt?: CommonDaoOptions,
// ): Promise<BM[]> {
// try {
// return await dao.runQuery(q, { ...opt, tx: this.tx })
// } catch (err) {
// await this.rollback()
// throw err
// }
// }
async save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
dao: CommonDao<BM, DBM>,
bm: Unsaved<BM>,
opt?: CommonDaoSaveOptions<BM, DBM>,
): Promise<BM> {
return await dao.save(bm, { ...opt, tx: this.tx })
}
async saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
dao: CommonDao<BM, DBM>,
bms: Unsaved<BM>[],
opt?: CommonDaoSaveBatchOptions<DBM>,
): Promise<BM[]> {
return await dao.saveBatch(bms, { ...opt, tx: this.tx })
}
/**
* DaoTransaction.patch does not load from DB.
* It assumes the bm was previously loaded in the same Transaction, hence could not be
* concurrently modified. Hence it's safe to not sync with DB.
*
* So, this method is a rather simple convenience "Object.assign and then save".
*/
async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
dao: CommonDao<BM, DBM, ID>,
bm: BM,
patch: Partial<BM>,
opt?: CommonDaoSaveOptions<BM, DBM>,
): Promise<BM> {
const skipIfEquals = _deepCopy(bm)
Object.assign(bm, patch)
return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx })
}
async deleteById<DAO extends AnyDao>(
dao: DAO,
id?: InferID<DAO> | null,
opt?: CommonDaoOptions,
): Promise<number> {
if (!id) return 0
return await this.deleteByIds(dao, [id], opt)
}
async deleteByIds<DAO extends AnyDao>(
dao: DAO,
ids: InferID<DAO>[],
opt?: CommonDaoOptions,
): Promise<number> {
return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
}
}