The jury is still out on this. Indexes are the "right" way to do this but are actually slower in some circumstances.
184 lines
3.9 KiB
JavaScript
184 lines
3.9 KiB
JavaScript
import core from 'pouchdb-core';
|
|
import idb from 'pouchdb-adapter-idb';
|
|
import http from 'pouchdb-adapter-http';
|
|
import replication from 'pouchdb-replication';
|
|
import find from 'pouchdb-find';
|
|
|
|
import { log, warn } from './console.js';
|
|
import { isObject } from '../utils/comparators.js';
|
|
import { LiveArray } from '../utils/livearray.js';
|
|
import { Watcher } from '../utils/watcher.js';
|
|
import { deepAssign, pouchDocHash } from '../utils/conversion.js';
|
|
|
|
export const PouchDB = core
|
|
.plugin(idb)
|
|
.plugin(http)
|
|
.plugin(replication)
|
|
.plugin(find)
|
|
.plugin(PouchORM);
|
|
|
|
export class TypeSpec {
|
|
constructor(props) {
|
|
this._populateId(props);
|
|
Object.assign(this, { $links: {} }, props, { type: this._prefix });
|
|
}
|
|
|
|
static getUniqueID(doc) {
|
|
throw 'NotImplemented';
|
|
}
|
|
|
|
static validate(doc) {}
|
|
|
|
instantiate(doc) {
|
|
return new this._cls(docs);
|
|
}
|
|
|
|
_populateId(doc) {
|
|
if (!doc._id) {
|
|
doc._id = `${this._prefix}_${this._cls.getUniqueID(doc)}`;
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
_hash() {
|
|
return pouchDocHash(this);
|
|
}
|
|
|
|
async delete() {
|
|
return await this.update({ _deleted: true });
|
|
}
|
|
|
|
async save() {
|
|
this._cls.validate(this);
|
|
const { rev } = await this._db.put(this);
|
|
this._rev = rev;
|
|
return this;
|
|
}
|
|
|
|
async addAttachment(attName, dataBlob) {
|
|
const { rev } = await this._db.putAttachment(
|
|
this._id,
|
|
attName,
|
|
this._rev,
|
|
dataBlob,
|
|
dataBlob.type
|
|
);
|
|
|
|
this._rev = rev;
|
|
return this;
|
|
}
|
|
|
|
async getAttachment(attName) {
|
|
return await this._db.getAttachment(this._id, attName);
|
|
}
|
|
|
|
async removeAttachment(attName) {
|
|
return await this._db.removeAttachment(this._id, attName, this._rev);
|
|
}
|
|
|
|
async update(props, save = true) {
|
|
deepAssign(this, props);
|
|
if (save) {
|
|
await this.save();
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export function PouchORM(PouchDB) {
|
|
PouchDB.registerType = (name, cls, db) => {
|
|
const prefix = name.toLowerCase();
|
|
const _db = db || PouchDB(prefix);
|
|
_db.setMaxListeners(1000);
|
|
const _baseSelector = Object.freeze({
|
|
_id: { $gt: `${prefix}_0`, $lt: `${prefix}_\ufff0` }
|
|
});
|
|
const watch = Watcher(_db, _baseSelector, { include_docs: true });
|
|
|
|
if (!cls.hasOwnProperty('validate')) {
|
|
warn(`${cls.name} has no validation.`);
|
|
}
|
|
|
|
const instantiate = doc => new cls(doc);
|
|
|
|
async function find(idOrSelector, opts = {}) {
|
|
if (typeof idOrSelector === 'string') {
|
|
return instantiate(await _db.get(idOrSelector));
|
|
}
|
|
|
|
const isSelector = isObject(idOrSelector);
|
|
|
|
const selector = Object.assign(
|
|
isSelector && idOrSelector._deleted ? { _deleted: true } : { _deleted: { exists: false } },
|
|
isSelector ? idOrSelector : _baseSelector
|
|
);
|
|
if (opts.index) {
|
|
opts.use_index = [prefix, opts.index];
|
|
delete opts.index;
|
|
}
|
|
if (opts.live) {
|
|
opts.mapper = instantiate;
|
|
return LiveArray(_db, idOrSelector, opts);
|
|
}
|
|
return (await _db.find(Object.assign({ selector: idOrSelector }, opts))).docs.map(instantiate);
|
|
}
|
|
|
|
async function getOrCreate(props) {
|
|
let doc = await new cls(props);
|
|
try {
|
|
await doc.save();
|
|
} catch (e) {
|
|
if (e.status !== 409) {
|
|
throw e;
|
|
}
|
|
doc = await find(doc._id);
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
async function _delete(id) {
|
|
try {
|
|
const doc = await find(id);
|
|
doc._deleted = true;
|
|
await _db.put(doc);
|
|
} catch (e) {
|
|
if (e.status !== 404) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function _index(name, fields) {
|
|
return _db.createIndex({
|
|
index: {
|
|
ddoc: prefix,
|
|
fields,
|
|
name
|
|
}
|
|
});
|
|
}
|
|
|
|
Object.defineProperties(cls.prototype, {
|
|
_name: { value: name },
|
|
_prefix: { value: prefix },
|
|
_db: { value: _db },
|
|
_cls: { value: cls },
|
|
_baseSelector: { value: _baseSelector }
|
|
});
|
|
|
|
Object.defineProperties(cls, {
|
|
getOrCreate: { value: getOrCreate },
|
|
find: { value: find },
|
|
index: { value: _index },
|
|
delete: { value: _delete },
|
|
subscribe: { value: watch },
|
|
db: { value: _db },
|
|
name: { value: name },
|
|
prefix: { value: prefix },
|
|
selector: { value: _baseSelector }
|
|
});
|
|
|
|
return cls;
|
|
};
|
|
}
|