UNPKG

@naturalcycles/db-lib

Version:

Lowest Common Denominator API to supported Databases

168 lines (167 loc) 5.43 kB
import { AppError } from '@naturalcycles/js-lib/error/error.util.js'; import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'; import { SKIP } from '@naturalcycles/js-lib/types'; import { decompressZstdOrInflateToString, deflateString, inflateToString, zstdCompress, zstdDecompressToString, } from '@naturalcycles/nodejs-lib/zip'; /** * @deprecated use zstd instead, gzip is obsolete */ export function commonKeyValueDaoDeflatedJsonTransformer() { return { valueToBuffer: async (v) => await deflateString(JSON.stringify(v)), bufferToValue: async (buf) => JSON.parse(await inflateToString(buf)), }; } export function commonKeyValueDaoZstdJsonTransformer(level) { return { valueToBuffer: async (v) => await zstdCompress(JSON.stringify(v), level), bufferToValue: async (buf) => JSON.parse(await zstdDecompressToString(buf)), }; } /** * Saves: zstd * Reads: zstd or deflate (backwards compatible) */ export function commonKeyValueDaoCompressedTransformer() { return { valueToBuffer: async (v) => await zstdCompress(JSON.stringify(v)), bufferToValue: async (buf) => JSON.parse(await decompressZstdOrInflateToString(buf)), }; } // todo: logging // todo: readonly export class CommonKeyValueDao { constructor(cfg) { this.cfg = { logger: console, ...cfg, }; } cfg; async ping() { await this.cfg.db.ping(); } async createTable(opt = {}) { await this.cfg.db.createTable(this.cfg.table, opt); } async getById(id) { if (!id) return null; const [r] = await this.getByIds([id]); return r?.[1] || null; } async getByIdAsBuffer(id) { if (!id) return null; const [r] = await this.cfg.db.getByIds(this.cfg.table, [id]); return r?.[1] || null; } async requireById(id) { const [r] = await this.getByIds([id]); if (!r) { const { table } = this.cfg; throw new AppError(`DB row required, but not found in ${table}`, { table, id, }); } return r[1]; } async requireByIdAsBuffer(id) { const [r] = await this.cfg.db.getByIds(this.cfg.table, [id]); if (!r) { const { table } = this.cfg; throw new AppError(`DB row required, but not found in ${table}`, { table, id, }); } return r[1]; } async getByIds(ids) { const entries = await this.cfg.db.getByIds(this.cfg.table, ids); if (!this.cfg.transformer) return entries; return await pMap(entries, async ([id, raw]) => [ id, await this.cfg.transformer.bufferToValue(raw), ]); } async getByIdsAsBuffer(ids) { return await this.cfg.db.getByIds(this.cfg.table, ids); } async save(id, value, opt) { await this.saveBatch([[id, value]], opt); } async saveBatch(entries, opt) { const { transformer } = this.cfg; let rawEntries; if (!transformer) { rawEntries = entries; } else { rawEntries = await pMap(entries, async ([id, v]) => [id, await transformer.valueToBuffer(v)]); } await this.cfg.db.saveBatch(this.cfg.table, rawEntries, opt); } async deleteByIds(ids) { await this.cfg.db.deleteByIds(this.cfg.table, ids); } async deleteById(id) { await this.cfg.db.deleteByIds(this.cfg.table, [id]); } streamIds(limit) { return this.cfg.db.streamIds(this.cfg.table, limit); } streamValues(limit) { const { transformer } = this.cfg; if (!transformer) { return this.cfg.db.streamValues(this.cfg.table, limit); } return this.cfg.db.streamValues(this.cfg.table, limit).map(async (buf) => { try { return await transformer.bufferToValue(buf); } catch (err) { this.cfg.logger.error(err); return SKIP; } }, { concurrency: 32 }); } streamEntries(limit) { const { transformer } = this.cfg; if (!transformer) { return this.cfg.db.streamEntries(this.cfg.table, limit); } return this.cfg.db.streamEntries(this.cfg.table, limit).map(async ([id, buf]) => { try { return [id, await transformer.bufferToValue(buf)]; } catch (err) { this.cfg.logger.error(err); return SKIP; } }, { concurrency: 32 }); } async getAllKeys(limit) { return await this.streamIds(limit).toArray(); } async getAllValues(limit) { return await this.streamValues(limit).toArray(); } async getAllEntries(limit) { return await this.streamEntries(limit).toArray(); } /** * Increments the `id` field by the amount specified in `by`, * or by 1 if `by` is not specified. * * Returns the new value of the field. */ async increment(id, by = 1) { const [t] = await this.cfg.db.incrementBatch(this.cfg.table, [[id, by]]); return t[1]; } async incrementBatch(entries) { return await this.cfg.db.incrementBatch(this.cfg.table, entries); } }