UNPKG

transactional

Version:

Reactive objects with transactional updates and automatic serialization

130 lines (104 loc) 3.57 kB
/** * Type spec engine. Declare attributes using chainable syntax, * and returns object with spec. */ import { Attribute, IAttributeOptions, ChangeAttrHandler } from './attribute.ts' import { assign } from '../tools.ts' import { EventsHash } from '../types.ts' export class AttributeSpecification { options : IAttributeOptions; constructor( options = {} ) { this.options = { getHooks : [], transforms : [], changeHandlers : []}; assign( this.options, options ); } get( fun ) { this.options.getHooks.push( fun ); } set( fun ) { this.options.transforms.push( function( next, options, prev, model ) { if( this.isChanged( next, prev ) ) { var changed = fun.call( model, next, name ); return changed === void 0 ? prev : changed; } return prev; } ); } // Event listeners events( map : EventsHash ){ this.options.changeHandlers.push( function( next, prev, record : IRecord ){ prev && record.stopListening( prev ); next && record.listenTo( next, map ); }); } onChange( handler : ChangeAttrHandler ){ this.options.onChange = handler; // TODO: assemble message map in Record.define, listenTo after Record.initialize // Or directly call in Record._notifyOnAttrChange /* const handler = this._onChange[ name ]; handler && handler.call( this, value, name ) */ } get has() { return this; } get isRequired() { this.options.isRequired = true; return this; } value( x ) { this.options.value = x; } createAttribute( name : string ) : Attribute { const type : any = this.options.type, AttributeCtor = type ? type._attribute : Attribute; return new AttributeCtor( name, this.options ); } } declare global { interface Function{ value : ( x : any ) => AttributeSpecification; isRequired : AttributeSpecification; has : AttributeSpecification; } } Function.prototype.value = function( x ) { return new AttributeSpecification( { type : this, value : x } ); }; Object.defineProperty( Function.prototype, 'isRequired', { get() { return new AttributeSpecification( { type : this, isRequired : true } ); } }); Object.defineProperty( Function.prototype, 'has', { get() { // workaround for sinon.js and other libraries overriding 'has' return this._has || new AttributeSpecification( { type : this } ); }, set( value ) { this._has = value; } } ); export type AttributeDefinition = AttributeSpecification | ( new ( ...args : any[] ) => any ) | {} export function createAttribute( spec : AttributeDefinition, name : string ) : Attribute { let typeSpec; if( spec && spec instanceof AttributeSpecification ) { typeSpec = spec; } else if( typeof spec === 'function' ) { typeSpec = new AttributeSpecification({ type : spec }); } else { typeSpec = new AttributeSpecification({ type : inferType( spec ), value : spec }); } return typeSpec.createAttribute( name ); } function inferType( value : {} ) : Function { switch( typeof value ) { case 'number' : return Number; case 'string' : return String; case 'boolean' : return Boolean; case 'object' : return value ? value.constructor : Object; } }