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