rubick/src/core/db/index.ts
2023-09-20 10:25:41 +08:00

208 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import path from 'path';
import fs from 'fs';
import PouchDB from 'pouchdb';
import { DBError, Doc, DocRes } from './types';
import WebDavOP from './webdav';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const replicationStream = require('pouchdb-replication-stream/dist/pouchdb.replication-stream.min.js');
// const load = require('pouchdb-load/dist/pouchdb.load.min.js');
PouchDB.plugin(replicationStream.plugin);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const load = require('pouchdb-load');
PouchDB.plugin({ loadIt: load.load });
PouchDB.adapter('writableStream', replicationStream.adapters.writableStream);
export default class DB {
readonly docMaxByteLength;
readonly docAttachmentMaxByteLength;
public dbpath;
public defaultDbName;
public pouchDB: any;
constructor(dbPath: string) {
this.docMaxByteLength = 2 * 1024 * 1024; // 2M
this.docAttachmentMaxByteLength = 20 * 1024 * 1024; // 20M
this.dbpath = dbPath;
this.defaultDbName = path.join(dbPath, 'default');
}
init(): void {
fs.existsSync(this.dbpath) || fs.mkdirSync(this.dbpath);
this.pouchDB = new PouchDB(this.defaultDbName, { auto_compaction: true });
}
getDocId(name: string, id: string): string {
return name + '/' + id;
}
replaceDocId(name: string, id: string): string {
return id.replace(name + '/', '');
}
errorInfo(name: string, message: string): DBError {
return { error: true, name, message };
}
private checkDocSize(doc: Doc<any>) {
if (Buffer.byteLength(JSON.stringify(doc)) > this.docMaxByteLength) {
return this.errorInfo(
'exception',
`doc max size ${this.docMaxByteLength / 1024 / 1024} M`
);
}
return false;
}
async put(
name: string,
doc: Doc<any>,
strict = true
): Promise<DBError | DocRes> {
if (strict) {
const err = this.checkDocSize(doc);
if (err) return err;
}
doc._id = this.getDocId(name, doc._id);
try {
const result: DocRes = await this.pouchDB.put(doc);
doc._id = result.id = this.replaceDocId(name, result.id);
return result;
} catch (e: any) {
doc._id = this.replaceDocId(name, doc._id);
return { id: doc._id, name: e.name, error: !0, message: e.message };
}
}
async get(name: string, id: string): Promise<DocRes | null> {
try {
const result: DocRes = await this.pouchDB.get(this.getDocId(name, id));
result._id = this.replaceDocId(name, result._id);
return result;
} catch (e) {
return null;
}
}
async remove(name: string, doc: Doc<any> | string) {
try {
let target;
if ('object' == typeof doc) {
target = doc;
if (!target._id || 'string' !== typeof target._id) {
return this.errorInfo('exception', 'doc _id error');
}
target._id = this.getDocId(name, target._id);
} else {
if ('string' !== typeof doc) {
return this.errorInfo('exception', 'param error');
}
target = await this.pouchDB.get(this.getDocId(name, doc));
}
const result: DocRes = await this.pouchDB.remove(target);
target._id = result.id = this.replaceDocId(name, result.id);
return result;
} catch (e: any) {
if ('object' === typeof doc) {
doc._id = this.replaceDocId(name, doc._id);
}
return this.errorInfo(e.name, e.message);
}
}
async bulkDocs(
name: string,
docs: Array<Doc<any>>
): Promise<DBError | Array<DocRes>> {
let result;
try {
if (!Array.isArray(docs)) return this.errorInfo('exception', 'not array');
if (docs.find((e) => !e._id))
return this.errorInfo('exception', 'doc not _id field');
if (new Set(docs.map((e) => e._id)).size !== docs.length)
return this.errorInfo('exception', '_id value exists as');
for (const doc of docs) {
const err = this.checkDocSize(doc);
if (err) return err;
doc._id = this.getDocId(name, doc._id);
}
result = await this.pouchDB.bulkDocs(docs);
result = result.map((res: any) => {
res.id = this.replaceDocId(name, res.id);
return res.error
? {
id: res.id,
name: res.name,
error: true,
message: res.message,
}
: res;
});
docs.forEach((doc) => {
doc._id = this.replaceDocId(name, doc._id);
});
} catch (e) {
//
}
return result;
}
async allDocs(
name: string,
key: string | Array<string>
): Promise<DBError | Array<DocRes>> {
const config: any = { include_docs: true };
if (key) {
if ('string' == typeof key) {
config.startkey = this.getDocId(name, key);
config.endkey = config.startkey + '';
} else {
if (!Array.isArray(key))
return this.errorInfo(
'exception',
'param only key(string) or keys(Array[string])'
);
config.keys = key.map((key) => this.getDocId(name, key));
}
} else {
config.startkey = this.getDocId(name, '');
config.endkey = config.startkey + '';
}
const result: Array<any> = [];
try {
(await this.pouchDB.allDocs(config)).rows.forEach((res: any) => {
if (!res.error && res.doc) {
res.doc._id = this.replaceDocId(name, res.doc._id);
result.push(res.doc);
}
});
} catch (e) {
//
}
return result;
}
public async dumpDb(config: {
url: string;
username: string;
password: string;
}): Promise<void> {
const webdavClient = new WebDavOP(config);
webdavClient.createWriteStream(this.pouchDB);
}
public async importDb(config: {
url: string;
username: string;
password: string;
}): Promise<void> {
const webdavClient = new WebDavOP(config);
await this.pouchDB.destroy();
const syncDb = new DB(this.dbpath);
syncDb.init();
this.pouchDB = syncDb.pouchDB;
await webdavClient.createReadStream(this.pouchDB);
}
}