@type-r/models
Version:
The serializable type system for JS and TypeScript
154 lines (119 loc) • 5.15 kB
text/typescript
import { Model } from '../model';
import { Transaction, transactionApi } from '../transactions';
import { addIndex, CollectionCore, CollectionOptions, CollectionTransaction, convertAndAquire, Elements, free, freeAll, IdIndex, logAggregationError, sortElements } from './commons';
const { begin, commit, markAsDirty } = transactionApi;
/** @private */
const silentOptions = { silent : true };
/** @private */
export function emptySetTransaction( collection : CollectionCore, items : Elements, options : CollectionOptions, silent? : boolean ){
const isRoot = begin( collection );
const added = _reallocateEmpty( collection, items, options );
if( added.length ){
const needSort = sortElements( collection, options );
if( markAsDirty( collection, silent ? silentOptions : options ) ){
// 'added' is the reference to this.models. Need to copy it.
return new CollectionTransaction( collection, isRoot, added.slice(), [], [], needSort );
}
if( collection._aggregationError ) logAggregationError( collection, options );
}
// No changes...
isRoot && commit( collection );
};
/** @private */
export function setTransaction( collection, items, options ){
const isRoot = begin( collection ),
nested = [];
var previous = collection.models,
added = _reallocate( collection, items, nested, options );
const reusedCount = collection.models.length - added.length,
removed = reusedCount < previous.length ? (
reusedCount ? _garbageCollect( collection, previous ) :
freeAll( collection, previous )
) : [];
const addedOrChanged = nested.length || added.length,
// As we are reallocating models array, it needs to be sorted even if there are no changes.
sorted = ( sortElements( collection, options ) && addedOrChanged ) || added.length || options.sorted;
if( addedOrChanged || removed.length || sorted ){
if( markAsDirty( collection, options ) ){
return new CollectionTransaction( collection, isRoot, added, removed, nested, sorted );
}
if( collection._aggregationError ) logAggregationError( collection, options );
}
isRoot && commit( collection );
};
// Remove references to all previous elements, which are not present in collection.
// Returns an array with removed elements.
/** @private */
function _garbageCollect( collection : CollectionCore, previous : Model[] ) : Model[]{
const { _byId } = collection,
removed = [];
// Filter out removed models and remove them from the index...
for( let record of previous ){
if( !_byId[ record.cid ] ){
removed.push( record );
free( collection, record );
}
}
return removed;
}
// reallocate model and index
/** @private */
function _reallocate( collection : CollectionCore, source : any[], nested : Transaction[], options ){
var models = Array( source.length ),
_byId : IdIndex = {},
merge = ( options.merge == null ? true : options.merge ) && !collection._shared,
_prevById = collection._byId,
prevModels = collection.models,
idAttribute = collection.model.prototype.idAttribute,
toAdd = [],
orderKept = true;
// for each item in source set...
for( var i = 0, j = 0; i < source.length; i++ ){
var item = source[ i ],
model : Model = null;
if( item ){
var id = item[ idAttribute ],
cid = item.cid;
if( _byId[ id ] || _byId[ cid ] ) continue;
model = _prevById[ id ] || _prevById[ cid ];
}
if( model ){
if( merge && item !== model ){
if( orderKept && prevModels[ j ] !== model ) orderKept = false;
var attrs = item.attributes || item;
const transaction = model._createTransaction( attrs, options );
transaction && nested.push( transaction );
}
}
else{
model = convertAndAquire( collection, item, options );
toAdd.push( model );
}
models[ j++ ] = model;
addIndex( _byId, model );
}
models.length = j;
collection.models = models;
collection._byId = _byId;
if( !orderKept ) options.sorted = true;
return toAdd;
}
/** @private */
function _reallocateEmpty( self, source, options ){
var len = source ? source.length : 0,
models = Array( len ),
_byId : IdIndex = {},
idAttribute = self.model.prototype.idAttribute;
for( var i = 0, j = 0; i < len; i++ ){
var src = source[ i ];
if( src && ( _byId[ src[ idAttribute ] ] || _byId[ src.cid ] ) ){
continue;
}
var model = convertAndAquire( self, src, options );
models[ j++ ] = model;
addIndex( _byId, model );
}
models.length = j;
self._byId = _byId;
return self.models = models;
}