@type-r/models
Version:
The serializable type system for JS and TypeScript
127 lines (100 loc) • 4.8 kB
text/typescript
import { ItemsBehavior, Transactional, transactionApi, TransactionOptions } from '../../transactions';
import { ValidationError } from '../../validation';
import { AnyType } from './any';
import { AttributesContainer, ConstructorOptions } from '../updates';
const { free, aquire } = transactionApi;
export class AggregatedType extends AnyType {
type : typeof Transactional
clone( value : Transactional ) : Transactional {
return value ? value.clone() : value;
}
toJSON( x, key : string, options : object ){ return x && x.toJSON( options ); }
doInit( value, record : AttributesContainer, options : ConstructorOptions ){
const v = options.clone ? this.clone( value ) : (
value === void 0 ? this.defaultValue() : value
);
const x = this.transform( v, void 0, record, options );
this.handleChange( x, void 0, record, options );
return x;
}
doUpdate( value, record, options, nested : any[] ){ // Last to things can be wrapped to an object, either transaction or ad-hoc
const key = this.name, { attributes } = record;
const prev = attributes[ key ];
let update;
// This can be moved to transactional attribute. And chained with the rest.
if( update = this.canBeUpdated( prev, value, options ) ) { // todo - skip empty updates.
const nestedTransaction = prev._createTransaction( update, options );
// TODO: sync update tokens
if( nestedTransaction ){
if( nested ){
nested.push( nestedTransaction );
}
else{
nestedTransaction.commit( record );
}
if( this.propagateChanges ) return true;
}
return false;
}
const next = this.transform( value, prev, record, options );
attributes[ key ] = next;
if( this.isChanged( next, prev ) ) { // Primitives and nested comparison can be inlined.
// Do the rest of the job after assignment
this.handleChange( next, prev, record, options );
return true;
}
return false;
}
canBeUpdated( prev : Transactional, next : any, options : TransactionOptions ) : any {
// If an object already exists, and new value is of incompatible type, let object handle the update.
if( prev && next != null ){
if( next instanceof this.type ){
// In case if merge option explicitly specified, force merge.
if( options.merge ) return next.__inner_state__;
}
else{
return next;
}
}
}
convert( next : any, prev : any, record : AttributesContainer, options : TransactionOptions ) : Transactional {
// Invoke class factory to handle abstract classes
if( next == null ) return next;
if( next instanceof this.type ){
if( next._shared && !( next._shared & ItemsBehavior.persistent ) ) { // TODO: think more about shared types assignment compatibility.
this._log( 'error', 'Type-R:InvalidCollection', 'aggregated collection attribute is assigned with shared collection type', next, record, options.logger );
}
// With explicit 'merge' option we need to clone an object if its previous value was 'null'.
// This is an only case we could be here when merge === true.
if( options.merge ) return next.clone();
if( next._owner ){
this._log( 'warn', 'Type-R:InvalidOwner', 'object alreay has an owner and was cloned. Use explicit object.clone() to dismiss this warning.', next, record, options.logger );
return next.clone();
}
return next;
}
return ( this.type as any).create( next, options );
}
dispose ( record : AttributesContainer, value : Transactional ){
if( value ){
this.handleChange( void 0, value, record, {} );
}
}
validate( record : AttributesContainer, value : Transactional ) : ValidationError {
var error = value && value.validationError;
if( error ) return error;
}
create() : Transactional {
return (<any>this.type).create(); // this the subclass of Transactional here.
}
initialize( options ){
options.changeHandlers.unshift( this._handleChange );
}
_handleChange( next : Transactional, prev : Transactional, record : AttributesContainer, options : TransactionOptions ){
if( prev ){
free( record, prev );
options.unset || prev.dispose();
}
if( next ) aquire( record, next, this.name );
}
}