Created
March 2, 2017 17:49
-
-
Save andrewluetgers/1a5a1d30cd325c00483f57ad89bbba61 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// work in progress | |
// problem: you push a change to the database now your whole app needs to also update to that change | |
// one solution is to fetch the data back from the server just after it has changed | |
// this assumes acid updates, elastic search is not acid, nor are many nosql databases | |
// this is an impossible solution without acid | |
// the alternative is to retain an in memory copy of the data and update it optimistically | |
// if there is an error from the server roll back the in memory change | |
// if there is no error the update is already in memory, barring any server side transformations not accounted for | |
// to make this work you need to keep each object in memory over time, | |
// rather than just the active app state anything that has been loaded in the current session must be kept | |
// furthermore this data must be held in a normalized structure for addressability such as by id in a map | |
// then if data is loaded from the server and it is older than in memory the in memory data wins as a change is probably in flight | |
// this code is the minimal approach to doing this in an existing app without having to rewrite the app to use normalized data | |
// a simple schema tells the syncStore how to accept incoming objects and normalize them and then return the original incoming | |
// data-structure with the more recent in-memory changes applied | |
// this can function mostly transparently to the app with minimal code changes | |
// todo refactor to use a syncDiff fn instead of a date check | |
function processItem(cache, idKey, d, updatedKey, dateParser, forceUpdate, remove) { | |
var id = d[idKey], | |
c = cache[id]; | |
if (forceUpdate || !c || c && dateParser(d[updatedKey]) > dateParser(c[updatedKey])) { | |
if (forceUpdate) { | |
d[updatedKey] = new Date().getTime(); | |
} | |
cache[id] = remove ? tombstone : d; | |
callbacks.forEach(cb => cb(d, c, d !== c, forceUpdate, remove)); | |
} | |
return cache[id]; | |
} | |
function _dateParser(d) { | |
return new Date(d).getTime(); | |
} | |
var callbacks = [], | |
tombstone = {deleted: true}; | |
module.exports = { | |
state: {}, | |
schemas: { | |
guid: {name: "guid", id: "id", updated: "updated", dateParser: _dateParser} | |
}, | |
onUpdate: function(cb) { | |
callbacks.push(cb); | |
}, | |
schema: function(name, schema) { | |
this.schemas[name] = schema; | |
}, | |
// todo load schema separately | |
// todo load schema by name | |
latest: function syncStore(data, schemaName, forceUpdate, remove) { | |
// schema = {id: "non-globally-unique id", name: "schemaKey", updated: "datestr", dateParser: fn (optional)} | |
// schema = {id: "globally-unique id", updated: "datestr", dateParser: fn (optional)} | |
schemaName = schemaName || "guid"; | |
if (!(schemaName in this.schemas)) { | |
this.schemas[schemaName] = {name: schemaName, id: "id", updated: "updated", dateParser: _dateParser}; | |
} | |
var schema = this.schemas[schemaName], | |
name = schema.name, | |
cache = this.state[name] = this.state[name] || {}, | |
idKey = schema.id, | |
updatedKey = schema.updated, | |
dateParser = schema.dateParser || _dateParser, | |
wrapped = "concat" in data, | |
dat = wrapped ? data : [data], | |
result; | |
result = dat.map(d => processItem(cache, idKey, d, updatedKey, dateParser, forceUpdate, remove)); | |
return wrapped ? result : result[0]; | |
}, | |
update: function(data, schemaName) { | |
return this.input(data, schemaName, true); | |
}, | |
delete: function(data, schemaName) { | |
this.update(data, schemaName, true, true); | |
}, | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment