journeyapps
Advanced tools
Comparing version 0.3.0 to 0.3.1
@@ -11,2 +11,3 @@ // # Database | ||
var evaluator = require('./evaluator'); | ||
var Batch = require('./Batch'); | ||
@@ -70,4 +71,14 @@ require('./primitives'); | ||
this._adapter = adapter; | ||
// Extends batch.Batch, passing adapter as an argument to the constructor. | ||
this.Batch = function() { | ||
Batch.call(this, adapter); | ||
}; | ||
this.Batch.prototype = Object.create(Batch.prototype); | ||
this.Batch.prototype.constructor = Batch; | ||
} | ||
// ## Collection | ||
@@ -287,4 +298,4 @@ // This is the external interface for an object collection, as in `DB.asset`. | ||
// Wait until this object and its relationships has finished loading, then save it. | ||
// We need to wait for the relationships so that we have their id's. | ||
// Returns a promise that is resolved when this object is saved. | ||
@@ -297,43 +308,67 @@ function _save() { | ||
if(saving) { | ||
// Prevent infinite recursion when saving related objects | ||
return Promise.resolve(); | ||
} | ||
saving = true; | ||
try { | ||
var data = objself.toData(false); | ||
var patch = null; | ||
if(persisted) { | ||
patch = objself.toData(true); | ||
// Create a batch with this object. | ||
// Note that other objects may implicitly be added to the batch via | ||
// belongs-to relationships. | ||
var batch = new Batch(adapter); | ||
batch.save(this); | ||
return batch.execute().catch(function(error) { | ||
if(error instanceof Batch.CrudError) { | ||
return Promise.reject(error.firstError()); | ||
} else { | ||
return Promise.reject(error); | ||
} | ||
}); | ||
} | ||
var promise = adapter.save(data, patch); | ||
/** | ||
* Mark the object as persisted. | ||
*/ | ||
function _persisted() { | ||
persisted = true; | ||
// We don't need to wait for the promise to complete - we can immediately reset the state. | ||
persisted = true; | ||
// Clear the "dirty" state | ||
attributesDirty = {}; | ||
belongsToDirty = {}; | ||
} | ||
/** | ||
* Mark the object as destroyed. | ||
*/ | ||
function _destroyed() { | ||
persisted = false; | ||
destroyed = true; | ||
} | ||
// Save related objects | ||
// TODO: we need a way to keep track of the dirty state between this object and related objects, | ||
// so that we don't have to save the entire tree every time. | ||
var promises = [promise]; | ||
/** | ||
* Returns an array of this object and any related objects that should be saved. | ||
* | ||
* @param {object[]} [into] - array to populate | ||
*/ | ||
function _getDirtyList(into) { | ||
if(into == null) { | ||
into = []; | ||
} | ||
if(destroyed) { | ||
// Don't save a destroyed object | ||
return into; | ||
} | ||
if(saving) { | ||
// Prevent recursion into self | ||
return into; | ||
} | ||
saving = true; | ||
into.push(objself); | ||
try { | ||
Object.keys(belongsToCache).forEach(function(key) { | ||
var related = belongsToCache[key]; | ||
// undefined means not dirty, while null means cleared | ||
if(related != null) { | ||
promises.push(related._save()); | ||
if(related._adapter === adapter) { | ||
related._getDirtyList(into); | ||
} else { | ||
// Different adapter - can't add to the same batch. | ||
// Don't save the related object in this case. | ||
} | ||
} | ||
}); | ||
// Clear the "dirty" state | ||
attributesDirty = {}; | ||
belongsToDirty = {}; | ||
return Promise.all(promises); | ||
return into; | ||
} finally { | ||
@@ -347,9 +382,9 @@ saving = false; | ||
function _destroy() { | ||
destroyed = true; | ||
if(id != null) { | ||
var promise = adapter.destroy(objself.type.name, objself.id); | ||
persisted = false; | ||
return promise; | ||
return adapter.destroy(objself.type.name, objself.id).then(function() { | ||
_destroyed(); | ||
}); | ||
} else { | ||
return adapter.deferred(); | ||
destroyed = true; | ||
return Promise.resolve(); | ||
} | ||
@@ -684,2 +719,5 @@ } | ||
defineHiddenConstant(this, '_display', _display); | ||
defineHiddenConstant(this, '_getDirtyList', _getDirtyList); | ||
defineHiddenConstant(this, '_persisted', _persisted); | ||
defineHiddenConstant(this, '_destroyed', _destroyed); | ||
@@ -962,7 +1000,9 @@ defineHiddenConstant(this, 'toData', toData); | ||
return this._fetch().then(function(objects) { | ||
var promises = []; | ||
var batch = new Batch(adapter); | ||
objects.forEach(function(object) { | ||
promises.push(object._destroy()); | ||
batch.destroy(object); | ||
}); | ||
return Promise.all(promises); | ||
return batch.execute(); | ||
}); | ||
@@ -969,0 +1009,0 @@ }; |
@@ -7,4 +7,4 @@ require('isomorphic-fetch'); | ||
var BATCH_LIMIT = 1000; | ||
function apiToInternalFormat(type, apiObj) { | ||
@@ -82,2 +82,3 @@ var dbObj = { | ||
this.name = 'api'; | ||
this.batchLimit = BATCH_LIMIT; | ||
var self = this; | ||
@@ -87,2 +88,14 @@ | ||
function responseHandler(response) { | ||
if(response.ok) { | ||
return response.json(); | ||
} else { | ||
return response.text().then(function(text) { | ||
var error = new Error("HTTP Error " + response.status + ": " + response.statusText + '\n' + text); | ||
error.body = text; | ||
return Promise.reject(error); | ||
}); | ||
} | ||
} | ||
function apiGet(url) { | ||
@@ -96,9 +109,3 @@ return fetch(url, Object.assign({}, | ||
}, credentials.apiAuthHeaders()) | ||
})).then(function(response) { | ||
if(response.ok) { | ||
return response.json(); | ||
} else { | ||
return Promise.reject(new Error("HTTP Error " + response.status + ": " + response.statusText)); | ||
} | ||
}); | ||
})).then(responseHandler); | ||
} | ||
@@ -116,9 +123,3 @@ | ||
body: JSON.stringify(data) | ||
})).then(function(response) { | ||
if(response.ok) { | ||
return response.json(); | ||
} else { | ||
return Promise.reject(new Error("HTTP Error " + response.status + ": " + response.statusText)); | ||
} | ||
}); | ||
})).then(responseHandler); | ||
} | ||
@@ -267,2 +268,7 @@ | ||
/** | ||
* Post crud actions. | ||
* | ||
* @param {*} actions - array of actions | ||
*/ | ||
function postCrud(actions) { | ||
@@ -291,3 +297,8 @@ // TODO: make this work for mobile credentials as well | ||
return postCrud(actions); | ||
return postCrud(actions).then(function(response) { | ||
var error = response.operations[0].error; | ||
if(error) { | ||
throw error; | ||
} | ||
}); | ||
@@ -306,4 +317,59 @@ // TODO: trigger attachment upload | ||
}); | ||
return postCrud(actions); | ||
return postCrud(actions).then(function(response) { | ||
var error = response.operations[0].error; | ||
// For idempotency, ignore OBJECT_NOT_FOUND | ||
if(error && error.type != 'OBJECT_NOT_FOUND') { | ||
throw error; | ||
} | ||
}); | ||
}; | ||
this.applyBatch = function(crud) { | ||
var actions = []; | ||
for(var i = 0; i < crud.length; i++) { | ||
var operation = crud[i]; | ||
var action = operation.op; | ||
if(action == 'put') { | ||
actions.push({ | ||
method: 'put', | ||
object: dbToApiObject(operation.data) | ||
}); | ||
} else if(action == 'patch') { | ||
actions.push({ | ||
method: 'patch', | ||
object: dbToApiObject(operation.patch) | ||
}); | ||
} else if(action == 'delete') { | ||
actions.push({ | ||
method: 'delete', | ||
object: { | ||
type: operation.type, | ||
id: operation.id | ||
} | ||
}); | ||
} else { | ||
throw new Error("Unknown action: " + action); | ||
} | ||
} | ||
// 4xx / 5xx errors are passed through | ||
return postCrud(actions).then(function(response) { | ||
if(response.operations.length != crud.length) { | ||
throw new Error('Internal error - invalid results received from server. Expected ' + crud.length + ' results, got ' + response.operations.length); | ||
} | ||
var results = []; | ||
for(var j = 0; j < crud.length; j++) { | ||
var error = response.operations[j].error; | ||
if(crud[j].op == 'delete' && error.type == 'OBJECT_NOT_FOUND') { | ||
// For idempotency, we ignore these. | ||
error = null; | ||
} | ||
var result = { | ||
error: error | ||
}; | ||
results.push(result); | ||
} | ||
return results; | ||
}); | ||
}; | ||
} | ||
@@ -310,0 +376,0 @@ |
@@ -258,3 +258,2 @@ var IndexedAdapter = require('./IndexedAdapter'); | ||
return this.promisedTransaction(function(tx) { | ||
// This default implementation does each operation in a separate transaction. | ||
for(var i = 0; i < crud.length; i++) { | ||
@@ -284,2 +283,34 @@ var operation = crud[i]; | ||
/** | ||
* Apply a batch of operations in a single transaction. | ||
* | ||
* The entire batch is either successful or not - we don't return errors for | ||
* individual objects. | ||
*/ | ||
WebSQLAdapter.prototype.applyBatch = function(crud) { | ||
this.ensureOpen(); | ||
var self = this; | ||
var results = []; | ||
return this.promisedTransaction(function(tx) { | ||
for(var i = 0; i < crud.length; i++) { | ||
var operation = crud[i]; | ||
var action = operation.op; | ||
if(action == 'put') { | ||
self.transactionSaveData(tx, operation.data, null); | ||
} else if(action == 'patch') { | ||
self.transactionSaveData(tx, operation.data, operation.patch); | ||
} else if(action == 'delete') { | ||
self.transactionDestroy(tx, operation.type, operation.id); | ||
} else { | ||
throw new Error("Unknown action: " + action); | ||
} | ||
results.push({}); | ||
} | ||
}).then(function() { | ||
return self.dataChanged(); | ||
}).then(function() { | ||
return results; | ||
}); | ||
}; | ||
// Persist an object and its relationships. | ||
@@ -286,0 +317,0 @@ // The object and it's relationships MUST be loaded before this is called. |
{ | ||
"name": "journeyapps", | ||
"version": "0.3.0", | ||
"version": "0.3.1", | ||
"description": "Journey JS library", | ||
@@ -41,3 +41,3 @@ "keywords": [ | ||
"eslint": "^0.24.1", | ||
"fetch-mock": "^4.1.1", | ||
"fetch-mock": "^5.9.4", | ||
"grunt": "~0.4.1", | ||
@@ -44,0 +44,0 @@ "grunt-cli": "~0.1.9", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
444160
66
11390