@naturalcycles/db-lib
Version:
Lowest Common Denominator API to supported Databases
168 lines (167 loc) • 5.43 kB
JavaScript
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);
}
}