UNPKG

transactional

Version:

Reactive objects with transactional updates and automatic serialization

159 lines (128 loc) 5.75 kB
import { Attribute } from './attribute.ts'; import { createAttribute } from './typespec.ts'; import { defaults, isValidJSON, transform } from '../tools.ts' import { log } from '../tools.ts' interface ICompiled { _attributes : AttrSpecs Attributes : CloneCtor properties : PropertyDescriptorMap forEach? : ForEach defaults : Defaults _toJSON : ToJSON _parse : Parse } interface AttrSpecs { [ key : string ] : Attribute } interface AttrValues { [ key : string ] : any } type CloneCtor = new ( x : AttrValues ) => AttrValues type ForEach = ( obj : {}, iteratee : ( val : any, key? : string, spec? : Attribute ) => void ) => void; type Defaults = ( attrs? : {} ) => {} type Parse = ( data : any ) => any; type ToJSON = () => any; // Compile attributes spec export function compile( rawSpecs : {}, baseAttributes : AttrSpecs ) : ICompiled { const myAttributes = transform( <AttrSpecs>{}, rawSpecs, createAttribute ), allAttributes = defaults( <AttrSpecs>{}, myAttributes, baseAttributes ), Attributes = createCloneCtor( allAttributes ), mixin : ICompiled = { Attributes : Attributes, _attributes : new Attributes( allAttributes ), properties : transform( <PropertyDescriptorMap>{}, myAttributes, x => x.createPropertyDescriptor() ), defaults : createDefaults( allAttributes ), _toJSON : createToJSON( allAttributes ), // <- TODO: profile and check if there is any real benefit. I doubt it. _parse : createParse( myAttributes, allAttributes ) }; // Enable optimized forEach if warnings are disabled. if( log.level > 0 ){ mixin.forEach = createForEach( allAttributes ); } return mixin; } export function createForEach( attrSpecs : AttrSpecs ) : ForEach { let statements = [ 'var v, _a=this._attributes;' ]; for( let name in attrSpecs ){ statements.push( `( v = a.${name} ) === void 0 || f( v, "${name}", _a.${name} );` ); } return <ForEach> new Function( 'a', 'f', statements.join( '' ) ); } export function createCloneCtor( attrSpecs : AttrSpecs ) : CloneCtor { var statements = []; for( let name in attrSpecs ){ statements.push( `this.${name} = x.${name};` ); } var CloneCtor = new Function( "x", statements.join( '' ) ); CloneCtor.prototype = Object.prototype; return <CloneCtor> CloneCtor; } // Create optimized model.defaults( attrs, options ) function function createDefaults( attrSpecs : AttrSpecs ) : Defaults { let assign_f = ['var v;'], create_f = []; function appendExpr( name, expr ){ assign_f.push( `this.${name} = ( v = a.${name} ) === void 0 ? ${expr} : v;` ); create_f.push( `this.${name} = ${expr};` ); } // Compile optimized constructor function for efficient deep copy of JSON literals in defaults. for( let name in attrSpecs ){ const attrSpec = attrSpecs[ name ], { value, type } = attrSpec; if( value === void 0 && type ){ // if type with no value is given, create an empty object appendExpr( name, `i.${name}.create()` );//TODO: consider adding owner reference } else{ // If value is given, type casting logic will do the job later, converting value to the proper type. if( isValidJSON( value ) ){ // JSON literals must be deep copied. appendExpr( name, JSON.stringify( value ) ); } else if( value === void 0 ){ // handle undefined value separately. Usual case for model ids. appendExpr( name, 'void 0' ); } else{ // otherwise, copy value by reference. appendExpr( name, `i.${name}.value` ); } } } const CreateDefaults : any = new Function( 'i', create_f.join( '' ) ), AssignDefaults : any = new Function( 'a', 'i', assign_f.join( '' ) ); CreateDefaults.prototype = AssignDefaults.prototype = Object.prototype; // Create model.defaults( attrs, options ) function // 'attrs' will override default values, options will be passed to nested backbone types return function( attrs? : {} ){ //TODO: Consider removing of the CreateDefaults. Currently is not used. May be used in Record costructor, though. return attrs ? new AssignDefaults( attrs, this._attributes ) : new CreateDefaults( this._attributes ); } } function createParse( allAttrSpecs : AttrSpecs, attrSpecs : AttrSpecs ) : Parse { var statements = [ 'var a=this._attributes;' ], create = false; for( let name in allAttrSpecs ){ const local = attrSpecs[ name ]; // Is there any 'parse' option in local model definition? if( local && local.parse ) create = true; // Add statement for each attribute with 'parse' option. if( allAttrSpecs[ name ].parse ){ const s = `r.${name} === void 0 ||( r.${name} = a.${name}.parse.call( this, r.${name}, "${name}") );`; statements.push( s ); } } if( create ){ statements.push( 'return r;' ); return <any> new Function( 'r', statements.join( '' ) ); } } function createToJSON( attrSpecs : AttrSpecs ) : ToJSON { let statements = [ `var json = {},v=this.attributes,a=this._attributes;` ]; for( let key in attrSpecs ){ const toJSON = attrSpecs[ key ].toJSON; if( toJSON ){ statements.push( `json.${key} = a.${key}.toJSON.call( this, v.${ key }, '${key}' );` ); } } statements.push( `return json;` ); return <any> new Function( statements.join( '' ) ); }