Skip to content

Instantly share code, notes, and snippets.

@andrewluetgers
Created March 2, 2017 17:49
Show Gist options
  • Save andrewluetgers/1a5a1d30cd325c00483f57ad89bbba61 to your computer and use it in GitHub Desktop.
Save andrewluetgers/1a5a1d30cd325c00483f57ad89bbba61 to your computer and use it in GitHub Desktop.
// 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