Class-based types for the ORM feel a little more natural.
This commit is contained in:
parent
278fc68831
commit
d2c1d3b63c
@ -1,35 +1,39 @@
|
||||
import { PouchDB, TYPES as t } from '../services/db.js';
|
||||
import { PouchDB, TypeSpec } from '../services/db.js';
|
||||
import { log } from '../services/console.js';
|
||||
import { sha256 } from '../utils/crypto.js';
|
||||
import { blobToArrayBuffer } from '../utils/conversion.js';
|
||||
|
||||
export const FileType = PouchDB.registerType({
|
||||
name: 'File',
|
||||
getUniqueID: doc => doc.digest.substr(0, 16),
|
||||
// schema: {
|
||||
// name: t.REQUIRED_STRING,
|
||||
// mimetype: t.REQUIRED_STRING,
|
||||
// digest: t.REQUIRED_STRING,
|
||||
// size: t.INTEGER,
|
||||
// modifiedDate: t.DATE,
|
||||
// addDate: t.DATE,
|
||||
// hasData: t.REQUIRED_BOOLEAN,
|
||||
// tags: {
|
||||
// type: "object",
|
||||
// additionalProperties: t.BOOLEAN
|
||||
// }
|
||||
// },
|
||||
methods: {
|
||||
getURL: doc => `/${FileType.prefix}/${doc._id}/data`,
|
||||
getFromURL: async path => {
|
||||
class FileSpec extends TypeSpec {
|
||||
static getUniqueID(doc) {
|
||||
return doc.digest.substr(0, 16);
|
||||
}
|
||||
|
||||
static getURL(doc, attachmentName = 'data') {
|
||||
const end = attachmentName ? '/' + attachmentName : '';
|
||||
return `/${doc._prefix}/${doc._id}` + end;
|
||||
}
|
||||
|
||||
static async getDocFromURL(path) {
|
||||
if (path.endsWith('/')) {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
const [_, db, id, attname] = path.split('/');
|
||||
return await FileType.find(id);
|
||||
}
|
||||
|
||||
static async getFromURL(path) {
|
||||
if (path.endsWith('/')) {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
const [_, db, id, attname] = path.split('/');
|
||||
const doc = await FileType.find(id);
|
||||
if (attname) {
|
||||
return await doc.getAttachment(attname);
|
||||
},
|
||||
upload: async function(blob) {
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
static async upload(blob) {
|
||||
const digest = await sha256(await blobToArrayBuffer(blob));
|
||||
const lastModified = blob.lastModified ? new Date(blob.lastModified) : new Date();
|
||||
return await FileType.getOrCreate({
|
||||
@ -48,5 +52,24 @@ export const FileType = PouchDB.registerType({
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
//
|
||||
// static validate(doc) {
|
||||
// // TODO actually validate perhaps against a JSON schema
|
||||
//
|
||||
// const schema = {
|
||||
// name: t.REQUIRED_STRING,
|
||||
// mimetype: t.REQUIRED_STRING,
|
||||
// digest: t.REQUIRED_STRING,
|
||||
// size: t.INTEGER,
|
||||
// modifiedDate: t.DATE,
|
||||
// addDate: t.DATE,
|
||||
// hasData: t.REQUIRED_BOOLEAN,
|
||||
// tags: {
|
||||
// type: "object",
|
||||
// additionalProperties: t.BOOLEAN
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
export const FileType = PouchDB.registerType('File', FileSpec);
|
||||
|
||||
@ -1,13 +1,35 @@
|
||||
import { PouchDB, TYPES as t } from '../services/db.js';
|
||||
import { PouchDB, TypeSpec } from '../services/db.js';
|
||||
import { blobToArrayBuffer } from '../utils/conversion.js';
|
||||
import { backgroundTask } from '../utils/event.js';
|
||||
import { FileType } from './file.js';
|
||||
|
||||
export const ImageType = PouchDB.registerType({
|
||||
name: 'Image',
|
||||
getUniqueID: doc => doc.digest.substr(0, 16),
|
||||
getSequence: doc => new Date(doc.originalDate).getTime(),
|
||||
// schema: {
|
||||
class ImageSpec extends TypeSpec {
|
||||
static async upload(blob) {
|
||||
const f = await FileType.upload(blob, false);
|
||||
return await ImageType.getOrCreate({
|
||||
digest: f.digest,
|
||||
originalDate: f.lastModified,
|
||||
importing: true,
|
||||
width: 0,
|
||||
height: 0,
|
||||
sizes: {
|
||||
full: FileType.getURL(f)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static getUniqueID(doc) {
|
||||
return doc.digest.substr(0, 16);
|
||||
}
|
||||
|
||||
static getSequence(doc) {
|
||||
return new Date(doc.originalDate).getTime();
|
||||
}
|
||||
//
|
||||
// static validate(doc) {
|
||||
// // TODO actually validate perhaps against a JSON schema
|
||||
//
|
||||
// const schema = {
|
||||
// originalDate: t.REQUIRED_DATE,
|
||||
// digest: t.REQUIRED_STRING,
|
||||
// width: t.INTEGER,
|
||||
@ -37,22 +59,11 @@ export const ImageType = PouchDB.registerType({
|
||||
// type: "object",
|
||||
// additionalProperties: t.BOOLEAN
|
||||
// }
|
||||
// },
|
||||
methods: {
|
||||
upload: async function(blob) {
|
||||
const f = await FileType.upload(blob, false);
|
||||
return await ImageType.getOrCreate({
|
||||
digest: f.digest,
|
||||
originalDate: f.lastModified,
|
||||
importing: true,
|
||||
width: 0,
|
||||
height: 0,
|
||||
sizes: {
|
||||
full: FileType.getURL(f)
|
||||
}
|
||||
});
|
||||
},
|
||||
processImportables: backgroundTask(async function _processImportables(importables) {
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
const processImportables = backgroundTask(async function _processImportables(importables) {
|
||||
if (!importables.length) {
|
||||
return;
|
||||
}
|
||||
@ -69,9 +80,7 @@ export const ImageType = PouchDB.registerType({
|
||||
const { tags, imageSize } = exifData;
|
||||
const { width, height } = imageSize;
|
||||
const originalDate = new Date(
|
||||
tags.DateTimeOriginal
|
||||
? new Date(tags.DateTimeOriginal * 1000).toISOString()
|
||||
: image.originalDate
|
||||
tags.DateTimeOriginal ? new Date(tags.DateTimeOriginal * 1000).toISOString() : image.originalDate
|
||||
).toISOString();
|
||||
|
||||
const img = await ImageType.getOrCreate({
|
||||
@ -97,8 +106,8 @@ export const ImageType = PouchDB.registerType({
|
||||
|
||||
const module = await import('../context/generateThumbnails');
|
||||
await module.generateThumbnailForImage(img);
|
||||
}, false)
|
||||
}
|
||||
});
|
||||
}, false);
|
||||
|
||||
ImageType.find({ importing: true }, true).then(fw => fw.subscribe(ImageType.processImportables));
|
||||
export const ImageType = PouchDB.registerType('Image', ImageSpec);
|
||||
|
||||
ImageType.find({ importing: true }, true).then(fw => fw.subscribe(processImportables));
|
||||
|
||||
@ -10,6 +10,12 @@ export function error(...args) {
|
||||
}
|
||||
}
|
||||
|
||||
export function warn(...args) {
|
||||
if (__DEV__) {
|
||||
console.warn(...args);
|
||||
}
|
||||
}
|
||||
|
||||
export function group(...args) {
|
||||
if (__DEV__) {
|
||||
console.group(...args);
|
||||
|
||||
@ -4,7 +4,7 @@ import http from 'pouchdb-adapter-http';
|
||||
import replication from 'pouchdb-replication';
|
||||
import find from 'pouchdb-find';
|
||||
|
||||
import { log } from './console.js';
|
||||
import { log, warn } from './console.js';
|
||||
import { isObject } from '../utils/comparators.js';
|
||||
import { LiveArray } from '../utils/livearray.js';
|
||||
import { deepAssign } from '../utils/conversion.js';
|
||||
@ -16,128 +16,102 @@ export const PouchDB = core
|
||||
.plugin(find)
|
||||
.plugin(PouchORM);
|
||||
|
||||
export function generateAttachmentUrl(dbName, docId, attachmentKey) {
|
||||
return `/_doc_attachments/${dbName}/${docId}/${attachmentKey}`;
|
||||
}
|
||||
|
||||
const dbs = new Map();
|
||||
export function getDatabase(name = 'gallery') {
|
||||
if (!dbs.has(name)) {
|
||||
dbs.set(name, new PouchDB(name));
|
||||
}
|
||||
return dbs.get(name);
|
||||
}
|
||||
|
||||
export async function getOrCreate(doc) {
|
||||
const db = getDatabase();
|
||||
try {
|
||||
const results = await db.get(doc._id);
|
||||
return [results, false];
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
const results = db.put(doc);
|
||||
return [results, true];
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function PouchORM(PouchDB) {
|
||||
async function update(props, save = true) {
|
||||
deepAssign(this, props);
|
||||
if (save) {
|
||||
await this.save();
|
||||
} else {
|
||||
this.validate();
|
||||
}
|
||||
return this;
|
||||
export class TypeSpec {
|
||||
constructor(props) {
|
||||
this._populateId(props);
|
||||
Object.assign(this, props);
|
||||
}
|
||||
|
||||
PouchDB.registerType = opts => {
|
||||
const { getUniqueID, getSequence, schema, name } = opts;
|
||||
const prefix = name.toLowerCase();
|
||||
const db = opts.db || new PouchDB(prefix);
|
||||
static getSequence(doc) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function populateId(doc) {
|
||||
static getUniqueID(doc) {
|
||||
throw 'NotImplemented';
|
||||
}
|
||||
|
||||
static validate(doc) {}
|
||||
|
||||
instantiate(doc) {
|
||||
return new this._cls(docs);
|
||||
}
|
||||
|
||||
_populateId(doc) {
|
||||
if (!doc._id) {
|
||||
const sequence = getSequence ? getSequence(doc).toString(36) : '';
|
||||
doc._id = `${prefix}_${sequence}_${getUniqueID(doc)}`;
|
||||
doc._id = `${this._prefix}_${this._cls.getSequence(doc)}_${this._cls.getUniqueID(doc)}`;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
// FIXME
|
||||
return this;
|
||||
async delete() {
|
||||
return await this.update({ _deleted: true });
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const { rev } = await db.put(this.validate());
|
||||
async save() {
|
||||
this._cls.validate(this);
|
||||
const { rev } = await this._db.put(this);
|
||||
this._rev = rev;
|
||||
return this;
|
||||
}
|
||||
|
||||
async function addAttachment(attName, dataBlob) {
|
||||
const { rev } = await db.putAttachment(this._id, attName, this._rev, dataBlob, dataBlob.type);
|
||||
async addAttachment(attName, dataBlob) {
|
||||
const { rev } = await this._db.putAttachment(
|
||||
this._id,
|
||||
attName,
|
||||
this._rev,
|
||||
dataBlob,
|
||||
dataBlob.type
|
||||
);
|
||||
|
||||
this._rev = rev;
|
||||
return this;
|
||||
}
|
||||
|
||||
async function getAttachment(attName) {
|
||||
return await db.getAttachment(this._id, attName);
|
||||
async getAttachment(attName) {
|
||||
return await this._db.getAttachment(this._id, attName);
|
||||
}
|
||||
|
||||
async function removeAttachment(attName) {
|
||||
return await db.removeAttachment(this._id, attName, this._rev);
|
||||
async removeAttachment(attName) {
|
||||
return await this._db.removeAttachment(this._id, attName, this._rev);
|
||||
}
|
||||
|
||||
function instantiate(docOrResultSet) {
|
||||
Object.defineProperties(docOrResultSet, {
|
||||
update: { value: update.bind(docOrResultSet) },
|
||||
save: { value: save.bind(docOrResultSet) },
|
||||
delete: { value: _delete.bind(docOrResultSet) },
|
||||
addAttachment: { value: addAttachment.bind(docOrResultSet) },
|
||||
getAttachment: { value: getAttachment.bind(docOrResultSet) },
|
||||
removeAttachment: { value: removeAttachment.bind(docOrResultSet) },
|
||||
validate: { value: validate.bind(docOrResultSet) }
|
||||
});
|
||||
return docOrResultSet;
|
||||
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);
|
||||
|
||||
if (!cls.hasOwnProperty('validate')) {
|
||||
warn(`${cls.name} has no validation.`);
|
||||
}
|
||||
|
||||
const instantiate = doc => new cls(doc);
|
||||
|
||||
async function find(idOrQuery, live = false) {
|
||||
let results = [];
|
||||
|
||||
if (typeof idOrQuery === 'string') {
|
||||
results = await db.get(idOrQuery);
|
||||
} else {
|
||||
return instantiate(await _db.get(idOrQuery));
|
||||
}
|
||||
|
||||
const selector = Object.assign(
|
||||
{ _deleted: { exists: false } },
|
||||
isObject(idOrQuery) ? idOrQuery : { _id: { $gt: `${prefix}_0`, $lt: `${prefix}_\ufff0` } }
|
||||
);
|
||||
if (live) {
|
||||
return LiveArray(db, idOrQuery, instantiate);
|
||||
return LiveArray(_db, idOrQuery, instantiate);
|
||||
}
|
||||
results = await db.find({ selector: idOrQuery });
|
||||
}
|
||||
|
||||
return instantiate(results);
|
||||
}
|
||||
|
||||
async function _delete() {
|
||||
return await this.update({ _deleted: true });
|
||||
}
|
||||
|
||||
async function _new(props, save = true) {
|
||||
const doc = instantiate(populateId(props));
|
||||
if (save) {
|
||||
await doc.save();
|
||||
}
|
||||
return doc;
|
||||
return (await _db.find({ selector: idOrQuery })).docs.map(instantiate);
|
||||
}
|
||||
|
||||
async function getOrCreate(props) {
|
||||
let doc = await _new(props, false);
|
||||
let doc = await new cls(props);
|
||||
try {
|
||||
await doc.save();
|
||||
} catch (e) {
|
||||
@ -149,28 +123,18 @@ export function PouchORM(PouchDB) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
new: _new,
|
||||
getOrCreate,
|
||||
find,
|
||||
prefix,
|
||||
db
|
||||
// delete: // FIXME
|
||||
},
|
||||
opts.methods || {}
|
||||
);
|
||||
Object.defineProperties(cls.prototype, {
|
||||
_name: { value: name },
|
||||
_prefix: { value: prefix },
|
||||
_db: { value: _db },
|
||||
_cls: { value: cls }
|
||||
});
|
||||
|
||||
Object.defineProperties(cls, {
|
||||
getOrCreate: { value: getOrCreate },
|
||||
find: { value: find }
|
||||
});
|
||||
|
||||
return cls;
|
||||
};
|
||||
}
|
||||
|
||||
export const TYPES = {
|
||||
STRING: { type: 'string' },
|
||||
INTEGER: { type: 'integer' },
|
||||
BOOLEAN: { type: 'boolean' },
|
||||
DATE: { type: 'date' }
|
||||
};
|
||||
|
||||
// Add required types
|
||||
Object.keys(TYPES).forEach(k => {
|
||||
TYPES['REQUIRED_' + k] = Object.assign({ required: true }, TYPES[k]);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user