import path from "path"; import fs from "fs"; import PouchDB from "pouchdb"; import { DBError, Doc, DocRes } from "./types"; export default class { 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) { 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, strict = true ): Promise { 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 { try { const result: DocRes = await this.pouchDB.get(this.getDocId(name, id)); result._id = this.replaceDocId(name, result._id); return result; } catch (e) { console.log(e); return null; } } async remove(name: string, doc: Doc | 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>): Promise> { 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): Promise> { 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 = []; 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; } }