breeze-client
Version:
Breeze data management for JavaScript clients
368 lines (364 loc) • 14.1 kB
JavaScript
import { EntityState, breeze } from 'breeze-client';
//#region Copyright, Version, and Description
function enableSaveQueuing(em, enable = true) {
let saveQueuing = em['_saveQueueing'] ||
(em['_saveQueuing'] = new SaveQueuing(em));
enable = (enable === undefined) ? true : enable;
saveQueuing._isEnabled = enable;
if (enable) {
// delegate to save changes queuing
em.saveChanges = saveChangesWithQueuing;
}
else {
// revert to the native EntityManager.saveChanges
em.saveChanges = em['_saveQueuing'].baseSaveChanges;
}
}
/**
* Replacement for EntityManager.saveChanges
* This version queues saveChanges calls while a real save is in progress
**/
function saveChangesWithQueuing(entities, saveOptions) {
try {
// `this` is an EntityManager
let saveQueuing = this._saveQueuing;
if (saveQueuing.isSaving) {
// save in progress; queue the save for later
return saveQueuing.queueSaveChanges(entities);
}
else {
// note that save is in progress; then save
saveQueuing.isSaving = true;
saveQueuing.saveOptions = saveOptions;
return saveQueuing.saveChanges(entities, saveOptions);
}
}
catch (err) {
return Promise.reject(err);
}
}
///////// SaveQueuing /////////
class SaveQueuing {
entityManager;
baseSaveChanges;
isSaving;
_isEnabled;
activeSaveDeferred;
nextSaveDeferred;
activeSaveMemo;
nextSaveMemo;
saveOptions;
constructor(entityManager) {
this.entityManager = entityManager;
this.baseSaveChanges = entityManager.saveChanges;
this.isSaving = false;
}
isEnabled() {
return this._isEnabled;
}
getSavedNothingResult() {
return { entities: [], keyMappings: [] };
}
queueSaveChanges(entities) {
let self = this; // `this` is a SaveQueuing
let em = self.entityManager;
let changes = entities || em.getChanges();
if (changes.length === 0) {
return Promise.resolve(this.getSavedNothingResult());
}
let valError = em.saveChangesValidateOnClient(changes);
if (valError) {
return Promise.reject(valError);
}
let saveMemo = self.nextSaveMemo || (self.nextSaveMemo = new SaveMemo());
memoizeChanges();
let deferred = self.nextSaveDeferred || (self.nextSaveDeferred = new Deferred());
return deferred.promise;
function memoizeChanges() {
if (changes.length === 0) {
return;
}
let queuedChanges = saveMemo.queuedChanges;
changes.forEach(e => {
if (!e.entityAspect.isBeingSaved && queuedChanges.indexOf(e) === -1) {
queuedChanges.push(e);
}
});
saveMemo.updateEntityMemos(changes);
}
}
saveChanges(entities, saveOptions) {
let self = this; // `this` is a SaveQueuing
let promise = self.baseSaveChanges.call(self.entityManager, entities, saveOptions || self.saveOptions)
.then(function (saveResult) { return self.saveSucceeded(saveResult); })
.then(null, function (error) { console.log(error); return self.saveFailed(error); });
rememberAddedOriginalValues(entities); // do it after ... so don't send OrigValues to the server
return promise;
function rememberAddedOriginalValues(entities) {
// added entities normally don't have original values but these will now
let added = entities ?
entities.filter(function (e) { return e.entityAspect.entityState.isAdded(); }) :
self.entityManager.getEntities(null, EntityState.Added);
added.forEach(entity => {
let props = entity.entityType.dataProperties;
let originalValues = entity.entityAspect.originalValues;
props.forEach(dp => {
if (dp.isPartOfKey) {
return;
}
originalValues[dp.name] = entity.getProperty(dp.name);
});
});
}
}
saveSucceeded(saveResult) {
let self = this; // `this` is a SaveQueueing
let activeSaveDeferred = self.activeSaveDeferred;
let nextSaveDeferred = self.nextSaveDeferred;
let nextSaveMemo = self.nextSaveMemo;
// prepare as if nothing queued or left to save
self.isSaving = false;
self.activeSaveDeferred = null;
self.activeSaveMemo = null;
self.nextSaveDeferred = null;
self.nextSaveMemo = null;
if (nextSaveMemo) {
// a save was queued since last save returned
nextSaveMemo.pkFixup(saveResult.keyMappings);
nextSaveMemo.applyToSavedEntities(self.entityManager, saveResult.entities);
// remove detached entities from queuedChanges
let queuedChanges = nextSaveMemo.queuedChanges.filter(e => {
return !e.entityAspect.entityState.isDetached();
});
if (queuedChanges.length > 0) {
// save again
self.isSaving = true;
// remember the queued changes that triggered this save
self.activeSaveDeferred = nextSaveDeferred;
self.activeSaveMemo = nextSaveMemo;
self.saveChanges(queuedChanges, this.saveOptions);
}
else if (nextSaveDeferred) {
nextSaveDeferred.resolve(this.getSavedNothingResult());
}
}
if (activeSaveDeferred) {
activeSaveDeferred.resolve(saveResult);
}
return saveResult; // for the current promise chain
}
saveFailed(error) {
let self = this; // `this` is a SaveQueueing
error = new QueuedSaveFailedError(error, self);
let activeSaveDeferred = self.activeSaveDeferred;
let nextSaveDeferred = self.nextSaveDeferred;
self.isSaving = false;
self.activeSaveDeferred = null;
self.activeSaveMemo = null;
self.nextSaveDeferred = null;
self.nextSaveMemo = null;
if (activeSaveDeferred) {
activeSaveDeferred.reject(error);
}
if (nextSaveDeferred) {
nextSaveDeferred.reject(error);
}
return Promise.reject(error); // let promise chain hear error
}
}
/// for backward compat with older Promise implementation
class Deferred {
resolve;
reject;
promise;
constructor() {
this.promise = new Promise(function (resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this));
}
}
////////// QueuedSaveFailedError /////////
// Error sub-class thrown when rejecting queued saves.
class QueuedSaveFailedError extends Error {
name = "QueuedSaveFailedError";
innerError;
message;
failedSaveMemo;
nextSaveMemo;
// Error sub-class thrown when rejecting queued saves.
// `innerError` is the actual save error
// `failedSaveMemo` is the saveMemo that prompted this save
// `nextSaveMemo` holds queued changes accumulated since that save.
// You may try to recover using this info. Good luck with that.
constructor(errObject, saveQueuing) {
super();
this.innerError = errObject;
this.message = "Queued save failed: " + errObject.message;
this.failedSaveMemo = saveQueuing.activeSaveMemo;
this.nextSaveMemo = saveQueuing.nextSaveMemo;
}
}
////////// SaveMemo ////////////////
// SaveMemo is a record of changes for a queued save, consisting of:
// entityMemos: info about entities that are being saved and
// have been changed since the save started
// queuedChanges: entities that are queued for save but
// are not currently being saved
class SaveMemo {
entityMemos;
queuedChanges;
constructor() {
this.entityMemos = {};
this.queuedChanges = [];
}
applyToSavedEntities(entityManager, savedEntities) {
let entityMemos = this.entityMemos; // `this` is a SaveMemo
let queuedChanges = this.queuedChanges;
let restorePublishing = this.disableManagerPublishing(entityManager);
try {
savedEntities.forEach(saved => {
let key = this.makeEntityMemoKey(saved);
let entityMemo = entityMemos[key];
let resave = entityMemo && entityMemo.applyToSavedEntity(saved);
if (resave) {
queuedChanges.push(saved);
}
});
}
finally {
restorePublishing();
// D#2651 hasChanges will be wrong if changes made while save in progress
let hasChanges = queuedChanges.length > 0;
// Must use breeze internal method to properly set this flag true
if (hasChanges) {
entityManager._setHasChanges(true);
}
}
}
disableManagerPublishing(manager) {
let Event = breeze.Event;
Event.enable('entityChanged', manager, false);
Event.enable('hasChangesChanged', manager, false);
return function restorePublishing() {
Event.enable('entityChanged', manager, true);
Event.enable('hasChangesChanged', manager, true);
};
}
pkFixup(keyMappings) {
let entityMemos = this.entityMemos; // `this` is a SaveMemo
keyMappings.forEach(km => {
let type = km.entityTypeName;
let tempKey = type + '|' + km.tempValue;
if (entityMemos[tempKey]) {
entityMemos[type + '|' + km.realValue] = entityMemos[tempKey];
delete entityMemos[tempKey];
}
for (let memoKey in entityMemos) {
entityMemos[memoKey].fkFixup(km);
}
});
}
makeEntityMemoKey(entity) {
let entityKey = entity.entityAspect.getKey();
return entityKey.entityType.name + '|' + entityKey.values;
}
updateEntityMemos(changes) {
let entityMemos = this.entityMemos; // `this` is a SaveMemo
changes.forEach(change => {
// only update entityMemo for entity being save
if (!change.entityAspect.isBeingSaved) {
return;
}
let key = this.makeEntityMemoKey(change);
let entityMemo = entityMemos[key] || (entityMemos[key] = new EntityMemo(change));
entityMemo.update();
});
}
}
///////// EntityMemo Type ///////////////
// Information about an entity that is being saved
// and which has been changed since that save started
class EntityMemo {
entity;
pendingChanges;
isDeleted;
constructor(entity) {
this.entity = entity;
this.pendingChanges = {};
}
applyToSavedEntity(saved) {
let entityMemo = this;
let aspect = saved.entityAspect;
if (aspect.entityState.isDetached()) {
return false;
}
else if (entityMemo.isDeleted) {
aspect.setDeleted();
return true;
}
// treat entity with pending changes as modified
let props = Object.keys(entityMemo.pendingChanges);
if (props.length === 0) {
return false;
}
let originalValues = aspect.originalValues;
props.forEach(name => {
originalValues[name] = saved.getProperty(name);
saved.setProperty(name, entityMemo.pendingChanges[name]);
});
aspect.setModified();
return true;
}
fkFixup(keyMapping) {
let entityMemo = this;
let type = entityMemo.entity.entityType;
let fkProps = type.foreignKeyProperties;
fkProps.forEach(fkProp => {
if (fkProp.parentType.name === keyMapping.entityTypeName &&
entityMemo.pendingChanges[fkProp.name] === keyMapping.tempValue) {
entityMemo.pendingChanges[fkProp.name] = keyMapping.realValue;
}
});
}
// update the entityMemo of changes to an entity being saved
// so that we know how to save it again later
update() {
let entityMemo = this;
let props;
let entity = entityMemo.entity;
let aspect = entity.entityAspect;
let stateName = aspect.entityState.name;
switch (stateName) {
case 'Added':
let originalValues = aspect.originalValues;
props = entity.entityType.dataProperties;
props.forEach(dp => {
if (dp.isPartOfKey) {
return;
}
let name = dp.name;
let value = entity.getProperty(name);
if (originalValues[name] !== value) {
entityMemo.pendingChanges[name] = value;
}
});
break;
case 'Deleted':
entityMemo.isDeleted = true;
entityMemo.pendingChanges = {};
break;
case 'Modified':
props = Object.keys(aspect.originalValues);
props.forEach(name => {
entityMemo.pendingChanges[name] = entity.getProperty(name);
});
break;
}
}
}
/**
* Generated bundle index. Do not edit.
*/
export { QueuedSaveFailedError, enableSaveQueuing };
//# sourceMappingURL=breeze-client-mixin-save-queuing.mjs.map