Skip to content

Instantly share code, notes, and snippets.

@ans-4175
Last active December 17, 2022 20:00
Show Gist options
  • Save ans-4175/d9b8055c40ff9dcafd86e463a9aba84a to your computer and use it in GitHub Desktop.
Save ans-4175/d9b8055c40ff9dcafd86e463a9aba84a to your computer and use it in GitHub Desktop.
Implementation PouchyStore Draft
import PouchStore from 'PouchStore';
import config from 'config';
class ImplementStore extends PouchStore {
get name() {
return this._name;
}
setName(userId) {
this._name = `db_${userId}`;
}
get urlRemote() {
return config.couchDBUrl;
}
get optionsRemote() {
return {
auth: config.couchDBAuth,
};
}
}
export default ImplementStore;
import IPouchDB from 'pouchdb';
export class PouchDB extends IPouchDB {
async getFailSafe(id) {
try {
const doc = await this.get(id);
return doc;
} catch (err) {
if (err.status === 404) {
return null;
}
throw err;
}
}
async update(id, obj) {
const doc = await this.getFailSafe(id) || { _id: id };
Object.assign(doc, obj);
const info = await this.put(doc);
return info;
}
createId() {
let id = (new Date()).getTime().toString(16);
while (id.length < 32) {
id += Math.random().toString(16).split('.').pop();
}
id = id.substr(0, 32);
id = id.replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, '$1-$2-$3-$4-$5');
return id;
}
async getDocs() {
const result = await this.allDocs({
include_docs: true,
});
const docs = result.rows.map(row => row.doc);
return docs;
}
}
import PouchDB from 'PouchDB';
export default class PouchStore {
constructor() {
// set default options
if (!('isUseData' in this)) {
this.isUseData = true;
}
if (!('isUseRemote' in this)) {
this.isUseRemote = true;
}
if (!('optionsRemote' in this)) {
this.optionsRemote = {};
}
this.initializeProperties();
}
initializeProperties() {
// initialize in-memory data
}
async initialize() {
// initalize the databases
this.dbLocal = new PouchDB(this.name, { auto_compaction: true });
this.dbMeta = new PouchDB(`${PREFIX_META_DB}${this.name}`, { auto_compaction: true });
if (this.isUseRemote) {
if (!this.urlRemote) {
throw new Error(`store's urlRemote should not be ${this.urlRemote}`);
}
this.dbRemote = new PouchDB(`${this.urlRemote}${this.name}`, this.optionsRemote);
}
// init metadata
this.dataMeta = await this.dbMeta.getFailSafe(ID_META_DOC) || this.dataMeta;
if (this.isUseRemote) {
// sync data local-remote
try {
await checkInternet(this.urlRemote);
await this.dbLocal.replicate.from(this.dbRemote);
await this.upload();
} catch (err) {
console.log(err);
}
}
// init data from PouchDB to memory
const docs = await this.dbLocal.getDocs();
if (this.single) {
this.data = docs.find(doc => doc._id === this.single) || this.data;
} else if (this.isUseData) {
this.data = docs.filter(doc => !('deletedAt' in doc) || doc.deletedAt === null);
this.sortData(this.data);
}
this.isInitialized = true;
if (this.single || this.isUseData) {
this.notifySubscribers(this.data);
} else {
this.notifySubscribers(docs);
}
this.watchRemote();
this.watchLocal();
}
async deinitialize() {
this.unwatchLocal();
this.unwatchRemote();
await this.dbLocal.close();
await this.dbMeta.close();
if (this.dbRemote) {
await this.dbRemote.close();
}
this.initializeProperties();
this.isInitialized = false;
}
updateMemory(doc) {
// update in memory data
}
sortData(data) {
// do no sorting, override this method to sort
}
async updateMeta(payload) {
// well explained
}
/* watch manager for local DB and remote DB */
watchRemote() {
if (!this.isUseRemote) return;
this.handlerRemoteChange = this.dbLocal.replicate.from(this.dbRemote, {
live: true,
retry: true,
}).on('change', change => {
for (let doc of change.docs) {
this.changeFromRemote[doc._id] = true;
this.updateMemory(doc);
}
this.notifySubscribers(change.docs);
}).on('error', err => {
console.log(`${this.name}.from`, 'error', err);
})
}
unwatchRemote() {
if (this.handlerRemoteChange) {
this.handlerRemoteChange.cancel();
}
}
watchLocal() {
this.handlerLocalChange = this.dbLocal.changes({
since: 'now',
live: true,
include_docs: true,
}).on('change', change => {
const doc = change.doc;
if (this.changeFromRemote[doc._id]) {
delete this.changeFromRemote[doc._id];
} else {
this.updateMemory(doc);
this.notifySubscribers([ doc ]);
}
}).on('error', err => {
console.log(`${this.name}.changes`, 'error', err);
});
}
unwatchLocal() {
if (this.handlerLocalChange) {
this.handlerLocalChange.cancel();
}
}
/* data upload (from local DB to remote DB) */
checkIsUploaded(doc) {
// well explained
}
async setUnuploaded(id, isUnuploaded=true) {
// well explained
}
countUnuploadeds() {
// well explained
}
async upload() {
if (!this.isUseRemote) return;
await checkInternet(this.urlRemote);
const unuploadeds = Object.keys(this.dataMeta.unuploadeds).map(_id => {
return { _id };
});
await this.dbLocal.replicate.to(this.dbRemote);
await this.updateMeta({
tsUpload: new Date().toJSON(),
unuploadeds: {},
});
this.notifySubscribers(unuploadeds);
}
/* manipulation of array data (non-single) */
async addItem(payload, user=null) {
// well explained
}
async addItemWithId(id, payload, user=null) {
// well explained
}
async editItem(id, payload, user=null) {
// well explained
}
async deleteItem(id, user=null) {
// well explained
}
/* manipulation of single data (non-array) */
async editSingle(payload) {
// well explained
}
async deleteSingle() {
// well explained
}
/* subscription manager */
subscribe(subscriber) {
// well explained
}
unsubscribe(subscriber) {
// well explained
}
notifySubscribers(docs) {
// well explained
}
}
import ImplementStore from 'ImplementStore';
const store = new ImplementStore();
// inisialisasi data
store.setName('user-id');
store.initialize();
// add data
store.addItem({
data: null,
})
// check unuploaded data
const countUnploaded = store.countUnuploadeds()
// upload data
store.upload();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment