diff --git a/.gitignore b/.gitignore index 35e573a8..d4345031 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -node_modules - ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio *.iml +node_modules +coverage/ + +*.orig + ## Directory-based project format: .idea/ .vscode/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e713f3a2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "node" +install: + - npm install \ No newline at end of file diff --git a/README.md b/README.md index 96e772cc..70fdb973 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,72 @@ -# TransactionalJS - -Minimalistic transactional model core. ES6 - -Will be the basis for next NestedTypes version. - - -Features ------------- -Model - - - new Model( attrs, options ) - - model.set( attrs, options ) - - model.toJSON() - - model.parse( data ) - - model.name = value; - - model.transaction( fun ); - - model.attributes spec - - Primitive Types - - Type inference - - Date - - Model - - Type.value( x ) support. - - NO backbone events depencency - - -1. Transactional API: - -- model.transaction( fun ) -- model.attr = x; - - every assignment is nested transaction - - immediate change:attr event - - optimized for primitives - - 'deep set' uses object sync API. - - every commited nested transaction is local `touch`. - -2. Object sync API. Mostly used inside of fetch. - -- model.set - - optimized for complete object set (unrolled attr loop). - - change:attr is delayed (two-phase commit, nested first) - - treat every nested commited transaction in the same way as local attr change. - - commit nested transactions ( -- ) - - commit self - Sequence (sync): - 0. Open transaction, if needed - 1. Set all local and nested attrs - Check if marked for delayed external commit (delayed change:attr), exit if true. - 2. Commit nested transactions (change:attr + change). - 3. Commit self - -- An API: - - Open transaction and update - - Commit. - +![master build](https://api.travis-ci.org/Volicon/Type-R.svg?branch=master) + +# Getting started + +Type-R is the modern JS data framework to manage complex domain and UI application state. Features: + +- _It's mapped to JSON by default_. The mapping can handle sophisticated scenarios with nested JSON and relations by id, and can be easily customized for every particular attribute or class. +- _All changes are observable_, happens in the scope of transactions, and there's the fine-grained change events system. +- _Validation_ is performed on the first access to the validation error and never happens twice for unchanged data. +- _Everything is typed at run-time_ and is protected from improper updates. The shape of generated JSON and data classes is guaranteed to match the definitions. +- It still looks like regular JS classes and is freaking fast. Type-R data structures are about 10 times faster than Backbone models and collections. + +![overview](docs/images/overview.png) + +Data layer is defined as a superposition of three kinds of building blocks: + +- *Record* classes with typed attributes. +- Ordered *collections* of records. +- *Stores* are records with a set of collections in its attributes used to resolve id-references in JSON. +- *IOEndpoints* is an entity encapsulating I/O transport which represent the persistent collection of records. + +Type-R is completely unopinionated on the client-server transport protocol and the view layer technology. It's your perfect M and VM in modern MVVM or MVC architecture. + +```javascript +import { define, Record } from 'type-r' + +// Define email attribute type with encapsulated validation check. +const Email = String.has.check( x => x! || x.indexOf( '@' ) >= 0, 'Invalid email' ); + +@define class User extends Record { + static attributes = { + name : String.isRequired, // should not be empty for the record to be valid. + email : Email.isRequired + } +} + +@define class Message extends Record { + static attributes = { + created : Date // = new Date() + author : User, // aggregated User record. + to : User.Collection, // aggregating collection of users + subject : '', + body : '' + } +} + +const msg = new Message(); +assert( !msg.isValid() ); // Is not valid because msg.author has empty attributes + +// Listen for the changes in aggregation tree... +msg.on( 'change', () => console.log( 'change!!!' ) ); + +msg.transaction( () => { // Prepare to make the sequence of changes on msg + msg.author.name = 'John Dee'; // No 'change' event yet as we're in the transaction. + msg.author.email = 'dee@void.com'; + + assert( msg.isValid() ); // Now msg is valid as all of its attributes are valid. +}); // Got single 'change!!!' message in the console. +``` + +## [Documentation](https://volicon.github.io/Type-R/) + +## Installation and requirements + +Is packed as UMD and ES6 module. No peer dependencies are required. + +`npm install type-r --save-dev` + + + + + diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..422c116b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.Nested={})}(this,function(s){"use strict";function a(t,e){for(var n in e)e.hasOwnProperty(n)&&!t.hasOwnProperty(n)&&(t[n]=e[n]);if(2( dest : T, ...sources : Object[] ) : T\nexport function defaults< T >( dest : T, source : Object ) : T {\n for( var name in source ) {\n if( source.hasOwnProperty( name ) && !dest.hasOwnProperty( name ) ) {\n dest[ name ] = source[ name ];\n }\n }\n\n if( arguments.length > 2 ){\n for( let i = 2; i < arguments.length; i++ ){\n const other = arguments[ i ];\n other && defaults( dest, other );\n }\n }\n\n return dest;\n}\n\n/** Check if value is raw JSON */\nexport function isValidJSON( value : any ) : boolean {\n if( value === null ){\n return true;\n }\n\n switch( typeof value ){\n case 'number' :\n case 'string' :\n case 'boolean' :\n return true;\n\n case 'object':\n var proto = Object.getPrototypeOf( value );\n\n if( proto === Object.prototype || proto === Array.prototype ){\n return every( value, isValidJSON );\n }\n }\n\n return false;\n}\n\n/** Get the base class constructor function.\n * @param Class Subclass constructor function.\n * @returns Base class constructor function.\n */\nexport function getBaseClass( Class : Function ) {\n return Object.getPrototypeOf( Class.prototype ).constructor\n}\n\nexport function assignToClassProto( Class, definition : T, ...names : K[] ) : void {\n for( let name of names ){\n const value = definition[ name ];\n value === void 0 || ( Class.prototype[ name ] = value );\n }\n}\n\n/** Checks whenever given object is an empty hash `{}` */\nexport function isEmpty( obj : {} ) : boolean {\n if( obj ){\n for( let key in obj ){\n if( obj.hasOwnProperty( key ) ){\n return false;\n }\n }\n }\n\n return true;\n}\n\nexport type Iteratee = ( value : any, key? : string | number ) => any;\n\n/** @hidden */\nfunction someArray( arr : any[], fun : Iteratee ) : any {\n let result;\n\n for( let i = 0; i < arr.length; i++ ){\n if( result = fun( arr[ i ], i ) ){\n return result;\n }\n }\n}\n\n/** @hidden */\nfunction someObject( obj : {}, fun : Iteratee ) : any {\n let result;\n\n for( let key in obj ){\n if( obj.hasOwnProperty( key ) ){\n if( result = fun( obj[ key ], key ) ){\n return result;\n }\n }\n }\n}\n\n/** Similar to underscore `_.some` */\nexport function some( obj, fun : Iteratee ) : any {\n if( Object.getPrototypeOf( obj ) === ArrayProto ){\n return someArray( obj, fun );\n }\n else{\n return someObject( obj, fun );\n }\n}\n\n/** Similar to underscore `_.every` */\nexport function every( obj : { }, predicate : Iteratee ) : boolean {\n return !some( obj, x => !predicate( x ) );\n}\n\n/** Similar to `getOwnPropertyDescriptor`, but traverse the whole prototype chain. */\nexport function getPropertyDescriptor( obj : {}, prop : string ) : PropertyDescriptor {\n let desc : PropertyDescriptor;\n\n for( let proto = obj; !desc && proto; proto = Object.getPrototypeOf( proto ) ) {\n desc = Object.getOwnPropertyDescriptor( proto, prop );\n }\n\n return desc;\n}\n\n/** Similar to underscore `_.omit` */\nexport function omit( source : {}, ...rest : string[] ) : {}\nexport function omit( source ) : {} {\n const dest = {}, discard = {};\n\n for( let i = 1; i < arguments.length; i ++ ){\n discard[ arguments[ i ] ] = true;\n }\n\n for( var name in source ) {\n if( !discard.hasOwnProperty( name ) && source.hasOwnProperty( name ) ) {\n dest[ name ] = source[ name ];\n }\n }\n\n return dest;\n}\n\n/** map `source` object properties with a given function, and assign the result to the `dest` object.\n * When `fun` returns `undefined`, skip this value. \n */\nexport function transform< A, B >( dest : { [ key : string ] : A }, source : { [ key : string ] : B }, fun : ( value : B, key : string ) => A | void ) : { [ key : string ] : A } {\n for( var name in source ) {\n if( source.hasOwnProperty( name ) ) {\n var value = fun( source[ name ], name );\n value === void 0 || ( dest[ name ] = < A >value );\n }\n }\n\n return dest;\n}\n\n/** @hidden */\nexport function fastAssign< A >( dest : A, source : {} ) : A {\n for( var name in source ) {\n dest[ name ] = source[ name ];\n }\n\n return dest;\n}\n\n/** @hidden */\nexport function fastDefaults< A >( dest : A, source : {} ) : A {\n for( var name in source ) {\n if( dest[ name ] === void 0 ){\n dest[ name ] = source[ name ];\n }\n }\n\n return dest;\n}\n\n/** Similar to underscore `_.extend` and `Object.assign` */\nexport function assign< T >( dest : T, ...sources : Object[] ) : T\nexport function assign< T >( dest : T, source : Object ) : T {\n for( var name in source ) {\n if( source.hasOwnProperty( name ) ) {\n dest[ name ] = source[ name ];\n }\n }\n\n if( arguments.length > 2 ){\n for( let i = 2; i < arguments.length; i++ ){\n const other = arguments[ i ];\n other && assign( dest, other );\n }\n }\n\n return dest;\n}\n\n/** Similar to underscore `_.keys` */\nexport function keys( o : any ) : string[]{\n return o ? Object.keys( o ) : [];\n}\n\n/** Similar to underscore `_.once` */\nexport function once( func : Function ) : Function {\n var memo, first = true;\n return function() {\n if ( first ) {\n first = false;\n memo = func.apply(this, arguments);\n func = null;\n }\n return memo;\n };\n}\n\n/** @hidden */\nconst ArrayProto = Array.prototype,\n DateProto = Date.prototype,\n ObjectProto = Object.prototype;\n\n/**\n * Determine whenever two values are not equal, deeply traversing \n * arrays and plain JS objects (hashes). Dates are compared by enclosed timestamps, all other\n * values are compared with strict comparison.\n */\nexport function notEqual( a : any, b : any) : boolean {\n if( a === b ) return false;\n\n if( a && b && typeof a == 'object' && typeof b == 'object' ) {\n const protoA = Object.getPrototypeOf( a );\n\n if( protoA !== Object.getPrototypeOf( b ) ) return true;\n\n switch( protoA ){\n case DateProto : return +a !== +b;\n case ArrayProto : return arraysNotEqual( a, b );\n case ObjectProto :\n case null:\n return objectsNotEqual( a, b );\n }\n }\n\n return true;\n}\n\n/** @hidden */\nfunction objectsNotEqual( a, b ) {\n const keysA = Object.keys( a );\n\n if( keysA.length !== Object.keys( b ).length ) return true;\n\n for( let i = 0; i < keysA.length; i++ ) {\n const key = keysA[ i ];\n\n if( !b.hasOwnProperty( key ) || notEqual( a[ key ], b[ key ] ) ) {\n return true;\n }\n }\n\n return false;\n}\n\n/** @hidden */\nfunction arraysNotEqual( a, b ) {\n if( a.length !== b.length ) return true;\n\n for( let i = 0; i < a.length; i++ ) {\n if( notEqual( a[ i ], b[ i ] ) ) return true;\n }\n\n return false;\n}\n\n/**\n * Create an object without Object prototype members except hasOwnProperty.\n * @param obj - optional parameter to populate the hash map from.\n */\nconst HashProto = Object.create( null );\nHashProto.hasOwnProperty = ObjectProto.hasOwnProperty;\n\nexport function hashMap( obj? ){\n const hash = Object.create( HashProto );\n return obj ? assign( hash, obj ) : hash;\n}","import { once as _once } from './tools'\n\n/*******************\n * Prebuilt events map, used for optimized bulk event subscriptions.\n *\n * const events = new EventMap({\n * 'change' : true, // Resend this event from self as it is.\n * 'change:attr' : 'localTargetFunction',\n * 'executedInTargetContext' : function(){ ... }\n * 'executedInNativeContext' : '^props.handler'\n * })\n */\n/** @hidden */\nexport interface EventsDefinition {\n [ events : string ] : Function | string | boolean\n}\n\n/** @hidden */\nexport class EventMap {\n handlers : EventDescriptor[] = [];\n\n constructor( map? : EventsDefinition | EventMap ){\n if( map ){\n if( map instanceof EventMap ){\n this.handlers = map.handlers.slice();\n }\n else{\n map && this.addEventsMap( map );\n }\n }\n }\n\n merge( map : EventMap ){\n this.handlers = this.handlers.concat( map.handlers );\n }\n\n addEventsMap( map : EventsDefinition ){\n for( let names in map ){\n this.addEvent( names, map[ names ] )\n }\n }\n\n bubbleEvents( names : string ){\n for( let name of names.split( eventSplitter ) ){\n this.addEvent( name, getBubblingHandler( name ) );\n }\n }\n\n addEvent( names : string, callback : Function | string | boolean ){\n const { handlers } = this;\n\n for( let name of names.split( eventSplitter ) ){\n handlers.push( new EventDescriptor( name, callback ) );\n }\n }\n\n subscribe( target : {}, source : EventSource ){\n for( let event of this.handlers ){\n on( source, event.name, event.callback, target );\n }\n }\n\n unsubscribe( target : {}, source : EventSource ){\n for( let event of this.handlers ){\n off( source, event.name, event.callback, target );\n }\n }\n}\n\n/** @hidden */\nexport class EventDescriptor {\n callback : Function\n\n constructor(\n public name : string,\n callback : Function | string | boolean\n ){\n if( callback === true ){\n this.callback = getBubblingHandler( name );\n }\n else if( typeof callback === 'string' ){\n this.callback =\n function localCallback(){\n const handler = this[ callback ];\n handler && handler.apply( this, arguments );\n };\n }\n else{\n this.callback = callback;\n }\n }\n}\n\n/** @hidden */\nconst _bubblingHandlers = {};\n\n/** @hidden */\nfunction getBubblingHandler( event : string ){\n return _bubblingHandlers[ event ] || (\n _bubblingHandlers[ event ] = function( a?, b?, c?, d?, e? ){\n if( d !== void 0 || e !== void 0 ) trigger5( this, event, a, b, c, d, e );\n if( c !== void 0 ) trigger3( this, event, a, b, c );\n else trigger2( this, event, a, b );\n }\n );\n}\n\n/** @hidden */\nexport interface HandlersByEvent {\n [ name : string ] : EventHandler\n}\n\n/** @hidden */\nexport class EventHandler {\n constructor( public callback : Callback, public context : any, public next = null ){}\n}\n\n/** @hidden */\nfunction listOff( _events : HandlersByEvent, name : string, callback : Callback, context : any ){\n const head = _events[ name ];\n\n let filteredHead, prev;\n\n for( let ev = head; ev; ev = ev.next ){\n // Element must be kept\n if( ( callback && callback !== ev.callback && callback !== ev.callback._callback ) ||\n ( context && context !== ev.context ) ){\n \n prev = ev;\n filteredHead || ( filteredHead = ev );\n }\n // Element must be skipped\n else{\n if( prev ) prev.next = ev.next;\n }\n }\n\n if( head !== filteredHead ) _events[ name ] = filteredHead;\n}\n\n/** @hidden */\nfunction listSend2( head : EventHandler, a, b ){\n for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b );\n}\n\n/** @hidden */\nfunction listSend3( head : EventHandler, a, b, c ){\n for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c );\n}\n\n/** @hidden */\nfunction listSend4( head : EventHandler, a, b, c, d ){\n for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d );\n}\n\n/** @hidden */\nfunction listSend5( head : EventHandler, a, b, c, d, e ){\n for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d, e );\n}\n\n/** @hidden */\nfunction listSend6( head : EventHandler, a, b, c, d, e, f ){\n for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d, e, f );\n}\n\n/** @hidden */\nexport interface Callback extends Function {\n _callback? : Function\n}\n\n/** @hidden */\nexport function on( source : EventSource, name : string, callback : Callback, context? : any ) : void {\n if( callback ){\n const _events = source._events || ( source._events = Object.create( null ) );\n _events[ name ] = new EventHandler( callback, context, _events[ name ] );\n }\n}\n\n/** @hidden */\nexport function once( source : EventSource, name : string, callback : Callback, context? : any ) : void {\n if( callback ){\n const once : Callback = _once( function(){\n off( source, name, once );\n callback.apply(this, arguments);\n });\n\n once._callback = callback;\n on( source, name, once, context );\n }\n}\n\n/** @hidden */\nexport function off( source : EventSource, name? : string, callback? : Callback, context? : any ) : void {\n const { _events } = source;\n if( _events ){\n if( callback || context ) {\n if( name ){\n listOff( _events, name, callback, context );\n }\n else{\n for( let name in _events ){\n listOff( _events, name, callback, context );\n }\n }\n }\n else if( name ){\n _events[ name ] = void 0;\n }\n else{\n source._events = void 0;\n }\n }\n}\n\n/** @hidden */\nexport interface EventSource {\n _events : HandlersByEvent\n}\n\n/** @hidden */\nconst eventSplitter = /\\s+/;\n\n/** @hidden */\nexport function strings( api : ApiEntry, source : EventSource, events : string, callback : Callback, context ){\n if( eventSplitter.test( events ) ){\n const names = events.split( eventSplitter );\n for( let name of names ) api( source, name, callback, context );\n }\n else api( source, events, callback, context );\n}\n\n/** @hidden */\nexport type ApiEntry = ( source : EventSource, event : string, callback : Callback, context? : any ) => void\n\n/*********************************\n * Event-triggering API\n */\n\n/** @hidden */\nexport function trigger2( self : EventSource, name : string, a, b ) : void {\n const { _events } = self;\n if( _events ){\n const queue = _events[ name ],\n { all } = _events;\n\n listSend2( queue, a, b );\n listSend3( all, name, a, b );\n }\n};\n\n/** @hidden */\nexport function trigger3( self : EventSource, name : string, a, b, c ) : void{\n const { _events } = self;\n if( _events ){\n const queue = _events[ name ],\n { all } = _events;\n\n listSend3( queue, a, b, c );\n listSend4( all, name, a, b, c );\n }\n};\n\n/** @hidden */\nexport function trigger5( self : EventSource, name : string, a, b, c, d, e ) : void{\n const { _events } = self;\n if( _events ){\n const queue = _events[ name ],\n { all } = _events;\n\n listSend5( queue, a, b, c, d, e );\n listSend6( all, name, a, b, c, d, e );\n }\n};","/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation. All rights reserved.\r\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\r\nthis file except in compliance with the License. You may obtain a copy of the\r\nLicense at http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\r\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\r\nMERCHANTABLITY OR NON-INFRINGEMENT.\r\n\r\nSee the Apache Version 2.0 License for specific language governing permissions\r\nand limitations under the License.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)\r\n t[p[i]] = s[p[i]];\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport function __exportStar(m, exports) {\r\n for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\r\n}\r\n\r\nexport function __values(o) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator], i = 0;\r\n if (m) return m.call(o);\r\n return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\r\n result.default = mod;\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n","/*****************************************************************\n * Mixins engine and @define metaprogramming class extensions\n *\n * Vlad Balin & Volicon, (c) 2016-2017\n */\nimport { __extends } from 'tslib';\nimport { assign, defaults, getBaseClass, hashMap, transform } from './tools';\n\nexport interface Subclass< T > extends MixableConstructor {\n new ( ...args ) : T\n prototype : T\n}\n\nexport interface MixableConstructor extends Function{\n __super__? : object;\n mixins? : MixinsState;\n onExtend? : ( BaseClass : Function ) => void;\n onDefine? : ( definition : object, BaseClass : Function ) => void;\n define? : ( definition? : object, statics? : object ) => MixableConstructor;\n extend? : ( definition? : T, statics? : object ) => Subclass;\n}\n\nexport interface MixableDefinition {\n mixins? : Mixin[]\n}\n\n/**\n * Base class, holding metaprogramming class extensions.\n * Supports mixins and Class.define metaprogramming method.\n */\nexport class Mixable {\n static onExtend : ( BaseClass : Function ) => void;\n static onDefine : ( definition : object, BaseClass : Function ) => object; \n static __super__ : object\n static mixins : MixinsState;\n\n /** \n * Must be called after inheritance and before 'define'.\n */\n static define( protoProps : MixableDefinition = {}, staticProps? : object ) : MixableConstructor {\n const BaseClass : MixableConstructor = getBaseClass( this );\n\n // Assign statics.\n staticProps && assign( this, staticProps );\n\n // Extract and apply mixins from the definition.\n const { mixins, ...defineMixin } = protoProps;\n mixins && this.mixins.merge( mixins );\n\n // Unshift definition to the the prototype.\n this.mixins.mergeObject( this.prototype, defineMixin, true );\n\n // Unshift definition from statics to the prototype.\n this.mixins.mergeObject( this.prototype, this.mixins.getStaticDefinitions( BaseClass ), true );\n\n // Call onDefine hook, if it's present.\n this.onDefine && this.onDefine( this.mixins.definitions, BaseClass );\n \n // Apply merge rules to inherited members. No mixins can be added after this point.\n this.mixins.mergeInheritedMembers( BaseClass );\n\n return this;\n }\n\n /** Backbone-compatible extend method to be used in ES5 and for backward compatibility */\n static extend< T extends object>(spec? : T, statics? : {} ) : Subclass< T > {\n let TheSubclass : Subclass< T >;\n\n // 1. Create the subclass (ES5 compatibility shim).\n // If constructor function is given...\n if( spec && spec.hasOwnProperty( 'constructor' ) ){\n // ...we need to manually call internal TypeScript __extend function. Hack! Hack!\n TheSubclass = spec.constructor as any;\n __extends( TheSubclass, this );\n }\n // Otherwise, create the subclall in usual way.\n else{\n TheSubclass = class Subclass extends this {} as any;\n }\n\n predefine( TheSubclass );\n spec && TheSubclass.define( spec, statics );\n\n return TheSubclass;\n }\n}\n\n/** @decorator `@predefine` for forward definitions. Can be used with [[Mixable]] classes only.\n * Forwards the call to the [[Mixable.predefine]];\n */\nexport function predefine( Constructor : MixableConstructor ) : void {\n const BaseClass : MixableConstructor = getBaseClass( Constructor );\n\n // Legacy systems support\n Constructor.__super__ = BaseClass.prototype;\n \n // Initialize mixins structures...\n Constructor.define || MixinsState.get( Mixable ).populate( Constructor );\n\n // Make sure Ctor.mixins are ready before the callback...\n MixinsState.get( Constructor );\n\n // Call extend hook.\n Constructor.onExtend && Constructor.onExtend( BaseClass );\n}\n\n/** @decorator `@define` for metaprogramming magic. Can be used with [[Mixable]] classes only.\n * Forwards the call to [[Mixable.define]].\n */\nexport function define( ClassOrDefinition : Function ) : void;\nexport function define( ClassOrDefinition : object ) : ClassDecorator;\nexport function define( ClassOrDefinition : object | MixableConstructor ){\n // @define class\n if( typeof ClassOrDefinition === 'function' ){\n predefine( ClassOrDefinition );\n ( ClassOrDefinition as MixableConstructor ).define();\n }\n // @define({ prop : val, ... }) class\n else{\n return function( Ctor : MixableConstructor ){\n predefine( Ctor );\n Ctor.define( ClassOrDefinition );\n } as any;\n }\n}\n\nexport function definitions( rules : MixinMergeRules ) : ClassDecorator {\n return ( Class : Function ) => {\n const mixins = MixinsState.get( Class );\n mixins.definitionRules = defaults( hashMap(), rules, mixins.definitionRules );\n }\n}\n\n// Create simple property list decorator\nexport function propertyListDecorator( listName: string ) : PropertyDecorator {\n return function propList(proto, name : string) {\n const list = proto.hasOwnProperty( listName ) ?\n proto[ listName ] : (proto[ listName ] = (proto[ listName ] || []).slice()); \n\n list.push(name);\n }\n}\n\nexport function definitionDecorator( definitionKey, value ){\n return ( proto : object, name : string ) => {\n MixinsState\n .get( proto.constructor )\n .mergeObject( proto, {\n [ definitionKey ] : {\n [ name ] : value\n }\n });\n }\n}\n\nexport class MixinsState {\n mergeRules : MixinMergeRules;\n definitionRules : MixinMergeRules;\n definitions : object = {};\n appliedMixins : Mixin[];\n\n // Return mixins state for the class. Initialize if it's not exist.\n static get( Class ) : MixinsState {\n const { mixins } = Class;\n \n return mixins && Class === mixins.Class ? mixins :\n Class.mixins = new MixinsState( Class );\n }\n\n constructor( public Class : MixableConstructor ){\n const { mixins } = getBaseClass( Class );\n\n this.mergeRules = ( mixins && mixins.mergeRules ) || hashMap();\n this.definitionRules = ( mixins && mixins.definitionRules ) || hashMap();\n this.appliedMixins = ( mixins && mixins.appliedMixins ) || [];\n }\n\n getStaticDefinitions( BaseClass : Function ){\n const definitions = hashMap(),\n { Class } = this;\n\n return transform( definitions, this.definitionRules, ( rule, name ) =>{\n if( BaseClass[ name ] !== Class[ name ]){\n return Class[ name ];\n }\n });\n }\n\n merge( mixins : Mixin[] ){\n const proto = this.Class.prototype,\n { mergeRules } = this;\n\n // Copy applied mixins array as it's going to be updated.\n const appliedMixins = this.appliedMixins = this.appliedMixins.slice();\n\n // Apply mixins in sequence...\n for( let mixin of mixins ) {\n // Mixins array should be flattened.\n if( Array.isArray( mixin ) ) {\n this.merge( mixin );\n }\n // Don't apply mixins twice.\n else if( appliedMixins.indexOf( mixin ) < 0 ){\n appliedMixins.push( mixin );\n\n // For constructors, merge _both_ static and prototype members.\n if( typeof mixin === 'function' ){\n // Merge static members\n this.mergeObject( this.Class, mixin );\n\n // merge definitionRules and mergeRules\n const sourceMixins = ( mixin as any ).mixins;\n if( sourceMixins ){\n this.mergeRules = defaults( hashMap(), this.mergeRules, sourceMixins.mergeRules );\n this.definitionRules = defaults( hashMap(), this.definitionRules, sourceMixins.definitionRules );\n this.appliedMixins = this.appliedMixins.concat( sourceMixins.appliedMixins );\n }\n\n // Prototypes are merged according with rules.\n this.mergeObject( proto, mixin.prototype );\n }\n // Handle plain object mixins.\n else {\n this.mergeObject( proto, mixin );\n }\n }\n }\n }\n\n populate( ...ctors : Function[] ){\n for( let Ctor of ctors ) {\n MixinsState.get( Ctor ).merge([ this.Class ]);\n }\n }\n\n mergeObject( dest : object, source : object, unshift? : boolean ) {\n forEachOwnProp( source, name => {\n const sourceProp = Object.getOwnPropertyDescriptor( source, name );\n let rule : MixinMergeRule;\n\n if( rule = this.definitionRules[ name ] ){\n assignProperty( this.definitions, name, sourceProp, rule, unshift );\n }\n\n if( !rule || rule === mixinRules.protoValue ){\n assignProperty( dest, name, sourceProp, this.mergeRules[ name ], unshift );\n }\n });\n }\n\n mergeInheritedMembers( BaseClass : Function ){\n const { mergeRules, Class } = this;\n\n if( mergeRules ){\n const proto = Class.prototype,\n baseProto = BaseClass.prototype;\n\n for( let name in mergeRules ) {\n const rule = mergeRules[ name ];\n\n if( proto.hasOwnProperty( name ) && name in baseProto ){\n proto[ name ] = resolveRule( proto[ name ], baseProto[ name ], rule );\n }\n }\n }\n }\n}\n\nconst dontMix = {\n function : hashMap({\n length : true,\n prototype : true,\n caller : true,\n arguments : true,\n name : true,\n __super__ : true\n }),\n \n object : hashMap({\n constructor : true\n }) \n}\n\nfunction forEachOwnProp( object : object, fun : ( name : string ) => void ){\n const ignore = dontMix[ typeof object ];\n\n for( let name of Object.getOwnPropertyNames( object ) ) {\n ignore[ name ] || fun( name );\n }\n}\n\nexport interface MixinMergeRules {\n [ name : string ] : MixinMergeRule\n}\n\nexport type MixinMergeRule = ( a : any, b : any ) => any\nexport type Mixin = { [ key : string ] : any } | Function\n\n// @mixins( A, B, ... ) decorator.\nexport interface MixinRulesDecorator {\n ( rules : MixinMergeRules ) : ClassDecorator\n value( a : object, b : object) : object;\n protoValue( a : object, b : object) : object;\n merge( a : object, b : object ) : object;\n pipe( a: Function, b : Function ) : Function;\n defaults( a: Function, b : Function ) : Function;\n classFirst( a: Function, b : Function ) : Function;\n classLast( a: Function, b : Function ) : Function;\n every( a: Function, b : Function ) : Function;\n some( a: Function, b : Function ) : Function;\n}\n\nexport const mixins = ( ...list : Mixin[] ) => (\n ( Class : Function ) => MixinsState.get( Class ).merge( list )\n);\n\n// @mixinRules({ name : rule, ... }) decorator.\nexport const mixinRules = ( ( rules : MixinMergeRules ) => (\n ( Class : Function ) => {\n const mixins = MixinsState.get( Class );\n mixins.mergeRules = defaults( rules, mixins.mergeRules );\n }\n) ) as MixinRulesDecorator;\n\n// Pre-defined mixin merge rules\n\nmixinRules.value = ( a, b ) => a;\n\nmixinRules.protoValue = ( a, b ) => a;\n\n// Recursively merge members\nmixinRules.merge = ( a, b ) => defaults( {}, a, b );\n\n // Execute methods in pipe, with the class method executed last.\nmixinRules.pipe = ( a, b ) => (\n function( x : any ) : any {\n return a.call( this, b.call( this, x ) );\n }\n);\n\n // Assume methods return an object, and merge results with defaults (class method executed first)\nmixinRules.defaults = ( a : Function, b : Function ) => (\n function() : object {\n return defaults( a.apply( this, arguments ), b.apply( this, arguments ) );\n }\n);\n\n// Execute methods in sequence staring with the class method.\nmixinRules.classFirst = ( a : Function, b : Function ) => (\n function() : void {\n a.apply( this, arguments );\n b.apply( this, arguments );\n }\n);\n\n // Execute methods in sequence ending with the class method.\nmixinRules.classLast = ( a : Function, b : Function ) => (\n function() : void {\n b.apply( this, arguments );\n a.apply( this, arguments );\n }\n)\n\n // Execute methods in sequence returning the first falsy result.\nmixinRules.every = ( a : Function, b : Function ) =>(\n function() : any {\n return a.apply( this, arguments ) && b.apply( this, arguments );\n }\n);\n // Execute methods in sequence returning the first truthy result.\nmixinRules.some = ( a : Function, b : Function ) =>(\n function() : any {\n return a.apply( this, arguments ) || b.apply( this, arguments );\n }\n);\n\n/**\n * Helpers\n */\n\nfunction assignProperty( dest : object, name : string, sourceProp : PropertyDescriptor, rule : MixinMergeRule, unshift? : boolean ){\n// Destination prop is defined, thus the merge rules must be applied.\n if( dest.hasOwnProperty( name ) ){\n const destProp = Object.getOwnPropertyDescriptor( dest, name );\n\n if( destProp.configurable && 'value' in destProp ){\n dest[ name ] = unshift ?\n resolveRule( sourceProp.value, destProp.value, rule ) :\n resolveRule( destProp.value, sourceProp.value, rule ) ;\n }\n }\n // If destination is empty, just copy the prop over.\n else{\n Object.defineProperty( dest, name, sourceProp );\n }\n}\n\nfunction resolveRule( dest, source, rule : MixinMergeRule ){\n // When destination is empty, take the source.\n if( dest === void 0 ) return source;\n\n // In these cases we take non-empty destination:\n if( !rule || source === void 0 ) return dest;\n\n // In other cases we must merge values.\n return rule( dest, source );\n}","import * as _eventsApi from './eventsource';\nimport { EventMap, EventsDefinition, EventSource, HandlersByEvent } from './eventsource';\nimport { define, definitions, Mixable, MixableConstructor, mixinRules, MixinsState } from './mixins';\nimport { omit, transform } from './tools';\n\nconst { strings, on, off, once, trigger5, trigger2, trigger3 } = _eventsApi;\n\n/** @hidden */\nlet _idCount = 0;\n\n/** @hidden */\nfunction uniqueId() : string {\n return 'l' + _idCount++;\n}\n\nexport { EventMap, EventsDefinition };\n\nexport interface MessengerDefinition {\n _localEvents? : EventMap\n localEvents? : EventsDefinition\n properties? : PropertyMap\n [ name : string ] : any\n}\n\nexport interface PropertyMap {\n [ name : string ] : Property\n}\n\nexport type Property = PropertyDescriptor | ( () => any )\n\n/** @hidden */\nexport interface MessengersByCid {\n [ cid : string ] : Messenger\n}\n\n/** @hidden */\nexport type CallbacksByEvents = { [ events : string ] : Function }\n\n/*************************\n * Messenger is mixable class with capabilities of sending and receiving synchronous events.\n * This class itself can serve as both mixin and base class.\n */\n\n@define\n@definitions({\n properties : mixinRules.merge,\n localEvents : mixinRules.merge\n})\nexport class Messenger implements Mixable, EventSource {\n // Define extendable mixin static properties.\n static __super__ : object;\n static mixins : MixinsState;\n static onExtend : ( BaseClass : Function ) => void;\n static define : ( definition? : MessengerDefinition, statics? : object ) => MixableConstructor;\n static extend : ( definition? : MessengerDefinition, statics? : object ) => MixableConstructor;\n static onDefine({ localEvents, _localEvents, properties } : MessengerDefinition, BaseClass? : typeof Mixable ){\n // Handle localEvents definition\n if( localEvents || _localEvents ){\n const eventsMap = new EventMap( this.prototype._localEvents );\n\n localEvents && eventsMap.addEventsMap( localEvents );\n _localEvents && eventsMap.merge( _localEvents );\n \n this.prototype._localEvents = eventsMap;\n }\n\n // Handle properties definitions...\n if( properties ){\n Object.defineProperties( this.prototype, transform( {}, properties, toPropertyDescriptor ) );\n }\n }\n\n /** @hidden */ \n _events : HandlersByEvent = void 0;\n\n /** @hidden */ \n _listeningTo : MessengersByCid = void 0\n\n /** Unique client-only id. */\n cid : string\n\n /** @hidden Prototype-only property to manage automatic local events subscription */ \n _localEvents : EventMap\n\n /** @hidden */ \n constructor(){\n this.cid = uniqueId();\n this.initialize.apply( this, arguments );\n\n // TODO: local events subscribe?\n }\n\n /** Method is called at the end of the constructor */\n initialize() : void {}\n \n on( events : string | CallbacksByEvents, callback, context? ) : this {\n if( typeof events === 'string' ) strings( on, this, events, callback, context );\n else for( let name in events ) strings( on, this, name, events[ name ], context || callback );\n\n return this;\n }\n\n once( events : string | CallbacksByEvents, callback, context? ) : this {\n if( typeof events === 'string' ) strings( once, this, events, callback, context );\n else for( let name in events ) strings( once, this, name, events[ name ], context || callback );\n\n return this;\n }\n\n off( events? : string | CallbacksByEvents, callback?, context? ) : this {\n if( !events ) off( this, void 0, callback, context );\n else if( typeof events === 'string' ) strings( off, this, events, callback, context );\n else for( let name in events ) strings( off, this, name, events[ name ], context || callback );\n\n return this;\n }\n\n // Trigger one or many events, firing all bound callbacks. Callbacks are\n // passed the same arguments as `trigger` is, apart from the event name\n // (unless you're listening on `\"all\"`, which will cause your callback to\n // receive the true name of the event as the first argument).\n trigger(name : string, a?, b?, c?, d?, e? ) : this {\n if( d !== void 0 || e !== void 0 ) trigger5( this, name, a, b, c, d, e );\n else if( c !== void 0 ) trigger3( this, name, a, b, c );\n else trigger2( this, name, a, b );\n return this;\n }\n\n listenTo( source : Messenger, a : string | CallbacksByEvents, b? : Function ) : this {\n if( source ){\n addReference( this, source );\n source.on( a, !b && typeof a === 'object' ? this : b, this );\n }\n\n return this;\n }\n\n listenToOnce( source : Messenger, a : string | CallbacksByEvents, b? : Function ) : this {\n if( source ){\n addReference( this, source );\n source.once( a, !b && typeof a === 'object' ? this : b, this );\n }\n\n return this;\n }\n\n stopListening( a_source? : Messenger, a? : string | CallbacksByEvents, b? : Function ) : this {\n const { _listeningTo } = this;\n if( _listeningTo ){\n const removeAll = !( a || b ),\n second = !b && typeof a === 'object' ? this : b;\n\n if( a_source ){\n const source = _listeningTo[ a_source.cid ];\n if( source ){\n if( removeAll ) delete _listeningTo[ a_source.cid ];\n source.off( a, second, this );\n }\n }\n else if( a_source == null ){\n for( let cid in _listeningTo ) _listeningTo[ cid ].off( a, second, this );\n\n if( removeAll ) ( this._listeningTo = void 0 );\n }\n }\n\n return this;\n }\n\n /**\n * Destructor. Stops messenger from listening to all objects,\n * and stop others from listening to the messenger. \n */\n _disposed : boolean\n\n dispose() : void {\n if( this._disposed ) return;\n\n this.stopListening();\n this.off();\n\n this._disposed = true;\n }\n}\n\n/**\n * Backbone 1.2 API conformant Events mixin.\n */\nexport const Events : Messenger = omit( Messenger.prototype, 'constructor', 'initialize' );\n\n/**\n * Messenger Private Helpers \n */\n\nfunction toPropertyDescriptor( x : Property ) : PropertyDescriptor {\n if( x ){\n return typeof x === 'function' ? { get : < () => any >x, configurable : true } : x;\n }\n}\n\n/** @hidden */\nfunction addReference( listener : Messenger, source : Messenger ){\n const listeningTo = listener._listeningTo || (listener._listeningTo = Object.create( null ) ),\n cid = source.cid || ( source.cid = uniqueId() );\n\n listeningTo[ cid ] = source;\n}","import { Messenger } from './events'\nimport { define } from './mixins';\n\nexport type LogLevel = 'error' | 'warn' | 'debug' | 'info' | 'log';\nexport type LoggerEventHandler = ( topic : string, msg : string, props : object ) => void;\n\nexport const isProduction = typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production',\n logEvents : LogLevel[] = isProduction ?\n [ 'error', 'info' ] :\n [ 'error', 'warn', 'debug', 'info', 'log' ];\n\n@define\nexport class Logger extends Messenger {\n counter : { [ level in LogLevel ]? : number } = {}\n\n // Log events of the given log level to the console, optionally filtered by topic\n logToConsole( level : LogLevel, filter? : RegExp ) : this {\n return this.on( level, ( topic, msg, props ) => {\n if( !filter || filter.test( topic ) ){\n const args = [ `[${topic}] ${msg}` ];\n \n for( let name in props ){\n args.push( `\\n\\t${name}:`, toString( props[ name ] ) );\n }\n \n console[ level ].apply( console, args );\n }\n });\n }\n\n // Fire exception on the events of the given log level, optionally filtered by topic\n throwOn( level : LogLevel, filter? : RegExp ) : this {\n return this.on( level, ( topic, msg, props ) => {\n if( !filter || filter.test( topic ) ){\n throw new Error( `[${topic}] ${msg}` );\n }\n });\n }\n\n // Count log events of the given level, optionally filtered by topic\n count( level : LogLevel, filter? : RegExp ) : this {\n return this.on( level, ( topic, msg, props ) => {\n if( !filter || filter.test( topic ) ){\n this.counter[ level ] = ( this.counter[ level ] || 0 ) + 1;\n }\n });\n }\n\n trigger : ( level : LogLevel, topic : string, message : string, props? : object ) => this;\n \n off : ( event? : LogLevel ) => this;\n\n\n on( handlers : { [ name in LogLevel ] : LoggerEventHandler } ) : this;\n on( handlers : LogLevel, handler : LoggerEventHandler ) : this;\n on( handlers : 'all', handler : ( level : LogLevel, topic : string, msg : string, props : object ) => void ) : this;\n on( a : any, b? : any ){\n return super.on( a, b );\n }\n}\n\n/**\n * Convert objects to the plain text friendly format.\n * primitives as in JSON.\n */\nlet toString = typeof window === 'undefined' ? \n something => {\n if( something && typeof something === 'object' ){\n const { __inner_state__ } = something,\n value = __inner_state__ || something,\n isArray = Array.isArray( value );\n\n const body = isArray ? `[ length = ${ value.length } ]` : `{ ${ Object.keys( value ).join( ', ' )} }`;\n\n return something.constructor.name + ' ' + body;\n }\n\n return JSON.stringify( something );\n }\n : x => x;\n\nexport const logger = new Logger();\n\nif( typeof console !== 'undefined' ) {\n for( let event of logEvents ){\n logger.logToConsole( event );\n }\n}\n\nexport const throwingLogger = new Logger();\nthrowingLogger.throwOn( 'error' ).throwOn( 'warn' );\n\nexport const log : typeof logger.trigger = logger.trigger.bind( logger );","export interface IONode {\n _endpoint : IOEndpoint\n _ioPromise : IOPromise< this >\n}\n\nexport interface IOPromise extends Promise {\n abort? : () => void\n}\n\nexport interface IOEndpoint {\n list( options : IOOptions, collection? ) : IOPromise\n create( json : any, options : IOOptions, record? ) : IOPromise\n update( id : string | number, json :any, options : IOOptions, record? ) : IOPromise\n read( id : string | number, options : IOOptions, record? ) : IOPromise\n destroy( id : string | number, options : IOOptions, record? ) : IOPromise\n subscribe( events : IOEvents, collection? ) : IOPromise\n unsubscribe( events : IOEvents, collection? ) : void\n}\n\nexport interface IOOptions {\n ioUpdate? : boolean\n}\n\nexport interface IOEvents {\n updated? : ( json : any ) => void\n removed? : ( json : any ) => void\n}\n\nexport function getOwnerEndpoint( self ) : IOEndpoint {\n // Check if we are the member of the collection...\n const { collection } = self;\n if( collection ){\n return getOwnerEndpoint( collection );\n }\n\n // Now, if we're the member of the model...\n if( self._owner ){\n const { _endpoints } = self._owner;\n return _endpoints && _endpoints[ self._ownerKey ];\n }\n}\n\n/**\n * Create abortable promise.\n * Adds `promise.abort()` function which rejects the promise by default\n * initialize() function takes third optional argument `abort : ( resolve, reject ) => void`,\n * which can be used to add custom abort handling.\n */\ndeclare var Promise: PromiseConstructorLike;\n\nexport function createIOPromise( initialize : InitIOPromise ) : IOPromise{\n let resolve, reject, onAbort;\n\n function abort( fn ){\n onAbort = fn;\n }\n\n const promise : IOPromise = new Promise( ( a_resolve, a_reject ) =>{\n reject = a_reject;\n resolve = a_resolve;\n initialize( resolve, reject, abort );\n }) as IOPromise;\n\n promise.abort = () => {\n onAbort ? onAbort( resolve, reject ) : reject( new Error( \"I/O Aborted\" ) );\n }\n\n return promise;\n}\n\nexport type InitIOPromise = ( resolve : ( x? : any ) => void, reject : ( x? : any ) => void, abort? : ( fn : Function ) => void ) => void;\n\nexport function startIO( self : IONode, promise : IOPromise, options : IOOptions, thenDo : ( json : any ) => any ) : IOPromise {\n // Stop pending I/O first...\n abortIO( self );\n\n // Mark future update transaction as IO transaction.\n options.ioUpdate = true;\n\n self._ioPromise = promise\n .then( resp => {\n self._ioPromise = null;\n \n const result = thenDo ? thenDo( resp ) : resp;\n \n triggerAndBubble( self, 'sync', self, resp, options );\n \n return result;\n } ) \n .catch( err => {\n self._ioPromise = null;\n \n // Overlaps with a new `error` event.\n triggerAndBubble( self, 'error', self, err, options );\n \n throw err;\n } ) as IOPromise;\n\n self._ioPromise.abort = promise.abort;\n\n return self._ioPromise;\n}\n\nexport function abortIO( self : IONode ){\n if( self._ioPromise && self._ioPromise.abort ){\n self._ioPromise.abort();\n self._ioPromise = null;\n }\n}\n\nexport function triggerAndBubble( eventSource, ...args ){\n eventSource.trigger.apply( eventSource, args );\n const { collection } = eventSource;\n collection && collection.trigger.apply( collection, args ); \n}","/**\n * Traversable objects and symbolic references\n */\nexport interface Traversable {\n getStore() : Traversable\n getOwner() : Traversable\n get( key : string ) : any \n}\n\nconst referenceMask = /\\^|(store\\.[^.]+)|([^.]+)/g;\n\n// Compile reference to function\nexport type ResolveReference = ( root : Traversable ) => any; \n\nexport class CompiledReference {\n resolve : ResolveReference\n tail : string\n local : boolean\n\n constructor( reference : string, splitTail : boolean = false ){\n const path = reference\n .match( referenceMask )\n .map( key => {\n if( key === '^' || key === 'owner' ) return 'getOwner()';\n\n if( key[ 0 ] === '~' ) return `getStore().get(\"${ key.substr( 1 ) }\")`;\n\n if( key.indexOf( 'store.' ) === 0 ) return `getStore().get(\"${ key.substr( 6 ) }\")`;\n \n return key;\n } );\n \n this.tail = splitTail && path.pop();\n this.local = !path.length;\n \n this.resolve = new Function( 'self', `\n var v = self.${ path.shift() };\n \n ${ path.map( x => `\n v = v && v.${ x };\n `).join('')}\n\n return v;\n ` );\n }\n}\n\nexport function resolveReference( root : Traversable, reference : string, action : ( object, key : string ) => any ) : any {\n const path = reference.match( referenceMask ),\n skip = path.length - 1;\n \n let self = root;\n\n for( var i = 0; i < skip; i++ ){\n const key = path[ i ];\n switch( key ){\n case '~' : self = self.getStore(); break;\n case '^' : self = self.getOwner(); break;\n default : self = self.get( key );\n }\n\n // Do nothing if object on the path doesn't exist.\n if( !self ) return;\n }\n\n return action( self, path[ skip ] );\n}","export interface ChildrenErrors {\n [ key : string ] : ValidationError | any\n} \n\nexport interface Validatable {\n _validateNested( errors : ChildrenErrors ) : number;\n validate( self : any ) : any\n get( key : string ) : any\n}\n\n// Validation error object.\nexport class ValidationError {\n // Invalid nested object keys \n nested : ChildrenErrors \n length : number\n\n // Local error\n error : any\n\n constructor( obj : Validatable ){\n this.length = obj._validateNested( this.nested = {} );\n\n if( this.error = obj.validate( obj ) ){\n this.length++;\n }\n }\n\n each( iteratee : ( value : any, key : string ) => void ) : void {\n const { error, nested } = this;\n\n if( error ) iteratee( error, null );\n\n for( const key in nested ){\n iteratee( nested[ key ], key );\n }\n }\n\n eachError( iteratee : ( error : any, key : string, object : Validatable ) => void, object : Validatable ) : void {\n this.each( ( value : any, key : string ) => {\n if( value instanceof ValidationError ){\n (value).eachError( iteratee, object.get( key ) );\n }\n else{\n iteratee( value, key, object );\n }\n });\n }\n}","import { abortIO, IOEndpoint, IONode, IOPromise } from './io-tools';\nimport { CallbacksByEvents, define, definitions, eventsApi, Logger, LogLevel, Messenger, MessengerDefinition, MessengersByCid, mixinRules, mixins, MixinsState, throwingLogger } from './object-plus';\nimport { resolveReference, Traversable } from './traversable';\nimport { ChildrenErrors, Validatable, ValidationError } from './validation';\n\nconst { trigger3, on, off } = eventsApi;\n/***\n * Abstract class implementing ownership tree, tho-phase transactions, and validation. \n * 1. createTransaction() - apply changes to an object tree, and if there are some events to send, transaction object is created.\n * 2. transaction.commit() - send and process all change events, and close transaction.\n */\n\n/** @private */\nexport interface TransactionalDefinition extends MessengerDefinition {\n endpoint? : IOEndpoint\n}\n\nexport enum ItemsBehavior {\n share = 0b0001,\n listen = 0b0010,\n persistent = 0b0100\n}\n\n// Transactional object interface\n@define\n@definitions({\n endpoint : mixinRules.value\n})\n@mixins( Messenger )\nexport abstract class Transactional implements Messenger, IONode, Validatable, Traversable {\n // Mixins are hard in TypeScript. We need to copy type signatures over...\n // Here goes 'Mixable' mixin.\n static endpoint : IOEndpoint;\n static __super__ : object;\n static mixins : MixinsState;\n static define : ( definition? : TransactionalDefinition, statics? : object ) => typeof Transactional;\n static extend : ( definition? : T, statics? : object ) => any;\n\n static onDefine( definitions : TransactionalDefinition, BaseClass : typeof Transactional ){\n if( definitions.endpoint ) this.prototype._endpoint = definitions.endpoint;\n Messenger.onDefine.call( this, definitions, BaseClass );\n };\n\n static onExtend( BaseClass : typeof Transactional ) : void {\n // Make sure we don't inherit class factories.\n if( BaseClass.create === this.create ) {\n this.create = Transactional.create;\n }\n }\n\n // Define extendable mixin static properties.\n static create( a : any, b? : any ) : Transactional {\n return new (this as any)( a, b );\n }\n\n /** Generic class factory. May be overridden for abstract classes. Not inherited. */\n on : ( events : string | CallbacksByEvents, callback, context? ) => this\n once : ( events : string | CallbacksByEvents, callback, context? ) => this\n off : ( events? : string | CallbacksByEvents, callback?, context? ) => this\n trigger : (name : string, a?, b?, c?, d?, e? ) => this\n\n stopListening : ( source? : Messenger, a? : string | CallbacksByEvents, b? : Function ) => this\n listenTo : ( source : Messenger, a : string | CallbacksByEvents, b? : Function ) => this\n listenToOnce : ( source : Messenger, a : string | CallbacksByEvents, b? : Function ) => this\n \n _disposed : boolean;\n\n // State accessor. \n readonly __inner_state__ : any;\n\n // Shared modifier (used by collections of shared models)\n _shared? : number; \n \n dispose() : void {\n if( this._disposed ) return;\n \n abortIO( this );\n this._owner = void 0;\n this._ownerKey = void 0;\n this.off();\n this.stopListening();\n this._disposed = true;\n }\n\n // Must be called at the end of the constructor in the subclass.\n initialize() : void {}\n\n /** @private */\n _events : eventsApi.HandlersByEvent = void 0;\n\n /** @private */\n _listeningTo : MessengersByCid\n\n /** @private */\n _localEvents : eventsApi.EventMap\n\n cid : string\n cidPrefix : string\n\n // Unique version token replaced on change\n /** @private */\n _changeToken : {} = {}\n\n // true while inside of the transaction\n /** @private */\n _transaction : boolean = false;\n\n // Holds current transaction's options, when in the middle of transaction and there're changes but is an unsent change event\n /** @private */\n _isDirty : TransactionOptions = null;\n\n // Backreference set by owner (Record, Collection, or other object)\n /** @private */\n _owner : Owner = void 0;\n\n // Key supplied by owner. Used by record to identify attribute key.\n // Only collections doesn't set the key, which is used to distinguish collections.\n /** @private */ \n _ownerKey : string = void 0;\n\n // Name of the change event\n /** @private */\n _changeEventName : string\n\n /**\n * Subsribe for the changes.\n */\n onChanges( handler : Function, target? : Messenger ){\n on( this, this._changeEventName, handler, target );\n }\n\n /**\n * Unsubscribe from changes.\n */\n offChanges( handler? : Function, target? : Messenger ){\n off( this, this._changeEventName, handler, target );\n }\n\n /**\n * Listen to changes event. \n */\n listenToChanges( target : Transactional, handler ){\n this.listenTo( target, target._changeEventName, handler );\n }\n\n constructor( cid : string | number ){\n this.cid = this.cidPrefix + cid;\n }\n\n // Deeply clone ownership subtree\n abstract clone( options? : CloneOptions ) : this\n \n // Execute given function in the scope of ad-hoc transaction.\n transaction( fun : ( self : this ) => void, options : TransactionOptions = {} ) : void{\n const isRoot = transactionApi.begin( this );\n const update = fun.call( this, this );\n update && this.set( update );\n isRoot && transactionApi.commit( this );\n }\n\n // Assign transactional object \"by value\", copying aggregated items.\n assignFrom( source : Transactional | Object ) : this {\n // Need to delay change events until change token willl by synced.\n this.transaction( () =>{\n this.set( ( source ).__inner_state__ || source, { merge : true } );\n\n // Synchronize change tokens\n const { _changeToken } = source as any;\n \n if( _changeToken ){\n this._changeToken = _changeToken;\n } \n });\n\n return this;\n }\n\n // Create object from JSON. Throw if validation fail.\n static from Transactional >( this : T, json : any, { strict, ...options } : { strict? : boolean } & TransactionOptions = {} ) : InstanceType{\n const obj : Transactional = ( this as any ).create( json, { ...options, logger : strict ? throwingLogger : void 0 } );\n\n if( strict && obj.validationError ){\n obj.eachValidationError( ( error, key, obj ) => {\n throw new Error( `${ obj.getClassName() }.${ key }: ${ error }` );\n });\n }\n\n return obj as any;\n }\n\n // Apply bulk object update without any notifications, and return open transaction.\n // Used internally to implement two-phase commit.\n // Returns null if there are no any changes.\n /** @private */ \n abstract _createTransaction( values : any, options? : TransactionOptions ) : Transaction | void\n\n // Apply bulk in-place object update in scope of ad-hoc transaction \n abstract set( values : any, options? : TransactionOptions ) : this;\n\n \n // Parse function applied when 'parse' option is set for transaction.\n parse( data : any, options? : TransactionOptions ) : any { return data }\n\n // Convert object to the serializable JSON structure\n abstract toJSON( options? : object ) : {}\n\n /*******************\n * Traversals and member access\n */\n \n // Get object member by its key.\n abstract get( key : string ) : any\n\n // Get object member by symbolic reference.\n deepGet( reference : string ) : any {\n return resolveReference( this, reference, ( object, key ) => object.get ? object.get( key ) : object[ key ] );\n }\n\n //_isCollection : boolean\n\n // Return owner skipping collections.\n getOwner() : Owner {\n return this._owner;\n }\n\n // Store used when owner chain store lookup failed. Static value in the prototype. \n /** @private */\n _defaultStore : Transactional\n\n // Locate the closest store. Store object stops traversal by overriding this method. \n getStore() : Transactional {\n const { _owner } = this;\n return _owner ? _owner.getStore() : this._defaultStore;\n }\n\n\n /***************************************************\n * Iteration API\n */\n\n // Loop through the members. Must be efficiently implemented in container class.\n\n _endpoint : IOEndpoint\n _ioPromise : IOPromise\n\n hasPendingIO() : IOPromise { return this._ioPromise; }\n\n fetch( options? : object ) : IOPromise { throw new Error( \"Not implemented\" ); }\n\n getEndpoint() : IOEndpoint {\n return getOwnerEndpoint( this ) || this._endpoint;\n }\n \n /*********************************\n * Validation API\n */\n\n // Lazily evaluated validation error\n /** @private */\n _validationError : ValidationError = void 0\n\n // Validate ownership tree and return valudation error \n get validationError() : ValidationError {\n const error = this._validationError || ( this._validationError = new ValidationError( this ) );\n return error.length ? error : null; \n }\n\n // Validate nested members. Returns errors count.\n /** @private */\n abstract _validateNested( errors : ChildrenErrors ) : number\n\n // Object-level validator. Returns validation error.\n validate( obj? : Transactional ) : any {}\n\n // Return validation error (or undefined) for nested object with the given key. \n getValidationError( key? : string ) : any {\n var error = this.validationError;\n return ( key ? error && error.nested[ key ] : error ) || null;\n }\n\n // Get validation error for the given symbolic reference.\n deepValidationError( reference : string ) : any {\n return resolveReference( this, reference, ( object, key ) => object.getValidationError( key ) );\n }\n\n // Iterate through all validation errors across the ownership tree.\n eachValidationError( iteratee : ( error : any, key : string, object : Transactional ) => void ) : void {\n const { validationError } = this;\n validationError && validationError.eachError( iteratee, this );\n }\n\n // Check whenever member with a given key is valid. \n isValid( key? : string ) : boolean {\n return !this.getValidationError( key );\n }\n\n valueOf() : Object { return this.cid; }\n toString(){ return this.cid; }\n\n // Get class name for an object instance. Works fine with ES6 classes definitions (not in IE).\n getClassName() : string {\n const { name } = this.constructor;\n if( name !== 'Subclass' ) return name;\n }\n\n // Logging interface for run time errors and warnings.\n abstract _log( level : LogLevel, topic : string, text : string, value : any, logger? : Logger ) : void\n}\n\nexport interface CloneOptions {\n // 'Pin store' shall assign this._defaultStore = this.getStore();\n pinStore? : boolean\n}\n\n// Owner must accept children update events. It's an only way children communicates with an owner.\n/** @private */\nexport interface Owner extends Traversable, Messenger {\n _onChildrenChange( child : Transactional, options : TransactionOptions ) : void;\n getOwner() : Owner\n getStore() : Transactional\n}\n\n// Transaction object used for two-phase commit protocol.\n// Must be implemented by subclasses.\n// Transaction must be created if there are actual changes and when markIsDirty returns true.\n/** @private */ \nexport interface Transaction {\n // Object transaction is being made on.\n object : Transactional\n\n // Send out change events, process update triggers, and close transaction.\n // Nested transactions must be marked with isNested flag (it suppress owner notification).\n commit( initiator? : Transactional )\n}\n\n// Options for distributed transaction \nexport interface TransactionOptions {\n // Invoke parsing \n parse? : boolean\n\n // Optional logger\n logger? : Logger\n\n // Suppress change notifications and update triggers\n silent? : boolean\n\n // Update existing transactional members in place, or skip the update (ignored by models)\n merge? : boolean // =true\n\n // Should collections remove elements in set (ignored by models) \n remove? : boolean // =true\n\n // Always replace enclosed objects with new instances\n reset? : boolean // = false\n\n // Do not dispose aggregated members\n unset? : boolean\n\n validate? : boolean\n\n // `true` if the transaction is initiated as a result of IO operation\n ioUpdate? : boolean\n\n // The hint for IOEndpoint\n // If `true`, `record.save()` will behave as \"upsert\" operation for the records having id.\n upsert? : boolean\n}\n\n/**\n * Low-level transactions API. Must be used like this:\n * const isRoot = begin( record );\n * ...\n * isRoot && commit( record, options );\n * \n * When committing nested transaction, the flag must be set to true. \n * commit( object, options, isNested ) \n */\n\nexport const transactionApi = {\n // Start transaction. Return true if it's the root one.\n /** @private */\n begin( object : Transactional ) : boolean {\n return object._transaction ? false : ( object._transaction = true ); \n },\n\n // Mark object having changes inside of the current transaction.\n // Returns true whenever there notifications are required.\n /** @private */\n markAsDirty( object : Transactional, options : TransactionOptions ) : boolean {\n // If silent option is in effect, don't set isDirty flag.\n const dirty = !options.silent;\n if( dirty ) object._isDirty = options;\n \n // Reset version token.\n object._changeToken = {};\n\n // Object is changed, so validation must happen again. Clear the cache.\n object._validationError = void 0;\n\n return dirty;\n },\n\n // Commit transaction. Send out change event and notify owner. Returns true if there were changes.\n // Must be executed for the root transaction only.\n /** @private */\n commit( object : Transactional, initiator? : Transactional ){\n let originalOptions = object._isDirty;\n\n if( originalOptions ){\n // Send the sequence of change events, handling chained handlers.\n while( object._isDirty ){\n const options = object._isDirty;\n object._isDirty = null; \n trigger3( object, object._changeEventName, object, options, initiator );\n }\n \n // Mark transaction as closed.\n object._transaction = false;\n\n // Notify owner on changes out of transaction scope. \n const { _owner } = object; \n if( _owner && _owner !== initiator ){ // If it's the nested transaction, owner is already aware there are some changes.\n _owner._onChildrenChange( object, originalOptions );\n }\n }\n else{\n // No changes. Silently close transaction.\n object._isDirty = null;\n object._transaction = false;\n }\n },\n\n /************************************\n * Ownership management\n */\n\n // Add reference to the record.\n /** @private */\n aquire( owner : Owner, child : Transactional, key? : string ) : void {\n if( child._owner ) throw new ReferenceError( 'Trying to aquire ownership for an object already having an owner' );\n\n child._owner = owner;\n child._ownerKey = key;\n },\n\n // Remove reference to the record.\n /** @private */\n free( owner : Owner, child : Transactional ) : void {\n if( owner === child._owner ){\n child._owner = void 0;\n child._ownerKey = void 0;\n }\n }\n}\n\nfunction getOwnerEndpoint( self : Transactional ) : IOEndpoint {\n // Check if we are the member of the collection...\n const { collection } = self as any;\n if( collection ){\n return getOwnerEndpoint( collection );\n }\n\n // Now, if we're the member of the model...\n if( self._owner ){\n const { _endpoints } = self._owner as any;\n return _endpoints && _endpoints[ self._ownerKey ];\n }\n}\n","import { eventsApi } from '../object-plus';\nimport { Owner, Transaction, Transactional, transactionApi, TransactionOptions } from \"../transactions\";\nconst { begin : _begin, markAsDirty : _markAsDirty, commit } = transactionApi;\n\nconst { trigger3 } = eventsApi;\n\nexport interface ConstructorsMixin {\n Attributes : AttributesConstructor\n AttributesCopy : AttributesCopyConstructor\n}\n\nexport interface ConstructorOptions extends TransactionOptions{\n clone? : boolean\n}\n\nexport type AttributesConstructor = new ( record : AttributesContainer, values : object, options : TransactionOptions ) => AttributesValues;\nexport type AttributesCopyConstructor = new ( values : object ) => AttributesValues;\n\nexport interface AttributesContainer extends Transactional, Owner, ConstructorsMixin {\n // Attribute descriptors.\n _attributes : AttributesDescriptors\n\n // Attribute values.\n attributes : AttributesValues\n\n // Previous attribute values.\n _previousAttributes : AttributesValues\n\n // Changed attributes cache. \n _changedAttributes : AttributesValues\n}\n\nexport interface AttributesValues {\n [ name : string ] : any\n}\n\nexport interface AttributesDescriptors {\n [ name : string ] : AttributeUpdatePipeline\n}\n\nexport interface AttributeUpdatePipeline{\n doUpdate( value, record : AttributesContainer, options : TransactionOptions, nested? : Transaction[] ) : boolean\n}\n\n // Optimized single attribute transactional update. To be called from attributes setters\n // options.silent === false, parse === false. \nexport function setAttribute( record : AttributesContainer, name : string, value : any ) : void {\n // Open the transaction.\n const isRoot = begin( record ),\n options = {};\n\n // Update attribute. \n if( record._attributes[ name ].doUpdate( value, record, options ) ){\n // Notify listeners on changes.\n markAsDirty( record, options );\n trigger3( record, 'change:' + name, record, record.attributes[ name ], options );\n }\n\n // Close the transaction.\n isRoot && commit( record );\n}\n\nfunction begin( record : AttributesContainer ){\n if( _begin( record ) ){\n record._previousAttributes = new record.AttributesCopy( record.attributes );\n record._changedAttributes = null;\n return true;\n }\n \n return false;\n}\n\nfunction markAsDirty( record : AttributesContainer, options : TransactionOptions ){\n // Need to recalculate changed attributes, when we have nested set in change:attr handler\n if( record._changedAttributes ){\n record._changedAttributes = null;\n }\n\n return _markAsDirty( record, options );\n}\n\n/**\n * TODO: There's an opportunity to create an optimized pipeline for primitive types and Date, which makes the majority\n * of attributes. It might create the major speedup.\n * \n * Create the dedicated pipeline for owned and shared attributes as well.\n * \n * Three elements of the pipeline:\n * - from constructor\n * - from assignment\n * - from `set`\n */\n\nexport const UpdateRecordMixin = {\n// Need to override it here, since begin/end transaction brackets are overriden. \n transaction( this : AttributesContainer, fun : ( self : AttributesContainer ) => void, options : TransactionOptions = {} ) : void{\n const isRoot = begin( this );\n fun.call( this, this );\n isRoot && commit( this );\n },\n \n // Handle nested changes. TODO: propagateChanges == false, same in transaction.\n _onChildrenChange( child : Transactional, options : TransactionOptions ) : void {\n const { _ownerKey } = child,\n attribute = this._attributes[ _ownerKey ];\n\n if( !attribute /* TODO: Must be an opposite, likely the bug */ || attribute.propagateChanges ) this.forceAttributeChange( _ownerKey, options );\n },\n\n // Simulate attribute change \n forceAttributeChange( key : string, options : TransactionOptions = {} ){\n // Touch an attribute in bounds of transaction\n const isRoot = begin( this );\n\n if( markAsDirty( this, options ) ){\n trigger3( this, 'change:' + key, this, this.attributes[ key ], options );\n }\n \n isRoot && commit( this );\n },\n\n _createTransaction( this : AttributesContainer, a_values : {}, options : TransactionOptions = {} ) : Transaction {\n const isRoot = begin( this ),\n changes : string[] = [],\n nested : RecordTransaction[]= [],\n { _attributes } = this,\n values = options.parse ? this.parse( a_values, options ) : a_values;\n\n let unknown;\n\n if( shouldBeAnObject( this, values, options ) ){\n for( let name in values ){\n const spec = _attributes[ name ];\n\n if( spec ){\n if( spec.doUpdate( values[ name ], this, options, nested ) ){\n changes.push( name );\n }\n }\n else{\n unknown || ( unknown = [] );\n unknown.push( `'${ name }'` );\n }\n }\n\n if( unknown ){\n unknownAttrsWarning( this, unknown, { values }, options );\n }\n }\n \n if( changes.length && markAsDirty( this, options ) ){\n return new RecordTransaction( this, isRoot, nested, changes );\n }\n \n // No changes, but there might be silent attributes with open transactions.\n for( let pendingTransaction of nested ){\n pendingTransaction.commit( this );\n }\n\n isRoot && commit( this );\n }\n};\n\nexport function unknownAttrsWarning( record : AttributesContainer, unknown : string[], props, options ){\n record._log( 'warn', 'Type-R:UnknownAttrs', `undefined attributes ${ unknown.join(', ')} are ignored.`, props, options.logger );\n}\n\n// One of the main performance tricks of Type-R.\n// Create loop unrolled constructors for internal attribute hash,\n// so the hidden class JIT optimization will be engaged and they will become static structs.\n// It dramatically improves record performance.\nexport function constructorsMixin( attrDefs : AttributesDescriptors ) : ConstructorsMixin {\n const attrs = Object.keys( attrDefs );\n\n const AttributesCopy : AttributesCopyConstructor = new Function( 'values', `\n ${ attrs.map( attr =>`\n this.${ attr } = values.${ attr };\n `).join( '' ) }\n `) as any;\n\n AttributesCopy.prototype = Object.prototype;\n\n const Attributes : AttributesConstructor = new Function( 'record', 'values', 'options', `\n var _attrs = record._attributes;\n\n ${ attrs.map( attr =>`\n this.${ attr } = _attrs.${ attr }.doInit( values.${ attr }, record, options );\n `).join( '' ) }\n `) as any;\n\n Attributes.prototype = Object.prototype;\n\n return { Attributes, AttributesCopy };\n}\n\nexport function shouldBeAnObject( record : AttributesContainer, values : object, options ){\n if( values && values.constructor === Object ) return true;\n\n record._log( 'error', 'Type-R:InvalidObject', 'update with non-object is ignored!', { values }, options.logger );\n return false;\n}\n\n// Transaction class. Implements two-phase transactions on object's tree. \n// Transaction must be created if there are actual changes and when markIsDirty returns true. \nexport class RecordTransaction implements Transaction {\n // open transaction\n constructor( public object : AttributesContainer,\n public isRoot : boolean,\n public nested : Transaction[],\n public changes : string[] ){}\n\n // commit transaction\n commit( initiator? : AttributesContainer ) : void {\n const { nested, object, changes } = this;\n\n // Commit all pending nested transactions...\n for( let transaction of nested ){ \n transaction.commit( object );\n }\n\n // Notify listeners on attribute changes...\n // Transaction is never created when silent option is set, so just send events out.\n const { attributes, _isDirty } = object;\n for( let key of changes ){\n trigger3( object, 'change:' + key, object, attributes[ key ], _isDirty );\n }\n\n this.isRoot && commit( object, initiator );\n }\n}","import { IOEndpoint } from '../../io-tools';\nimport { LogLevel, tools, Logger } from '../../object-plus';\nimport { TransactionOptions } from '../../transactions';\nimport { AttributesContainer, AttributeUpdatePipeline, RecordTransaction, setAttribute } from '../updates';\n\nconst { notEqual, assign} = tools;\n\nexport type Transform = ( this : AnyType, next : any, prev : any, record : AttributesContainer, options : TransactionOptions ) => any;\nexport type ChangeHandler = ( this : AnyType, next : any, prev : any, record : AttributesContainer, options : TransactionOptions ) => void;\n\nexport interface AttributeOptions {\n _metatype? : typeof AnyType\n validate? : ( record : AttributesContainer, value : any, key : string ) => any\n isRequired? : boolean\n changeEvents? : boolean\n\n endpoint? : IOEndpoint\n\n type? : Function\n value? : any\n hasCustomDefault? : boolean\n\n parse? : Parse\n toJSON? : AttributeToJSON\n \n getHooks? : GetHook[]\n transforms? : Transform[]\n changeHandlers? : ChangeHandler[]\n\n _onChange? : ChangeAttrHandler\n}\n\nexport type Parse = ( value : any, key : string ) => any;\nexport type GetHook = ( value : any, key : string ) => any;\nexport type AttributeToJSON = ( value : any, key : string ) => any\nexport type AttributeParse = ( value : any, key : string ) => any\nexport type ChangeAttrHandler = ( ( value : any, attr : string ) => void ) | string;\n\n// TODO: interface differs from options, do something obout it\nconst emptyOptions : TransactionOptions = {};\n\n/**\n * Typeless attribute. Is the base class for all other attributes.\n */\nexport class AnyType implements AttributeUpdatePipeline {\n // Factory method to create attribute from options \n static create : ( options : AttributeOptions, name : string ) => AnyType;\n \n /**\n * Update pipeline functions\n * =========================\n *\n * Stage 0. canBeUpdated( value )\n * - presence of this function implies attribute's ability to update in place.\n */\n canBeUpdated( prev, next, options : TransactionOptions ) : any {}\n\n /**\n * Stage 1. Transform stage\n */\n transform( next : any, prev : any, model : AttributesContainer, options : TransactionOptions ) : any { return next; }\n\n // convert attribute type to `this.type`.\n convert( next : any, prev : any, model : AttributesContainer, options : TransactionOptions ) : any { return next; }\n\n /**\n * Stage 2. Check if attr value is changed\n */\n isChanged( a : any, b : any ) : boolean {\n return notEqual( a, b );\n }\n\n /**\n * Stage 3. Handle attribute change\n */\n handleChange( next : any, prev : any, model : AttributesContainer, options : TransactionOptions ) {}\n\n /**\n * End update pipeline definitions.\n */\n\n // create empty object passing backbone options to constructor...\n create() { return void 0; }\n\n // generic clone function for typeless attributes\n // Must be overriden in sublass\n clone( value : any, record : AttributesContainer ) {\n return value;\n }\n\n dispose( record : AttributesContainer, value : any ) : void {\n this.handleChange( void 0, value, record, emptyOptions );\n }\n\n validate( record : AttributesContainer, value : any, key : string ) : any {}\n\n toJSON( value, key, options? : object ) {\n return value && value.toJSON ? value.toJSON( options ) : value;\n }\n\n createPropertyDescriptor() : PropertyDescriptor | void {\n const { name, getHook } = this;\n\n if( name !== 'id' ){\n return {\n // call to optimized set function for single argument.\n set( value ){\n setAttribute( this, name, value );\n },\n\n // attach get hook to the getter function, if it present\n get : (\n getHook ?\n function() {\n return getHook.call( this, this.attributes[ name ], name );\n } :\n function() { return this.attributes[ name ]; }\n ),\n\n configurable : true\n }\n }\n }\n\n value : any\n\n // Used as global default value for the given metatype\n static defaultValue : any;\n\n type : Function\n\n initialize( name : string, options : TransactionOptions ){}\n\n options : AttributeOptions\n\n doInit( value, record : AttributesContainer, options : TransactionOptions ){\n const v = value === void 0 ? this.defaultValue() : value,\n x = this.transform( v, void 0, record, options );\n \n this.handleChange( x, void 0, record, options );\n return x;\n }\n\n doUpdate( value, record : AttributesContainer, options : TransactionOptions, nested? : RecordTransaction[] ){\n const { name } = this,\n { attributes } = record,\n prev = attributes[ name ];\n\n const next = this.transform( value, prev, record, options );\n attributes[ name ] = next;\n\n if( this.isChanged( next, prev ) ) {\n // Do the rest of the job after assignment\n this.handleChange( next, prev, record, options );\n return true;\n }\n\n return false;\n }\n\n propagateChanges : boolean\n\n protected _log( level : LogLevel, code : string, text : string, value, record : AttributesContainer, logger : Logger ){\n record._log( level, code, `${record.getClassName()}.${ this.name } ${ text }`, {\n 'New value' : value,\n 'Prev. value' : record.attributes[ this.name ]\n }, logger );\n }\n\n defaultValue(){\n return this.value;\n }\n\n constructor( public name : string, a_options : AttributeOptions ) { \n // Save original options...\n this.options = a_options;\n\n // Clone options.\n const options : AttributeOptions = assign( { getHooks : [], transforms : [], changeHandlers : [] }, a_options );\n options.getHooks = options.getHooks.slice();\n options.transforms = options.transforms.slice();\n options.changeHandlers = options.changeHandlers.slice();\n\n const {\n value, type, parse, toJSON, changeEvents,\n validate, getHooks, transforms, changeHandlers\n } = options;\n\n // Initialize default value...\n this.value = value;\n this.type = type;\n\n // TODO: An opportunity to optimize for attribute subtype.\n if( !options.hasCustomDefault && type ){\n this.defaultValue = this.create;\n }\n else if( tools.isValidJSON( value ) ){ \n // JSON literals must be deep copied.\n this.defaultValue = new Function( `return ${ JSON.stringify( value ) };` ) as any;\n }\n else{\n this.defaultValue = this.defaultValue;\n }\n\n // Changes must be bubbled when they are not disabled for an attribute and transactional object.\n this.propagateChanges = changeEvents !== false;\n\n this.toJSON = toJSON === void 0 ? this.toJSON : toJSON;\n\n this.validate = validate || this.validate;\n \n if( options.isRequired ){\n this.validate = wrapIsRequired( this.validate );\n }\n\n /**\n * Assemble pipelines...\n */\n\n // `convert` is default transform, which is always present...\n transforms.unshift( this.convert );\n\n // Get hook from the attribute will be used first...\n if( this.get ) getHooks.unshift( this.get );\n\n // let subclasses configure the pipeline...\n this.initialize.call( this, options );\n\n // let attribute spec configure the pipeline...\n if( getHooks.length ){\n const getHook = this.getHook = getHooks.reduce( chainGetHooks );\n\n const { validate } = this;\n this.validate = function( record : AttributesContainer, value : any, key : string ){\n return validate.call( this, record, getHook.call( record, value, key ), key );\n }\n }\n \n this.transform = transforms.length ? transforms.reduce( chainTransforms ) : this.transform;\n \n this.handleChange = changeHandlers.length ? changeHandlers.reduce( chainChangeHandlers ) : this.handleChange;\n\n // Attribute-level parse transform are attached as update hooks modifiers...\n const { doInit, doUpdate } = this;\n this.doInit = parse ? function( value, record : AttributesContainer, options : TransactionOptions ){\n return doInit.call( this, options.parse && value !== void 0 ? parse.call( record, value, this.name ) : value, record, options );\n } : doInit;\n\n this.doUpdate = parse ? function( value, record : AttributesContainer, options : TransactionOptions, nested? : RecordTransaction[] ){\n return doUpdate.call( this, options.parse && value !== void 0 ? parse.call( record, value, this.name ) : value, record, options, nested );\n } : doUpdate;\n }\n\n getHook : ( value, key : string ) => any = null\n get : ( value, key : string ) => any\n}\n\n\nfunction chainGetHooks( prevHook : GetHook, nextHook : GetHook ) : GetHook {\n return function( value, name ) {\n return nextHook.call( this, prevHook.call( this, value, name ), name );\n }\n}\n\nfunction chainTransforms( prevTransform : Transform, nextTransform : Transform ) : Transform {\n return function( next, prev, record, options ) {\n return nextTransform.call( this, prevTransform.call( this, next, prev, record, options ), prev, record, options );\n }\n}\n\nfunction chainChangeHandlers( prevHandler : ChangeHandler, nextHandler : ChangeHandler ) : ChangeHandler {\n return function( next, prev, record, options ) {\n prevHandler.call( this, next, prev, record, options );\n nextHandler.call( this, next, prev, record, options );\n }\n}\n\nfunction wrapIsRequired( validate ){\n return function( record : AttributesContainer, value : any, key : string ){\n return value ? validate.call( this, record, value, key ) : 'Required';\n }\n}","/**\n * Built-in JSON types attributes: Object, Array, Number, String, Boolean, and immutable class.\n * \n * Adds type assertions, default validation, and optimized update pipeline.\n */\n\nimport { TransactionOptions } from '../../transactions';\nimport { AnyType } from './any';\nimport { AttributesContainer } from '../updates';\n\n/**\n * Custom class must be immutable class which implements toJSON() method\n * with a constructor taking json.\n */\nexport class ImmutableClassType extends AnyType {\n type : new ( value? : any ) => {}\n\n create(){\n return new this.type();\n }\n\n convert( next : any ) : any {\n return next == null || next instanceof this.type ? next : new this.type( next );\n }\n\n toJSON( value, key? : string, options? : object ){\n return value && value.toJSON ? value.toJSON( options ) : value;\n }\n\n clone( value ) {\n return new this.type( this.toJSON( value ) );\n }\n\n isChanged( a, b ){\n return a !== b;\n }\n}\n\n/**\n * Optimized attribute of primitive type.\n * \n * Primitives has specialized simplified pipeline.\n */\nexport class PrimitiveType extends AnyType {\n type : NumberConstructor | StringConstructor | BooleanConstructor\n\n dispose(){}\n create() { return this.type(); }\n\n toJSON( value ) { return value; }\n\n convert( next ) { return next == null ? next : this.type( next ); }\n\n isChanged( a, b ) { return a !== b; }\n\n clone( value ) { return value; }\n\n doInit( value, record : AttributesContainer, options : TransactionOptions ){\n return this.transform( value === void 0 ? this.value : value, void 0, record, options );\n }\n\n doUpdate( value, record, options, nested ){\n const { name } = this,\n { attributes } = record,\n prev = attributes[ name ];\n \n return prev !== ( attributes[ name ] = this.transform( value, prev, record, options ) );\n }\n\n initialize(){\n if( !this.options.hasCustomDefault ){\n this.value = this.type();\n }\n }\n}\n\n// Number type with special validation algothim.\n/** @private */ \nexport class NumericType extends PrimitiveType {\n type : NumberConstructor\n\n create(){\n return 0;\n }\n\n convert( next, prev?, record?, options? ) {\n const num = next == null ? next : this.type( next );\n\n if( num !== num ){\n this._log( 'error', 'Type-R:InvalidNumber', 'Number attribute is assigned with an invalid number', next, record, options.logger );\n }\n \n return num;\n }\n\n validate( model, value, name ) {\n // Whatever is not symmetrically serializable to JSON, is not valid by default.\n if( value != null && !isFinite( value ) ) {\n return name + ' is not valid number';\n }\n }\n}\n\n/**\n * Compatibility wrapper for Array type.\n * @private\n */ \nexport class ArrayType extends AnyType {\n toJSON( value ) { return value; }\n dispose(){}\n create(){ return []; }\n\n convert( next, prev, record, options ) {\n // Fix incompatible constructor behaviour of Array...\n if( next == null || Array.isArray( next ) ) return next;\n\n this._log( 'error', 'Type-R:InvalidArray', 'Array attribute assigned with non-array value', next, record, options.logger );\n\n return [];\n }\n\n clone( value ){\n return value && value.slice();\n }\n}\n\nexport class ObjectType extends AnyType {\n create(){ return {}; }\n\n convert( next, prev, record, options ) {\n if( next == null || typeof next === 'object' ) return next;\n \n this._log( 'error', 'Type-R:InvalidObject', 'Object attribute is assigned with non-object value', next, record, options.logger );\n return {};\n }\n}\n\nexport function doNothing(){}\n\nexport class FunctionType extends AnyType {\n // Functions are not serialized.\n toJSON( value ) { return void 0; }\n create(){ return doNothing; }\n dispose(){}\n\n convert( next, prev, record, options ) {\n // Fix incompatible constructor behaviour of Function...\n if( next == null || typeof next === 'function' ) return next;\n\n this._log( 'error', 'Type-R:InvalidFunction', 'Function attribute assigned with non-function value', next, record, options.logger );\n\n return doNothing;\n }\n\n // Functions are not cloned.\n clone( value ){ return value; }\n}\n","/**\n * Date attribute type.\n * \n * Implements validation, cross-browser compatibility fixes, variety of Date serialization formats,\n * and optimized update pipeline.\n */\nimport { TransactionOptions } from '../../transactions';\nimport { AnyType } from './any';\nimport { AttributesContainer } from '../updates';\n\n// Date Attribute\n/** @private */\nexport class DateType extends AnyType {\n create(){\n return new Date();\n }\n \n convert( next : any, a, record, options ){\n if( next == null || next instanceof Date ) return next;\n\n const date = new Date( next ),\n timestamp = date.getTime();\n\n if( timestamp !== timestamp ){\n this._log( 'error', 'Type-R:InvalidDate', 'Date attribute assigned with invalid date', next, record, options.logger );\n }\n\n return date;\n }\n\n validate( model, value, name ) {\n if( value != null ){\n const timestamp = value.getTime(); \n if( timestamp !== timestamp ) return name + ' is Invalid Date';\n }\n }\n\n toJSON( value ) { return value && value.toISOString(); }\n\n isChanged( a, b ) { return ( a && a.getTime() ) !== ( b && b.getTime() ); }\n\n doInit( value, record : AttributesContainer, options : TransactionOptions ){\n // Date don't have handleChanges step.\n return this.transform( value === void 0 ? this.defaultValue() : value, void 0, record, options );\n }\n\n doUpdate( value, record, options, nested ){\n const { name } = this,\n { attributes } = record,\n prev = attributes[ name ];\n \n // Date don't have handleChanges step.\n return this.isChanged( prev , attributes[ name ] = this.transform( value, prev, record, options ) );\n }\n\n clone( value ) { return value && new Date( value.getTime() ); }\n dispose(){}\n}\n\n// If ISO date is not supported by date constructor (such as in Safari), polyfill it.\nfunction supportsDate( date ){\n return !isNaN( ( new Date( date ) ).getTime() );\n}\n\nif( !supportsDate('2011-11-29T15:52:30.5') ||\n !supportsDate('2011-11-29T15:52:30.52') ||\n !supportsDate('2011-11-29T15:52:18.867') ||\n !supportsDate('2011-11-29T15:52:18.867Z') ||\n !supportsDate('2011-11-29T15:52:18.867-03:30') ){\n\n DateType.prototype.convert = function( value ){\n return value == null || value instanceof Date ? value : new Date( safeParseDate( value ) );\n }\n}\n\nconst numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ],\n isoDatePattern = /^(\\d{4}|[+\\-]\\d{6})(?:-(\\d{2})(?:-(\\d{2}))?)?(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3}))?)?(?:(Z)|([+\\-])(\\d{2})(?::(\\d{2}))?)?)?$/;\n\nfunction safeParseDate( date : string ) : number {\n var timestamp, struct : any[], minutesOffset = 0;\n\n if( ( struct = isoDatePattern.exec( date )) ) {\n // avoid NaN timestamps caused by undefined values being passed to Date.UTC\n for( var i = 0, k; ( k = numericKeys[ i ] ); ++i ) {\n struct[ k ] = +struct[ k ] || 0;\n }\n\n // allow undefined days and months\n struct[ 2 ] = (+struct[ 2 ] || 1) - 1;\n struct[ 3 ] = +struct[ 3 ] || 1;\n\n if( struct[ 8 ] !== 'Z' && struct[ 9 ] !== undefined ) {\n minutesOffset = struct[ 10 ] * 60 + struct[ 11 ];\n\n if( struct[ 9 ] === '+' ) {\n minutesOffset = 0 - minutesOffset;\n }\n }\n\n timestamp =\n Date.UTC( struct[ 1 ], struct[ 2 ], struct[ 3 ], struct[ 4 ], struct[ 5 ] + minutesOffset, struct[ 6 ],\n struct[ 7 ] );\n }\n else {\n timestamp = Date.parse( date );\n }\n\n return timestamp;\n}","import { ItemsBehavior, Transactional, transactionApi, TransactionOptions } from '../../transactions';\nimport { ValidationError } from '../../validation';\nimport { AnyType } from './any';\nimport { AttributesContainer, ConstructorOptions } from '../updates';\n\nconst { free, aquire } = transactionApi;\n\nexport class AggregatedType extends AnyType {\n type : typeof Transactional\n\n clone( value : Transactional ) : Transactional {\n return value ? value.clone() : value;\n }\n\n toJSON( x, key : string, options : object ){ return x && x.toJSON( options ); }\n\n doInit( value, record : AttributesContainer, options : ConstructorOptions ){\n const v = options.clone ? this.clone( value ) : (\n value === void 0 ? this.defaultValue() : value\n );\n\n const x = this.transform( v, void 0, record, options );\n this.handleChange( x, void 0, record, options );\n return x;\n }\n\n doUpdate( value, record, options, nested : any[] ){ // Last to things can be wrapped to an object, either transaction or ad-hoc\n const key = this.name, { attributes } = record; \n const prev = attributes[ key ];\n let update;\n\n // This can be moved to transactional attribute. And chained with the rest.\n if( update = this.canBeUpdated( prev, value, options ) ) { // todo - skip empty updates.\n const nestedTransaction = prev._createTransaction( update, options );\n if( nestedTransaction ){\n if( nested ){\n nested.push( nestedTransaction );\n }\n else{\n nestedTransaction.commit( record );\n }\n\n if( this.propagateChanges ) return true;\n }\n\n return false;\n }\n\n const next = this.transform( value, prev, record, options );\n attributes[ key ] = next;\n\n if( this.isChanged( next, prev ) ) { // Primitives and nested comparison can be inlined.\n // Do the rest of the job after assignment\n this.handleChange( next, prev, record, options );\n\n return true;\n }\n\n return false;\n }\n\n canBeUpdated( prev : Transactional, next : any, options : TransactionOptions ) : any {\n // If an object already exists, and new value is of incompatible type, let object handle the update.\n if( prev && next != null ){\n if( next instanceof this.type ){\n // In case if merge option explicitly specified, force merge.\n if( options.merge ) return next.__inner_state__;\n }\n else{\n return next;\n }\n }\n }\n\n convert( next : any, prev : any, record : AttributesContainer, options : TransactionOptions ) : Transactional {\n // Invoke class factory to handle abstract classes\n if( next == null ) return next;\n \n if( next instanceof this.type ){\n if( next._shared && !( next._shared & ItemsBehavior.persistent ) ) { // TODO: think more about shared types assignment compatibility. \n this._log( 'error', 'Type-R:InvalidCollection', 'aggregated collection attribute is assigned with shared collection type', next, record, options.logger );\n }\n\n // With explicit 'merge' option we need to clone an object if its previous value was 'null'.\n // This is an only case we could be here when merge === true.\n if( options.merge ) return next.clone();\n\n if( next._owner ){\n 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 );\n return next.clone();\n }\n\n return next;\n }\n\n return ( this.type as any).create( next, options );\n }\n\n dispose ( record : AttributesContainer, value : Transactional ){\n if( value ){\n this.handleChange( void 0, value, record, {} );\n }\n }\n\n validate( record : AttributesContainer, value : Transactional ) : ValidationError {\n var error = value && value.validationError;\n if( error ) return error;\n }\n\n create() : Transactional {\n return (this.type).create(); // this the subclass of Transactional here.\n }\n\n initialize( options ){\n options.changeHandlers.unshift( this._handleChange );\n }\n\n _handleChange( next : Transactional, prev : Transactional, record : AttributesContainer, options : TransactionOptions ){\n if( prev ){\n free( record, prev );\n options.unset || prev.dispose();\n } \n \n if( next ) aquire( record, next, this.name );\n }\n}","import { eventsApi } from '../../object-plus';\nimport { ItemsBehavior, Transactional, transactionApi, TransactionOptions } from '../../transactions';\nimport { AnyType } from './any';\nimport { AttributesContainer, ConstructorOptions } from '../updates';\nimport { ChainableAttributeSpec } from '../attrDef';\n\nconst { on, off } = eventsApi,\n { free, aquire } = transactionApi;\n\n/************************\n * Shared attribute definition.\n * - Not serialized.\n * - Listening to the changes.\n * - Doesn't take ownership when assigned with object of proper type.\n * - Takes ownership on objects which are converted.\n */\n\nconst shareAndListen = ItemsBehavior.listen | ItemsBehavior.share;\n\n/** @private */\nexport class SharedType extends AnyType {\n type : typeof Transactional\n\n doInit( value, record : AttributesContainer, options : ConstructorOptions ){\n const v = options.clone ? this.clone( value, record ) : (\n value === void 0 ? this.defaultValue() : value\n );\n\n const x = this.transform( v, void 0, record, options );\n this.handleChange( x, void 0, record, options );\n return x;\n }\n\n doUpdate( value, record, options, nested : any[] ){ // Last to things can be wrapped to an object, either transaction or ad-hoc\n const key = this.name, { attributes } = record; \n const prev = attributes[ key ];\n let update;\n\n // This can be moved to transactional attribute. And chained with the rest.\n if( update = this.canBeUpdated( prev, value, options ) ) { // todo - skip empty updates.\n const nestedTransaction = prev._createTransaction( update, options );\n if( nestedTransaction ){\n if( nested ){\n nested.push( nestedTransaction );\n }\n else{\n nestedTransaction.commit( record );\n }\n\n if( this.propagateChanges ) return true;\n }\n\n return false;\n }\n\n const next = this.transform( value, prev, record, options );\n attributes[ key ] = next;\n\n if( this.isChanged( next, prev ) ) { // Primitives and nested comparison can be inlined.\n // Do the rest of the job after assignment\n this.handleChange( next, prev, record, options );\n\n return true;\n }\n\n return false;\n }\n\n clone( value : Transactional, record : AttributesContainer ) : Transactional {\n // References are not cloned.\n if( !value || value._owner !== record ) return value;\n\n // Implicitly created objects are cloned.\n const clone = value.clone();\n aquire( record, clone, this.name );\n return clone;\n }\n\n // Do not serialize by default.\n toJSON(){}\n\n canBeUpdated( prev : Transactional, next : any, options : TransactionOptions ) : any {\n // If an object already exists, and new value is of incompatible type, let object handle the update.\n if( prev && next != null && !( next instanceof this.type ) ){\n return next;\n }\n }\n\n convert( next : any, prev : any, record : AttributesContainer, options : TransactionOptions ) : Transactional {\n if( next == null || next instanceof this.type ) return next;\n\n // Convert type using implicitly created transactional object.\n const implicitObject = new ( this.type as any )( next, options, shareAndListen );\n\n // To prevent a leak, we need to take an ownership on it.\n aquire( record, implicitObject, this.name );\n\n return implicitObject;\n }\n\n // Refs are always valid.\n validate( model, value, name ){}\n\n // They are always created as null.\n create() : Transactional {\n return null;\n }\n\n // Listening to the change events\n _handleChange( next : Transactional, prev : Transactional, record : AttributesContainer, options ){\n if( prev ){\n // If there was an implicitly created object, remove an ownership.\n if( prev._owner === record ){\n free( record, prev );\n options.unset || prev.dispose();\n }\n else{\n off( prev, prev._changeEventName, this._onChange, record );\n }\n } \n \n if( next ){\n // No need to take an ownership for an implicit object - already done in convert or clone.\n if( next._owner !== record ){\n on( next, next._changeEventName, this._onChange, record );\n }\n } \n }\n\n dispose( record : AttributesContainer, value : Transactional ){\n if( value ){\n this.handleChange( void 0, value, record, {} );\n }\n }\n\n _onChange : ( child : Transactional, options : TransactionOptions, initiator : Transactional ) => void \n\n initialize( options ){\n // Create change event handler which knows current attribute name. \n const attribute = this;\n this._onChange = this.propagateChanges ? function( child, options, initiator ){\n this === initiator || this.forceAttributeChange( attribute.name, options );\n } : ignore;\n\n options.changeHandlers.unshift( this._handleChange );\n }\n}\n\nfunction ignore(){}","import { PrimitiveType, NumericType, ObjectType, ImmutableClassType, FunctionType, ArrayType } from './basic';\nimport { DateType } from './date';\nimport { AnyType, AttributeOptions } from './any';\n\nexport * from './any';\nexport * from './basic';\nexport * from './date';\nexport * from './owned';\nexport * from './shared';\n\n/**\n * Every record attribute type has the corresponding metatype controlling its behavior.\n * For built-in types, Type-R uses the predefined list to resolve metatype in order to avoid global objects modifications.\n * For user-defined types, static `_metatype` constructor member is used.\n */\n\nconst builtins : Function[] = [ String, Number, Boolean, Date, Object, Array, Function ],\n metatypes = [ PrimitiveType, NumericType, PrimitiveType, DateType, ObjectType, ArrayType, FunctionType ];\n\nexport function getMetatype( Ctor : Function ){\n return ( Ctor as any )._metatype || resolveBuiltins( Ctor );\n}\n\nAnyType.create = ( options : AttributeOptions, name : string ) => {\n const type = options.type,\n AttributeCtor = options._metatype || ( type ? getMetatype( type ): AnyType );\n\n return new AttributeCtor( name, options );\n}\n\nfunction resolveBuiltins( Ctor : Function ){\n const idx = builtins.indexOf( Ctor );\n return idx < 0 ? ImmutableClassType : metatypes[ idx ];\n}","/**\n * Type spec engine. Declare attributes using chainable syntax,\n * and returns object with spec.\n */\nimport { IOEndpoint } from '../io-tools';\nimport { definitionDecorator, EventMap, EventsDefinition, tools } from '../object-plus';\nimport { Transactional } from '../transactions';\nimport { AttributeOptions, Parse, AnyType, getMetatype, SharedType } from './metatypes';\nimport { AttributesContainer } from './updates';\n\nconst { assign } = tools;\n\nexport interface AttributeCheck {\n ( value : any, key : string ) : boolean\n error? : any\n}\n\n// Infer the proper TS type from a Type-R attribute spec.\nexport type Infer =\n A extends ChainableAttributeSpec ? TrueReturnType :\n A extends Function ? TrueReturnType :\n A;\n \n// Extract the proper TS return type for a function or constructor.\ntype TrueReturnType =\n F extends DateConstructor ? Date :\n F extends ( ...args : any[] ) => infer R ? R :\n F extends new ( ...args : any[] ) => infer R ? R :\n void;\n\nexport class ChainableAttributeSpec{\n options : AttributeOptions & { type? : F };\n\n constructor( options : AttributeOptions ) {\n // Shallow copy options, fill it with defaults.\n this.options = { getHooks : [], transforms : [], changeHandlers : []};\n if( options ) assign( this.options, options );\n }\n\n check( check : AttributeCheck, error? : any ) : this {\n function validate( model, value, name ){\n if( !check.call( model, value, name ) ){\n const msg = error || check.error || name + ' is not valid';\n return typeof msg === 'function' ? msg.call( model, name ) : msg;\n }\n }\n\n const prev = this.options.validate;\n\n return this.metadata({\n validate : prev ? (\n function( model, value, name ){\n return prev( model, value, name ) || validate( model, value, name );\n }\n ) : validate\n });\n }\n\n get as() : PropertyDecorator {\n return definitionDecorator( 'attributes', this );\n }\n\n get isRequired() : this {\n return this.required;\n }\n\n get required() : this {\n return this.metadata({ isRequired : true }); \n }\n\n endpoint( endpoint : IOEndpoint ) : this {\n return this.metadata({ endpoint });\n }\n\n watcher( ref : string | ( ( value : any, key : string ) => void ) ) : this {\n return this.metadata({ _onChange : ref });\n }\n\n // Attribute-specific parse transform\n parse( fun : Parse ) : this {\n return this.metadata({ parse : fun });\n }\n\n toJSON( fun ) : this {\n return this.metadata({\n toJSON : typeof fun === 'function' ? fun : ( fun ? ( x, k, o ) => x && x.toJSON( o ) : emptyFunction ) \n });\n }\n\n // Attribute get hook.\n get( fun ) : this {\n return this.metadata({\n getHooks : this.options.getHooks.concat( fun )\n });\n }\n\n // Attribute set hook.\n set( fun ) : this {\n function handleSetHook( next, prev, record : AttributesContainer, options ) {\n if( this.isChanged( next, prev ) ) {\n const changed = fun.call( record, next, this.name );\n return changed === void 0 ? prev : this.convert( changed, prev, record, options );\n }\n\n return prev;\n }\n\n return this.metadata({\n transforms : this.options.transforms.concat( handleSetHook )\n });\n }\n\n changeEvents( events : boolean ) : this {\n return this.metadata({ changeEvents : events });\n }\n\n // Subsribe to events from an attribute.\n events( map : EventsDefinition ) : this {\n const eventMap = new EventMap( map );\n\n function handleEventsSubscribtion( next, prev, record : AttributesContainer ){\n prev && prev.trigger && eventMap.unsubscribe( record, prev );\n\n next && next.trigger && eventMap.subscribe( record, next );\n }\n\n return this.metadata({\n changeHandlers : this.options.changeHandlers.concat( handleEventsSubscribtion )\n });\n }\n\n // Creates a copy of the spec.\n get has() : this {\n return this;\n }\n\n metadata( options : object ) : this {\n const cloned = new ChainableAttributeSpec( this.options );\n assign( cloned.options, options );\n return cloned as any;\n }\n\n value( x ) : this {\n return this.metadata({ value : x, hasCustomDefault : true });\n }\n\n static from( spec : any ) : ChainableAttributeSpec {\n // Pass metatype through untouched...\n if( spec && spec instanceof ChainableAttributeSpec ) {\n return spec;\n }\n\n return typeof spec === 'function' ? type( spec ) : value( spec );\n }\n}\n\nfunction emptyFunction(){}\n\nexport function type( this : void, Type : ChainableAttributeSpec | F, value? : any ) : ChainableAttributeSpec {\n if( Type instanceof ChainableAttributeSpec ) return Type;\n\n const attrDef = new ChainableAttributeSpec({ type : Type }),\n defaultValue = Type && value === void 0 ? getMetatype( Type ).defaultValue : value;\n\n return defaultValue === void 0 ? attrDef : attrDef.value( defaultValue );\n}\n\nexport function shared( this : void, Constructor : C ) : ChainableAttributeSpec {\n return new ChainableAttributeSpec({\n value : null,\n type : Constructor,\n _metatype : SharedType\n });\n}\n\n// Create attribute metatype inferring the type from the value.\nexport function value( this : void, x : any ) : ChainableAttributeSpec {\n const Type = inferType( x ),\n // Transactional types inferred from values must have shared type. \n AttrDef = Type && Type.prototype instanceof Transactional ? shared( Type ) :\n type( Type );\n\n return AttrDef.value( x );\n}\n\nfunction inferType( value : any ) : Function {\n switch( typeof value ) {\n case 'number' :\n return Number;\n case 'string' :\n return String;\n case 'boolean' :\n return Boolean;\n case 'function' :\n return Function;\n case 'undefined' :\n return void 0;\n case 'object' :\n return value ? value.constructor : void 0;\n }\n}","import { IOEndpoint } from '../io-tools';\nimport { eventsApi, tools as _ } from '../object-plus';\nimport { CompiledReference } from '../traversable';\nimport { ChainableAttributeSpec } from './attrDef';\nimport { AnyType } from './metatypes';\nimport { ConstructorsMixin, constructorsMixin } from './updates';\n\nexport interface RecordAttributesMixin extends ConstructorsMixin {\n // Attributes descriptors\n _attributes : AttributeDescriptors\n _attributesArray : AnyType[]\n \n // Attribute's property descriptors\n properties : PropertyDescriptorMap\n\n // Event map for record's local events.\n _localEvents? : eventsApi.EventMap,\n\n _endpoints : { [ name : string ] : IOEndpoint }\n}\n\nexport interface AttributeDescriptors {\n [ name : string ] : AnyType\n}\n\n// Create attribute from the type spec.\nexport function createAttribute( spec : any, name : string ) : AnyType {\n return AnyType.create( ChainableAttributeSpec.from( spec ).options, name );\n}\n\n// Create record mixin from the given record's attributes definition\nexport function createAttributesMixin( attributesDefinition : object, baseClassAttributes : AttributeDescriptors ) : RecordAttributesMixin {\n const myAttributes = _.transform( {} as AttributeDescriptors, attributesDefinition, createAttribute ),\n allAttributes = _.defaults( {} as AttributeDescriptors, myAttributes, baseClassAttributes );\n\n const ConstructorsMixin = constructorsMixin( allAttributes );\n\n return {\n ...ConstructorsMixin,\n _attributes : new ConstructorsMixin.AttributesCopy( allAttributes ),\n _attributesArray : Object.keys( allAttributes ).map( key => allAttributes[ key ] ),\n properties : _.transform( {}, myAttributes, x => x.createPropertyDescriptor() ),\n ...localEventsMixin( myAttributes ),\n _endpoints : _.transform( {}, allAttributes, attrDef => attrDef.options.endpoint )\n } \n}\n\ninterface LocalEventsMixin {\n _localEvents? : eventsApi.EventMap\n}\n\nfunction localEventsMixin( attrSpecs : AttributeDescriptors ) : LocalEventsMixin {\n let _localEvents : eventsApi.EventMap;\n\n for( var key in attrSpecs ){\n const attribute = attrSpecs[ key ],\n { _onChange } = attribute.options; \n\n if( _onChange ){\n _localEvents || ( _localEvents = new eventsApi.EventMap() );\n\n _localEvents.addEvent( 'change:' + key,\n typeof _onChange === 'string' ?\n createWatcherFromRef( _onChange, key ) : \n wrapWatcher( _onChange, key ) );\n }\n }\n\n return _localEvents ? { _localEvents } : {};\n}\n\nfunction wrapWatcher( watcher, key ){\n return function( record, value ){\n watcher.call( record, value, key );\n } \n}\n\nfunction createWatcherFromRef( ref : string, key : string ){\n const { local, resolve, tail } = new CompiledReference( ref, true );\n return local ?\n function( record, value ){\n record[ tail ]( value, key );\n } :\n function( record, value ){\n resolve( record )[ tail ]( value, key );\n }\n}","import { IOEndpoint, IONode, IOOptions, IOPromise, startIO } from '../io-tools';\n\nexport interface IORecord extends IONode {\n getEndpoint() : IOEndpoint\n save( options? : IOOptions ) : IOPromise\n fetch( options? : IOOptions ) : IOPromise\n destroy( options? : IOOptions ) : IOPromise\n toJSON( options? : object ) : any\n isNew() : boolean\n id : string | number\n set( json : object, options : object )\n}\n\nexport const IORecordMixin = {\n save( this : IORecord, options : IOOptions = {} ){\n const endpoint = this.getEndpoint(),\n json = this.toJSON( options );\n\n return startIO(\n this,\n this.isNew() ?\n endpoint.create( json, options, this ) :\n endpoint.update( this.id, json, options, this ),\n options,\n\n update => {\n this.set( update, { parse : true, ...options } );\n }\n );\n },\n\n fetch( options : IOOptions = {} ){\n return startIO(\n this,\n this.getEndpoint().read( this.id, options, this ),\n options,\n\n json => this.set( json, { parse : true, ...options } )\n );\n },\n\n destroy( options : IOOptions = {} ){ \n return startIO(\n this,\n this.getEndpoint().destroy( this.id, options, this ),\n options,\n\n () => {\n const { collection } = this;\n if( collection ){\n collection.remove( this, options );\n }\n else{\n this.dispose();\n }\n\n return this;\n }\n )\n }\n}","/**\n * Record core implementing transactional updates.\n * The root of all definitions. \n */\n\nimport { CollectionConstructor } from '../collection';\nimport { IOEndpoint, IOPromise } from '../io-tools';\nimport { define, definitions, isProduction, Logger, logger, LogLevel, mixinRules, tools } from '../object-plus';\nimport { CloneOptions, Owner, Transaction, Transactional, TransactionalDefinition, TransactionOptions } from '../transactions';\nimport { ChildrenErrors } from '../validation';\nimport { AggregatedType, AnyType } from './metatypes';\nimport { IORecord, IORecordMixin } from './io-mixin';\nimport { AttributesConstructor, AttributesContainer, AttributesCopyConstructor, AttributesValues, setAttribute, shouldBeAnObject, unknownAttrsWarning, UpdateRecordMixin } from './updates';\nimport { type } from './attrDef';\n\n\nconst { assign, isEmpty } = tools;\n\n/*******************************************************\n * Record core implementation\n */\n\nexport interface ConstructorOptions extends TransactionOptions{\n clone? : boolean\n}\n\n// Client unique id counter\nlet _cidCounter : number = 0;\n\n/***************************************************************\n * Record Definition as accepted by Record.define( definition )\n */\nexport interface RecordDefinition extends TransactionalDefinition {\n idAttribute? : string\n attributes? : AttributesValues\n collection? : object\n Collection? : typeof Transactional\n}\n\n@define({\n // Default client id prefix \n cidPrefix : 'm',\n\n // Name of the change event\n _changeEventName : 'change',\n\n // Default id attribute name\n idAttribute : 'id'\n})\n@definitions({\n defaults : mixinRules.merge,\n attributes : mixinRules.merge,\n collection : mixinRules.merge,\n Collection : mixinRules.value,\n idAttribute : mixinRules.protoValue\n})\nexport class Record extends Transactional implements IORecord, AttributesContainer, Iterable {\n static _metatype = AggregatedType;\n\n // Hack\n static onDefine( definition, BaseClass ){}\n\n static Collection : CollectionConstructor;\n static DefaultCollection : CollectionConstructor;\n \n // Attribute type for the record id.\n static id = type( String ).value( null );\n \n // Lazy object reference, serializable as id.\n static get ref(){\n return type( this )\n .toJSON( x => x ? x.id : null )\n .parse( x => {\n return { [ this.prototype.idAttribute ] : x };\n });\n }\n\n static defaults( attrs : AttributesValues ) : typeof Record {\n return this.extend({ attributes : attrs });\n }\n \n static attributes : AttributesValues\n\n /********************\n * IO Methods\n */\n _endpoints : { [ name : string ] : IOEndpoint }\n\n // Save record\n save( options? : object ) : IOPromise { throw new Error( 'Implemented by mixin' ); }\n\n // Destroy record\n destroy( options? : object ) : IOPromise { throw new Error( 'Implemented by mixin' ); }\n\n /***********************************\n * Core Members\n */\n // Previous attributes\n _previousAttributes : {}\n\n previousAttributes(){ return new this.AttributesCopy( this._previousAttributes ); } \n\n // Current attributes \n attributes : AttributesValues\n\n // Polymorphic accessor for aggregated attribute's canBeUpdated().\n get __inner_state__(){ return this.attributes; }\n\n // Lazily evaluated changed attributes hash\n _changedAttributes : AttributesValues\n\n get changed(){\n let changed = this._changedAttributes;\n\n if( !changed ){\n const prev = this._previousAttributes;\n changed = {};\n\n const { attributes } = this;\n\n for( let attr of this._attributesArray ){\n const key = attr.name,\n value = attributes[ key ];\n\n if( attr.isChanged( value, prev[ key ] ) ){\n changed[ key ] = value;\n }\n }\n\n this._changedAttributes = changed;\n }\n\n return changed; \n }\n\n changedAttributes( diff? : {} ) : boolean | {} {\n if( !diff ) return this.hasChanged() ? assign( {}, this.changed ) : false;\n\n var val, changed : {} | boolean = false,\n old = this._transaction ? this._previousAttributes : this.attributes,\n attrSpecs = this._attributes;\n\n for( var attr in diff ){\n if( !attrSpecs[ attr ].isChanged( old[ attr ], ( val = diff[ attr ] ) ) ) continue;\n (changed || (changed = {}))[ attr ] = val;\n }\n\n return changed; \n }\n\n hasChanged( key? : string ) : boolean {\n const { _previousAttributes } = this;\n if( !_previousAttributes ) return false;\n\n return key ?\n this._attributes[ key ].isChanged( this.attributes[ key ], _previousAttributes[ key ] ) :\n !isEmpty( this.changed );\n }\n\n previous( key : string ) : any {\n if( key ){\n const { _previousAttributes } = this;\n if( _previousAttributes ) return _previousAttributes[ key ];\n }\n \n return null;\n }\n\n isNew() : boolean {\n return this.id == null;\n }\n\n has( key : string ) : boolean {\n return this[ key ] != void 0;\n }\n\n // Return attribute value, setting an attribute to undefined.\n // TODO: If attribute was aggregated, don't dispose it.\n unset( key : string, options? ) : any {\n const value = this[ key ];\n this.set({ [ key ] : void 0 }, { unset : true, ...options });\n return value;\n }\n\n // Undocumented. Move to NestedTypes?\n clear( options? ) : this {\n const nullify = options && options.nullify;\n\n this.transaction( () =>{\n this.forEach( ( value, key ) => this[ key ] = nullify ? null : void 0 );\n }, options );\n\n return this;\n }\n\n // Returns Record owner skipping collections. TODO: Move out\n getOwner() : Owner {\n const owner : any = this._owner;\n\n // If there are no key, owner must be transactional object, and it's the collection.\n // We don't expect that collection can be the member of collection, so we're skipping just one level up. An optimization.\n return this._ownerKey ? owner : owner && owner._owner;\n }\n\n /***********************************\n * Identity managements\n */\n\n // Id attribute name ('id' by default)\n idAttribute : string;\n\n // Fixed 'id' property pointing to id attribute\n get id() : string { return this.attributes[ this.idAttribute ]; }\n set id( x : string ){ setAttribute( this, this.idAttribute, x ); }\n\n /***********************************\n * Dynamically compiled stuff\n */\n\n // Attributes specifications \n _attributes : { [ key : string ] : AnyType }\n _attributesArray : AnyType[]\n\n // Attributes object copy constructor\n Attributes : AttributesConstructor\n AttributesCopy : AttributesCopyConstructor\n\n // Create record default values, optionally augmenting given values.\n defaults( values = {} ){\n const defaults = {},\n { _attributesArray } = this;\n\n for( let attr of _attributesArray ){\n const key = attr.name,\n value = values[ key ];\n\n defaults[ key ] = value === void 0 ? attr.defaultValue() : value;\n }\n\n return defaults;\n }\n\n /***************************************************\n * Record construction\n */\n // Create record, optionally setting an owner\n constructor( a_values? : any, a_options? : ConstructorOptions ){\n super( _cidCounter++ );\n this.attributes = {};\n \n const options = a_options || {},\n values = ( options.parse ? this.parse( a_values, options ) : a_values ) || {};\n\n isProduction || typeCheck( this, values, options );\n\n this._previousAttributes = this.attributes = new this.Attributes( this, values, options );\n\n this.initialize( a_values, a_options );\n\n if( this._localEvents ) this._localEvents.subscribe( this, this );\n }\n\n // Initialization callback, to be overriden by the subclasses \n initialize( values? : Partial, options? ){}\n\n // Deeply clone record, optionally setting new owner.\n clone( options : CloneOptions = {} ) : this {\n const copy : this = new (this.constructor)( this.attributes, { clone : true } );\n \n if( options.pinStore ) copy._defaultStore = this.getStore();\n\n return copy;\n }\n\n // Validate attributes.\n _validateNested( errors : ChildrenErrors ) : number {\n var length = 0;\n\n const { attributes } = this;\n\n for( let attribute of this._attributesArray ){\n const { name } = attribute,\n error = attribute.validate( this, attributes[ name ], name );\n\n if( error ){\n errors[ name ] = error;\n length++;\n }\n }\n\n return length;\n }\n\n // Get attribute by key\n get( key : string ) : any {\n return this[ key ];\n }\n\n // Apply bulk in-place object update in scope of ad-hoc transaction \n set( values : any, options? : TransactionOptions ) : this {\n if( values ){ \n const transaction = this._createTransaction( values, options );\n transaction && transaction.commit();\n }\n\n return this;\n }\n\n /**\n * Serialization control\n */\n\n // Default record-level serializer, to be overriden by subclasses \n toJSON( options? : object ) : any {\n const json = {},\n { attributes } = this;\n\n for( let attribute of this._attributesArray ){\n const { name } = attribute,\n value = attributes[ name ];\n\n if( value !== void 0 ){\n // ...serialize it according to its spec.\n const asJson = attribute.toJSON.call( this, value, name, options );\n\n // ...skipping undefined values. Such an attributes are excluded.\n if( asJson !== void 0 ) json[ name ] = asJson; \n }\n }\n\n return json;\n }\n \n // Default record-level parser, to be overriden by the subclasses.\n parse( data, options? : TransactionOptions ){\n return data;\n }\n\n /**\n * Transactional control\n */\n deepSet( name : string, value : any, options? : any ){\n // Operation might involve series of nested object updates, thus it's wrapped in transaction.\n this.transaction( () => {\n const path = name.split( '.' ),\n l = path.length - 1,\n attr = path[ l ];\n\n let model = this;\n\n // Locate the model, traversing the path.\n for( let i = 0; i < l; i++ ){\n const key = path[ i ];\n\n // There might be collections in path, so use `get`.\n let next = model.get ? model.get( key ) : model[ key ];\n\n // Create models, if they are not exist.\n if( !next ){\n const attrSpecs = model._attributes;\n if( attrSpecs ){\n // If current object is model, create default attribute\n var newModel = attrSpecs[ key ].create();\n\n // If created object is model, nullify attributes when requested\n if( options && options.nullify && newModel._attributes ){\n newModel.clear( options );\n }\n\n model[ key ] = next = newModel;\n }\n // Silently fail in other case.\n else return;\n }\n \n model = next;\n }\n\n // Set model attribute.\n if( model.set ){\n model.set({ [ attr ] : value }, options );\n }\n else{\n model[ attr ] = value;\n }\n });\n\n return this;\n }\n \n // Returns owner without the key (usually it's collection)\n get collection() : any {\n return this._ownerKey ? null : this._owner;\n }\n\n // Dispose object and all childrens\n dispose(){\n if( this._disposed ) return;\n\n const { attributes } = this;\n\n for( let attr of this._attributesArray ){\n attr.dispose( this, attributes[ attr.name ] );\n }\n \n super.dispose();\n }\n\n _log( level : LogLevel, topic: string, text : string, props : object, a_logger? : Logger ) : void {\n ( a_logger || logger ).trigger( level, topic, this.getClassName() + ' ' + text, {\n ...props,\n 'Record' : this,\n 'Attributes definition' : this._attributes\n });\n }\n\n getClassName() : string {\n return super.getClassName() || 'Record';\n }\n\n // Dummies to \n _createTransaction( values : object, options : TransactionOptions ) : Transaction { return void 0; }\n // Simulate attribute change \n forceAttributeChange : ( key : string, options : TransactionOptions ) => void\n _onChildrenChange : ( child : Transactional, options : TransactionOptions ) => void\n\n\n /**\n * Map methods\n */\n\n forEach( iteratee : ( value? : any, key? : string ) => void, context? : any ){\n const fun = context !== void 0 ? ( v, k ) => iteratee.call( context, v, k ) : iteratee,\n { attributes } = this;\n\n for( const key in this.attributes ){\n const value = attributes[ key ];\n if( value !== void 0 ) fun( value, key );\n }\n }\n\n mapObject( a_fun : ( value, key ) => any, context? : any ) : object {\n const fun = context === void 0 ? a_fun : a_fun.bind( context );\n return tools.transform( {}, this.attributes, fun );\n }\n\n [ Symbol.iterator ](){\n return new RecordEntriesIterator( this );\n }\n\n entries(){\n return new RecordEntriesIterator( this );\n }\n\n // Get array of attribute keys (Record) or record ids (Collection) \n keys() : string[] {\n const keys : string[] = [];\n\n this.forEach( ( value, key ) => keys.push( key ) );\n\n return keys;\n }\n};\n\nassign( Record.prototype, UpdateRecordMixin, IORecordMixin );\n\n/***********************************************\n * Helper functions\n */\n\nclass BaseRecordAttributes {\n id : string\n\n constructor( record : Record, x : AttributesValues, options : TransactionOptions ) {\n this.id = x.id;\n }\n}\n\nRecord.prototype.Attributes = BaseRecordAttributes;\n\nclass BaseRecordAttributesCopy {\n id : string\n\n constructor( x : AttributesValues ) {\n this.id = x.id;\n }\n}\n\nRecord.prototype.AttributesCopy = BaseRecordAttributesCopy;\n\nconst IdAttribute = AnyType.create({ value : void 0 }, 'id' );\nRecord.prototype._attributes = { id : IdAttribute };\nRecord.prototype._attributesArray = [ IdAttribute ];\n\nfunction typeCheck( record : Record, values : object, options ){\n if( shouldBeAnObject( record, values, options ) ){\n const { _attributes } = record;\n let unknown : string[];\n\n for( let name in values ){\n if( !_attributes[ name ] ){\n unknown || ( unknown = [] );\n unknown.push( `'${ name }'` );\n }\n }\n\n if( unknown ){\n unknownAttrsWarning( record, unknown, { values }, options );\n }\n }\n}\n\nexport class RecordEntriesIterator implements Iterator<[string, any]> {\n private idx = 0;\n \n constructor( private readonly record : Record){}\n\n next() : IteratorResult<[string, any]> {\n const { record } = this,\n metatype = record._attributesArray[ this.idx++ ];\n\n return {\n done : !metatype,\n value : metatype ? [ metatype.name, record[ metatype.name ] ] : void 0\n };\n }\n}","import { CollectionConstructor } from '../collection';\nimport { define, predefine, TheType, tools } from '../object-plus';\nimport { Transactional } from '../transactions';\nimport { Infer, type } from './attrDef';\nimport { createAttributesMixin } from './mixin';\nimport { Record, RecordDefinition } from './record';\n\nexport * from './attrDef';\nexport * from './metatypes';\nexport { Record };\n\nconst { assign, defaults } = tools;\n\nexport type InferAttrs = {\n [K in keyof A]: Infer\n};\n\nexport interface RecordConstructor extends TheType {\n new ( attrs? : Partial, options? : object ) : Record & A\n prototype : Record\n Collection : CollectionConstructor\n}\n\nexport function attributes( attrDefs : D ) : RecordConstructor> {\n @define class DefaultRecord extends Record {\n static attributes = attrDefs;\n }\n\n return DefaultRecord as any;\n}\n\nRecord.onExtend = function( this : typeof Record, BaseClass : typeof Record ){\n Transactional.onExtend.call( this, BaseClass );\n\n // Create the default collection\n const Class = this;\n\n @predefine class DefaultCollection extends BaseClass.Collection {\n static model = Class;\n }\n\n this.DefaultCollection = DefaultCollection;\n\n // If there are no collection defined in statics, use the default collection.\n // It will appear in onDefine's definition, overriding all other settings.\n if( Class.Collection === BaseClass.Collection ){\n this.Collection = DefaultCollection;\n }\n}\n\nRecord.onDefine = function( definition : RecordDefinition, BaseClass : typeof Record ){\n const baseProto : Record = BaseClass.prototype;\n\n // Compile attributes spec, creating definition mixin.\n const { properties, _localEvents, ...dynamicMixin } = createAttributesMixin( this.attributes = getAttributes( definition ), baseProto._attributes );\n assign( this.prototype, dynamicMixin );\n \n definition.properties = defaults( definition.properties || {}, properties );\n definition._localEvents = _localEvents;\n \n Transactional.onDefine.call( this, definition, BaseClass );\n\n // Finalize the definition of the default collection.\n this.DefaultCollection.define( definition.collection || {} );\n\n // assign collection from the definition.\n this.Collection = definition.Collection;\n this.Collection.prototype.model = this;\n\n if( definition.endpoint ) this.Collection.prototype._endpoint = definition.endpoint; \n}\n\nfunction getAttributes({ defaults, attributes, idAttribute } : RecordDefinition ) {\n const definition = attributes || defaults || {};\n \n // If there is an undeclared idAttribute, add its definition as untyped generic attribute.\n if( idAttribute && !( idAttribute in definition ) ){\n definition[ idAttribute ] = void 0;\n }\n\n return definition;\n}\n\ndeclare var Reflect;\n\nexport function auto( value : any ) : PropertyDecorator;\nexport function auto( proto : object, attrName : string ) : void;\nexport function auto( proto, attrName? : string ) : any {\n if( typeof Reflect !== 'undefined' && Reflect.getMetadata ){\n if( attrName ){\n type( Reflect.getMetadata( \"design:type\", proto, attrName ) ).as( proto, attrName );\n }\n else{\n const value = proto;\n return ( proto : object, attrName : string ) : void => {\n type( Reflect.getMetadata( \"design:type\", proto, attrName ) ).value( value ).as( proto, attrName );\n }\n } \n }\n else{\n proto._log( 'error', 'Type-R:MissingImport', 'Add import \"reflect-metadata\"; as the first line of your app.' );\n } \n}","import { eventsApi, Logger } from '../object-plus';\nimport { Record } from '../record';\nimport { ItemsBehavior, Owner, Transaction, Transactional, transactionApi, TransactionOptions } from '../transactions';\n\n\nconst { trigger2, trigger3, on, off } = eventsApi,\n { commit } = transactionApi,\n _aquire = transactionApi.aquire, _free = transactionApi.free;\n\n/** @private */\nexport interface CollectionCore extends Transactional, Owner {\n _byId : IdIndex\n models : Record[]\n model : typeof Record\n idAttribute : string // TODO: Refactor inconsistent idAttribute usage\n _comparator : Comparator\n get( objOrId : string | Record | Object ) : Record \n _itemEvents? : eventsApi.EventMap\n _shared : number\n _aggregationError : Record[]\n\n _log( level : string, topic : string, text : string, value : any, logger : Logger ) : void\n}\n\n// Collection's manipulation methods elements\nexport type Elements = ( Object | Record )[];\n\nexport interface CollectionOptions extends TransactionOptions {\n sort? : boolean\n}\n\nexport type Comparator = ( a : Record, b : Record ) => number; \n\n/** @private */\nexport function dispose( collection : CollectionCore ) : Record[]{\n const { models } = collection;\n\n collection.models = [];\n collection._byId = {};\n\n freeAll( collection, models );\n return models;\n}\n\n/** @private */\nexport function convertAndAquire( collection : CollectionCore, attrs : {} | Record, options : CollectionOptions ){\n const { model } = collection;\n \n let record : Record;\n\n if( collection._shared ){\n record = attrs instanceof model ? attrs : model.create( attrs, options );\n\n if( collection._shared & ItemsBehavior.listen ){\n on( record, record._changeEventName, collection._onChildrenChange, collection );\n }\n }\n else{\n record = attrs instanceof model ? ( options.merge ? attrs.clone() : attrs ) : model.create( attrs, options );\n\n if( record._owner ){\n if( record._owner !== collection ){\n _aquire( collection, record.clone() );\n const errors = collection._aggregationError || ( collection._aggregationError = [] );\n errors.push( record );\n }\n }\n else{\n _aquire( collection, record ); \n }\n } \n\n // Subscribe for events...\n const { _itemEvents } = collection;\n _itemEvents && _itemEvents.subscribe( collection, record );\n\n return record;\n}\n\n/** @private */\nexport function free( owner : CollectionCore, child : Record, unset? : boolean ) : void {\n if( owner._shared ){\n if( owner._shared & ItemsBehavior.listen ){\n off( child, child._changeEventName, owner._onChildrenChange, owner );\n }\n }\n else{\n _free( owner, child );\n unset || child.dispose();\n }\n\n const { _itemEvents } = owner;\n _itemEvents && _itemEvents.unsubscribe( owner, child );\n}\n\n/** @private */\nexport function freeAll( collection : CollectionCore, children : Record[] ) : Record[] {\n for( let child of children ){\n free( collection, child );\n }\n\n return children;\n}\n\n/**\n * Silently sort collection, if its required. Returns true if sort happened.\n * @private\n */ \nexport function sortElements( collection : CollectionCore, options : CollectionOptions ) : boolean {\n let { _comparator } = collection;\n if( _comparator && options.sort !== false ){\n collection.models.sort( _comparator );\n return true;\n }\n\n return false;\n}\n\n/**********************************\n * Collection Index\n * @private \n */\nexport interface IdIndex {\n [ id : string ] : Record\n}\n\n/** @private Add record */ \nexport function addIndex( index : IdIndex, model : Record ) : void {\n index[ model.cid ] = model;\n var id = model.id;\n \n if( id || ( id as any ) === 0 ){\n index[ id ] = model;\n }\n}\n\n/** @private Remove record */ \nexport function removeIndex( index : IdIndex, model : Record ) : void {\n delete index[ model.cid ];\n var id = model.id;\n if( id || ( id as any ) === 0 ){\n delete index[ id ];\n }\n}\n\nexport function updateIndex( index : IdIndex, model : Record ){\n delete index[ model.previous( model.idAttribute ) ];\n\n const { id } = model;\n id == null || ( index[ id ] = model );\n}\n\n/***\n * In Collections, transactions appears only when\n * add remove or change events might be emitted.\n * reset doesn't require transaction.\n * \n * Transaction holds information regarding events, and knows how to emit them.\n * \n * Two major optimization cases.\n * 1) Population of an empty collection\n * 2) Update of the collection (no or little changes) - it's crucial to reject empty transactions.\n */\n\n\n// Transaction class. Implements two-phase transactions on object's tree.\n/** @private */ \nexport class CollectionTransaction implements Transaction {\n // open transaction\n constructor( public object : CollectionCore,\n public isRoot : boolean,\n public added : Record[],\n public removed : Record[],\n public nested : Transaction[],\n public sorted : boolean ){}\n\n // commit transaction\n commit( initiator? : Transactional ){\n const { nested, object } = this,\n { _isDirty } = object;\n\n // Commit all nested transactions...\n for( let transaction of nested ){\n transaction.commit( object );\n }\n\n if( object._aggregationError ){\n logAggregationError( object, _isDirty );\n }\n\n // Just trigger 'change' on collection, it must be already triggered for models during nested commits.\n // ??? TODO: do it in nested transactions loop? This way appears to be more correct. \n for( let transaction of nested ){\n trigger2( object, 'change', transaction.object, _isDirty );\n }\n\n // Notify listeners on attribute changes...\n const { added, removed } = this;\n\n // Trigger `add` events for both model and collection.\n for( let record of added ){\n trigger3( record, 'add', record, object, _isDirty );\n trigger3( object, 'add', record, object, _isDirty );\n }\n\n // Trigger `remove` events for both model and collection.\n for( let record of removed ){\n trigger3( record, 'remove', record, object, _isDirty );\n trigger3( object, 'remove', record, object, _isDirty );\n }\n\n if( this.sorted ){\n trigger2( object, 'sort', object, _isDirty );\n }\n\n if( added.length || removed.length ){\n trigger2( object, 'update', object, _isDirty );\n }\n\n this.isRoot && commit( object, initiator );\n }\n}\n\nexport function logAggregationError( collection : CollectionCore, options : TransactionOptions ){\n collection._log( 'warn', 'Type-R:InvalidOwner', 'added records already have an owner and were cloned. Use explicit record.clone() to dismiss this warning.', collection._aggregationError, options.logger );\n collection._aggregationError = void 0;\n}","import { Record } from '../record';\nimport { Transaction, transactionApi } from '../transactions';\nimport { addIndex, CollectionCore, CollectionOptions, CollectionTransaction, convertAndAquire, logAggregationError, sortElements, updateIndex } from './commons';\n\nconst { begin, commit, markAsDirty } = transactionApi;\n\nexport interface AddOptions extends CollectionOptions {\n at? : number \n}\n\n/** @private */\nexport function addTransaction( collection : CollectionCore, items : any[], options : AddOptions, merge? : boolean ){\n const isRoot = begin( collection ),\n nested : Transaction[]= [];\n\n var added = appendElements( collection, items, nested, options, merge );\n\n if( added.length || nested.length ){\n let needSort = sortOrMoveElements( collection, added, options );\n if( markAsDirty( collection, options ) ){\n return new CollectionTransaction( collection, isRoot, added, [], nested, needSort );\n }\n\n if( collection._aggregationError ) logAggregationError( collection, options );\n }\n\n // No changes...\n isRoot && commit( collection );\n};\n\n// Handle sort or insert at options for add operation. Reurns true if sort happened.\n/** @private */ \nfunction sortOrMoveElements( collection : CollectionCore, added : Record[], options : AddOptions ) : boolean {\n let at = options.at;\n\n // if `at` option is given, it overrides sorting option...\n if( at != null ){\n // Take an original collection's length. \n const length = collection.models.length - added.length;\n\n // Crazy Backbone rules about `at` index. I don't know what that guys smoke.\n at = Number( at );\n if( at < 0 ) at += length + 1;\n if( at < 0 ) at = 0;\n if( at > length ) at = length;\n\n // Move added elements to desired position. In place.\n moveElements( collection.models, at, added );\n return false;\n }\n\n return sortElements( collection, options );\n}\n\n/** @private */\nfunction moveElements( source : any[], at : number, added : any[] ) : void {\n for( var j = source.length - 1, i = j - added.length; i >= at; i--, j-- ){\n source[ j ] = source[ i ];\n }\n\n for( i = 0, j = at; i < added.length; i++, j++ ){\n source[ j ] = added[ i ];\n }\n}\n\n// append data to model and index\n/** @private */\nfunction appendElements( collection : CollectionCore, a_items : any[], nested : Transaction[], a_options : AddOptions, forceMerge : boolean ){\n var { _byId, models } = collection,\n merge = ( forceMerge || a_options.merge ) && !collection._shared,\n parse = a_options.parse,\n idAttribute = collection.model.prototype.idAttribute,\n prevLength = models.length;\n\n for( const item of a_items ){\n let model = item ? _byId[ item[ idAttribute ] ] || _byId[ item.cid ] : null;\n\n if( model ){\n if( merge && item !== model ){\n var attrs = item.attributes || item;\n const transaction = model._createTransaction( attrs, a_options );\n transaction && nested.push( transaction );\n\n if( model.hasChanged( idAttribute ) ){\n updateIndex( _byId, model );\n }\n }\n }\n else{\n model = convertAndAquire( collection, item, a_options );\n models.push( model );\n addIndex( _byId, model );\n }\n }\n\n return models.slice( prevLength );\n}\n","/*************\n * Remove items from collections.\n * \n * Cannot be a part of two-phase transaction on object tree.\n * Can be executed in the scope of ad-hoc transaction or from the trigger, though.\n *\n * Implemented with low-level API. \n * Most frequent operation - single element remove. Thus, it have the fast-path.\n */\n\nimport { eventsApi } from '../object-plus';\nimport { Record } from '../record';\nimport { transactionApi, TransactionOptions } from '../transactions';\nimport { CollectionCore, CollectionTransaction, free, removeIndex } from './commons';\n\nconst { trigger2, trigger3 } = eventsApi,\n { markAsDirty, begin, commit } = transactionApi;\n\n/** @private */\nexport function removeOne( collection : CollectionCore, el : Record | {} | string, options : TransactionOptions ) : Record {\n var model : Record = collection.get( el );\n\n if( model ){\n const isRoot = begin( collection ),\n models = collection.models;\n\n // Remove model form the collection. \n models.splice( models.indexOf( model ), 1 );\n removeIndex( collection._byId, model );\n \n // Mark transaction as dirty. \n const notify = markAsDirty( collection, options );\n\n // Send out events.\n if( notify ){\n trigger3( model, 'remove', model, collection, options );\n trigger3( collection, 'remove', model, collection, options );\n } \n\n free( collection, model, options.unset );\n\n notify && trigger2( collection, 'update', collection, options );\n\n // Commit transaction.\n isRoot && commit( collection );\n\n return model;\n }\n};\n\n/** Optimized for removing many elements\n * 1. Remove elements from the index, checking for duplicates\n * 2. Create new models array matching index\n * 3. Send notifications and remove references\n */\n\n/** @private */\nexport function removeMany( collection : CollectionCore, toRemove : any[], options ){\n const removed = _removeFromIndex( collection, toRemove, options.unset );\n if( removed.length ){\n const isRoot = begin( collection );\n\n _reallocate( collection, removed.length );\n\n if( markAsDirty( collection, options ) ){\n const transaction = new CollectionTransaction( collection, isRoot, [], removed, [], false );\n transaction.commit();\n }\n else{\n // Commit transaction.\n isRoot && commit( collection );\n }\n }\n\n return removed;\n};\n\n// remove models from the index...\n/** @private */\nfunction _removeFromIndex( collection, toRemove, unset : boolean ){\n var removed = Array( toRemove.length ),\n _byId = collection._byId;\n\n for( var i = 0, j = 0; i < toRemove.length; i++ ){\n var model = collection.get( toRemove[ i ] );\n if( model ){\n removed[ j++ ] = model;\n removeIndex( _byId, model );\n free( collection, model, unset );\n }\n }\n\n removed.length = j;\n\n return removed;\n}\n\n// Allocate new models array removing models not present in the index.\n/** @private */\nfunction _reallocate( collection, removed ){\n var prev = collection.models,\n models = collection.models = Array( prev.length - removed ),\n _byId = collection._byId;\n\n for( var i = 0, j = 0; i < prev.length; i++ ){\n var model = prev[ i ];\n\n if( _byId[ model.cid ] ){\n models[ j++ ] = model;\n }\n }\n\n models.length = j;\n}","import { Record } from '../record';\nimport { Transaction, transactionApi } from '../transactions';\nimport { addIndex, CollectionCore, CollectionOptions, CollectionTransaction, convertAndAquire, Elements, free, freeAll, IdIndex, logAggregationError, sortElements } from './commons';\n\nconst { begin, commit, markAsDirty } = transactionApi;\n\n/** @private */\nconst silentOptions = { silent : true };\n\n/** @private */\nexport function emptySetTransaction( collection : CollectionCore, items : Elements, options : CollectionOptions, silent? : boolean ){\n const isRoot = begin( collection );\n\n const added = _reallocateEmpty( collection, items, options );\n\n if( added.length ){\n const needSort = sortElements( collection, options );\n\n if( markAsDirty( collection, silent ? silentOptions : options ) ){\n // 'added' is the reference to this.models. Need to copy it.\n return new CollectionTransaction( collection, isRoot, added.slice(), [], [], needSort );\n }\n\n if( collection._aggregationError ) logAggregationError( collection, options );\n }\n\n // No changes...\n isRoot && commit( collection );\n};\n\n/** @private */\nexport function setTransaction( collection, items, options ){\n const isRoot = begin( collection ),\n nested = [];\n\n var previous = collection.models,\n added = _reallocate( collection, items, nested, options );\n\n const reusedCount = collection.models.length - added.length,\n removed = reusedCount < previous.length ? (\n reusedCount ? _garbageCollect( collection, previous ) :\n freeAll( collection, previous )\n ) : []; \n \n const addedOrChanged = nested.length || added.length,\n // As we are reallocating models array, it needs to be sorted even if there are no changes.\n sorted = ( sortElements( collection, options ) && addedOrChanged ) || added.length || options.sorted;\n\n if( addedOrChanged || removed.length || sorted ){\n if( markAsDirty( collection, options ) ){ \n return new CollectionTransaction( collection, isRoot, added, removed, nested, sorted );\n }\n\n if( collection._aggregationError ) logAggregationError( collection, options );\n }\n\n isRoot && commit( collection );\n};\n\n// Remove references to all previous elements, which are not present in collection.\n// Returns an array with removed elements.\n/** @private */\nfunction _garbageCollect( collection : CollectionCore, previous : Record[] ) : Record[]{\n const { _byId } = collection,\n removed = [];\n\n // Filter out removed models and remove them from the index...\n for( let record of previous ){\n if( !_byId[ record.cid ] ){\n removed.push( record );\n free( collection, record );\n }\n }\n\n return removed;\n}\n\n// reallocate model and index\n/** @private */\nfunction _reallocate( collection : CollectionCore, source : any[], nested : Transaction[], options ){\n var models = Array( source.length ),\n _byId : IdIndex = {},\n merge = ( options.merge == null ? true : options.merge ) && !collection._shared,\n _prevById = collection._byId,\n prevModels = collection.models, \n idAttribute = collection.model.prototype.idAttribute,\n toAdd = [],\n orderKept = true;\n\n // for each item in source set...\n for( var i = 0, j = 0; i < source.length; i++ ){\n var item = source[ i ],\n model : Record = null;\n\n if( item ){\n var id = item[ idAttribute ],\n cid = item.cid;\n\n if( _byId[ id ] || _byId[ cid ] ) continue;\n\n model = _prevById[ id ] || _prevById[ cid ];\n }\n\n if( model ){\n if( merge && item !== model ){\n if( orderKept && prevModels[ j ] !== model ) orderKept = false;\n\n var attrs = item.attributes || item;\n const transaction = model._createTransaction( attrs, options );\n transaction && nested.push( transaction );\n }\n }\n else{\n model = convertAndAquire( collection, item, options );\n toAdd.push( model );\n }\n\n models[ j++ ] = model;\n addIndex( _byId, model );\n }\n\n models.length = j;\n collection.models = models;\n collection._byId = _byId;\n\n if( !orderKept ) options.sorted = true;\n\n return toAdd;\n}\n\n/** @private */\nfunction _reallocateEmpty( self, source, options ){\n var len = source ? source.length : 0,\n models = Array( len ),\n _byId : IdIndex = {},\n idAttribute = self.model.prototype.idAttribute;\n\n for( var i = 0, j = 0; i < len; i++ ){\n var src = source[ i ];\n\n if( src && ( _byId[ src[ idAttribute ] ] || _byId[ src.cid ] ) ){\n continue;\n }\n\n var model = convertAndAquire( self, src, options );\n models[ j++ ] = model;\n addIndex( _byId, model );\n }\n\n models.length = j;\n self._byId = _byId;\n\n return self.models = models;\n}","import { IOPromise, startIO } from '../io-tools';\nimport { define, definitions, EventMap, eventsApi, EventsDefinition, Logger, logger, LogLevel, Mixable, mixinRules, TheType, tools } from '../object-plus';\nimport { AggregatedType, Record, SharedType } from '../record';\nimport { CloneOptions, ItemsBehavior, Transactional, TransactionalDefinition, transactionApi, TransactionOptions } from '../transactions';\nimport { AddOptions, addTransaction } from './add';\nimport { CollectionCore, CollectionTransaction, Elements, free, sortElements, updateIndex } from './commons';\nimport { removeMany, removeOne } from './remove';\nimport { emptySetTransaction, setTransaction } from './set';\n\n\nconst { trigger2 } = eventsApi,\n { begin, commit, markAsDirty } = transactionApi,\n { assign, defaults } = tools;\n\nlet _count = 0;\n\nexport type GenericComparator = string | ( ( x : Record ) => number ) | ( ( a : Record, b : Record ) => number ); \n\nexport interface CollectionOptions extends TransactionOptions {\n comparator? : GenericComparator\n model? : typeof Record\n}\n\nexport type Predicate = ( ( val : R, key? : number ) => boolean ) | Partial;\n\nexport interface CollectionDefinition extends TransactionalDefinition {\n model? : typeof Record,\n itemEvents? : EventsDefinition\n _itemEvents? : EventMap\n}\n\nclass CollectionRefsType extends SharedType {\n static defaultValue = [];\n}\n\nexport interface CollectionConstructor extends TheType {\n new ( records? : ElementsArg, options?: CollectionOptions ) : Collection\n prototype : Collection\n Refs : CollectionConstructor\n};\n\n@define({\n // Default client id prefix \n cidPrefix : 'c',\n model : Record,\n _changeEventName : 'changes',\n _aggregationError : null\n})\n@definitions({\n comparator : mixinRules.value,\n model : mixinRules.protoValue,\n itemEvents : mixinRules.merge\n})\nexport class Collection< R extends Record = Record> extends Transactional implements CollectionCore, Iterable {\n _shared : number\n _aggregationError : R[]\n\n static Subset : typeof Collection\n static Refs : CollectionConstructor\n static _SubsetOf : typeof Collection\n \n createSubset( models : ElementsArg, options? : CollectionOptions) : Collection{\n throw new ReferenceError( 'Failed dependency injection' )\n }\n\n static onExtend( BaseClass : typeof Transactional ){\n // Cached subset collection must not be inherited.\n const Ctor = this;\n this._SubsetOf = null;\n\n function RefsCollection( a, b, listen? ){\n Ctor.call( this, a, b, ItemsBehavior.share | ( listen ? ItemsBehavior.listen : 0 ) );\n }\n\n Mixable.mixins.populate( RefsCollection );\n \n RefsCollection.prototype = this.prototype;\n RefsCollection._metatype = CollectionRefsType;\n\n this.Refs = this.Subset = RefsCollection;\n\n Transactional.onExtend.call( this, BaseClass );\n }\n \n static onDefine( definition : CollectionDefinition, BaseClass : any ){\n if( definition.itemEvents ){\n const eventsMap = new EventMap( BaseClass.prototype._itemEvents );\n eventsMap.addEventsMap( definition.itemEvents );\n this.prototype._itemEvents = eventsMap;\n }\n\n if( definition.comparator !== void 0 ) this.prototype.comparator = definition.comparator;\n\n Transactional.onDefine.call( this, definition );\n }\n \n _itemEvents : EventMap\n\n /***********************************\n * Core Members\n */\n // Array of the records\n models : R[]\n\n // Polymorphic accessor for aggregated attribute's canBeUpdated().\n get __inner_state__(){ return this.models; }\n\n // Index by id and cid\n _byId : { [ id : string ] : R }\n\n set comparator( x : GenericComparator ){\n\n switch( typeof x ){\n case 'string' :\n this._comparator = ( a, b ) => {\n const aa = a[ x ], bb = b[ x ];\n if( aa === bb ) return 0;\n return aa < bb ? -1 : + 1;\n } \n break;\n case 'function' :\n if( x.length === 1 ){\n this._comparator = ( a, b ) => {\n const aa = (x).call( this, a ), bb = (x).call( this, b );\n if( aa === bb ) return 0;\n return aa < bb ? -1 : + 1;\n }\n }\n else{\n this._comparator = ( a, b ) => (x).call( this, a, b );\n }\n break;\n \n default :\n this._comparator = null;\n }\n }\n \n // TODO: Improve typing\n getStore() : Transactional {\n return this._store || ( this._store = this._owner ? this._owner.getStore() : this._defaultStore );\n }\n\n _store : Transactional\n\n get comparator(){ return this._comparator; }\n _comparator : ( a : R, b : R ) => number\n\n _onChildrenChange( record : R, options : TransactionOptions = {}, initiator? : Transactional ){\n // Ignore updates from nested transactions.\n if( initiator === this ) return;\n\n const { idAttribute } = this;\n\n if( record.hasChanged( idAttribute ) ){\n updateIndex( this._byId, record );\n }\n\n const isRoot = begin( this );\n\n if( markAsDirty( this, options ) ){\n // Forward change event from the record.\n trigger2( this, 'change', record, options )\n }\n\n isRoot && commit( this );\n }\n\n get( objOrId : string | { id? : string, cid? : string } ) : R {\n if( objOrId == null ) return;\n\n if( typeof objOrId === 'object' ){\n const id = objOrId[ this.idAttribute ];\n return ( id !== void 0 && this._byId[ id ] ) || this._byId[ objOrId.cid ];\n }\n else{\n return this._byId[ objOrId ];\n } \n }\n\n each( iteratee : ( val : R, key? : number ) => void, context? : any ) : void {\n this.models.forEach( iteratee, context );\n }\n\n // Loop through the members in the scope of transaction.\n // Transactional version of each()\n updateEach( iteratee : ( val : R, key? : number ) => void ){\n const isRoot = transactionApi.begin( this );\n this.models.forEach( iteratee );\n isRoot && transactionApi.commit( this );\n }\n\n _validateNested( errors : {} ) : number {\n // Don't validate if not aggregated.\n if( this._shared ) return 0;\n\n let count = 0;\n\n this.each( record => {\n const error = record.validationError;\n if( error ){\n errors[ record.cid ] = error;\n count++;\n }\n });\n\n return count;\n }\n\n model : typeof Record\n\n // idAttribute extracted from the model type.\n idAttribute : string\n\n constructor( records? : ElementsArg, options : CollectionOptions = {}, shared? : number ){\n super( _count++ );\n this.models = [];\n this._byId = {};\n \n this.comparator = this.comparator;\n\n if( options.comparator !== void 0 ){\n this.comparator = options.comparator;\n options.comparator = void 0;\n }\n \n this.model = this.model;\n \n if( options.model ){\n this.model = options.model;\n options.model = void 0;\n }\n\n this.idAttribute = this.model.prototype.idAttribute; //TODO: Remove?\n\n this._shared = shared || 0;\n\n if( records ){\n const elements = toElements( this, records, options );\n emptySetTransaction( this, elements, options, true );\n }\n\n this.initialize.apply( this, arguments );\n\n if( this._localEvents ) this._localEvents.subscribe( this, this );\n }\n\n initialize(){}\n\n first() : R { return this.models[ 0 ]; }\n last() : R { return this.models[ this.models.length - 1 ]; }\n at( a_index : number ) : R {\n const index = a_index < 0 ? a_index + this.models.length : a_index; \n return this.models[ index ];\n }\n\n // Deeply clone collection, optionally setting new owner.\n clone( options : CloneOptions = {} ) : this {\n const models = this._shared & ItemsBehavior.share ? this.models : this.map( model => model.clone() ),\n copy : this = new (this.constructor)( models, { model : this.model, comparator : this.comparator }, this._shared );\n \n if( options.pinStore ) copy._defaultStore = this.getStore();\n \n return copy;\n }\n\n toJSON( options? : object ) : any {\n return this.models.map( model => model.toJSON( options ) );\n }\n\n // Apply bulk in-place object update in scope of ad-hoc transaction \n set( elements : ElementsArg = [], options : TransactionOptions = {} ) : this {\n if( (options).add !== void 0 ){\n this._log( 'warn', \"Type-R:InvalidOption\", \"Collection.set doesn't support 'add' option, behaving as if options.add === true.\", options );\n }\n\n // Handle reset option here - no way it will be populated from the top as nested transaction.\n if( options.reset ){\n this.reset( elements, options )\n }\n else{\n const transaction = this._createTransaction( elements, options );\n transaction && transaction.commit();\n } \n\n return this; \n }\n\n /**\n * Enable or disable live updates.\n * \n * `true` enables full collection synchronization.\n * `false` cancel live updates.\n * `json => true | false` - filter updates\n */\n liveUpdates( enabled : LiveUpdatesOption ) : IOPromise {\n if( enabled ){\n this.liveUpdates( false );\n\n const filter = typeof enabled === 'function' ? enabled : () => true;\n\n this._liveUpdates = {\n updated : json => {\n filter( json ) && this.add( json, { parse : true, merge : true } );\n },\n\n removed : id => this.remove( id )\n };\n\n return this.getEndpoint().subscribe( this._liveUpdates, this ).then( () => this );\n }\n else{\n if( this._liveUpdates ){\n this.getEndpoint().unsubscribe( this._liveUpdates, this );\n this._liveUpdates = null;\n }\n\n // TODO: Return the resolved promise.\n }\n }\n\n _liveUpdates : object\n\n fetch( a_options : { liveUpdates? : LiveUpdatesOption } & TransactionOptions = {} ) : IOPromise {\n const options = { parse : true, ...a_options },\n endpoint = this.getEndpoint();\n\n return startIO(\n this,\n endpoint.list( options, this ),\n options,\n\n json => {\n let result : any = this.set( json, { parse : true, ...options } as TransactionOptions );\n \n if( options.liveUpdates ){\n result = this.liveUpdates( options.liveUpdates );\n }\n\n return result;\n }\n );\n }\n\n dispose() : void {\n if( this._disposed ) return;\n\n const aggregated = !this._shared;\n\n for( let record of this.models ){\n free( this, record );\n\n if( aggregated ) record.dispose();\n }\n\n this.liveUpdates( false );\n\n super.dispose();\n }\n\n reset( a_elements? : ElementsArg, options : TransactionOptions = {} ) : R[] {\n const isRoot = begin( this ),\n previousModels = this.models;\n\n // Make all changes required, but be silent.\n if( a_elements ){ \n emptySetTransaction( this, toElements( this, a_elements, options ), options, true );\n }\n else{\n this._byId = {};\n this.models = [];\n }\n\n markAsDirty( this, options );\n\n options.silent || trigger2( this, 'reset', this, defaults( { previousModels : previousModels }, options ) );\n\n // Dispose models which are not in the updated collection.\n const { _byId } = this;\n \n for( let toDispose of previousModels ){\n _byId[ toDispose.cid ] || free( this, toDispose );\n }\n\n isRoot && commit( this );\n return this.models;\n }\n\n // Add elements to collection.\n add( a_elements : ElementsArg , options : AddOptions = {} ){\n const elements = toElements( this, a_elements, options ),\n transaction = this.models.length ?\n addTransaction( this, elements, options ) :\n emptySetTransaction( this, elements, options );\n\n if( transaction ){\n transaction.commit();\n return transaction.added;\n }\n }\n\n // Remove elements. \n remove( recordsOrIds : any, options : CollectionOptions = {} ) : R[] | R {\n if( recordsOrIds ){\n return Array.isArray( recordsOrIds ) ?\n removeMany( this, recordsOrIds, options ) as R[]:\n removeOne( this, recordsOrIds, options ) as R;\n }\n\n return [];\n }\n\n // Apply bulk object update without any notifications, and return open transaction.\n // Used internally to implement two-phase commit. \n _createTransaction( a_elements : ElementsArg, options : TransactionOptions = {} ) : CollectionTransaction | void {\n const elements = toElements( this, a_elements, options );\n\n if( this.models.length ){\n return options.remove === false ?\n addTransaction( this, elements, options, true ) :\n setTransaction( this, elements, options );\n }\n else{\n return emptySetTransaction( this, elements, options );\n }\n }\n\n static _metatype = AggregatedType;\n\n /***********************************\n * Collection manipulation methods\n */\n\n pluck( key : K ) : R[K][] {\n return this.models.map( model => model[ key ] );\n }\n\n sort( options : TransactionOptions = {} ) : this {\n if( sortElements( this, options ) ){\n const isRoot = begin( this );\n \n if( markAsDirty( this, options ) ){\n trigger2( this, 'sort', this, options );\n }\n\n isRoot && commit( this );\n }\n\n return this;\n }\n\n // Remove and return given model.\n unset( modelOrId : R | string, options? ) : R {\n const value = this.get( modelOrId );\n this.remove( modelOrId, { unset : true, ...options } );\n return value;\n }\n\n modelId( attrs : {} ) : any {\n return attrs[ this.model.prototype.idAttribute ];\n }\n\n // Toggle model in collection.\n toggle( model : R, a_next? : boolean ) : boolean {\n var prev = Boolean( this.get( model ) ),\n next = a_next === void 0 ? !prev : Boolean( a_next );\n\n if( prev !== next ){\n if( prev ){\n this.remove( model );\n }\n else{\n this.add( model );\n }\n }\n\n return next;\n }\n\n _log( level : LogLevel, topic : string, text : string, value : object, a_logger? : Logger ) : void {\n ( a_logger || logger ).trigger( level, topic, `${ this.model.prototype.getClassName() }.${ this.getClassName() }: ` + text, {\n Argument : value,\n 'Attributes spec' : this.model.prototype._attributes\n });\n }\n\n getClassName() : string {\n return super.getClassName() || 'Collection';\n }\n\n /***********************************\n * Proxied Array methods\n */\n\n get length() : number { return this.models.length; }\n\n // Add a model to the end of the collection.\n push(model : ElementsArg, options? : CollectionOptions ) {\n return this.add(model, assign({at: this.length}, options));\n }\n\n // Remove a model from the end of the collection.\n pop( options? : CollectionOptions ) : R {\n var model = this.at(this.length - 1);\n this.remove(model, { unset : true, ...options });\n return model;\n }\n\n // Add a model to the beginning of the collection.\n unshift(model : ElementsArg, options? : CollectionOptions ) {\n return this.add(model, assign({at: 0}, options));\n }\n \n // Remove a model from the beginning of the collection.\n shift( options? : CollectionOptions ) : R {\n const model = this.at(0);\n this.remove( model, { unset : true, ...options } );\n return model;\n }\n\n // Slice out a sub-array of models from the collection.\n slice( begin? : number, end? : number ) : R[] {\n return this.models.slice( begin, end );\n }\n \n indexOf( modelOrId : string | Partial ) : number {\n return this.models.indexOf( this.get( modelOrId ) );\n }\n\n filter( iteratee : Predicate, context? : any ) : R[] {\n return this.models.filter( toPredicateFunction( iteratee ), context );\n }\n\n find( iteratee : Predicate, context? : any ) : R {\n return this.models.find( toPredicateFunction( iteratee ), context );\n }\n\n some( iteratee : Predicate, context? : any ) : boolean {\n return this.models.some( toPredicateFunction( iteratee ), context );\n }\n\n forEach( iteratee : ( val : R, key? : number ) => void, context? : any ) : void {\n this.models.forEach( iteratee, context );\n }\n \n [ Symbol.iterator ]() : IterableIterator {\n return this.models[ Symbol.iterator ]();\n }\n\n values() : IterableIterator {\n return this.models.values();\n }\n\n entries() : IterableIterator<[ number, R ]>{\n return this.models.entries();\n }\n\n every( iteratee : Predicate, context? : any ) : boolean {\n return this.models.every( toPredicateFunction( iteratee ), context );\n }\n\n includes( idOrObj : string | Partial ){\n return Boolean( this.get( idOrObj ) );\n }\n\n // Map members to an array\n map( iteratee : ( val : R, key? : number ) => T, context? : any ) : T[]{\n return this.models.map( iteratee, context );\n }\n\n reduce( iteratee : (previousValue: R, currentValue: R, currentIndex?: number ) => R ) : R\n reduce( iteratee : (previousValue: T, currentValue: R, currentIndex?: number ) => T, init? : any ) : T\n reduce( iteratee : (previousValue: any, currentValue: any, currentIndex?: number ) => any, init? : any ) : T | R {\n return init === void 0 ? this.models.reduce( iteratee ) : this.models.reduce( iteratee, init );\n }\n}\n\nexport type LiveUpdatesOption = boolean | ( ( x : any ) => boolean );\n\nexport type ElementsArg = Partial | Partial[]\n\n// TODO: make is safe for parse to return null (?)\nfunction toElements( collection : Collection, elements : ElementsArg, options : CollectionOptions ) : Elements {\n const parsed = options.parse ? collection.parse( elements, options ) : elements; \n return Array.isArray( parsed ) ? parsed : [ parsed ];\n}\n\nRecord.Collection = Collection;\n\nfunction toPredicateFunction( iteratee : Predicate ){\n switch( typeof iteratee ){\n case 'function' : return iteratee;\n case 'object' :\n const keys = Object.keys( iteratee );\n \n return x => {\n for( let key of keys ){\n if( iteratee[ key ] !== x[ key ] )\n return false;\n }\n\n return true;\n }\n default : throw new Error( 'Invalid iteratee' );\n }\n}","import { Collection } from '../collection';\nimport { Record } from '../record';\nimport { CompiledReference } from '../traversable';\n\nexport type CollectionReference = ( () => Collection ) | Collection | string; \n\n/** @private */\nexport function parseReference( collectionRef : CollectionReference ) : ( root : Record ) => Collection {\n switch( typeof collectionRef ){\n case 'function' :\n return root => (collectionRef).call( root );\n case 'object' :\n return () => collectionRef;\n case 'string' :\n const { resolve } = new CompiledReference( collectionRef );\n return resolve;\n }\n}","import { AnyType, ChainableAttributeSpec, Record } from '../record';\nimport { CollectionReference, parseReference } from './commons';\n\n\n/********\n * Reference to model by id.\n * \n * Untyped attribute. Holds model id, when unresolved. When resolved, is substituted\n * with a real model.\n * \n * No model changes are detected and counted as owner's change. That's intentional.\n */\n\n/** @private */\ntype RecordRefValue = Record | string;\n\n/** @private */\nclass RecordRefType extends AnyType {\n // It is always serialized as an id, whenever it's resolved or not. \n toJSON( value : RecordRefValue ){\n return value && typeof value === 'object' ? value.id : value;\n }\n\n // Wne \n clone( value : RecordRefValue ){\n return value && typeof value === 'object' ? value.id : value;\n }\n\n // Model refs by id are equal when their ids are equal.\n isChanged( a : RecordRefValue, b : RecordRefValue){\n var aId = a && ( (a).id == null ? a : (a).id ),\n bId = b && ( (b).id == null ? b : (b).id );\n\n return aId !== bId;\n }\n\n // Refs are always valid.\n validate( model, value, name ){}\n}\n\nexport function memberOf( this : void, masterCollection : CollectionReference, T? : R ) : ChainableAttributeSpec {\n const getMasterCollection = parseReference( masterCollection );\n\n const typeSpec = new ChainableAttributeSpec({\n value : null,\n _metatype : RecordRefType\n });\n \n return typeSpec\n .get( function( objOrId : RecordRefValue, name : string ) : Record {\n if( typeof objOrId === 'object' ) return objOrId;\n\n // So, we're dealing with an id reference. Resolve it.\n const collection = getMasterCollection( this );\n let record : Record = null;\n\n // If master collection exists and is not empty...\n if( collection && collection.length ){\n // Silently update attribute with record from this collection.\n record = collection.get( objOrId ) || null;\n this.attributes[ name ] = record;\n\n // Subscribe for events manually. delegateEvents won't be invoked.\n record && this._attributes[ name ].handleChange( record, null, this, {} );\n }\n\n return record;\n });\n}","import { Collection, CollectionConstructor } from '../collection';\nimport { define, tools } from '../object-plus';\nimport { AggregatedType, ChainableAttributeSpec, Record, type } from '../record';\nimport { ItemsBehavior, transactionApi } from '../transactions';\nimport { CollectionReference, parseReference } from './commons';\n\n\ntype RecordsIds = ( string | number )[];\n\nexport function subsetOf, R extends Record>( this : void, masterCollection : CollectionReference, T? : X ) : ChainableAttributeSpec{\n const CollectionClass = T || Collection,\n // Lazily define class for subset collection, if it's not defined already...\n SubsetOf = CollectionClass._SubsetOf || ( CollectionClass._SubsetOf = defineSubsetCollection( CollectionClass as any ) as any ),\n getMasterCollection = parseReference( masterCollection );\n\n return type( SubsetOf ).get(\n function( refs ){\n !refs || refs.resolvedWith || refs.resolve( getMasterCollection( this ) );\n return refs;\n }\n );\n}\n\nCollection.prototype.createSubset = function( models : any, options ) : Collection {\n const SubsetOf = subsetOf( this, this.constructor ).options.type,\n subset = new SubsetOf( models, options );\n \n subset.resolve( this );\n return subset;\n}\n\nconst subsetOfBehavior = ItemsBehavior.share | ItemsBehavior.persistent;\n\nfunction defineSubsetCollection( CollectionClass : typeof Collection ) {\n @define class SubsetOfCollection extends CollectionClass {\n refs : any[];\n resolvedWith : Collection = null;\n\n _metatype : AggregatedType\n\n get __inner_state__(){ return this.refs || this.models; }\n\n constructor( recordsOrIds?, options? ){\n super( [], options, subsetOfBehavior );\n this.refs = toArray( recordsOrIds );\n }\n\n // Remove should work fine as it already accepts ids. Add won't...\n add( a_elements, options = {} ){\n const { resolvedWith } = this,\n toAdd = toArray( a_elements );\n \n if( resolvedWith ){\n // If the collection is resolved already, everything is simple.\n return super.add( resolveRefs( resolvedWith, toAdd ), options );\n }\n else{\n // Collection is not resolved yet. So, we prepare the delayed computation.\n if( toAdd.length ){\n const isRoot = transactionApi.begin( this );\n\n // Save elements to resolve in future...\n this.refs = this.refs ? this.refs.concat( toAdd ) : toAdd.slice();\n\n transactionApi.markAsDirty( this, options );\n\n // And throw the 'changes' event.\n isRoot && transactionApi.commit( this );\n }\n }\n }\n\n reset( a_elements?, options = {} ){\n const { resolvedWith } = this,\n elements = toArray( a_elements );\n \n return resolvedWith ?\n // Collection is resolved, so parse ids and forward the call to set.\n super.reset( resolveRefs( resolvedWith, elements ), options ) :\n // Collection is not resolved yet. So, we prepare the delayed computation.\n delaySet( this, elements, options ) as any || [];\n }\n\n _createTransaction( a_elements, options? ){\n const { resolvedWith } = this,\n elements = toArray( a_elements );\n \n return resolvedWith ?\n // Collection is resolved, so parse ids and forward the call to set.\n super._createTransaction( resolveRefs( resolvedWith, elements ), options ) :\n // Collection is not resolved yet. So, we prepare the delayed computation.\n delaySet( this, elements, options );\n }\n\n // Serialized as an array of model ids.\n toJSON() : RecordsIds {\n return this.refs ?\n this.refs.map( objOrId => objOrId.id || objOrId ) :\n this.models.map( model => model.id );\n }\n\n // Subset is always valid.\n _validateNested(){ return 0; }\n\n get length() : number {\n return this.models.length || ( this.refs ? this.refs.length : 0 );\n }\n\n // Must be shallow copied on clone.\n clone( owner? ){\n var Ctor = (this).constructor,\n copy = new Ctor( [], {\n model : this.model,\n comparator : this.comparator\n });\n\n if( this.resolvedWith ){\n // TODO: bug here. \n copy.resolvedWith = this.resolvedWith;\n copy.refs = null;\n copy.reset( this.models, { silent : true } );\n }\n else{\n copy.refs = this.refs.slice();\n }\n\n return copy;\n }\n\n // Clean up the custom parse method possibly defined in the base class.\n parse( raw : any ) : Record[] {\n return raw;\n }\n\n resolve( collection : Collection ) : this {\n if( collection && collection.length ){\n this.resolvedWith = collection;\n\n if( this.refs ){\n this.reset( this.refs, { silent : true } );\n this.refs = null;\n }\n }\n\n return this;\n }\n\n getModelIds() : RecordsIds { return this.toJSON(); }\n\n toggle( modelOrId : any, val : boolean ) : boolean {\n return super.toggle( this.resolvedWith.get( modelOrId ), val );\n }\n\n addAll() : Record[] {\n if( this.resolvedWith ){\n this.set( this.resolvedWith.models );\n return this.models;\n }\n\n throw new Error( \"Cannot add elemens because the subset collection is not resolved yet.\" );\n }\n\n toggleAll() : Record[] {\n return this.length ? this.reset() : this.addAll();\n }\n }\n\n // Clean up all custom item events to prevent memory leaks.\n SubsetOfCollection.prototype._itemEvents = void 0;\n\n return SubsetOfCollection;\n}\n\nfunction resolveRefs( master, elements ){\n const records = [];\n \n for( let el of elements ){\n const record = master.get( el );\n if( record ) records.push( record );\n }\n\n return records;\n}\n\nfunction delaySet( collection, elements, options ) : void {\n if( tools.notEqual( collection.refs, elements ) ){\n const isRoot = transactionApi.begin( collection );\n\n // Save elements to resolve in future...\n collection.refs = elements.slice();\n\n transactionApi.markAsDirty( collection, options );\n \n // And throw the 'changes' event.\n isRoot && transactionApi.commit( collection );\n }\n}\n\nfunction toArray( elements ){\n return elements ? ( \n Array.isArray( elements ) ? elements : [ elements ]\n ) : [];\n}","import { Record } from '../record';\nimport { Transactional } from '../transactions';\n\nlet _store : Store = null;\n\nexport class Store extends Record {\n getStore() : Store { return this; }\n \n // delegate item lookup to owner, and to the global store if undefined\n get( name : string ) : any {\n // Lookup for resource in the current store. \n let local = this[ name ];\n\n // If something is found or it's the global store, return result.\n if( local || this === this._defaultStore ) return local;\n\n // Forward failed lookup to owner or global store.\n return this._owner ? this._owner.get( name ) : this._defaultStore.get( name ); \n }\n\n static get global(){ return _store; }\n static set global( store : Store ){\n if( _store ){\n _store.dispose();\n }\n\n Transactional.prototype._defaultStore = _store = store;\n }\n}\n\nStore.global = new Store();","// Dummy polyfill to prevent exception in IE.\nif( typeof Symbol === 'undefined' ){\n Object.defineProperty( window, 'Symbol', { value : { iterator : 'Symbol.iterator' }, configurable : true });\n}\n\nimport { Events, Mixable as Class } from './object-plus/';\n// Define synonims for NestedTypes backward compatibility.\nimport { Record as Model } from './record';\n\n/**\n * Export everything \n */\nexport * from './collection';\nexport * from './io-tools';\nexport * from './object-plus';\nexport * from './record';\nexport * from './relations';\nexport * from './transactions';\nexport { Model, Class };\n\n\nexport const { on, off, trigger, once, listenTo, stopListening, listenToOnce } = Events;\n\n/** Wrap model or collection method in transaction. */\nexport function transaction< F extends Function >( method : F ) : F {\n return function( ...args ){\n let result;\n \n this.transaction( () => {\n result = method.apply( this, args );\n });\n \n return result;\n }\n}"],"names":["defaults","dest","source","name","hasOwnProperty","arguments","length","i","other","isValidJSON","value","proto","Object","getPrototypeOf","prototype","Array","every","getBaseClass","Class","constructor","isEmpty","obj","key","some","fun","ArrayProto","arr","result","someArray","someObject","predicate","x","omit","discard","transform","assign","once","func","memo","first","apply","this","DateProto","Date","ObjectProto","notEqual","a","b","protoA","arraysNotEqual","keysA","keys","objectsNotEqual","HashProto","create","hashMap","hash","definition","_i","names","names_1","_a","name_1","prop","desc","getOwnPropertyDescriptor","o","map","EventMap","handlers","slice","addEventsMap","concat","addEvent","split","eventSplitter","getBubblingHandler","callback","name_2","push","EventDescriptor","target","event_1","on","event_2","off","handler","_bubblingHandlers","event","c","d","e","trigger5","trigger3","trigger2","context","next","listOff","_events","filteredHead","prev","head","ev","_callback","listSend3","call","EventHandler","once_1","_once","name_3","strings","api","events","test","self","queue","all","listSend2","listSend4","listSend5","f","listSend6","extendStatics","setPrototypeOf","__proto__","p","__extends","__","__assign","t","s","n","__rest","indexOf","getOwnPropertySymbols","__decorate","decorators","r","Reflect","decorate","defineProperty","Mixable","protoProps","staticProps","BaseClass","mixins","defineMixin","merge","mergeObject","getStaticDefinitions","onDefine","definitions","mergeInheritedMembers","spec","statics","TheSubclass","tslib_1.__extends","predefine","define","Constructor","__super__","MixinsState","get","populate","onExtend","ClassOrDefinition","Ctor","rules","definitionRules","definitionDecorator","definitionKey","mergeRules","appliedMixins","rule","mixins_1","mixin","isArray","sourceMixins","ctors","ctors_1","unshift","object","ignore","dontMix","getOwnPropertyNames","forEachOwnProp","sourceProp","_this","assignProperty","mixinRules","protoValue","baseProto","resolveRule","function","caller","list","destProp","configurable","pipe","classFirst","classLast","_idCount","uniqueId","cid","initialize","Messenger","localEvents","_localEvents","properties","eventsMap","defineProperties","toPropertyDescriptor","addReference","a_source","_listeningTo","removeAll","second","_disposed","stopListening","Events","listener","isProduction","process","env","NODE_ENV","logEvents","Logger","level","filter","topic","msg","props","args","toString","console","Error","counter","_super","window","something","body","join","JSON","stringify","logger","logEvents_1","logToConsole","throwingLogger","throwOn","log","trigger","bind","startIO","promise","options","thenDo","abortIO","ioUpdate","_ioPromise","then","resp","triggerAndBubble","catch","err","abort","eventSource","collection","referenceMask","reference","splitTail","path","match","substr","tail","pop","local","resolve","Function","shift","resolveReference","root","action","skip","getStore","getOwner","ItemsBehavior","_validateNested","nested","error","validate","ValidationError","iteratee","each","eachError","cidPrefix","Transactional","endpoint","_endpoint","Transactional_1","_owner","_ownerKey","_changeEventName","listenTo","isRoot","transactionApi","begin","update","set","commit","transaction","__inner_state__","_changeToken","json","strict","validationError","eachValidationError","getClassName","data","_defaultStore","getOwnerEndpoint","_endpoints","_validationError","getValidationError","_transaction","markAsDirty","dirty","silent","_isDirty","initiator","originalOptions","_onChildrenChange","aquire","owner","child","ReferenceError","free","_begin","_markAsDirty","setAttribute","record","_attributes","doUpdate","attributes","_previousAttributes","AttributesCopy","_changedAttributes","UpdateRecordMixin","attribute","propagateChanges","forceAttributeChange","_createTransaction","a_values","unknown","changes","values","parse","shouldBeAnObject","unknownAttrsWarning","RecordTransaction","nested_1","_log","nested_2","changes_1","_b","emptyOptions","a_options","getHooks","transforms","changeHandlers","type","toJSON","changeEvents","hasCustomDefault","defaultValue","tools.isValidJSON","isRequired","convert","getHook_1","getHook","reduce","chainGetHooks","validate_1","chainTransforms","handleChange","chainChangeHandlers","doInit","AnyType","model","v","isChanged","code","text","New value","Prev. value","prevHook","nextHook","prevTransform","nextTransform","prevHandler","nextHandler","ImmutableClassType","PrimitiveType","NumericType","num","isFinite","ArrayType","ObjectType","doNothing","FunctionType","DateType","date","timestamp","getTime","toISOString","supportsDate","isNaN","struct","minutesOffset","isoDatePattern","exec","k","numericKeys","undefined","UTC","safeParseDate","AggregatedType","clone","canBeUpdated","nestedTransaction","_shared","persistent","_handleChange","unset","dispose","shareAndListen","listen","share","SharedType","implicitObject","_onChange","builtins","String","Number","Boolean","metatypes","getMetatype","_metatype","idx","ChainableAttributeSpec","check","metadata","required","ref","emptyFunction","changed","eventMap","unsubscribe","subscribe","cloned","Type","attrDef","shared","inferType","createAttribute","from","createAttributesMixin","attributesDefinition","baseClassAttributes","myAttributes","_.transform","allAttributes","_.defaults","ConstructorsMixin","attrDefs","attrs","attr","Attributes","constructorsMixin","_attributesArray","createPropertyDescriptor","attrSpecs","eventsApi.EventMap","createWatcherFromRef","wrapWatcher","localEventsMixin","watcher","IORecordMixin","save","getEndpoint","isNew","id","fetch","read","destroy","remove","_cidCounter","typeCheck","Record","idAttribute","extend","diff","hasChanged","val","old","nullify","forEach","_attributesArray_1","copy","pinStore","errors","asJson","l","newModel","clear","a_logger","Attributes definition","a_fun","tools.transform","Symbol","iterator","RecordEntriesIterator","Collection","BaseRecordAttributes","BaseRecordAttributesCopy","IdAttribute","metatype","done","DefaultCollection","dynamicMixin","_aquire","_free","convertAndAquire","_aggregationError","_itemEvents","sortElements","_comparator","sort","models","addIndex","index","removeIndex","updateIndex","previous","added","removed","sorted","CollectionTransaction","logAggregationError","added_1","_d","removed_1","_e","addTransaction","items","a_items","forceMerge","_byId","prevLength","a_items_1","item","appendElements","needSort","at","length_1","j","moveElements","sortOrMoveElements","removeMany","toRemove","_removeFromIndex","_reallocate","silentOptions","emptySetTransaction","len","src","_reallocateEmpty","setTransaction","_prevById","prevModels","toAdd","orderKept","reusedCount","previous_1","_garbageCollect","children","children_1","freeAll","addedOrChanged","_count","CollectionRefsType","records","comparator","toElements","RefsCollection","_SubsetOf","Refs","Subset","itemEvents","aa","bb","_store","objOrId","count","a_index","elements","add","reset","enabled","liveUpdates","filter_1","_liveUpdates","updated","tslib_1.__assign","aggregated","a_elements","previousModels","previousModels_1","toDispose","recordsOrIds","el","splice","notify","removeOne","modelOrId","a_next","Argument","Attributes spec","end","toPredicateFunction","find","entries","idOrObj","init","parsed","keys_1","keys_2","parseReference","collectionRef","RecordRefType","subsetOf","masterCollection","T","CollectionClass","SubsetOf","subsetOfBehavior","refs","toArray","SubsetOfCollection","resolvedWith","resolveRefs","delaySet","raw","toggle","addAll","getMasterCollection","createSubset","subset","master","elements_1","tools.notEqual","Store","store","global","listenToOnce","method","reject","onAbort","fn","Promise","a_resolve","a_reject","listName","DefaultRecord","attrName","getMetadata","value_1","as"],"mappings":"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"} \ No newline at end of file diff --git a/docs/build.js b/docs/build.js new file mode 100644 index 00000000..6deb930f --- /dev/null +++ b/docs/build.js @@ -0,0 +1,120 @@ +/* +Copyright 2015 Jesse Manek +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under +*/ + +var marked = require('marked') +var fs = require('fs') +var highlight = require('highlight.js') +var Handlebars = require('handlebars') +var path = require('path') + +marked.setOptions({ + renderer: new marked.Renderer(), + gfm: true, + tables: true, + breaks: false, + pedantic: true, + sanitize: false, + smartLists: true, + smartypants: false, + highlight: function (code, lang) { + return highlight.highlight(lang || 'javascript', code).value + } +}) + +// Create syntax-highlighting alias 'shell' for 'bash' +var bash = highlight.getLanguage('bash') +highlight.registerLanguage('shell', function (highlight) { + return bash +}) + +// Easier than changing Slate's js +marked.defaults.langPrefix = 'highlight ' + +Handlebars.registerHelper('str', function (item) { + return '"' + item + '"' +}) + +Handlebars.registerHelper('html', function (content) { + return new Handlebars.SafeString(content) +}) + +fs.readFile('./docs/index.md', 'utf8', function (err, content) { + if (err) console.log(err) + + content = content.split(/---/g) + + if (content.length === 1) { + throw new Error('No markdown page settings found!') + } + + var data = {} + var tokens = new marked.Lexer().lex(content[1]) + var token + var listName + + for (var idx = 0; idx < tokens.length; idx++) { + token = tokens[idx] + if (token.type === 'list_item_start') { + token = tokens[idx + 1].text + + if (listName === 'language_tabs') { + var lang = token + var langSplit = lang.split(':') + if (langSplit.length === 1) token = {name: langSplit[0], text: langSplit[0]} + if (langSplit.length === 2) token = {name: langSplit[0], text: langSplit[1]} + } + data[listName].push(token) + idx += 2 + } + + if (token.type === 'paragraph') { + if (tokens[idx + 1] !== undefined && tokens[idx + 1].type === 'list_start') { + listName = token.text.slice(0, -1) + data[listName] = [] + } else { + token = token.text.split(': ') + data[token[0]] = token[1] + } + } + } + + if (data.includes) { + // create partials + for (var i = 0; i < data.includes.length; i++) { + var includeFileName = data.includes[i] + var includeFilePath = path.resolve(__dirname, './chapters', includeFileName + '.md') + var includeContent = fs.readFileSync(includeFilePath, {encoding: 'utf8'}) + var markedInclude = marked(includeContent) + Handlebars.registerPartial(includeFileName, markedInclude) + } + } + fs.readFile('./docs/lib/layouts/layout.html', 'utf8', function (err, source) { + if (err) console.log(err) + + if (data.includes) { + var includes = '' + for (var i = 0; i < data.includes.length; i++) { + var include = data.includes[i] + includes += '\n' + '{{> ' + include + ' }}' + } + source = source.replace(/{{{html content}}}/g, '{{{html content}}}' + includes) + } + + var template = Handlebars.compile(source) + data['content'] = marked(content.slice(2).join('')) + + fs.writeFile('./docs/index.html', template(data), function (err) { + if (err) console.log(err) + }) + }) +}) diff --git a/docs/chapters/collection.md b/docs/chapters/collection.md new file mode 100644 index 00000000..9e10619e --- /dev/null +++ b/docs/chapters/collection.md @@ -0,0 +1,433 @@ +# Collection + +Collections are ordered sets of records. The collection is an array-like object exposing ES6 Array and BackboneJS Collection interface. It encapsulates JS Array of records (`collection.models`) and a hashmap for a fast O(1) access by the record `id` and `cid` (`collection.get( id )`). + +Collactions are deeply observable. You can bind "changes" events to be notified when the collection has been modified, listen for the record "add", "remove", and "change" events. + +Every `Record` class has an implicitly defined `Collection` accessible as a static member of a record's constructor. In a most cases, you don't need to define the custom collection class. + +```javascript +@define class Book extends Record { + static attributes = { + title : String + author : Author + } +} + +// Implicitly defined collection. +const books = new Book.Collection(); +``` +```typescript +@define class Book extends Record { + @auto title : string + @auto author : Author + + // Tell TypeScript the proper type. + static Collection : CollectionConstructor +} + +const books = new Book.Collection(); +``` + +You can define custom collection classes extending `Record.Collection` or any other collection class. It can either replace the default Collection type, or + +```javascript +// Define custom collection class. +@define class Library extends Record.Collection { + doSomething(){ ... } +} + +@define class Book extends Record { + // Override the default collection. + static Collection = Library; +} + +// Define another custom collection class. +@define class OtherLibrary extends Record.Collection { + // Specify the record so the collection will be able to restore itself from JSON. + static model = Book; +} +``` +```typescript +// Define custom collection class. +@define class Library extends Collection { + doSomething(){ ... } +} + +@define class Book extends Record { + // Override the default collection. + static Collection = Library; +} + +// Define another custom collection class. +@define class OtherLibrary extends Collection { + // Specify the record so the collection will be able to restore itself from JSON. + static model = Book; +} + +// An alternative way of overriding the default collection class in TypeScript. +namespace Book { + @define class Collection extends Collection { + static model = Book; + } +} +``` + + + +## Collection types + +### `constructor` CollectionClass( records?, options? ) + +The most common collection type is an **aggregating serializable collection**. By default, collection aggregates its elements which are treated as an integral part of the collection (serialized, cloned, disposed, and validated recursively). An aggregation means the _single owner_, as the single object cannot be an integral part of two distinct things. The collection will take ownership on its records and will put an error in the console if it can't. + +When creating a Collection, you may choose to pass in the initial array of records. + +```javascript +@define class Role extends Record { + static attributes = { + name : String + } +} + +const roles = new Role.Collection( json, { parse : true } ); +``` + +```typescript +@define class Role extends Record { + // In typescript, you have to specify record's Collection type expicitly. + static Collection : CollectionConstructor + + @auto name : string +} + +@define class User extends Record { + @auto name : string + + // Type-R cannot infer a Collection metatype from the TypeScript type automatically. + // Full attribute type annotation is required. + @type( Role.Collection ).as roles : Collection +} +``` + +### `constructor` CollectionClass.Refs( records?, options? ) + +Collection of record references is a **non-aggregating non-serializable collection**. `Collection.Refs` doesn't aggregate its elements, which means that containing records are not considered as an integral part of the enclosing collection and not being validated, cloned, disposed, and serialized recursively. + +It is useful for a local non-persistent application state. + +### `attrDef` subsetOf(masterRef, CollectionClass?) + +The subset of other collections are **non-aggregating serializable collection**. Subset-of collection is serialized as an array of record ids and used to model many-to-many relationships. The collection object itself is recursively created and cloned, however, its records are not aggregated by the collection thus they are not recursively cloned, validated, or disposed. `CollectionClass` argument may be omitted unless you need the record's attribute to be an instance of the particular collection class. + + + + + +Must have a reference to the master collection which is used to resolve record ids to records. `masterRef` may be: + +- direct reference to a singleton collection. +- function, returning the reference to the collection. +- symbolic dot-separated path to the master collection resolved relative to the record's `this`. You may use `owner` and `store` macro in path: + - `owner` is the reference to the record's owner. `owner.some.path` works as `() => this.getOwner().some.path`. + - `store` is the reference to the closes store. `store.some.path` works as `() => this.getStore().some.path`. + +```javascript +@define class Role extends Record { + static attributes = { + name : String, + ... + } +} + +@define class User extends Record { + static attributes = { + name : String, + roles : subsetOf( 'owner.roles', Role.Collection ) + } +} + +@define class UsersDirectory extends Store { + static attributes = { + roles : Role.Collection, + users : User.Collection // `~roles` references will be resolved against this.roles + } +} +``` +```typescript +@define class Role extends Record { + static Collection : CollectionConstructor + + @auto name : string + ... +} + +@define class User extends Record { + static Collection : CollectionConstructor + + @auto name : string + @subsetOf('store.roles').as roles : Collection +} + +@define class UsersDirectory extends Store { + @type(Role.Collection).as roles : Collection, + @type(User.Collection).as users : Collection // <- `store.roles` references will be resolved against this.roles +} +``` + +## Array API + +A collection class is an array-like object implementing ES6 Array methods and properties. + +### collection.length + +Like an array, a Collection maintains a length property, counting the number of records it contains. + +### collection.slice( begin, end ) + +Return a shallow copy of the `collection.models`, using the same options as native Array#slice. + +### collection.indexOf( recordOrId : any ) : number + +Return an index of the record in the collection, and -1 if there is no such a record in the collection. + +Can take the record itself as an argument, `id`, or `cid` of the record. + +### collection.forEach( iteratee : ( val : Record, index ) => void, context? ) + +Iterate through the elements of the collection. + + + +### collection.map( iteratee : ( val : Record, index ) => T, context? ) + +Map elements of the collection. Similar to `Array.map`. + +### collection.filter( iteratee : Predicate, context? ) + +Return the filtered array of records matching the predicate. + +The predicate is either the iteratee function returning boolean, or an object with attribute values used to match with record's attributes. + +### collection.every( iteratee : Predicate, context? ) : boolean + +Return `true` if all records match the predicate. + +### collection.some( iteratee : Predicate, context? ) : boolean + +Return `true` if at least one record matches the predicated. + +### collection.push( record, options? ) + +Add a record at the end of a collection. Takes the same options as `add()`. + +### collection.pop( options? ) +Remove and return the last record from a collection. Takes the same options as `remove()`. + +### collection.unshift( record, options? ) + +Add a record at the beginning of a collection. Takes the same options as `add()`. + +### collection.shift( options? ) +Remove and return the first record from a collection. Takes the same options as `remove()`. + +## Backbone API + +Common options used by Backbone API methods: + +- `{ sort : false }` - do not sort the collection. +- `{ parse : true }` - parse raw JSON (used to set collection with data from the server). + +### `callback` collection.initialize( records?, options? ) + +Initialization function which is called at the end of the constructor. + +### collection.clone() + +Clone the collection. An aggregating collection will be recursively cloned, non-aggregated collections will be shallow cloned. + +### collection.models + +Raw access to the JavaScript array of records inside of the collection. Usually, you'll want to use `get`, `at`, or the other methods to access record objects, but occasionally a direct reference to the array is desired. + +### collection.get( id ) +Get a record from a collection, specified by an `id`, a `cid`, or by passing in a record. + +```javascript +const book = library.get(110); +``` + +### collection.at( index ) + +Get a record from a collection, specified by index. Useful if your collection is sorted, and if your collection isn't sorted, at will still retrieve records in insertion order. When passed a negative index, it will retrieve the record from the back of the collection. + +### collection.add( records, options? ) + +Add a record (or an array of records) to the collection. If this is the `Record.Collection`, you may also pass raw attributes objects, and have them be vivified as instances of the `Record`. Returns the added (or preexisting, if duplicate) records. + +Pass `{at: index}` to splice the record into the collection at the specified index. If you're adding records to the collection that are already in the collection, they'll be ignored, unless you pass `{merge: true}`, in which case their attributes will be merged into the corresponding records. + +1. Trigger the one event per record: + - `add`(record, collection, options) for each record added. + - `change`(record, options) for each record changed (if the `{merge: true}` option is passed). +3. Trigger the single event: + - `update`(collection, options) if any records were added. + - `sort`(collection, options) if an order of records was changed. +4. Trigger `changes` event in case if any changes were made to the collection and objects inside. + +### collection.remove( records, options? ) + +Remove a record (or an array of records) from the collection, and return them. Each record can be a record instance, an id string or a JS object, any value acceptable as the id argument of collection.get. + +1. Trigger `remove`(record, collection, options) for each record removed. +3. If any records were removed, trigger: + - `update`(collection, options) + - `changes`(collection, options). + +### collection.set( records, options? ) + +The set method performs a "smart" update of the collection with the passed list of records. If a record in the list isn't yet in the collection it will be added; if the record is already in the collection its attributes will be merged; and if the collection contains any records that aren't present in the list, they'll be removed. All of the appropriate "add", "remove", and "change" events are fired as this happens. Returns the touched records in the collection. If you'd like to customize the behavior, you can disable it with options: `{remove: false}`, or `{merge: false}`. + +#### Events +1. Trigger the one event per record: + - `add`(record, collection, options) for each record added. + - `remove`(record, collection, options) for each record removed. + - `change`(record, options) for each record changed. +3. Trigger the single event: + - `update`(collection, options) if any records were added. + - `sort`(collection, options) if an order of records was changed. +4. Trigger `changes` event in case if any changes were made to the collection and objects inside. + +```javascript +const vanHalen = new Man.Collection([ eddie, alex, stone, roth ]); + +vanHalen.set([ eddie, alex, stone, hagar ]); + +// Fires a "remove" event for roth, and an "add" event for hagar. +// Updates any of stone, alex, and eddie's attributes that may have +// changed over the years. +``` + +### collection.reset(records, options?) + +Replace the collection's content with the new records. More efficient than `collection.set`, but does not send record-level events. + +Calling `collection.reset()` without passing any records as arguments will empty the entire collection. + +1. Trigger event `reset`(collection, options). +2. Trigger event `changes`(collection, options). + +### collection.pluck(attribute) + +Pluck an attribute from each model in the collection. Equivalent to calling map and returning a single attribute from the iterator. + +```javascript +const users = new UserCollection([ + {name: "Curly"}, + {name: "Larry"}, + {name: "Moe"} +]); + +const names = users.pluck("name"); + +alert(JSON.stringify(names)); +``` + +## Sorting + +Type-R implements BackboneJS Collection sorting API with some extensions. + +### collection.sort(options?) + +Force a collection to re-sort itself. You don't need to call this under normal circumstances, as a collection with a comparator will sort itself whenever a record is added. To disable sorting when adding a record, pass `{sort: false}` to add. Calling sort triggers a "sort" event on the collection. + +By default, there is no comparator for a collection. If you define a comparator, it will be used to maintain the collection in sorted order. This means that as records are added, they are inserted at the correct index in `collection.models`. + +Note that Type-R depends on the arity of your comparator function to determine between the two styles, so be careful if your comparator function is bound. + +Collections with a comparator will not automatically re-sort if you later change record attributes, so you may wish to call sort after changing record attributes that would affect the order. + +### `static` comparator = 'attrName' + +Maintain the collection in sorted order by the given record's attribute. + +### `static` comparator = x => number | string + +Maintain the collection in sorted order according to the "sortBy" comparator function. + +"sortBy" comparator functions take a record and return a numeric or string value by which the record should be ordered relative to others. + +### `static` comparator = (x, y) => -1 | 0 | 1 + +Maintain the collection in sorted order according to the "sort" comparator function. + +"sort" comparator functions take two records and return -1 if the first record should come before the second, 0 if they are of the same rank and 1 if the first record should come after. + +Note how even though all of the chapters in this example are added backward, they come out in the proper order: + +```javascript +@define class Chapter extends Record { + static attributes = { + page : Number, + title : String + } +} + +var chapters = new Chapter.Collection(); + +chapters.comparator = 'page'; + +chapters.add({page: 9, title: "The End"}); +chapters.add({page: 5, title: "The Middle"}); +chapters.add({page: 1, title: "The Beginning"}); + +alert(chapters.map( x => x.title )); +``` + +## Other methods + +### CollectionClass.from( models, options? ) + +Create `CollectionClass` from the array of models. Similar to direct collection creation, but supports additional option for strict data validation. +If `{ strict : true }` option is passed the validation will be performed and an exception will be thrown in case of an error. + +Please note, that Type-R always performs type checks on assignments, convert types, and reject improper updates reporting it as an error. It won't, however, execute custom validation +rules on every update as they are evaluated lazily. `strict` option will invoke custom validators and will throw on every error or warning instead of reporting them and continue. + +```javascript +// Validate the body of an incoming HTTP request. +// Throw an exception if validation fails. +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true }); +``` + +```typescript +// Validate the body of an incoming HTTP request. +// Throw an exception if validation fails. +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true }); +``` + +### collection.createSubset( records?, options? ) + +Create the collection which is a subset of a source collection serializable as an array of record ids. Takes the same arguments as the collection's constructor. + +The created collection is an instance of `subsetOf( sourceCollection, CollectionCtor )` attribute type (non-aggregating serializable collection). + + + +### collection.assignFrom( otherCollection ) + +Synchronize the state of the collection and its aggregation tree with other collection of the same type. Updates existing objects in place. Record in the collection is considered to be "existing" if it has the same `id`. + +Equivalent to `collection.set( otherCollection.models, { merge : true } )` and triggers similar events on change. + +### collection.dispose() + +Dispose of the collection. An aggregating collection will recursively dispose of its records. \ No newline at end of file diff --git a/docs/chapters/core.md b/docs/chapters/core.md new file mode 100644 index 00000000..028a6a99 --- /dev/null +++ b/docs/chapters/core.md @@ -0,0 +1,403 @@ +# Core Data Structires +## Overview + +## Aggregation Tree + +## Definitions + +Record definition must: + +- be the class extending the `Record`; +- be preceded with the `@define` decorator; +- have `static attributes` definition. + +### `decorator` @define + +_Must_ be placed before record class definition. + +```javascript +import { define, Record } from 'type-r' + +@define class X extends Record { + ... +} +``` + +### `static` attributes = { name : `attrDef`, ... } + +Record's attributes definition. Lists attribute names along with their types, default values, and metadata controlling different aspects of attribute behavior. + +```javascript +@define class User extends Record { + static attributes = { + name : type( String ).value( 'John Dow' ), + email : 'john.dow@mail.com', // Same as type( String ).value( 'john.dow@mail.com' ) + address : String, // Same as type( String ).value( '' ) + } +} +``` + +The Record guarantee that _every attribute will retain the value of the declared type_. Whenever an attribute is being assigned with the value which is not compatible with its declared type, the type is being converted with an invocation of the constructor: `new Type( value )` (primitive types are treated specially). + +### `static` idAttribute = 'attrName' + +A record's unique identifier is stored under the pre-defined `id` attribute. +If you're directly communicating with a backend (CouchDB, MongoDB) that uses a different unique key, you may set a Record's `idAttribute` to transparently map from that key to id. + +Record's `id` property will still be linked to Record's id, no matter which value `idAttribute` has. + +```javascript +@define class Meal extends Record { + static idAttribute = "_id"; + static attributes = { + _id : Number, + name : '' + } +} + +const cake = new Meal({ _id: 1, name: "Cake" }); +alert("Cake id: " + cake.id); +``` + +### `attrDef` : Type + +When the function is used as `attrDef`, it's treated as the constructor function. Any constructor function which behaves as _converting constructor_ (like `new Date( msecs )`) may be used as an attribute type. + +```javascript +@define class Person extends Record { + static attributes = { + name : String // String attribute which is "" by default. + createdAt : Date // Date attribute + ... + } +} +``` + +### `attrDef` : defaultValue + +When value of other type than function is used as `attrDef` it's treated as attribute's default value. Attribute's type is being inferred from the value. + +Use the general form of attribute definition for attributes of `Function` type: `type( Function ).value( theFunction )`. + +```javascript +@define class GridColumn extends Record { + static attributes = { + name : '', // String attribute which is '' by default. + render : type( Function ).value( x => x ), + ... + } +} +``` + +### `attrDef` : type( Type ).value( defaultValue ) + +Declare attribute with custom default value. + +```javascript +@define class Person extends Record { + static attributes = { + phone : type( String ).value( null ) // String attribute which is null by default. + ... + } +} +``` + +The record is _recursive_ if it's uses the type of itself in its attribute definition. + +### `attrDef` : Date + +Date attribute initialized as `new Date()`. Represented in JSON as string or number depending on the type: + +* `Date` - as ISO date string. +* `Date.microsoft` - as Microsoft's `"/Date(msecs)/"` string. +* `Date.timestamp` - as UNIX integer timestamp. + +### `static` Collection + +The default record's collection class automatically defined for every Record subclass. Can be referenced as `Record.Collection`. + +May be explicitly assigned in record's definition with custom collection class. + +```javascript +// Declare the collection class. +@define class Comments extends Record.Collection {} + +@define class Comment extends Record({ + static Collection = Comments; // Make it the default Comment collection. + + attributes : { + text : String, + replies : Comments + } +}); +``` + +## Record + +Record behaves as regular ES6 class with attributes accessible as properties. + +### new Record() + +Create an instance of the record with default attribute values taken from the attributes definition. + +When no default value is explicitly provided for an attribute, it's initialized as `new Type()` (just `Type()` for primitives). When the default value is provided and it's not compatible with the attribute type, it's converted with `new Type( defaultValue )` call. + +### new Record({ attrName : value, ... }, options? ) + +When creating an instance of a record, you can pass in the initial attribute values to override the defaults. + +If `{parse: true}` option is used, `attrs` is assumed to be the JSON. + +If the value of the particular attribute is not compatible with its type, it's converted to the declared type invoking the constructor `new Type( value )` (just `Type( value )` for primitives). + +```javascript +@define class Book extends Record { + static attributes = { + title : '', + author : '' + } +} + +const book = new Book({ + title: "One Thousand and One Nights", + author: "Scheherazade" +}); +``` + +### record.clone() + +Create the deep copy of the aggregation tree, recursively cloning all aggregated records and collections. References to shared members will be copied, but not shared members themselves. + +### `callback` record.initialize( attrs?, options? ) + +Called at the end of the `Record` constructor when all attributes are assigned and the record's inner state is properly initialized. Takes the same arguments as +a constructor. + +### record.dispose() + +Recursively dispose the record and its aggregated members. "Dispose" means that elements of the aggregation tree will unsubscribe from all event sources. It's crucial to prevent memory leaks in SPA. + +The whole aggregation tree will be recursively disposed, shared members won't. + +### record.cid + +Read-only client-side record's identifier. Generated upon creation of the record and is unique for every record's instance. Cloned records will have different `cid`. + +### record.id + +Predefined record's attribute, the `id` is an arbitrary string (integer id or UUID). `id` is typically generated by the server. It is used in JSON for id-references. + +Records can be retrieved by `id` from collections, and there can be just one instance of the record with the same `id` in the particular collection. + +### record.isNew() + +Has this record been saved to the server yet? If the record does not yet have an `id`, it is considered to be new. + +### record.attrName + +Record's attributes may be directly accessed as `record.name`. + + + +```javascript +@define class Account extends Record { + static attributes = { + name : String, + balance : Number + } +} + +const myAccount = new Account({ name : 'mine' }); +myAccount.balance += 1000000; // That works. Good, eh? +``` + +### record.attrName = value + +Assign the record's attribute. If the value is not compatible with attribute's type from the declaration, it is converted: + +- with `Type( value )` call, for primitive types; +- with `record.attrName.set( value )`, for existing record or collection (updated in place); +- with `new Type( value )` in all other cases. + +Record triggers events on changes: +- `change:attrName` *( record, value )*. +- `change` *( record )*. + +```javascript +@define class Book extends Record { + static attributes = { + title : String, + author : String + price : Number, + publishedAt : Date, + available : Boolean + } +} + +const myBook = new Book({ title : "State management with Type-R" }); +myBook.author = 'Vlad'; // That works. +myBook.price = 'Too much'; // Converted with Number( 'Too much' ), resulting in NaN. +myBook.price = '123'; // = Number( '123' ). +myBook.publishedAt = new Date(); // Type is compatible, no conversion. +myBook.publishedAt = '1678-10-15 12:00'; // new Date( '1678-10-15 12:00' ) +myBook.available = some && weird || condition; // Will always be Boolean. Or null. +``` + +### record.set( { attrName : value, ... }, options? : `options` ) + +Bulk assign record's attributes, possibly taking options. + +If the value is not compatible with attribute's type from the declaration, it is converted: + +- with `Type( value )` call, for primitive types. +- with `record.attrName.set( value )`, for existing record or collection (updated in place). +- with `new Type( value )` in all other cases. + +Record triggers events after all changes are applied: + +1. `change:attrName` *( record, val, options )* for any changed attribute. +2. `change` *(record, options)*, if there were changed attributes. + +### record.assignFrom( otherRecord ) + +Makes an existing `record` to be the full clone of `otherRecord`, recursively assigning all attributes. + +```javascript +// Another way of doing the bestSeller.clone() +const book = new Book(); +book.assignFrom( bestSeller ); +``` + +### record.transaction( fun ) + +Execute the all changes made to the record in `fun` as single transaction triggering the single `change` event. + +All record updates occurs in the scope of transactions. Transaction is the sequence of changes which results in a single `change` event. +Transaction can be opened either manually or implicitly with calling `set()` or assigning an attribute. +Any additional changes made to the record in `change:attr` event handler will be executed in the scope of the original transaction, and won't trigger additional `change` events. + + +```javascript +some.record.transaction( record => { + record.a = 1; // `change:a` event is triggered. + record.b = 2; // `change:b` event is triggered. +}); // `change` event is triggered. +``` + +Manual transactions with attribute assignments are superior to `record.set()` in terms of both performance and flexibility. + +### `attrDef` : type( Type ).get( `hook` ) + +Attach get hook to the record's attribute. `hook` is the function of signature `( value, attr ) => value` which is used to transform the attribute's value _before it will be read_. Hook is executed in the context of the record. + +### `attrDef` : type( Type ).set( `hook` ) + +Attach the set hook to the record's attribute. `hook` is the function of signature `( value, attr ) => value` which is used to transform the attribute's value _before it will be assigned_. Hook is executed in the context of the record. + +If set hook will return `undefined`, it will cancel attribute update. + +## Collection + +## Nested records and collections + +Record's attributes can hold other Records and Collections, forming indefinitely nested data structures of arbitrary complexity. +To create nested record or collection you should just mention its constructor function in attribute's definition. + +```javascript +import { Record } from 'type-r' + +@define class User extends Record { + static attributes = { + name : String, + email : String, + isActive : true + } +} + +@define class UsersListState extends Record { + static attributes = { + users : User.Collection + } +} +``` + +All nested records and collections are *aggregated* by default and behave as integral parts of the containing record. Aggregated attributes are _exclusively owned_ by the record, and taken with it together form an _ownership tree_. Many operations are performed recursively on aggregated elements: + +- They are created when the owner record is created. +- They are cloned when the record is cloned. +- They are disposed when the record is disposed. +- They are validated as part of the record. +- They are serialized as nested JSON. + +### `attrDef` : RecordOrCollection + +Aggregated record or collection. Represented as nested object or array in record's JSON. Aggregated members are owned by the record and treated as its _integral part_ (recursively created, cloned, serialized, validated, and disposed). +One object can have single owner. The record with its aggregated attributes forms an _aggregation tree_. + +All changes in aggregated record or collections are detected and cause change events on the containing record. + +### record.getOwner() + +Return the record which is an owner of the current record, or `null` there are no one. + +Due to the nature of _aggregation_, an object may have one and only one owner. + +### record.collection + +Return the collection which aggregates the record, or `null` if there are no one. + +### `attrDef` : shared( RecordOrCollection ) + +Non-serializable reference to the record or collection possibly from the different aggregation tree. Initialized with `null`. Is not recursively cloned, serialized, validated, or disposed. + +All changes in shared records or collections are detected and cause change events of the containing record. + + + +```javascript +@define class UsersListState extends Record { + static attributes = { + users : User.Collection, + selected : shared( User ) // Can be assigned with the user from this.users + } +} +``` + +### `attrDef` : Collection.Refs + +Non-aggregating collection. Collection of references to shared records which itself is _aggregated_ by the record, but _does not aggregate_ its elements. In contrast to the `shared( Collection )`, `Collection.Refs` creates an instance of collection which _is the part the parent record_. + +The collection itself is recursively created and cloned. However, its records are not aggregated by the collection thus they are not recursively cloned, validated, serialized, or disposed. + +All changes in the collection and its elements are detected and cause change events of the containing record. + + + +```javascript + @define class MyRecord extends Record { + static attributes = { + notCloned : shared( SomeCollection ), // Reference to the _shared collection_ object. + cloned : SomeCollection.Refs // _Aggregated_ collection of references to the _shared records_. + } +``` + +### `decorator` @predefine + +Make forward declaration for the record to define its attributes later with `RecordClass.define()`. Used instead of `@define` for recursive record definitions. + +Creates the default `RecordClass.Collection` type which can be referenced in attribute definitions. + +### `static` define({ attributes : { name : `attrDef`, ... } }) + +May be called to define attributes in conjunction with `@predefine` decorator to make recursive record definitions. + +```javascript +@predefine class Comment extends Record{} + +Comment.define({ + attributes : { + text : String, + replies : Comment.Collection + } +}); +``` diff --git a/docs/chapters/io.md b/docs/chapters/io.md new file mode 100644 index 00000000..548f6db6 --- /dev/null +++ b/docs/chapters/io.md @@ -0,0 +1,507 @@ +# I/O and Serialization + +## Overview + +Type-R implements generalized IO on top of the `IOEndpoint` interface, with JSON serialization handled by Record and Collection classes. + +IOEndpoint defines the set of CRUD + list methods operating on raw JSON. +Attachment of an endpoint to the record or collection enables I/O API. There are few endpoints bundled with Type-R, for instance `memoryIO()` which can be used for mock testing. + +```javascript +@define class User extends Record { + static endpoint = memoryIO(); + + static attributes = { + name : '', + email : '' + } +} + +const users = new User.Collection(); +users + .add({ name : 'John' }) + .save() + .then( () => console.log( user.id ); +``` + +## I/O API + +### `static` endpoint + +I/O endpoint declaration which should be used in Record or Collection definition to enable I/O API. + +If an endpoint is defined for the `MyRecord`, it's automatically defined for the corresponding `MyRecord.Collection` as well. + +### `attrDef` : type( Type ).endpoint( `endpoint` ) + +Override or define an I/O endpoint for the specific record's attribute. + +### obj.getEndpoint() + +Returns an object's IO endpoint. Normally, this is an endpoint which is defined in object's `static endpoint = ...` declaration, but it might be overridden by the parent's record using `type( Type ).endpoint( ... )` attribute declaration. + +```javascript +@define class User extends Record { + static endpoint = restfulIO( '/api/users' ); + ... +} + +@define class UserRole extends Record { + static endpoint = restfulIO( '/api/roles' ); + static attributes = { + // Use the relative path '/api/roles/:id/users' + users : type( User.Collection ).endpoint( restfulIO( './users' ) ), + ... + } +} +``` + +### record.fetch( options? ) + +Asynchronously fetch the record using `endpoint.read()` method. Returns an abortable ES6 promise. + +An endpoint must be defined for the record in order to use that method. + +### record.save( options? ) + +Asynchronously save the record using `endpoint.create()` (if there are no id) or `endpoint.update()` (if id is present) method. Returns an abortable ES6 promise. + +An endpoint must be defined for the record in order to use that method. + +### record.destroy( options? ) + +Asynchronously destroy the record using `endpoint.destroy()` method. Returns an abortable ES6 promise. The record is removed from the aggregating collection upon the completion of the I/O request. + +An endpoint must be defined for the record in order to use that method. + +### collection.fetch( options? ) + +Fetch the collection. Returns an abortable promise. + +`options` accepts an optional `liveUpdates` parameter. When `true`, collection subscribes for the live updates when I/O is finished. + +### collection.liveUpdates( true | false ) + +Subscribe for the live data updates if an I/O endpoint supports it (`subscribe()`/`unsubscribe()` IOEndpoint methods). + + + +### obj.hasPendingIO() + +Returns an abortable promise if there's any I/O pending with the object, or `null` otherwise. + +Can be used to check for active I/O in progress or to abort pending I/O operation. Please note, that all pending I/O is aborted automatically when new I/O operation is started or an object is disposed. When I/O is aborted, the promise is rejected. + +```javascript +const promise = users.hasPendingIO(); +if( promise && promise.abort ) promise.abort(); +``` + +## I/O endpoints + +### restfulIO( url, options? ) + +HTTP REST client endpoint. Requires `window.fetch` available natively or through the polyfill. Implements standard BackboneJS REST semantic. + +All I/O methods append an optional `options.params` object to the URL parameters translating them to string with `JSON.stringify()`. + +- `record.save()` makes: + - `POST url`, if the model has no id. Expects to receive `{ id : recordId }`. + - `PUT url/:id`, if the model has an id. +- `collection.fetch()` makes `GET url`. +- `record.destroy()` makes `DELETE url`. + +Supports URI relative to owner (`./relative/url` resolves as `/owner/:id/relative/url/:id` ). + +```javascript +import { restfulIO } from 'type-r/endpoints/restful' + +@define class Role extends Record { + static endpoint = restfulIO( '/api/roles' ); + ... +} + +@define class User extends Record { + static endpoint = restfulIO( '/api/users' ); + + static attributes = { + // Roles collection here has relative url /api/users/:user_id/roles/ + roles : type( Role.Collection ).endpoint( restfulIO( './roles' ) ), + ... + } +} +``` + +### memoryIO( mockData?, delay? ) + +Endpoint for mock testing. Takes optional array with mock data, and optional `delay` parameter which is the simulated I/O delay in milliseconds. + +```javascript +import { memoryIO } from 'type-r/endpoints/memory' + +@define class User extends Record { + static endpoint = memoryIO(); + ... +} +``` + +### localStorageIO( key ) + +Endpoint for localStorage. Takes `key` parameter which must be unique for the persistent record's collection. + +```javascript +import { localStorageIO } from 'type-r/endpoints/localStorage' + +@define class User extends Record { + static endpoint = localStorageIO( '/users' ); + ... +} +``` + +### attributesIO() + +Endpoint for I/O composition. Redirects record's `fetch()` request to its attributes and returns the combined abortable promise. Does not enable any other I/O methods and can be used with `record.fetch()` only. + +It's common pattern to use attributesIO endpoint in conjunction with Store to fetch all the data required by SPA page. + +```javascript +import { localStorageIO } from 'type-r/endpoints/attributes' + +@define class PageStore extends Store { + static endpoint = attributesIO(); + static attributes = { + users : User.Collection, + roles : UserRole.Collection, + } +} +... +const store = new PageStore(); +store.fetch().then( () => renderUI() ); +``` + +### proxyIO( RecordCtor ) + +Create IO endpoint from the Record class. This endpoint is designed for use on the server side with a data layer managed by Type-R. + +Assuming that you have Type-R records with endpoints working with the database, you can create an endpoint which will use +an existing Record subclass as a transport. This endpoint can be connected to the RESTful endpoint API on the server side which will serve JSON to the restfulIO endpoint on the client. + +An advantage of this approach is that JSON schema will be transparently validated on the server side by the Type-R. + +```javascript + import { proxyIO } from 'type-r/endpoint/proxy' + + ... + + const usersIO = proxyIO( User ); +``` + +## IOEndpoint Interface + +An IO endpoint is an "plug-in" abstraction representing the persistent collection of JSON objects, which is required to enable records and collections I/O API. There are several pre-defined endpoints included in Type-R package which can be used for HTTP REST I/O, mock testing, working with localStorage, and IO composition. + +You will need to define custom endpoint if you would like to implement or customize serialization transport for Type-R objects. Use built-in endpoints as an example and the starting boilerplate. + +All IOEndpoint methods might return standard Promises or abortable promises (created with `createIOPromise()`). An IOEndpoint instance is shared by all of the class instances it's attached to and therefore it's normally *must be stateless*. + +### endpoint.read( id, options, record ) + +Reads an object with a given id. Used by `record.fetch()` method. Must return JSON wrapped in abortable promise. + +### endpoint.update( id, json, options, record ) + +Updates or creates an object with a given id. Used by `record.save()` method when record *already has* an id. Must return abortable promise. + +### endpoint.create( json, options, record ) + +Creates an object. Used by `record.save()` method when record *does not* have an id. Must return abortable promise. + +### endpoint.destroy( id, options, record ) + +Destroys the object with the given id. Used by `record.destroy()` method. Must return abortable promise. + +### endpoint.list( options, collection ) + +Fetch an array of objects. Used by `collection.fetch()` method. Must returns abortable promise. + +### endpoint.subscribe( `callbacks`, collection ) + +Optional method to enable the live updates subscription. Used by `collection.liveUpdates( true )` method. Must returns abortable promise. + +Method `callbacks` argument is an object of the following shape: + +```javascript +{ + // Endpoint must call it when an object is created or updated. + updated( json ){} + + // Endpoint must call it when an object is removed. + removed( json ){} +} +``` + +### endpoint.unsubscribe( `callbacks`, collection ) + +Unsubscribe from the live updates. Used by `collection.liveUpdates( false )` method. Takes the same `callbacks` object as `subscribe()`. + +### createIOPromise( init ) + +Service function to create an abortable version of ES6 promise (with `promise.abort()` which meant to stop pending I/O and reject the promise). + +`init` function takes the third `onAbort` argument to register an optional abort handler. If no handler is registered, the default implementation of `promise.abort()` will just reject the promise. + +```javascript +import { createIOPromise } from 'type-r' + +const abortablePromise = createIOPromise( ( resolve, reject, onAbort ) =>{ + ... + onAbort( () => { + reject( 'I/O Aborted' ); + }); +}); +``` + +## Serialization + +Record and Collection has a portion of common API related to the I/O and serialization. + +### obj.toJSON( options? ) + +Serialize record or collection to JSON. Used internally by I/O methods. Can be overridden to customize serialization. + +Produces the JSON for the given record or collection and its aggregated members. Aggregation tree is serialized as nested JSON. Record corresponds to an object in JSON, while the collection is represented as an array of objects. + +If you override `toJSON()`, it usually means that you must override `parse()` as well, and vice versa. + + + +```javascript +@define class Comment extends Record { + static attributes = { + body : '' + } +} + +@define class BlogPost extends Record { + static attributes = { + title : '', + body : '', + comments : Comment.Collection + } +} + +const post = new BlogPost({ + title: "Type-R is cool!", + comments : [ { body : "Agree" }] +}); + +const rawJSON = post.toJSON() +// { title : "Type-R is cool!", body : "", comments : [{ body : "Agree" }] } +``` + +### `option` { parse : true } + +`obj.set()` and constructor's option to force parsing of the raw JSON. Is used internally by I/O methods to parse the data received from the server. + +```javascript +// Another way of doing the bestSeller.clone() +// Amazingly, this is guaranteed to work by default. +const book = new Book(); +book.set( bestSeller.toJSON(), { parse : true } ); +``` + +### `callback` obj.parse( json ) + +Optional hook called to transform the JSON when it's passes to the record or collection with `set( json, { parse : true })` call. Used internally by I/O methods. + +If you override `toJSON()`, it usually means that you must override `parse()` as well, and vice versa. + + + +### `attrDef` : type( Type ).toJSON( false ) + +Do _not_ serialize the specific attribute. + +### `attrDef` : type( Type ).toJSON( ( value, name, options ) => json ) + +Override the default serialization for the specific record's attribute. + +Attribute is not serialized when the function return `undefined`. + +### `attrDef` : type( Type ).parse( ( json, name ) => value ) + +Transform the data before it will be assigned to the record's attribute. + +Invoked when the `{ parse : true }` option is set. + +```javascript +// Define custom boolean attribute type which is serialized as 0 or 1. +const MyWeirdBool = type( Boolean ) + .parse( x => x === 1 ) + .toJSON( x => x ? 1 : 0 ); +``` + +### `static` create( attrs, options ) + +Static factory function used internally by Type-R to create instances of the record. + +May be redefined in the abstract Record base class to make it serializable type. + +```javascript +@define class Widget extends Record { + static attributes = { + type : String + } + + static create( attrs, options ){ + switch( attrs.type ){ + case "typeA" : return new TypeA( attrs, options ); + case "typeB" : return new TypeB( attrs, options ); + } + } +} + +@define class TypeA extends Widget { + static attributes = { + type : "typeA", + ... + } +} + +@define class TypeB extends Widget { + static attributes = { + type : "typeB", + ... + } +} +``` + +## Normalized data + +Type-R has first-class support for working with normalized data represented as a set of collections with cross-references by record id. References are represented as record ids in JSON, and being transparently resolved to record instances on the first access. + +`Store` class is the special record class which serves as a placeholder for the set of interlinked collections of normalized records. Id-references are defined as record attributes of the special type representing the serializable reference to the records from the specified master collection. + +### `attrDef` : memberOf( `sourceCollection` ) + +Serializable reference to the record from the particular collection. +Initialized as `null` and serialized as `record.id`. Is not recursively cloned, validated, or disposed. Used to model one-to-many relationships. + +Changes in shared record are not detected. + +`sourceCollection` may be: +- the JS variable pointing to the collection singleton; +- the function returning the collection; +- the string with the dot-separated _relative object path_ to the collection. It is resolved dynamically relative to the record's `this`. Following shortcuts may be used in path: + - `owner.path` (or `^path`) works as `() => this.getOwner().path`. + - `store.path` (or `~path`) works as `() => this.getStore().path`. + +```javascript + @define class State extends Record { + static attributes = { + items : Item.Collection, + selected : memberOf( 'items' ) // Will resolve to `this.items` + } + } +``` + +```typescript + @define class State extends Record { + @type( Item.Collection ).as items : Collection; + @memberOf( 'items' ).as selected : Item + } +``` + + + +### `attrDef` : subsetOf( `sourceCollection`, CollectionCtor? ) + +Serializable non-aggregating collection which is the subset of the existing collection. Serialized as an array of record ids. Used to model many-to-many relationships. `CollectionCtor` argument may be omitted unless you need it to be a sublass of the particular collection type. + +The collection object itself is recursively created and cloned. However, its records are not aggregated by the collection thus they are not recursively cloned, validated, or disposed. + +`sourceCollection` is the same reference as used by `memberOf( sourceCollection )`. + +```javascript +@define class Role extends Record { + static attributes = { + name : String, + ... + } +} + +@define class User extends Record { + static attributes = { + name : String, + roles : subsetOf( '~roles', Role.Collection ) + } +} + +@define class UsersDirectory extends Store { + static attributes = { + roles : Role.Collection, + users : User.Collection // `~roles` references will be resolved against this.roles + } +} +``` + +### sourceCollection.createSubset( records?, options? ) + +Create an instance of `subsetOf( sourceCollection, CollectionCtor )` type (non-aggregating serializable collection) which is the subset of the given collection. Takes the same arguments as the collection's constructor. + + + +### `class` Store + +`Store` is the special kind of record which serves as a root for id references. + +For all records inside of the store's aggregation tree `~attrName` will resolve to the attribute of the store class found with `record.getStore()` method. If there are no such an attribute in the store, the next available store upper in aggregation tree will be used (as regular records stores can be nested), or the default store if there are no one. + + + +Store is the subclass of the Record. It's defined extending the `Store` abstract base class. It behaves as a regular record in most aspects. + +### store._defaultStore + +Reference to the master store used for lookups if the current store doesn't have the required attribute and there are no other store found upper in the ownership chain. + +Defaults to the `Store.global`. May be explicitly defined to create custom store lookup chains across the ownership hierarchy. + +### `static` Store.global + +The default singleton store class. Is always the last store to lookup when resolving ~reference. + +Use the default store for the _globally shared data only_. Each application page must have its local store. + +```javascript +@define class MyStore extends Store { + static attributes = { + users : User.Collection, + roles : Role.Collection + } +} + +Store.global = new MyStore(); + +// Now the reference '~users` will point to users collection from the MyStore. +``` + +### recordOrCollection.getStore() + +Return the closest store. Used internally to resolve symbolic `~reference` relative to the store. + +Method looks for the `Store` subclass traversing the ownership chain of current aggregation tree upwards. If there are no store found this way, default Store from `Store.global` is returned. + +### recordOrCollection.clone({ pinStore : true }) + +Make the cloned object to preserve the reference to its original store. + +Cloned objects don't have an owner by default, thus they loose the reference to their store as no ownership chain can be traversed. `pinStore` option should be used in such a cases. \ No newline at end of file diff --git a/docs/chapters/observable.md b/docs/chapters/observable.md new file mode 100644 index 00000000..df43fa4b --- /dev/null +++ b/docs/chapters/observable.md @@ -0,0 +1,325 @@ +# Observable Changes + +## Overview + +Type-R implements *deeply observable changes* on the object graph constructed of records and collection. + +All of the record and collection updates happens in a scope of the transaction followed by the change event. Every record or collection update operation opens _implicit_ transaction. Several update operations can be groped to the single _explicit_ transaction if executed in the scope of the `obj.transaction()` or `col.updateEach()` call. + +```javascript +@define class Author extends Record { + static attributes = { + name : '' + } +} + +@define class Book extends Record { + static attributes = { + name : '', + datePublished : Date, + author : Author + } +} + +const book = new Book(); +book.on( 'change', () => console.log( 'Book is changed') ); + +// Implicit transaction, prints to the console +book.author.name = 'John Smith'; +``` + +## Record + +### Events mixin methods (7) + +Record implements [Events](#events-mixin) mixin. + +### `event` "change" ( record ) + +Triggered by the record at the end of the attributes update transaction in case if there were any changes applied. + +### `event` "change:attrName" ( record, value ) + +Triggered by the record during the attributes update transaction for every changed attribute. + +### `attrDef` : type( Type ).watcher( watcher ) + +Attach `change:attr` event listener to the particular record's attribute. `watcher` can either be the record's method name or the function `( newValue, attr ) => void`. Watcher is always executed in the context of the record. + +```javascript +@define class User extends Record { + static attributes = { + name : type( String ).watcher( 'onNameChange' ), + isAdmin : Boolean, + } + + onNameChange(){ + // Cruel. But we need it for the purpose of the example. + this.isAdmin = this.name.indexOf( 'Admin' ) >= 0; + } +} +``` + +### `attrDef` : type( Type ).changeEvents( false ) + +Turn off changes observation for nested records or collections. + +Record automatically listens to change events of all nested records and collections, triggering appropriate change events for its attributes. This declaration turns it off for the specific attribute. + +### `attrDef` : type( Type ).events({ eventName : handler, ... }) + +Automatically manage custom event subscription for the attribute. `handler` is either the method name or the handler function. + +### record.changed + +The `changed` property is the internal hash containing all the attributes that have changed during its last transaction. +Please do not update `changed` directly since its state is internally maintained by `set()`. +A copy of `changed` can be acquired from `changedAttributes()`. + +### record.changedAttributes( attrs? ) + +Retrieve a hash of only the record's attributes that have changed during the last transaction, +or false if there are none. Optionally, an external attributes hash can be passed in, +returning the attributes in that hash which differ from the record. +This can be used to figure out which portions of a view should be updated, +or what calls need to be made to sync the changes to the server. + +### record.previous( attr ) + +During a "change" event, this method can be used to get the previous value of a changed attribute. + +```javascript +@define class Person extends Record{ + static attributes = { + name: '' + } +} + +const bill = new Person({ + name: "Bill Smith" +}); + +bill.on("change:name", ( record, name ) => { + alert( `Changed name from ${ bill.previous('name') } to ${ name }`); +}); + +bill.name = "Bill Jones"; +``` + +### record.previousAttributes() + +Return a copy of the record's previous attributes. Useful for getting a diff between versions of a record, or getting back to a valid state after an error occurs. + +## Collection + +All changes in the records cause change events in the collections they are contained in. + +Subset collections is an exception; they don't observe changes of its elements by default. + +### Events mixin methods (7) + +Collection implements [Events](#events-mixin) mixin. + +### collection.transaction( fun ) + +Execute the sequence of updates in `fun` function in the scope of the transaction. + +All collection updates occurs in the scope of transactions. Transaction is the sequence of changes which results in a single `changes` event. + +Transaction can be opened either manually or implicitly with calling any of collection update methods. +Any additional changes made to the collection or its items in event handlers will be executed in the scope of the original transaction, and won't trigger an additional `changes` events. + +### collection.updateEach( iteratee : ( val : Record, index ) => void, context? ) + +Similar to the `collection.each`, but wraps an iteration in a transaction. The single `changes` event will be emitted for the group of changes to the records made in `updateEach`. + +### `static` itemEvents = { eventName : `handler`, ... } + +Subscribe for events from records. The `hander` is either the collection's method name, the handler function, or `true`. + +When `true` is passed as a handler, the corresponding event will be triggered on the collection. + +### `event` "changes" (collection, options) + +When collection has changed. Single event triggered when the collection has been changed. + +### `event` "reset" (collection, options) + +When the collection's entire contents have been reset (`reset()` method was called). + +### `event` "update" (collection, options) + +Single event triggered after any number of records have been added or removed from a collection. + +### `event` "sort" (collection, options) + +When the collection has been re-sorted. + +### `event` "add" (record, collection, options) + +When a record is added to a collection. + +### `event` "remove" (record, collection, options) + +When a record is removed from a collection. + +### `event` "change" (record, options) + +When a record inside of the collection is changed. + +## Events mixin + +Type-R uses an efficient synchronous events implementation which is backward compatible with Backbone 1.1 Events API but is about twice faster in all major browsers. It comes in form of `Events` mixin and the `Messenger` base class. + +`Events` is a [mixin](#mixins) giving the object the ability to bind and trigger custom named events. Events do not have to be declared before they are bound, and may take passed arguments. + +Both `source` and `listener` mentioned in method signatures must implement Events methods. + +```javascript +import { mixins, Events } from 'type-r' + +@mixins( Events ) +class EventfulClass { + ... +} +``` + + + +### source.trigger(event, arg1, arg2, ... ) + +Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks. + +### listener.listenTo(source, event, callback) +Tell an object to listen to a particular event on an other object. The advantage of using this form, instead of other.on(event, callback, object), is that listenTo allows the object to keep track of the events, and they can be removed all at once later on. The callback will always be called with object as context. + +```javascript + view.listenTo(record, 'change', view.render ); +``` + + + +### listener.stopListening([source], [event], [callback]) + +Tell an object to stop listening to events. Either call stopListening with no arguments to have the object remove all of its registered callbacks ... or be more precise by telling it to remove just the events it's listening to on a specific object, or a specific event, or just a specific callback. + +```javascript + view.stopListening(); // Unsubscribe from all events + + view.stopListening(record); // Unsubscribe from all events from the record +``` + + + +### listener.listenToOnce(source, event, callback) + +Just like `listenTo()`, but causes the bound callback to fire only once before being automatically removed. + +### source.on(event, callback, [context]) + +Bind a callback function to an object. The callback will be invoked whenever the event is fired. If you have a large number of different events on a page, the convention is to use colons to namespace them: `poll:start`, or `change:selection`. The event string may also be a space-delimited list of several events... + +```javascript + book.on("change:title change:author", ...); +``` + +Callbacks bound to the special "all" event will be triggered when any event occurs, and are passed the name of the event as the first argument. For example, to proxy all events from one object to another: + +```javascript + proxy.on("all", function(eventName) { + object.trigger(eventName); + }); +``` + +All event methods also support an event map syntax, as an alternative to positional arguments: + +```javascript + book.on({ + "change:author": authorPane.update, + "change:title change:subtitle": titleView.update, + "destroy": bookView.remove + }); +``` + +To supply a context value for this when the callback is invoked, pass the optional last argument: `record.on('change', this.render, this)` or `record.on({change: this.render}, this)`. + + + +### source.off([event], [callback], [context]) + +Remove a previously bound callback function from an object. If no context is specified, all of the versions of the callback with different contexts will be removed. If no callback is specified, all callbacks for the event will be removed. If no event is specified, callbacks for all events will be removed. + +```javascript + // Removes just the `onChange` callback. + object.off("change", onChange); + + // Removes all "change" callbacks. + object.off("change"); + + // Removes the `onChange` callback for all events. + object.off(null, onChange); + + // Removes all callbacks for `context` for all events. + object.off(null, null, context); + + // Removes all callbacks on `object`. + object.off(); +``` + +Note that calling `record.off()`, for example, will indeed remove all events on the record — including events that Backbone uses for internal bookkeeping. + +### source.once(event, callback, [context]) +Just like `on()`, but causes the bound callback to fire only once before being removed. Handy for saying "the next time that X happens, do this". When multiple events are passed in using the space separated syntax, the event will fire once for every event you passed in, not once for a combination of all events + +### Built-in events + +All Type-R objects implement Events mixin and use events to notify listeners on changes. + +Record and Store change events: + +Event name | Handler arguments | When triggered +-------|-------------------|------------ +change | (record, options) | At the end of any changes. +change:attrName | (record, value, options) | The record's attribute has been changed. + +Collection change events: + +Event name | Handler arguments | When triggered +-------|-------------------|------------ +changes | (collection, options) | At the end of any changes. +reset | (collection, options) | `reset()` method was called. +update | (collection, options) | Any records added or removed. +sort | (collection, options) | Order of records is changed. +add | (record, collection, options) | The record is added to a collection. +remove | (record, collection, options) | The record is removed from a collection. +change | (record, options) | The record is changed inside of collection. + +## Messenger class + +Messenger is an abstract base class implementing Events mixin and some convenience methods. + +```javascript +import { define, Messenger } from 'type-r' + +class MyMessenger extends Messenger { + +} +``` + +### Events mixin methods (7) + +Messenger implements [Events](#events-mixin) mixin. + +### messenger.cid + +Unique run-time only messenger instance id (string). + +### `callback` messenger.initialize() + +Callback which is called at the end of the constructor. + +### messenger.dispose() + +Executes `messenger.stopListening()` and `messenger.off()`. + +Objects must be disposed to prevent memory leaks caused by subscribing for events from singletons. \ No newline at end of file diff --git a/docs/chapters/record.md b/docs/chapters/record.md new file mode 100644 index 00000000..2bc57425 --- /dev/null +++ b/docs/chapters/record.md @@ -0,0 +1,670 @@ +# Record + +Record is an optionally persistent class having the predefined set of attributes. Each attribute is the property of known type which is protected from improper assigments at run-time, is serializable to JSON by default, has deeply observable changes, and may have custom validation rules attached. + +Records may have other records and collections of records stored in its attributes describing an application state of an arbitrary complexity. These nested records and collections are considered to be an integral part of the parent record forming an *aggregation tree* which can be serialized to JSON, cloned, and disposed of as a whole. + +All aspects of an attribute behavior are controlled with attribute metadata, which (taken together with its type) is called *attribite metatype*. Metatypes can be declared separately and reused across multiple records definitions. + +```javascript +import { define, type, Record } from 'type-r' + +// ⤹ required to make magic work +@define class User extends Record { + // ⤹ attribute's declaration + static attributes = { + firstName : '', // ⟵ String type is inferred from the default value + lastName : String, // ⟵ Or you can just mention its constructor + email : type(String).value(null), //⟵ Or you can provide both + createdAt : Date, // ⟵ And it works for any constructor. + // And you can attach ⤹ metadata to fine-tune attribute's behavior + lastLogin : type(Date).value(null).toJSON(false) // ⟵ not serializable + } +} + +const user = new User(); +console.log( user.createdAt ); // ⟵ this is an instance of Date created for you. + +const users = new User.Collection(); // ⟵ Collections are defined automatically. +users.on( 'changes', () => updateUI( users ) ); // ⟵ listen to the changes. + +users.set( json, { parse : true } ); // ⟵ parse raw JSON from the server. +users.updateEach( user => user.firstName = '' ); // ⟵ bulk update triggering 'changes' once +``` + +```typescript +import { define, attr, type, Record } from 'type-r' +import "reflect-metadata" // Required for @auto without arguments + +// ⤹ required to make the magic work +@define class User extends Record { + // ⤹ attribute's declaration + // IMPORTANT: attributes will be initialized even if no default value is provided. + @auto lastName : string // ⟵ @auto decorator extracts type from the Reflect metadata + @auto createdAt : Date // ⟵ It works for any constructor. + @auto('somestring') firstName : string //⟵ The custom default value must be passed to @auto decorator. + @auto(null) updatedAt : Date + + // You have to pass the type explicitly if reflect-metadata is not used. + @type(String).as email : string + + // Or, you can tell Type-R to infer type from the default value. + @value('').as email2 : string + + // Type cannot be inferred from null default values, and needs to be specified explicitly + @type(String).value(null).as email3 : string + + // You can attach ⤹ metadata to fine-tune attribute's behavior + @type(Date).toJSON(false).as + lastLogin : Date// ⟵ not serializable +} + +const user = new User(); +console.log(user.createdAt); // ⟵ this is an instance of Date created for you. + +const users : Collection = new User.Collection(); // ⟵ Collections are defined automatically. +users.on('changes', () => updateUI(users)); // ⟵ listen to the changes. + +users.set(json, { parse : true }); // ⟵ parse raw JSON from the server. +users.updateEach( user => user.firstName = '' ); // ⟵ bulk update triggering 'changes' once +``` + +## Definition + +Record definition is ES6 class extending `Record` preceeded by `@define` class decorator. + +Unlike in the majority of the JS state management framework, Record is not the key-value hash. Record has typed attributes with metadata controlling different aspects of attribute beavior. Therefore, developer needs to create the Record subclass to describe the data structure of specific shape, in a similar way as it's done in statically typed languages. The combination of an attribute type and metadata is called *metatype* and can be reused across record definitions. + +The minimal record definition looks like this: + +```javascript +@define class MyRecord extends Record { + static attributes = { + name : '' + } +} +``` + +```typescript +@define class MyRecord extends Record { + @auto name : string +} +``` + +### `static` attributes = { name : `attrDef`, ... } + +Record's attributes definition. Lists attribute names along with their types, default values, and metadata controlling different aspects of attribute behavior. + +```javascript +@define class User extends Record { + static attributes = { + name : type( String ).value( 'John Dow' ), + email : 'john.dow@mail.com', // Same as type( String ).value( 'john.dow@mail.com' ) + address : String, // Same as type( String ).value( '' ) + } +} +``` + +```typescript +// You should not use `static attributes` in TypeScript. Use decorators instead. +@define class User extends Record { + // Complete form of an attribute definition. + @type( String ).value( 'John Dow' ).as name : string, + + // Attribute type is inferred from the default value. + @value( 'john.dow@mail.com' ).as email : string , // Same as @type( String ).value( 'john.dow@mail.com' ).as + + // Attribute type is inferred from the TypeScript type declaration. + @auto address : string, // Same as @type( String ).value( '' ) + + // Same as above, but with a custom default value. + @auto( 'john.dow@mail.com' ) email2 : string // Same as @value( 'john.dow@mail.com' ).as +} + +``` + +The Record guarantee that _every attribute will retain the value of the declared type_. Whenever an attribute is being assigned with the value which is not compatible with its declared type, the type is being converted with an invocation of the constructor: `new Type( value )` (primitive types are treated specially). + +### `static` idAttribute = 'attrName' + +A record's unique identifier is stored under the pre-defined `id` attribute. +If you're directly communicating with a backend (CouchDB, MongoDB) that uses a different unique key, you may set a Record's `idAttribute` to transparently map from that key to id. + +Record's `id` property will still be linked to Record's id, no matter which value `idAttribute` has. + +```javascript +@define class Meal extends Record { + static idAttribute = "_id"; + static attributes = { + _id : Number, + name : '' + } +} + +const cake = new Meal({ _id: 1, name: "Cake" }); +alert("Cake id: " + cake.id); +``` +```typescript +@define class Meal extends Record { + static idAttribute = "_id"; + + @auto _id : number + @auto name : string +} + +const cake = new Meal({ _id: 1, name: "Cake" }); +alert("Cake id: " + cake.id); +``` + +### `attrDef` : Constructor + +Constructor function is the simplest form of attribute definition. Any constructor function which behaves as _converting constructor_ (like `new Date( msecs )`) may be used as an attribute type. + +```javascript +@define class Person extends Record { + static attributes = { + name : String, // String attribute which is "" by default. + createdAt : Date, // Date attribute + ... + } +} +``` + +```typescript +// In typescript, @auto decorator will extract constructor function from the TypeScript type +@define class Person extends Record { + @auto name : string // String attribute which is "" by default. + @auto createdAt : Date // Date attribute + + // Or, it can be specified explicitly with @type decorator. + @type( Date ).as updatedAt : Date // Date attribute + ... +} +``` + +### `attrDef` : defaultValue + +Any non-function value used as attribute definition is treated as an attribute's default value. Attribute's type is being inferred from the value. + +Type cannot be properly inferred from the `null` values and functions. +Use the general form of attribute definition in such cases: `value( theFunction )`, `type( Boolean ).value( null )`. + +```javascript +@define class GridColumn extends Record { + static attributes = { + name : '', // String attribute which is '' by default. + render : value( x => x ), // Infer Function type from the default value. + ... + } +} +``` + +```typescript +// In typescript, @value decorator will extract constructor function from the default value. +@define class GridColumn extends Record { + @value( '' ).as name : string // String attribute which is '' by default. + @value( x => x ).as render : Function + ... +} +``` + +### `attrDef` : type(Constructor).value(defaultValue) + +Declare an attribute with type T having the custom `defaultValue`. + +```javascript +@define class Person extends Record { + static attributes = { + phone : type( String ).value( null ) // String attribute which is null by default. + ... + } +} +``` + +```typescript +@define class Person extends Record { + @type( String ).value( null ).as phone : string // String attribute which is null by default. + + // There's an easy way of doing that in TypeScript. + @auto( null ).as phone : string + ... +} +``` + +If record needs to reference itself in its attributes definition, `@predefine` decorator with subsequent `MyRecord.define()` needs to be used. + +### `attrDef` : Date + +Date attribute initialized as `new Date()`, and represented in JSON as UTC ISO string. + +There are other popular Date serialization options available in `type-r/ext-types` package. + +* `MicrosoftDate` - Date serialized as Microsoft's `"/Date(msecs)/"` string. +* `Timestamp` - Date serializaed as UNIX integer timestamp (`date.getTime()`). + +```typescript +@define class Person extends Record { + @auto justDate : Date + // MicrosoftDate is an attribute metatype, not a real type, so you must pass it explictly. + @type( Timestamp ).as createdAt : Date + ... +} +``` + +### `static` Collection + +The default record's collection class automatically defined for every Record subclass. Can be referenced as `Record.Collection`. + +May be explicitly assigned in record's definition with custom collection class. + +```javascript +// Declare the collection class. +@define class Comments extends Record.Collection {} + +@define class Comment extends Record{ + static Collection = Comments; // Make it the default Comment collection. + + static attributes = { + text : String, + replies : Comments + } +} +``` + +```typescript +// Declare the collection class. +@define class Comments extends Collection {} + +@define class Comment extends Record{ + static Collection = Comments; // Make it the default Comment collection. + + @auto text : String + @auto replies : Comments +} +``` + +### `attrDef` type(Type) + +Attribute definition can have different metadata attached which affects various aspects of attribute's behavior. Metadata is attached with +a chain of calls after the `type( Ctor )` call. Attribute's default value is the most common example of such a metadata and is the single option which can be applied to the constructor function directly. + +```javascript +import { define, type, Record } + +@define class Dummy extends Record { + static attributes = { + a : type( String ).value( "a" ) + } +} +``` + +```typescript +import { define, type, Record } + +@define class Dummy extends Record { + @type( String ).value( "a" ).as a : string +} +``` + +## Definitions in TypeScript + +Type-R supports several options to define record attributes. + +### `decorator` @auto + +Turns TypeScript class property definition to the record's attribute, automatically extracting attribute type from the TypeScript type annotation. Requires `reflect-metadata` npm package and `emitDecoratorMetadata` option set to true in the `tsconfig.json`. + +`@auto` may take a single parameter as an attribute default value. No other attribute metadata can be attached. + +```typescript +import { define, auto, Record } from 'type-r' + +@define class User extends Record { + @auto name : string + @auto( "john@verizon.com" ) email : string + @auto( null ) updatedAt : Date +} +``` + +### `decorator` @`attrDef`.as + +Attribute definition creates the TypeScript property decorator when being appended with `.as` suffix. It's an alternative syntax to `@auto`. + +```typescript +import { define, type, Record } from 'type-r' + +@define class User extends Record { + @value( "5" ).as name : string + @type( String ).toJSON( false ).as email : string +} +``` + +## Create and dispose + +Record behaves as regular ES6 class with attributes accessible as properties. + +### new Record() + +Create an instance of the record with default attribute values taken from the attributes definition. + +When no default value is explicitly provided for an attribute, it's initialized as `new Type()` (just `Type()` for primitives). When the default value is provided and it's not compatible with the attribute type, it's converted with `new Type( defaultValue )` call. + +### new Record({ attrName : value, ... }, options?) + +When creating an instance of a record, you can pass in the initial attribute values to override the defaults. + +If `{parse: true}` option is used, `attrs` is assumed to be the JSON. + +If the value of the particular attribute is not compatible with its type, it's converted to the declared type invoking the constructor `new Type( value )` (just `Type( value )` for primitives). + +```javascript +@define class Book extends Record { + static attributes = { + title : '', + author : '' + } +} + +const book = new Book({ + title: "One Thousand and One Nights", + author: "Scheherazade" +}); +``` + +```typescript +@define class Book extends Record { + @auto title : string + @auto author : string +} + +const book = new Book({ + title: "One Thousand and One Nights", + author: "Scheherazade" +}); +``` + +### record.clone() + +Create the deep copy of the aggregation tree, recursively cloning all aggregated records and collections. References to shared members will be copied, but not shared members themselves. + +### `callback` record.initialize(attrs?, options?) + +Called at the end of the `Record` constructor when all attributes are assigned and the record's inner state is properly initialized. Takes the same arguments as +a constructor. + +### record.dispose() + +Recursively dispose the record and its aggregated members. "Dispose" means that elements of the aggregation tree will unsubscribe from all event sources. It's crucial to prevent memory leaks in SPA. + +The whole aggregation tree will be recursively disposed, shared members won't. + +## Read and Update + +### record.cid + +Read-only client-side record's identifier. Generated upon creation of the record and is unique for every record's instance. Cloned records will have different `cid`. + +### record.id + +Predefined record's attribute, the `id` is an arbitrary string (integer id or UUID). `id` is typically generated by the server. It is used in JSON for id-references. + +Records can be retrieved by `id` from collections, and there can be just one instance of the record with the same `id` in the particular collection. + +### record.isNew() + +Has this record been saved to the server yet? If the record does not yet have an `id`, it is considered to be new. + +### record.attrName + +Record's attributes may be directly accessed as `record.name`. + + + +```javascript +@define class Account extends Record { + static attributes = { + name : String, + balance : Number + } +} + +const myAccount = new Account({ name : 'mine' }); +myAccount.balance += 1000000; // That works. Good, eh? +``` + +### record.attrName = value + +Assign the record's attribute. If the value is not compatible with attribute's type from the declaration, it is converted: + +- with `Type( value )` call, for primitive types; +- with `record.attrName.set( value )`, for existing record or collection (updated in place); +- with `new Type( value )` in all other cases. + +Record triggers events on changes: +- `change:attrName` *( record, value )*. +- `change` *( record )*. + +```javascript +@define class Book extends Record { + static attributes = { + title : String, + author : String + price : Number, + publishedAt : Date, + available : Boolean + } +} + +const myBook = new Book({ title : "State management with Type-R" }); +myBook.author = 'Vlad'; // That works. +myBook.price = 'Too much'; // Converted with Number( 'Too much' ), resulting in NaN. +myBook.price = '123'; // = Number( '123' ). +myBook.publishedAt = new Date(); // Type is compatible, no conversion. +myBook.publishedAt = '1678-10-15 12:00'; // new Date( '1678-10-15 12:00' ) +myBook.available = some && weird || condition; // Will always be Boolean. Or null. +``` + +### record.set({ attrName : value, ... }, options? : `options`) + +Bulk assign record's attributes, possibly taking options. + +If the value is not compatible with attribute's type from the declaration, it is converted: + +- with `Type( value )` call, for primitive types. +- with `record.attrName.set( value )`, for existing record or collection (updated in place). +- with `new Type( value )` in all other cases. + +Record triggers events after all changes are applied: + +1. `change:attrName` *( record, val, options )* for any changed attribute. +2. `change` *(record, options)*, if there were changed attributes. + + +### RecordClass.from(attrs, options?) + +Create `RecordClass` from attributes. Similar to direct record creation, but supports additional option for strict data validation. +If `{ strict : true }` option is passed the validation will be performed and an exception will be thrown in case of an error. + +Please note, that Type-R always perform type checks on assignments, convert types, and reject improper updates reporting it as error. It won't, however, execute custom validation +rules on every updates as they are evaluated lazily. `strict` option will invoke custom validators and will throw on every error or warning instead of reporting them and continue. + +```javascript +// Fetch record with a given id. +const book = await Book.from({ id : 5 }).fetch(); + +// Validate the body of an incoming HTTP request. +// Throw an exception if validation fails. +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true }); +``` + +```typescript +// Fetch record with a given id. +const book = await Book.from({ id : 5 }).fetch(); + +// Validate the body of an incoming HTTP request. +// Throw an exception if validation fails. +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true }); +``` + +### record.assignFrom(otherRecord) + +Makes an existing `record` to be the full clone of `otherRecord`, recursively assigning all attributes. +In contracts to `record.clone()`, the record is updated in place. + +```javascript +// Another way of doing the bestSeller.clone() +const book = new Book(); +book.assignFrom(bestSeller); +``` + +### record.transaction(fun) + +Execute the all changes made to the record in `fun` as single transaction triggering the single `change` event. + +All record updates occurs in the scope of transactions. Transaction is the sequence of changes which results in a single `change` event. +Transaction can be opened either manually or implicitly with calling `set()` or assigning an attribute. +Any additional changes made to the record in `change:attr` event handler will be executed in the scope of the original transaction, and won't trigger additional `change` events. + + +```javascript +some.record.transaction( record => { + record.a = 1; // `change:a` event is triggered. + record.b = 2; // `change:b` event is triggered. +}); // `change` event is triggered. +``` + +Manual transactions with attribute assignments are superior to `record.set()` in terms of both performance and flexibility. + +### `attrDef` : type(Type).get(`hook`) + +Attach get hook to the record's attribute. `hook` is the function of signature `( value, attr ) => value` which is used to transform the attribute's value _before it will be read_. Hook is executed in the context of the record. + +### `attrDef` : type(Type).set(`hook`) + +Attach the set hook to the record's attribute. `hook` is the function of signature `( value, attr ) => value` which is used to transform the attribute's value _before it will be assigned_. Hook is executed in the context of the record. + +If set hook will return `undefined`, it will cancel attribute update. + +## Nested records and collections + +Record's attributes can hold other Records and Collections, forming indefinitely nested data structures of arbitrary complexity. +To create nested record or collection you should just mention its constructor function in attribute's definition. + +```javascript +import { Record } from 'type-r' + +@define class User extends Record { + static attributes = { + name : String, + email : String, + isActive : true + } +} + +@define class UsersListState extends Record { + static attributes = { + users : User.Collection + } +} +``` + +All nested records and collections are *aggregated* by default and behave as integral parts of the containing record. Aggregated attributes are _exclusively owned_ by the record, and taken with it together form an _ownership tree_. Many operations are performed recursively on aggregated elements: + +- They are created when the owner record is created. +- They are cloned when the record is cloned. +- They are disposed when the record is disposed. +- They are validated as part of the record. +- They are serialized as nested JSON. + +The nature of aggregation relationship in OO is explained in this [article](https://medium.com/@gaperton/nestedtypes-2-0-meet-an-aggregation-and-the-rest-of-oo-animals-a9fca7c36ecf). + +### `attrDef` : RecordOrCollection + +Aggregated record or collection. Represented as nested object or array in record's JSON. Aggregated members are owned by the record and treated as its _integral part_ (recursively created, cloned, serialized, validated, and disposed). +One object can have single owner. The record with its aggregated attributes forms an _aggregation tree_. + +All changes in aggregated record or collections are detected and cause change events on the containing record. + +### record.getOwner() + +Return the record which is an owner of the current record, or `null` there are no one. + +Due to the nature of _aggregation_, an object may have one and only one owner. + +### record.collection + +Return the collection which aggregates the record, or `null` if there are no one. + +### `attrDef` : shared(RecordOrCollection) + +Non-serializable reference to the record or collection possibly from the different aggregation tree. Initialized with `null`. Is not recursively cloned, serialized, validated, or disposed. + +All changes in shared records or collections are detected and cause change events of the containing record. + + + +```javascript +@define class UsersListState extends Record { + static attributes = { + users : User.Collection, + selected : shared( User ) // Can be assigned with the user from this.users + } +} +``` + +```typescript +@define class UsersListState extends Record { + @type( User.Collection ).as users : Collection, + @shared( User ).as selected : User // Can be assigned with the user from this.users +} +``` + +### `attrDef` : Collection.Refs + +Non-aggregating collection. Collection of references to shared records which itself is _aggregated_ by the record, but _does not aggregate_ its elements. In contrast to the `shared( Collection )`, `Collection.Refs` is an actual constructor and creates an instance of collection which _is the part the parent record_. + +The collection itself is recursively created and cloned. However, its records are not aggregated by the collection thus they are not recursively cloned, validated, serialized, or disposed. + +All changes in the collection and its elements are detected and cause change events of the containing record. + + + +```javascript + @define class MyRecord extends Record { + static attributes = { + notCloned : shared( SomeCollection ), // Reference to the _shared collection_ object. + cloned : SomeCollection.Refs // _Aggregated_ collection of references to the _shared records_. + } + } +``` + +```typescript + @define class MyRecord extends Record { + // Reference to the _shared collection_ object. + @shared( SomeCollection ).as notCloned : Collection + + // _Aggregated_ collection of references to the _shared records_. + @type( SomeCollection.Refs ).as cloned : SomeCollection + } +``` + +### `decorator` @predefine + +Make forward declaration for the record to define its attributes later with `RecordClass.define()`. Used instead of `@define` for recursive record definitions. + +Creates the default `RecordClass.Collection` type which can be referenced in attribute definitions. + +### `static` define({ attributes : { name : `attrDef`, ... }}) + +May be called to define attributes in conjunction with `@predefine` decorator to make recursive record definitions. + +```javascript +@predefine class Comment extends Record{} + +Comment.define({ + attributes : { + text : String, + replies : Comment.Collection + } +}); +``` diff --git a/docs/chapters/releasenotes.md b/docs/chapters/releasenotes.md new file mode 100644 index 00000000..657d6880 --- /dev/null +++ b/docs/chapters/releasenotes.md @@ -0,0 +1,221 @@ +# Release Notes + +## 3.0.0 + +### Breaking changes + +Changed semantic which needs to be refactored: + + | 2.x | 3.x + -|-|- +Typeless attribute | `value(x)` | `type(null).value(x)` +Infer type from the value | `x` (except functions) | `value(x)`, or `x` (except functions) +record.parse() override | `record._parse(json)` | no such a method, remove it +record attributes iteration | `record.forEachAttr(obj, iteratee)` | `record.forEach(iteratee)` +Shared object | `User.shared` | `shared( User )` +one-to-many relationship | `RecordClass.from( ref )` | `memberOf( ref )` +many-to-many relationship | `CollectionClass.from( ref )` | `subsetOf( ref, CollectionClass? )` +construct from object/array | - | `RecordOrCollectionClass.from( json, options? )` + +### New attribute definition notation + +Starting from version 3.X, Type-R does not modify built-in global JS objects. New `type(T)` attribute definition notation is introduced to replace `T.has.` + +There's `type-r/globals` package for compatibility with version 2.x which must be imported once with `import 'type-r/globals'`. +If this package is not used, the code must be refactored according to the rules below. + +| 2.x | 3.x + -|-|- +UNIX Timestamp | `Date.timestamp` | `import { Timestamp } from 'type-r/ext-types'` +Microsoft date | `Date.microsoft` | `import { MicrosoftDate } from 'type-r/ext-types'` +Integer | `Integer` and `Number.integer` | `import { Integer } from 'type-r/ext-types'` +Create metatype from constructor | `Ctor.has` | `type(Ctor)` +Typed attribute with default value | `Ctor.value(default)` | `type(Ctor).value(default)` +Attribute "Required" check | `Ctor.isRequired` | `type(Ctor).required` + +### First-class TypeScript support + +- `Infer` infers TypeScript type from the Type-R attribute metatype. +- `InferAttrs` infers TypeScript type for the Type-R attributes definitions. +- `attributes({ attrDefs })` returns the properly typed TypeScript Record class. + +TypeScript attributes definitions: + +| 2.x | 3.x + -|-|- +Extract Type-R type with Reflect.metadata | `@attr name : T` | `@auto name : T` +Extract Type-R type & specify the default value | not possible | `@auto(default) name : T` +Explicitly specify the type | `@attr(T) name : T` | `@type(T).as name : T` +Infer Type-R type from default value | `@attr(default) name : T` | `@value(default).as name : T` +Specify type and default value | `@attr(T.value(default)) name : T` | `@type(T).value(default).as name : T` + +### Other improvements + +- `Collection` class now proxies ES6 Array methods +- New logger API which easy to override or turn off. +- Improved error messages. +- `Type.from( json, options? )` method to restore object from JSON with a strict type check and validation. + +```typescript +@define class User extends Record { + // There's an HTTP REST enpoint for users. + static endpoint = restfulIO( '/api/users' ); + + @auto name : string + + // Collection of Role records represented as an array of role.id in JSON. + // When the "roles" attribute will be accessed for the first time, + // User will look-up for a 'roles' attribute of the nearest store to resolve ids to actual Users. + @subsetOf( '~roles' ).as roles : Collection +} + +@define class Role extends Record { + static endpoint = restfulIO( '/api/roles' ); + @auto name : string +} + +// Store is the regular Record, nothing special. +@define class UsersDirectory extends Store { + // When this record is fetched, fetch all the attributes instead. + static endpoint = attributesIO(); + + // '~roles' references from all aggregated collections + // will point to here, because this is the nearest store. + @type( User.Collection ).as users : Collection + @type( Role.Collection ).as roles : Collection +} + +const directory = new UsersDirectory(); +await directory.fetch(); + +for( let user of directory.users ){ + assert( user.roles.first().users.first() instanceOf User ); +} +``` + +## 2.1.0 + +This release adds long-awaited HTTP REST endpoint. + +- IO endpoints moved outside of the man sources tree. Creation of the custom endpoints is easier than ever. +- Added HTTP REST endpoint `restfulIO` with relative urls support (https://volicon.github.io/Type-R/#endpoint-restfulio-url-options-). +- Added proxyIO endpoint for creating endpoints from records on the server side (https://volicon.github.io/Type-R/#endpoint-proxyio-recordctor-). + +## 2.0.0 + +This release brings new features which fixes problems with component's inheritance in React bindings and implements long-awaited generic IO implementation based on ES6 promises. + +There shouldn't be breaking changes _unless_ you're using custom logger or React bindings (formerly known as React-MVx, with a name changed to React-R in new release). + +### Generic IO support + +New [IOEndpoint]() concept is introduced, making it easy to create IO abstractions. To enable `Record` and `Collection` IO API, you need to assign IO endpoint in the class definition. + +Endpoint is the class defining CRUD and list operations on JSON data, as well as the methods to subscribe for the data changes. There are two endpoints included with 2.0 release, `memoryIO` which is suitable for mock testing and `localStorageIO` which could be used in demos and prototypes. They can be used as a references as starting points to define your own IO endpoints. + +```javascript +@define class User extends Record { + static endpoint = memoryIO(); + static attributes = { + name : String, + ... + } +} +``` + +There are three Record IO methods (`save()`, `fetch()`, and `destroy()`) and two collection IO method (`fetch()` and `liveUpdates()`) ). All IO methods returns ES6 promises, so you either must have the runtime supporting ES6 or use the ES6 promise polyfill. The promises are modified to be _abortable_ (all of them have `abort()` method). + +```javascript +const user = new User({ name : 'John' }); +user.save().then( () => { + console.log( `new user is added ${ user.id }` ) +}); +``` + +There's the special `attributesIO()` endpoint to fetch all of attributes independently and return the combined promise. This is the recommended way of fetching the data required by SPA page. + +```javascript +@define class PageStore extends Store { + static endpoint = attributesIO(); + static attributes = { + users : User.Collection, + roles : UserRole.Collection, + ... + } +} + +const store = new PageStore(); +store.fetch().then( () =>{ + // render your page +}); +``` + +It's possible to define or override the defined endpoint for the nested model or collection using `type().endpoint()` type-R attribute annotation. + +```javascript +@define class PageStore extends Store { + static endpoint = attributesIO(); + static attributes = { + users : type( User.Collection ).endpoint( restful( '/api/users' ) ), + roles : type( UserRole.Collection ).endpoint( restful( '/api/userroles' ) ), + ... + } +} +``` + + + +### New mixins engine + +Type-R metaprogramming system built on powerful mixins composition with configurable member merge rules. In 2.0 release, mixins engine was rewritten to properly apply merge rules on inheritance. This feature is heavily used in Type-R React's bindings and is crucial to prevent errors when extending the `React.Component` subclasses. + +An example illustrating the principle: + +```javascript +@define +// Define the class with +@mixinRules({ + componentWillMount : mixinRules.classLast, + componentWillUnmount : mixinRules.classFirst +}) +class Component { + componentWillMount(){ + console.log( 1 ); + } + + componentWillUnmount(){ + console.log( 3 ); + } +} + +@define +@mixins({ + componentWillMount(){ + console.log( 2 ); + }, + + componentWillUnmount(){ + console.log( 2 ); + } +}) +class MyBaseComponent extends Component { + componentWillMount(){ + console.log( 3 ); + } + + componentWillUnmount(){ + console.log( 1 ); + } +} +``` + +In this example, all of the methods defined in the mixin, base class, and subclass will be called in the order specified in the `console.log`. + +### Other changes + +- Update pipeline was rewritten to improve record's initialization speed (collection's fetch speed is improved by 30%). +- Fixed bug causing dynamic type checks to be disabled in records constructors. +- New implementation of the `Collection.subsetOf` which both fixes some edge case bugs and is more efficient. +- New logger handling NODE_ENV variable setting. diff --git a/docs/chapters/tools.md b/docs/chapters/tools.md new file mode 100644 index 00000000..43089484 --- /dev/null +++ b/docs/chapters/tools.md @@ -0,0 +1,296 @@ +# Tools + +## Logging + +Type-r doesn't attempt to manage logs. Instead, it treat logs as an event stream and uses the `logger` singleton as a log router. + +By default, the `logger` has the default listener writing events to the console. + +### log( level, topic, msg, props? ) + +Method used to trigger the log event. Same as `logger.trigger( level, topic, msg, props? )`. + +The `level` corresponds to the logging methods of the `console` object: `error`, `warn`, `info`, `log`, `debug`. + +`topic` is the short string used to denote the log source source and functional area. Type-R topics are prefixed with `TR`, and looks like `TR:TypeError`. +If you want to use Type-R + +```javascript +import { log } from 'type-r' + +log( 'error', 'client-api:users', 'No user with the given id', { user } ); +``` + +### logger.off() + +```javascript +import { logger } from 'type-r' + +// Remove all the listeners +logger.off(); + +// Remove specific log level listeners (corresponds to the console methods, like console.log, console.warn, etc) +logger.off( 'warn' ); +``` + +### logger.throwOn( level ) + +Sometimes (for instance, in a test suite) developer would like Type-R to throw exceptions on type errors instead of the console warnings. + +```javascript +import { logger } from 'type-r' + +logger.off().throwOn( 'error' ).throwOn( 'warn' ); +``` + +Or, there might be a need to throw exceptions on error in the specific situation (e.g. throw if the incoming HTTP request is not valid to respond with 500 HTTP code). + +```javascript +import { Logger } from 'type-r' + +async function processRequest( ... ){ + // Create an empty logger + const logger = new Logger(); + + // Tell it to throw exceptions. + logger.throwOn( 'error' ).throwOn( 'warn' ); + + // Override the default logger with option. Constructor will throw on error or warning. + const request = new RequestBody( json, { parse : true, logger }); + ... +} +``` + +### logger.on( level, handler ) + +Type-R log message is the regular event. It's easy to attach custom listeners to integrate third-party log management libraries. + +```javascript +import { logger } from 'type-r' + +logger + .off() + .on( 'error', ( topic, msg, props ) => { + // Log errors with bunyan + } ); +``` + +## Class Definitions + +Type-R mechanic is based on class transformations at the moment of module load. These transformations are controlled by _definitions_ in static class members. + +### `decorator` @definitions({ propName : `rule`, ... }) + +Treat specified static class members as _definitions_. When `@define` decorator is being called, definitions are extracted from static class members and mixins and passed as an argument to the `Class.onDefine( definition )`. + +Class definitions are intended to use in the abstract base classes and they are inherited by subclasses. You don't need to add any new definitions to existing Type-R classes unless you want to extend the library, which you're welcome to do. + +### `rule` mixinRules.value + +Merge rule used to mark class definitions. The same rule is also applied to all mixin members if other rule is not specified. + +```javascript +@define +@definitions({ + urlRoot : mixinRules.value +}) +class X { + static urlRoot = '/api'; + + static onDefine( definition ){ + this.prototype.urlRoot = definition.urlRoot; + } +} +``` + +### `rule` mixinRules.protoValue + +Same as `mixinRules.value`, but the value is being assigned to the class prototype. + +```javascript +@define +@definitions({ + urlRoot : mixinRules.protoValue +}) +class X { + static urlRoot = '/api'; +} + +assert( X.prototype.urlRoot === '/api' ); +``` + +### `rule` mixinRules.merge + +Assume the property to be the key-value hash. Properties with the same name from mixins are merged. + +```javascript +const M = { + attributes : { + b : 1 + } +}; + +@define +@mixins( M ) +@definitions({ + attributes : mixinRules.merge +}) +class X { + static attributes = { + a : 1 + }; + + onDefine( definitions ){ + const { attributes } = definitions; + assert( attributes.a === attributes.b === 1 ); + } +} +``` + +### `decorator` @define + +Extract class definitions, call class definition hooks, and apply mixin merge rules to inherited class members. + +1. Call static `onExtend( BaseClass )` hook. +2. Extract definitions from static class members and all the mixins applied, and pass them to `onDefine( definitions, BaseClass )` hook. +4. Apply _merge rules_ for overriden class methods. + +All Type-R class definitions must be precedeed with the `@define` (or `@predefine`) decorator. + +```javascript +@define +@definitions({ + attributes : mixinRules.merge +}) +class Record { + static onDefine( definitions, BaseClass ){ + definitions.attributes && console.log( JSON.stringify( definitions.attributes ) ); + } +} + +// Will print "{ "a" : 1 }" +@define class A extends Record { + static attributes = { + a : 1 + } +} + +// Will print "{ "b" : 1 }" +@define class B extends Record { + static attributes = { + b : 1 + } +} +``` + +### `decorator` @define( mixin ) + +When called with an argument, `@define` decorator applies the given mixin as if it would be the first mixin applied. +In other aspects, it behaves the same as the `@default` decorator without argument. + +### `static` Class.onExtend( BaseClass ) + +Called from the `@predefine` or as the first action of the `@define`. Takes base class constructor as an argument. + +### `static` Class.onDefine( definition, BaseClass ) + +Called from the `@define` or `Class.define()` method. Takes class definition (see the `@definitions` decorator) as the first argument. + +### `decorator` @predefine + +The sequence of `@predefine` with the following `Class.define()` call is equivalent to `@define` decorator. It should be used in the case if the class definition must reference itself, or multiple definitions contain circular dependency. + +It calls static `onExtend( BaseClass )` function if it's defined. It assumes that the `Class.define( definitions )` method will be called later, and attaches `Class.define` method to the class if it was not defined. + +### `static` Class.define( definitions? ) + +Finalized the class definition started with `@predefine` decorator. Has the same effect as the `@define` decorator excepts it assumes that `Class.onExtend()` static function was called already. + +## Mixins + +### `decorator` @mixins( mixinA, mixinB, ... ) class X ... + +Merge specified mixins to the class definition. Both plain JS object and class constructor may be used as mixin. In the case of the class constructor, missing static members will copied over as well. + +```javascript + import { mixins, Events } from 'type-r' + ... + + @define + @mixins( Events, plainObject, MyClass, ... ) + class X { + ... + } +``` + +### `static` Class.mixins + +Class member holding the state of the class mixins. + + + +## Merge rules + +### `decorator` @mixinRules({ propName : `rule`, ... }) + +The `rule` is the reducer function which is applied when there are several values for the particular class members are defined in different mixins or the class, or if the class member is overriden by the subclass. + + + +### `rule` mixinRules.classFirst + +Assume the property to be the function. Call functions from mixins in sequence: `f1.apply( this, arguments ); f2.apply( this, arguments );...` + +### `rule` mixinRules.classLast +Same as sequence, but functions are called in the reverse sequence. + +```javascript +@define +@mixinRules({ + componentWillMount : mixinRules.classLast +}) +class Component { + componentWillMount(){ + console.log( 1 ); + } +} + +const M = { + componentWillMount(){ + console.log( 2 ); + } +} + +@define +@mixins( M ) +class X extends Component { + componentWillMount(){ + console.log( 3 ); + } +} + +const x = new X(); +x.componentWillMount(); +// Will print 1, 2, 3 + +``` + +### `rule` mixinRules.pipe + +Assume the property to be the function with a signature `( x : T ) => T`. Join functions from mixins in a pipe: `f1( f2( f3( x ) ) )`. + +### `rule` mixinRules.defaults + +Assume the property to be the function returning object. Merge objects returned by functions from mixins, executing them in sequence. + +### `rule` mixinRules.every + +Assume property to be the function returning boolean. Return `true` if all functions from mixins return truthy values. + +### `rule` mixinRules.some + +Same as `every`, but return true when at least one function from mixins returns true. diff --git a/docs/chapters/validation.md b/docs/chapters/validation.md new file mode 100644 index 00000000..f159643d --- /dev/null +++ b/docs/chapters/validation.md @@ -0,0 +1,112 @@ +# Type Safety and Validation + +Type-R records and collections are _dynamically type safe_. It's guaranteed that Type-R data structures will always conform to the declared shape. +Records and collections convert values to the declared types on assignment, and reject an update (logging an error in a console) if it cannot be done. + +In addition to that, Type-R supports validation API allowing developer to attach custom validation rules to attributes, records, and collections. Type-R validation mechanics based on following principles: + +- Validation happens transparently on the first access to the validation error. There's no special API to trigger the validation. +- Validation is performed recursively on the aggregation tree formed by nested records and collections. If an element at the bottom of the tree is not valid, the whole object tree is not valid. +- Validation results are cached across the aggregation tree, thus consequent validation error reads are cheap. Only changed parts of aggregation tree will be revalidated when necessary. + +## Attribute-level checks + +### `attrDef` : type( Type ).check( predicate, errorMsg? ) + +Attribute-level validator. + +- `predicate : value => boolean` is the function taking attribute's value and returning `true` whenever the value is valid. +- optional `errorMsg` is the error message which will be passed in case if the validation fail. + +If `errorMsg` is omitted, error message will be taken from `predicate.error`. It makes possible to define reusable validation functions. + +```javascript +function isAge( years ){ + return years >= 0 && years < 200; +} + +isAge.error = "Age must be between 0 and 200"; +``` + +Attribute may have any number of checks attached which are being executed in a sequence. Validation stops when first check in sequence fails. +It can be used to define reusable attribute types as demonstrated below: + +```javascript +// Define new attribute metatypes encapsulating validation checks. +const Age = type( Number ) + .check( x => x == null || x >= 0, 'I guess you are a bit older' ) + .check( x => x == null || x < 200, 'No way man can be that old' ); + +const Word = type( String ).check( x => indexOf( ' ' ) < 0, 'No spaces allowed' ); + +@define class Person extends Record { + static attributes = { + firstName : Word, + lastName : Word, + age : Age + } +} +``` + +### `attrDef` : type( Type ).required + +The special case of attribute-level check cutting out empty values. Attribute value must be truthy to pass, `"Required"` is used as validation error. + +`isRequired` is the first validator to check, no matter in which order validators were attached. + +## Record + +### rec.isValid( attrName ) + +Returns `true` if the specified record's attribute is valid. + +### rec.getValidationError( attrName ) + +Return the validation error for the given attribute or `null` if it's valid. + +## Record and Collection + +Record and Collection share the same validation API. `key` is the attribute name for the record and record's id/cid for the collection. + +### `callback` obj.validate() + +Override this method in subclass to define object-level validation rules. Whatever is returned from `validate()` is treated as validation error. + + + +### obj.isValid() + +Returns `true` if the object is valid. Has same effect as `!object.validationError`. + +### obj.isValid( key ) + +Returns `true` if the specified record's attribute or collection element is valid. `key` is an attribute's name for the record or record's id/cid for the collection. + +### obj.validationError + +`null` if an object is valid, or the the ValidationError object with detailed information on validation results. + +ValidationError object has following shape: + +```javascript +{ + error : /* as returned from collection.validate() */, + + // Members validation errors. + nested : { + // key is an attrName for the record, and record.cid for the collcation + key : validationError, + ... + } +} +``` + +### obj.getValidationError( key ) + +Return the validation error for the given attribute or collection's item. +`key` is an attribute's name for the record or record's id/cid for the collection. + +### obj.eachValidationError( iteratee : ( error, key, obj ) => void ) + +Recursively traverse aggregation tree validation errors. `key` is `null` for the object-level validation error returned by `obj.validate()`. +`obj` is the reference to the current object. \ No newline at end of file diff --git a/docs/images/3-layer-client.png b/docs/images/3-layer-client.png new file mode 100644 index 00000000..390804d4 Binary files /dev/null and b/docs/images/3-layer-client.png differ diff --git a/docs/images/3-layer-server.png b/docs/images/3-layer-server.png new file mode 100644 index 00000000..fd499cd9 Binary files /dev/null and b/docs/images/3-layer-server.png differ diff --git a/docs/images/logo-dark.png b/docs/images/logo-dark.png new file mode 100644 index 00000000..fc24f97b Binary files /dev/null and b/docs/images/logo-dark.png differ diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 00000000..9b965005 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/images/navbar.png b/docs/images/navbar.png new file mode 100644 index 00000000..df38e90d Binary files /dev/null and b/docs/images/navbar.png differ diff --git a/docs/images/overview.png b/docs/images/overview.png new file mode 100644 index 00000000..699beb31 Binary files /dev/null and b/docs/images/overview.png differ diff --git a/docs/images/volicon_verizon_dm.png b/docs/images/volicon_verizon_dm.png new file mode 100644 index 00000000..ac137c43 Binary files /dev/null and b/docs/images/volicon_verizon_dm.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..5016c343 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,2359 @@ + + + + + + + Type-R 3.0 API Reference + + + + + + + + + + + + + + + + + NAV + + + +
+
+ +
+
Type-R 3.0
+
universal state management
+
+ +
+ + +
    +
    +
    + +
    +
    +
    +

    Getting started

    +

    Overview

    +

    Type-R is the TypeScript and JavaScript model framework helping to define and manage the complex application state as a combination of reusable parts. Type-R cover the needs of business logic and data layers in 3-tier application architecture, providing the presentation layer with the unified technique to handle the UI and domain state. Type-R data structures look and feel (and, in some aspects, behaves) more like classes in the statically typed languages.

    +

    Type-R in unopinionated on the way how an application state should be managed ("single source of truth" or "distributed state"). It can support all approaches equally well being not dependent on singletons and having powerful capabilities for state synchronization.

    +

    overview

    +

    A state is defined as a superposition of typed records and collections. A record is a class with a known set of attributes of predefined types possibly holding other records and collections in its attributes, describing the data structure of arbitrary complexity. Record with its attributes forms an aggregation tree with deeply observable attributes changes. Attribute types are checked on assignments and invalid changes are being rejected, therefore it is guaranteed that the application state will preserve the valid shape.

    +

    Application state defined with Type-R is serializable to JSON by default. Aggregation tree of records and collections is mapped in JSON as a tree of plain objects and arrays. Normalized data represented as a set of collections of records cross-referencing each other are supported as first-class serialization scenario.

    +

    A record may have an associated IOEndpont representing the I/O protocol for CRUD and collection fetch operations which enables the persistence API for the particular record/collection class pair. Some useful endpoints (restfulIO, localStorageIO, etc) are provided by type-r/endpoints/* packages, and developers can define their own I/O endpoints implementing any particular persistence transport or API.

    +

    Record attributes may have custom validation rules attached to them. Validation is being triggered transparently on demand and its result is cached across the record/collection aggregation tree, making subsequent calls to the validation API extremely cheap.

    +

    All aspects of record behavior including serialization and validation can be controlled on attribute level with declarative definitions combining attribute types with metadata. Attribute definitions ("metatypes") can be reused across different models forming the domain-specific language of model declarations. Some useful attribute metatypes (Email, Url, MicrosoftDate, etc) are provided by type-r/ext-types package.

    +

    How Type-R compares to X?

    +

    Type-R (former "NestedTypes") project was started in 2014 in Volicon as a modern successor to BackboneJS models, which would match Ember Data in its capabilities to work with a complex state while retaining the BackboneJS simplicity, modularity, and some degree of backward API compatibility. It replaced BackboneJS in the model layer of Volicon products, and it became the key technology in Volicon's strategy to gradually move from BackboneJS Views to React in the view layer.

    +

    Ember Data is the closest thing to Type-R by its capabilities, with BackboneJS models and collections being the closest thing by the API, and mobx being pretty close in the way how the UI state is managed.

    +

    Type-R, however, takes a very different approach to all of them:

    +
      +
    • Type-R models look and feel more like classes in a statically typed language with the majority of features being controlled by attribute metadata.
    • +
    • Type-R is built around the concept of aggregation trees formed by nested records and collections and it knows how to clone, serialize, and validate complex objects with cross-references properly.
    • +
    • In contrast to BackboneJS, Record is not an object hash but the class with statically typed and dynamically checked attributes.
    • +
    • In contrast to mobx, Type-R detects deeply nested changes.
    • +
    • In contrast to Ember Data, Type-R doesn't require the singleton global store. In Type-R, stores are a special kind of records and there might be as many dynamically created and disposed of stores as you need, starting with no stores at all.
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FeatureType-RBackbone ModelsEmber Datamobx
    Observable changes in object graph--
    JSON Serialization-
    Validation-
    Dynamic Type Safety-for serialization only-
    Aggregation---
    Relations by id--
    Generalized I/Osync function-
    +

    Features by example

    +

    Here's the brief overview of features groped by application purpose.

    +

    Persistent domain state

    +

    The basic building block is the Record class. To fetch data from the server, a developer creates the subclass of the Record describing its attribute types and attaches the restfulIO endpoint. It enables the persistence API allowing the developer to fetch the collection from the server. restfulIO expects the server to implement the standard RESTful API expected by BackboneJS models.

    +
      +
    • GET /api/users - fetch all the users
    • +
    • POST /api/users - create the user
    • +
    • GET /api/users/:id - fetch the user with a given id
    • +
    • PUT /api/users/:id - update the user with a given id
    • +
    • DELETE /api/users/:id - delete the user with a given id
    • +
    +

    Record and collection are serializable to and can be parsed from JSON with no additional effort. A mapping to JSON can be customized for collections, records, and individual attributes. The Record validates all updates casting attribute values to declared attribute types to protect the state structure from the protocol incompatibilities and improper assignments.

    +
    @define User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +    static attributes = {
    +        name : String,
    +        email : String,
    +        createdAt : Date
    +    }
    +}
    +
    +const users = new User.Collection();
    +await users.fetch();
    +
    +expect( users.first().createdAt ).toBeInstanceOf( Date );
    +expect( typeof users.toJSON()[ 0 ].createdAt ).toBe( "string" );
    +
    +
    @define User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +
    +    // Type-R can infer attribute types from TypeScript type annotations.
    +    @auto name : string
    +    @auto email : string
    +    @auto createdAt : Date
    +}
    +
    +const users : Collection<User> = new User.Collection();
    +await users.fetch();
    +
    +expect( users.first().createdAt ).toBeInstanceOf( Date );
    +expect( typeof users.toJSON()[ 0 ].createdAt ).toBe( "string" );
    +
    +

    UI state and observable changes

    +

    Type-R provides the universal technique to working with the UI and domain state. To define the UI state, a developer creates the subclass of the Record with attributes holding all the necessary state data possibly along with the persistent data which can become the part of the same local UI state. The UI state itself can be a part of some particular view or UI component, it can be managed as a singleton ("single source of truth"), or both at the same time. Type-R is unopinionated on the application state structure leaving this decision to the developer.

    +

    Records and collections form an aggregation tree with deeply observable changes, so it's enough to subscribe to the single change event from the UIState to get updates on both data arrival and local changes of the state attributes. Records and collections can be indefinitely nested to describe a state of arbitrary complexity. The developer can attach reactions on changes to the records, their individual attributes, and collections. Additional changes made in reactions will be executed in the scope of the same "change transaction" and won't trigger additional change events.

    +
    @define UIState extends Record {
    +    static attributes = {
    +        users : User.Collection,
    +        selectedUser : from( 'users' )
    +    }
    +}
    +
    +const uiState = new UIState();
    +
    +uiState.on( 'change', () => {
    +    console.log( 'Something is changed' );
    +    updateUI();
    +});
    +
    +uiState.users.fetch();
    +
    +
    @define UIState extends Record {
    +    // For collections and more complex types attribute type must be provided explicitly
    +    @type( User.Collection ).as users : Collection<User>
    +
    +    @from( 'users' ).as selectedUser : User
    +}
    +
    +const uiState = new UIState();
    +
    +uiState.on( 'change', () => {
    +    console.log( 'Something is changed' );
    +    updateUI();
    +});
    +
    +uiState.users.fetch();
    +
    +

    Validation

    +

    Type-R supports validation as attribute-level checks attached to attribute definitions as metadata. Attribute type together with checks forms an "attribute metatype", which can be defined separately and reused across multiple record definitions.

    +

    Validation rules are evaluated recursively on the aggregation tree on first access to the validation API, and validations results are cached in records and collections across the tree till the next update. The validation is automatic, subsequent calls to the validation API are cheap, and the developer doesn't need to manually trigger the validation on data changes.

    +

    The majority of checks in a real application will be a part of attribute "metatypes", while the custom validation can be also defined on the Record and Collection level to check data integrity and cross-attributes dependencies.

    +
    const Email = type( String )
    +    .check( x => !x || x.indexOf( '@' ) >= 0, "Doesn't look like an email" );
    +
    +@define User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +    static attributes = {
    +        name : type( String ).required,
    +        email : type( Email ).required,
    +        createdAt : type( Date ).check( x => x.getTime() <= Date.now() )
    +    }
    +}
    +
    +const users = new User.Collection();
    +users.add({ email : 'john' });
    +expect( users.isValid() ).toBe( false );
    +expect( users.first().isValid() ).toBe( false );
    +
    +users.first().name = "John";
    +users.first().email = "john@ny.com";
    +expect( users.isValid() ).toBe( true );
    +
    +
    const Email = type( String )
    +    .check( x => !x || x.indexOf( '@' ) >= 0, "Doesn't look like an email" );
    +
    +@define User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +
    +    // @type(...).as converts Type-R attribute type definition to the TypeScript decorator.
    +    @type( String ).required.as
    +        name : string
    +
    +    @type( Email ).required.as
    +        email : string
    +
    +    @type( Date ).check( x => x.getTime() <= Date.now() ).as
    +        createdAt : Date
    +}
    +
    +const users = new User.Collection();
    +users.add({ email : 'john' });
    +expect( users.isValid() ).toBe( false );
    +expect( users.first().isValid() ).toBe( false );
    +
    +users.first().name = "John";
    +users.first().email = "john@ny.com";
    +expect( users.isValid() ).toBe( true );
    +
    +

    Installation and requirements

    +

    Is packed as UMD and ES6 module. No peer dependencies are required.

    +

    npm install type-r --save-dev

    + + + + +

    ReactJS bindings

    +

    React-MVx is a glue framework which uses Type-R to manage the UI state in React and the NestedLink library to implement two-way data binding. React-MVx provides the complete MVVM solution on top of ReactJS, featuring:

    + +

    Usage with NodeJS

    +

    Type-R can be used at the server side to build the business logic layer by defining the custom I/O endpoints to store data in a database. Type-R dynamic type safety features are particularly advantageous when schema-less JSON databases (like Couchbase) are being used.

    +

    server

    + +

    Record

    +

    Record is an optionally persistent class having the predefined set of attributes. Each attribute is the property of known type which is protected from improper assigments at run-time, is serializable to JSON by default, has deeply observable changes, and may have custom validation rules attached.

    +

    Records may have other records and collections of records stored in its attributes describing an application state of an arbitrary complexity. These nested records and collections are considered to be an integral part of the parent record forming an aggregation tree which can be serialized to JSON, cloned, and disposed of as a whole.

    +

    All aspects of an attribute behavior are controlled with attribute metadata, which (taken together with its type) is called attribite metatype. Metatypes can be declared separately and reused across multiple records definitions.

    +
    import { define, type, Record } from 'type-r'
    +
    +// ⤹ required to make magic work  
    +@define class User extends Record {
    +    // ⤹ attribute's declaration
    +    static attributes = {
    +        firstName : '', // ⟵ String type is inferred from the default value
    +        lastName  : String, // ⟵ Or you can just mention its constructor
    +        email     : type(String).value(null), //⟵ Or you can provide both
    +        createdAt : Date, // ⟵ And it works for any constructor.
    +        // And you can attach ⤹ metadata to fine-tune attribute's behavior
    +        lastLogin : type(Date).value(null).toJSON(false) // ⟵ not serializable
    +    }
    +}
    +
    +const user = new User();
    +console.log( user.createdAt ); // ⟵ this is an instance of Date created for you.
    +
    +const users = new User.Collection(); // ⟵ Collections are defined automatically.
    +users.on( 'changes', () => updateUI( users ) ); // ⟵ listen to the changes.
    +
    +users.set( json, { parse : true } ); // ⟵ parse raw JSON from the server.
    +users.updateEach( user => user.firstName = '' ); // ⟵ bulk update triggering 'changes' once
    +
    +
    import { define, attr, type, Record } from 'type-r'
    +import "reflect-metadata" // Required for @auto without arguments
    +
    +// ⤹ required to make the magic work  
    +@define class User extends Record {
    +    // ⤹ attribute's declaration
    +    // IMPORTANT: attributes will be initialized even if no default value is provided.
    +    @auto lastName  : string // ⟵ @auto decorator extracts type from the Reflect metadata
    +    @auto createdAt : Date // ⟵ It works for any constructor.
    +    @auto('somestring') firstName : string //⟵ The custom default value must be passed to @auto decorator.
    +    @auto(null) updatedAt : Date 
    +
    +    // You have to pass the type explicitly if reflect-metadata is not used.
    +    @type(String).as email : string
    +
    +    // Or, you can tell Type-R to infer type from the default value.
    +    @value('').as email2 : string
    +
    +    // Type cannot be inferred from null default values, and needs to be specified explicitly
    +    @type(String).value(null).as email3 : string 
    +
    +    // You can attach ⤹ metadata to fine-tune attribute's behavior
    +    @type(Date).toJSON(false).as
    +        lastLogin : Date// ⟵ not serializable
    +}
    +
    +const user = new User();
    +console.log(user.createdAt); // ⟵ this is an instance of Date created for you.
    +
    +const users : Collection<User> = new User.Collection(); // ⟵ Collections are defined automatically.
    +users.on('changes', () => updateUI(users)); // ⟵ listen to the changes.
    +
    +users.set(json, { parse : true }); // ⟵ parse raw JSON from the server.
    +users.updateEach( user => user.firstName = '' ); // ⟵ bulk update triggering 'changes' once
    +
    +

    Definition

    +

    Record definition is ES6 class extending Record preceeded by @define class decorator.

    +

    Unlike in the majority of the JS state management framework, Record is not the key-value hash. Record has typed attributes with metadata controlling different aspects of attribute beavior. Therefore, developer needs to create the Record subclass to describe the data structure of specific shape, in a similar way as it's done in statically typed languages. The combination of an attribute type and metadata is called metatype and can be reused across record definitions.

    +

    The minimal record definition looks like this:

    +
    @define class MyRecord extends Record {
    +    static attributes = {
    +        name : ''
    +    }
    +}
    +
    +
    @define class MyRecord extends Record {
    +    @auto name : string
    +}
    +
    +

    static attributes = { name : attrDef, ... }

    +

    Record's attributes definition. Lists attribute names along with their types, default values, and metadata controlling different aspects of attribute behavior.

    +
    @define class User extends Record {
    +    static attributes = {
    +        name    : type( String ).value( 'John Dow' ),
    +        email   : 'john.dow@mail.com', // Same as type( String ).value( 'john.dow@mail.com' )
    +        address : String, // Same as type( String ).value( '' )
    +    }
    +}
    +
    +
    // You should not use `static attributes` in TypeScript. Use decorators instead.
    +@define class User extends Record {
    +    // Complete form of an attribute definition.
    +    @type( String ).value( 'John Dow' ).as name : string,
    +
    +    // Attribute type is inferred from the default value.
    +    @value( 'john.dow@mail.com' ).as email : string , // Same as @type( String ).value( 'john.dow@mail.com' ).as
    +
    +    // Attribute type is inferred from the TypeScript type declaration.
    +    @auto address : string, // Same as @type( String ).value( '' )
    +
    +    // Same as above, but with a custom default value.
    +    @auto( 'john.dow@mail.com' ) email2 : string // Same as @value( 'john.dow@mail.com' ).as
    +}
    +
    +
    +

    The Record guarantee that every attribute will retain the value of the declared type. Whenever an attribute is being assigned with the value which is not compatible with its declared type, the type is being converted with an invocation of the constructor: new Type( value ) (primitive types are treated specially).

    +

    static idAttribute = 'attrName'

    +

    A record's unique identifier is stored under the pre-defined id attribute. +If you're directly communicating with a backend (CouchDB, MongoDB) that uses a different unique key, you may set a Record's idAttribute to transparently map from that key to id.

    +

    Record's id property will still be linked to Record's id, no matter which value idAttribute has.

    +
    @define class Meal extends Record {
    +  static idAttribute =  "_id";
    +  static attributes = {
    +      _id : Number,
    +      name : ''
    +  }
    +}
    +
    +const cake = new Meal({ _id: 1, name: "Cake" });
    +alert("Cake id: " + cake.id);
    +
    +
    @define class Meal extends Record {
    +  static idAttribute =  "_id";
    +
    +  @auto _id : number
    +  @auto name : string
    +}
    +
    +const cake = new Meal({ _id: 1, name: "Cake" });
    +alert("Cake id: " + cake.id);
    +
    +

    attrDef : Constructor

    +

    Constructor function is the simplest form of attribute definition. Any constructor function which behaves as converting constructor (like new Date( msecs )) may be used as an attribute type.

    +
    @define class Person extends Record {
    +    static attributes = {
    +        name : String, // String attribute which is "" by default.
    +        createdAt : Date, // Date attribute
    +        ...
    +    }
    +}
    +
    +
    // In typescript, @auto decorator will extract constructor function from the TypeScript type
    +@define class Person extends Record {
    +    @auto name : string // String attribute which is "" by default.
    +    @auto createdAt : Date // Date attribute
    +
    +    // Or, it can be specified explicitly with @type decorator.
    +    @type( Date ).as updatedAt : Date // Date attribute
    +    ...
    +}
    +
    +

    attrDef : defaultValue

    +

    Any non-function value used as attribute definition is treated as an attribute's default value. Attribute's type is being inferred from the value.

    +

    Type cannot be properly inferred from the null values and functions. +Use the general form of attribute definition in such cases: value( theFunction ), type( Boolean ).value( null ).

    +
    @define class GridColumn extends Record {
    +    static attributes = {
    +        name : '', // String attribute which is '' by default.
    +        render : value( x => x ), // Infer Function type from the default value.
    +        ...
    +    }
    +}
    +
    +
    // In typescript, @value decorator will extract constructor function from the default value.
    +@define class GridColumn extends Record {
    +    @value( '' ).as name : string // String attribute which is '' by default.
    +    @value( x => x ).as render : Function
    +    ...
    +}
    +
    +

    attrDef : type(Constructor).value(defaultValue)

    +

    Declare an attribute with type T having the custom defaultValue.

    +
    @define class Person extends Record {
    +    static attributes = {
    +        phone : type( String ).value( null ) // String attribute which is null by default.
    +        ...
    +    }
    +}
    +
    +
    @define class Person extends Record {
    +    @type( String ).value( null ).as phone : string // String attribute which is null by default.
    +
    +    // There's an easy way of doing that in TypeScript.
    +    @auto( null ).as phone : string
    +    ...
    +}
    +
    +

    If record needs to reference itself in its attributes definition, @predefine decorator with subsequent MyRecord.define() needs to be used.

    +

    attrDef : Date

    +

    Date attribute initialized as new Date(), and represented in JSON as UTC ISO string.

    +

    There are other popular Date serialization options available in type-r/ext-types package.

    +
      +
    • MicrosoftDate - Date serialized as Microsoft's "/Date(msecs)/" string.
    • +
    • Timestamp - Date serializaed as UNIX integer timestamp (date.getTime()).
    • +
    +
    @define class Person extends Record {
    +    @auto justDate : Date
    +    // MicrosoftDate is an attribute metatype, not a real type, so you must pass it explictly.
    +    @type( Timestamp ).as createdAt : Date
    +    ...
    +}
    +
    +

    static Collection

    +

    The default record's collection class automatically defined for every Record subclass. Can be referenced as Record.Collection.

    +

    May be explicitly assigned in record's definition with custom collection class.

    +
    // Declare the collection class.
    +@define class Comments extends Record.Collection {}
    +
    +@define class Comment extends Record{
    +    static Collection = Comments; // Make it the default Comment collection.
    +
    +    static attributes = {
    +        text : String,
    +        replies : Comments
    +    }
    +}
    +
    +
    // Declare the collection class.
    +@define class Comments extends Collection<Comment> {}
    +
    +@define class Comment extends Record{
    +    static Collection = Comments; // Make it the default Comment collection.
    +
    +    @auto text : String
    +    @auto replies : Comments
    +}
    +
    +

    attrDef type(Type)

    +

    Attribute definition can have different metadata attached which affects various aspects of attribute's behavior. Metadata is attached with +a chain of calls after the type( Ctor ) call. Attribute's default value is the most common example of such a metadata and is the single option which can be applied to the constructor function directly.

    +
    import { define, type, Record }
    +
    +@define class Dummy extends Record {
    +    static attributes = {
    +        a : type( String ).value( "a" )
    +    }
    +}
    +
    +
    import { define, type, Record }
    +
    +@define class Dummy extends Record {
    +    @type( String ).value( "a" ).as a : string
    +}
    +
    +

    Definitions in TypeScript

    +

    Type-R supports several options to define record attributes.

    +

    decorator @auto

    +

    Turns TypeScript class property definition to the record's attribute, automatically extracting attribute type from the TypeScript type annotation. Requires reflect-metadata npm package and emitDecoratorMetadata option set to true in the tsconfig.json.

    +

    @auto may take a single parameter as an attribute default value. No other attribute metadata can be attached.

    +
    import { define, auto, Record } from 'type-r'
    +
    +@define class User extends Record {
    +    @auto name : string
    +    @auto( "john@verizon.com" ) email : string
    +    @auto( null ) updatedAt : Date
    +}
    +
    +

    decorator @attrDef.as

    +

    Attribute definition creates the TypeScript property decorator when being appended with .as suffix. It's an alternative syntax to @auto.

    +
    import { define, type, Record } from 'type-r'
    +
    +@define class User extends Record {
    +    @value( "5" ).as name : string
    +    @type( String ).toJSON( false ).as email : string
    +}
    +
    +

    Create and dispose

    +

    Record behaves as regular ES6 class with attributes accessible as properties.

    +

    new Record()

    +

    Create an instance of the record with default attribute values taken from the attributes definition.

    +

    When no default value is explicitly provided for an attribute, it's initialized as new Type() (just Type() for primitives). When the default value is provided and it's not compatible with the attribute type, it's converted with new Type( defaultValue ) call.

    +

    new Record({ attrName : value, ... }, options?)

    +

    When creating an instance of a record, you can pass in the initial attribute values to override the defaults.

    +

    If {parse: true} option is used, attrs is assumed to be the JSON.

    +

    If the value of the particular attribute is not compatible with its type, it's converted to the declared type invoking the constructor new Type( value ) (just Type( value ) for primitives).

    +
    @define class Book extends Record {
    +    static attributes = {
    +        title  : '',
    +        author : ''
    +    }
    +}
    +
    +const book = new Book({
    +  title: "One Thousand and One Nights",
    +  author: "Scheherazade"
    +});
    +
    +
    @define class Book extends Record {
    +    @auto title : string
    +    @auto author : string
    +}
    +
    +const book = new Book({
    +  title: "One Thousand and One Nights",
    +  author: "Scheherazade"
    +});
    +
    +

    record.clone()

    +

    Create the deep copy of the aggregation tree, recursively cloning all aggregated records and collections. References to shared members will be copied, but not shared members themselves.

    +

    callback record.initialize(attrs?, options?)

    +

    Called at the end of the Record constructor when all attributes are assigned and the record's inner state is properly initialized. Takes the same arguments as +a constructor.

    +

    record.dispose()

    +

    Recursively dispose the record and its aggregated members. "Dispose" means that elements of the aggregation tree will unsubscribe from all event sources. It's crucial to prevent memory leaks in SPA.

    +

    The whole aggregation tree will be recursively disposed, shared members won't.

    +

    Read and Update

    +

    record.cid

    +

    Read-only client-side record's identifier. Generated upon creation of the record and is unique for every record's instance. Cloned records will have different cid.

    +

    record.id

    +

    Predefined record's attribute, the id is an arbitrary string (integer id or UUID). id is typically generated by the server. It is used in JSON for id-references.

    +

    Records can be retrieved by id from collections, and there can be just one instance of the record with the same id in the particular collection.

    +

    record.isNew()

    +

    Has this record been saved to the server yet? If the record does not yet have an id, it is considered to be new.

    +

    record.attrName

    +

    Record's attributes may be directly accessed as record.name.

    + + +
    @define class Account extends Record {
    +    static attributes = {
    +        name : String,
    +        balance : Number
    +    }
    +}
    +
    +const myAccount = new Account({ name : 'mine' });
    +myAccount.balance += 1000000; // That works. Good, eh?
    +
    +

    record.attrName = value

    +

    Assign the record's attribute. If the value is not compatible with attribute's type from the declaration, it is converted:

    +
      +
    • with Type( value ) call, for primitive types;
    • +
    • with record.attrName.set( value ), for existing record or collection (updated in place);
    • +
    • with new Type( value ) in all other cases.
    • +
    +

    Record triggers events on changes:

    +
      +
    • change:attrName ( record, value ).
    • +
    • change ( record ).
    • +
    +
    @define class Book extends Record {
    +    static attributes = {
    +        title : String,
    +        author : String
    +        price : Number,
    +        publishedAt : Date,
    +        available : Boolean
    +    }
    +}
    +
    +const myBook = new Book({ title : "State management with Type-R" });
    +myBook.author = 'Vlad'; // That works.
    +myBook.price = 'Too much'; // Converted with Number( 'Too much' ), resulting in NaN.
    +myBook.price = '123'; // = Number( '123' ).
    +myBook.publishedAt = new Date(); // Type is compatible, no conversion.
    +myBook.publishedAt = '1678-10-15 12:00'; // new Date( '1678-10-15 12:00' )
    +myBook.available = some && weird || condition; // Will always be Boolean. Or null.
    +
    +

    record.set({ attrName : value, ... }, options? : options)

    +

    Bulk assign record's attributes, possibly taking options.

    +

    If the value is not compatible with attribute's type from the declaration, it is converted:

    +
      +
    • with Type( value ) call, for primitive types.
    • +
    • with record.attrName.set( value ), for existing record or collection (updated in place).
    • +
    • with new Type( value ) in all other cases.
    • +
    +

    Record triggers events after all changes are applied:

    +
      +
    1. change:attrName ( record, val, options ) for any changed attribute.
    2. +
    3. change (record, options), if there were changed attributes.
    4. +
    +

    RecordClass.from(attrs, options?)

    +

    Create RecordClass from attributes. Similar to direct record creation, but supports additional option for strict data validation. +If { strict : true } option is passed the validation will be performed and an exception will be thrown in case of an error.

    +

    Please note, that Type-R always perform type checks on assignments, convert types, and reject improper updates reporting it as error. It won't, however, execute custom validation +rules on every updates as they are evaluated lazily. strict option will invoke custom validators and will throw on every error or warning instead of reporting them and continue.

    +
    // Fetch record with a given id.
    +const book = await Book.from({ id : 5 }).fetch();
    +
    +// Validate the body of an incoming HTTP request.
    +// Throw an exception if validation fails.
    +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true });
    +
    +
    // Fetch record with a given id.
    +const book = await Book.from({ id : 5 }).fetch();
    +
    +// Validate the body of an incoming HTTP request.
    +// Throw an exception if validation fails.
    +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true });
    +
    +

    record.assignFrom(otherRecord)

    +

    Makes an existing record to be the full clone of otherRecord, recursively assigning all attributes. +In contracts to record.clone(), the record is updated in place.

    +
    // Another way of doing the bestSeller.clone()
    +const book = new Book();
    +book.assignFrom(bestSeller);
    +
    +

    record.transaction(fun)

    +

    Execute the all changes made to the record in fun as single transaction triggering the single change event.

    +

    All record updates occurs in the scope of transactions. Transaction is the sequence of changes which results in a single change event. +Transaction can be opened either manually or implicitly with calling set() or assigning an attribute. +Any additional changes made to the record in change:attr event handler will be executed in the scope of the original transaction, and won't trigger additional change events.

    +
    some.record.transaction( record => {
    +    record.a = 1; // `change:a` event is triggered.
    +    record.b = 2; // `change:b` event is triggered.
    +}); // `change` event is triggered.
    +
    +

    Manual transactions with attribute assignments are superior to record.set() in terms of both performance and flexibility.

    +

    attrDef : type(Type).get(hook)

    +

    Attach get hook to the record's attribute. hook is the function of signature ( value, attr ) => value which is used to transform the attribute's value before it will be read. Hook is executed in the context of the record.

    +

    attrDef : type(Type).set(hook)

    +

    Attach the set hook to the record's attribute. hook is the function of signature ( value, attr ) => value which is used to transform the attribute's value before it will be assigned. Hook is executed in the context of the record.

    +

    If set hook will return undefined, it will cancel attribute update.

    +

    Nested records and collections

    +

    Record's attributes can hold other Records and Collections, forming indefinitely nested data structures of arbitrary complexity. +To create nested record or collection you should just mention its constructor function in attribute's definition.

    +
    import { Record } from 'type-r'
    +
    +@define class User extends Record {
    +    static attributes = {
    +        name : String,
    +        email : String,
    +        isActive : true
    +    }
    +}
    +
    +@define class UsersListState extends Record {
    +    static attributes = {
    +        users : User.Collection
    +    }
    +}
    +
    +

    All nested records and collections are aggregated by default and behave as integral parts of the containing record. Aggregated attributes are exclusively owned by the record, and taken with it together form an ownership tree. Many operations are performed recursively on aggregated elements:

    +
      +
    • They are created when the owner record is created.
    • +
    • They are cloned when the record is cloned.
    • +
    • They are disposed when the record is disposed.
    • +
    • They are validated as part of the record.
    • +
    • They are serialized as nested JSON.
    • +
    +

    The nature of aggregation relationship in OO is explained in this article.

    +

    attrDef : RecordOrCollection

    +

    Aggregated record or collection. Represented as nested object or array in record's JSON. Aggregated members are owned by the record and treated as its integral part (recursively created, cloned, serialized, validated, and disposed). +One object can have single owner. The record with its aggregated attributes forms an aggregation tree.

    +

    All changes in aggregated record or collections are detected and cause change events on the containing record.

    +

    record.getOwner()

    +

    Return the record which is an owner of the current record, or null there are no one.

    +

    Due to the nature of aggregation, an object may have one and only one owner.

    +

    record.collection

    +

    Return the collection which aggregates the record, or null if there are no one.

    +

    attrDef : shared(RecordOrCollection)

    +

    Non-serializable reference to the record or collection possibly from the different aggregation tree. Initialized with null. Is not recursively cloned, serialized, validated, or disposed.

    +

    All changes in shared records or collections are detected and cause change events of the containing record.

    + + +
    @define class UsersListState extends Record {
    +    static attributes = {
    +        users : User.Collection,
    +        selected : shared( User ) // Can be assigned with the user from this.users
    +    }
    +}
    +
    +
    @define class UsersListState extends Record {
    +    @type( User.Collection ).as users : Collection<User>,
    +    @shared( User ).as selected : User // Can be assigned with the user from this.users
    +}
    +
    +

    attrDef : Collection.Refs

    +

    Non-aggregating collection. Collection of references to shared records which itself is aggregated by the record, but does not aggregate its elements. In contrast to the shared( Collection ), Collection.Refs is an actual constructor and creates an instance of collection which is the part the parent record.

    +

    The collection itself is recursively created and cloned. However, its records are not aggregated by the collection thus they are not recursively cloned, validated, serialized, or disposed.

    +

    All changes in the collection and its elements are detected and cause change events of the containing record.

    + + +
        @define class MyRecord extends Record {
    +        static attributes = {
    +            notCloned : shared( SomeCollection ), // Reference to the _shared collection_ object.
    +            cloned : SomeCollection.Refs // _Aggregated_ collection of references to the _shared records_.
    +        }
    +    }
    +
    +
        @define class MyRecord extends Record {
    +        // Reference to the _shared collection_ object.
    +        @shared( SomeCollection ).as notCloned : Collection<Some>
    +
    +        // _Aggregated_ collection of references to the _shared records_.
    +        @type( SomeCollection.Refs ).as cloned : SomeCollection
    +    }
    +
    +

    decorator @predefine

    +

    Make forward declaration for the record to define its attributes later with RecordClass.define(). Used instead of @define for recursive record definitions.

    +

    Creates the default RecordClass.Collection type which can be referenced in attribute definitions.

    +

    static define({ attributes : { name : attrDef, ... }})

    +

    May be called to define attributes in conjunction with @predefine decorator to make recursive record definitions.

    +
    @predefine class Comment extends Record{}
    +
    +Comment.define({
    +    attributes : {
    +        text : String,
    +        replies : Comment.Collection
    +    }
    +});
    +
    +

    Collection

    +

    Collections are ordered sets of records. The collection is an array-like object exposing ES6 Array and BackboneJS Collection interface. It encapsulates JS Array of records (collection.models) and a hashmap for a fast O(1) access by the record id and cid (collection.get( id )).

    +

    Collactions are deeply observable. You can bind "changes" events to be notified when the collection has been modified, listen for the record "add", "remove", and "change" events.

    +

    Every Record class has an implicitly defined Collection accessible as a static member of a record's constructor. In a most cases, you don't need to define the custom collection class.

    +
    @define class Book extends Record {
    +    static attributes = {
    +        title : String
    +        author : Author
    +    }
    +}
    +
    +// Implicitly defined collection.
    +const books = new Book.Collection();
    +
    +
    @define class Book extends Record {
    +    @auto title : string
    +    @auto author : Author
    +
    +    // Tell TypeScript the proper type.
    +    static Collection : CollectionConstructor<Book>
    +}
    +
    +const books = new Book.Collection();
    +
    +

    You can define custom collection classes extending Record.Collection or any other collection class. It can either replace the default Collection type, or

    +
    // Define custom collection class.
    +@define class Library extends Record.Collection {
    +    doSomething(){ ... }
    +}
    +
    +@define class Book extends Record {
    +    // Override the default collection.
    +    static Collection = Library;
    +}
    +
    +// Define another custom collection class.
    +@define class OtherLibrary extends Record.Collection {
    +    // Specify the record so the collection will be able to restore itself from JSON.
    +    static model = Book; 
    +}
    +
    +
    // Define custom collection class.
    +@define class Library extends Collection<Book> {
    +    doSomething(){ ... }
    +}
    +
    +@define class Book extends Record {
    +    // Override the default collection.
    +    static Collection = Library;
    +}
    +
    +// Define another custom collection class.
    +@define class OtherLibrary extends Collection<Book> {
    +    // Specify the record so the collection will be able to restore itself from JSON.
    +    static model = Book;
    +}
    +
    +// An alternative way of overriding the default collection class in TypeScript.
    +namespace Book {
    +    @define class Collection extends Collection<Book> {
    +        static model = Book;
    +    }
    +}
    +
    + + +

    Collection types

    +

    constructor CollectionClass( records?, options? )

    +

    The most common collection type is an aggregating serializable collection. By default, collection aggregates its elements which are treated as an integral part of the collection (serialized, cloned, disposed, and validated recursively). An aggregation means the single owner, as the single object cannot be an integral part of two distinct things. The collection will take ownership on its records and will put an error in the console if it can't.

    +

    When creating a Collection, you may choose to pass in the initial array of records.

    +
    @define class Role extends Record {
    +    static attributes = {
    +        name : String
    +    }
    +}
    +
    +const roles = new Role.Collection( json, { parse : true } );
    +
    +
    @define class Role extends Record {
    +    // In typescript, you have to specify record's Collection type expicitly.
    +    static Collection : CollectionConstructor<Role>
    +
    +    @auto name : string
    +}
    +
    +@define class User extends Record {
    +    @auto name : string
    +
    +    // Type-R cannot infer a Collection metatype from the TypeScript type automatically.
    +    // Full attribute type annotation is required.
    +    @type( Role.Collection ).as roles : Collection<User>
    +}
    +
    +

    constructor CollectionClass.Refs( records?, options? )

    +

    Collection of record references is a non-aggregating non-serializable collection. Collection.Refs doesn't aggregate its elements, which means that containing records are not considered as an integral part of the enclosing collection and not being validated, cloned, disposed, and serialized recursively.

    +

    It is useful for a local non-persistent application state.

    +

    attrDef subsetOf(masterRef, CollectionClass?)

    +

    The subset of other collections are non-aggregating serializable collection. Subset-of collection is serialized as an array of record ids and used to model many-to-many relationships. The collection object itself is recursively created and cloned, however, its records are not aggregated by the collection thus they are not recursively cloned, validated, or disposed. CollectionClass argument may be omitted unless you need the record's attribute to be an instance of the particular collection class.

    + + + + +

    Must have a reference to the master collection which is used to resolve record ids to records. masterRef may be:

    +
      +
    • direct reference to a singleton collection.
    • +
    • function, returning the reference to the collection.
    • +
    • symbolic dot-separated path to the master collection resolved relative to the record's this. You may use owner and store macro in path:
        +
      • owner is the reference to the record's owner. owner.some.path works as () => this.getOwner().some.path.
      • +
      • store is the reference to the closes store. store.some.path works as () => this.getStore().some.path.
      • +
      +
    • +
    +
    @define class Role extends Record {
    +    static attributes = {
    +        name : String,
    +        ...
    +    }
    +}
    +
    +@define class User extends Record {
    +    static attributes = {
    +        name : String,
    +        roles : subsetOf( 'owner.roles', Role.Collection )
    +    }
    +}
    +
    +@define class UsersDirectory extends Store {
    +    static attributes = {
    +        roles : Role.Collection,
    +        users : User.Collection // `~roles` references will be resolved against this.roles
    +    }
    +}
    +
    +
    @define class Role extends Record {
    +    static Collection : CollectionConstructor<Role>
    +
    +    @auto name : string
    +    ...
    +}
    +
    +@define class User extends Record {
    +    static Collection : CollectionConstructor<User>
    +
    +    @auto name : string
    +    @subsetOf('store.roles').as roles : Collection<Role>
    +}
    +
    +@define class UsersDirectory extends Store {
    +    @type(Role.Collection).as roles : Collection<Role>,
    +    @type(User.Collection).as users : Collection<User> // <- `store.roles` references will be resolved against this.roles
    +}
    +
    +

    Array API

    +

    A collection class is an array-like object implementing ES6 Array methods and properties.

    +

    collection.length

    +

    Like an array, a Collection maintains a length property, counting the number of records it contains.

    +

    collection.slice( begin, end )

    +

    Return a shallow copy of the collection.models, using the same options as native Array#slice.

    +

    collection.indexOf( recordOrId : any ) : number

    +

    Return an index of the record in the collection, and -1 if there is no such a record in the collection.

    +

    Can take the record itself as an argument, id, or cid of the record.

    +

    collection.forEach( iteratee : ( val : Record, index ) => void, context? )

    +

    Iterate through the elements of the collection.

    + + +

    collection.map( iteratee : ( val : Record, index ) => T, context? )

    +

    Map elements of the collection. Similar to Array.map.

    +

    collection.filter( iteratee : Predicate, context? )

    +

    Return the filtered array of records matching the predicate.

    +

    The predicate is either the iteratee function returning boolean, or an object with attribute values used to match with record's attributes.

    +

    collection.every( iteratee : Predicate, context? ) : boolean

    +

    Return true if all records match the predicate.

    +

    collection.some( iteratee : Predicate, context? ) : boolean

    +

    Return true if at least one record matches the predicated.

    +

    collection.push( record, options? )

    +

    Add a record at the end of a collection. Takes the same options as add().

    +

    collection.pop( options? )

    +

    Remove and return the last record from a collection. Takes the same options as remove().

    +

    collection.unshift( record, options? )

    +

    Add a record at the beginning of a collection. Takes the same options as add().

    +

    collection.shift( options? )

    +

    Remove and return the first record from a collection. Takes the same options as remove().

    +

    Backbone API

    +

    Common options used by Backbone API methods:

    +
      +
    • { sort : false } - do not sort the collection.
    • +
    • { parse : true } - parse raw JSON (used to set collection with data from the server).
    • +
    +

    callback collection.initialize( records?, options? )

    +

    Initialization function which is called at the end of the constructor.

    +

    collection.clone()

    +

    Clone the collection. An aggregating collection will be recursively cloned, non-aggregated collections will be shallow cloned.

    +

    collection.models

    +

    Raw access to the JavaScript array of records inside of the collection. Usually, you'll want to use get, at, or the other methods to access record objects, but occasionally a direct reference to the array is desired.

    +

    collection.get( id )

    +

    Get a record from a collection, specified by an id, a cid, or by passing in a record.

    +
    const book = library.get(110);
    +
    +

    collection.at( index )

    +

    Get a record from a collection, specified by index. Useful if your collection is sorted, and if your collection isn't sorted, at will still retrieve records in insertion order. When passed a negative index, it will retrieve the record from the back of the collection.

    +

    collection.add( records, options? )

    +

    Add a record (or an array of records) to the collection. If this is the Record.Collection, you may also pass raw attributes objects, and have them be vivified as instances of the Record. Returns the added (or preexisting, if duplicate) records.

    +

    Pass {at: index} to splice the record into the collection at the specified index. If you're adding records to the collection that are already in the collection, they'll be ignored, unless you pass {merge: true}, in which case their attributes will be merged into the corresponding records.

    +
      +
    1. Trigger the one event per record:
        +
      • add(record, collection, options) for each record added.
      • +
      • change(record, options) for each record changed (if the {merge: true} option is passed).
      • +
      +
    2. +
    3. Trigger the single event:
        +
      • update(collection, options) if any records were added.
      • +
      • sort(collection, options) if an order of records was changed.
      • +
      +
    4. +
    5. Trigger changes event in case if any changes were made to the collection and objects inside.
    6. +
    +

    collection.remove( records, options? )

    +

    Remove a record (or an array of records) from the collection, and return them. Each record can be a record instance, an id string or a JS object, any value acceptable as the id argument of collection.get.

    +
      +
    1. Trigger remove(record, collection, options) for each record removed.
    2. +
    3. If any records were removed, trigger:
        +
      • update(collection, options)
      • +
      • changes(collection, options).
      • +
      +
    4. +
    +

    collection.set( records, options? )

    +

    The set method performs a "smart" update of the collection with the passed list of records. If a record in the list isn't yet in the collection it will be added; if the record is already in the collection its attributes will be merged; and if the collection contains any records that aren't present in the list, they'll be removed. All of the appropriate "add", "remove", and "change" events are fired as this happens. Returns the touched records in the collection. If you'd like to customize the behavior, you can disable it with options: {remove: false}, or {merge: false}.

    +

    Events

    +
      +
    1. Trigger the one event per record:
        +
      • add(record, collection, options) for each record added.
      • +
      • remove(record, collection, options) for each record removed.
      • +
      • change(record, options) for each record changed.
      • +
      +
    2. +
    3. Trigger the single event:
        +
      • update(collection, options) if any records were added.
      • +
      • sort(collection, options) if an order of records was changed.
      • +
      +
    4. +
    5. Trigger changes event in case if any changes were made to the collection and objects inside.
    6. +
    +
    const vanHalen = new Man.Collection([ eddie, alex, stone, roth ]);
    +
    +vanHalen.set([ eddie, alex, stone, hagar ]);
    +
    +// Fires a "remove" event for roth, and an "add" event for hagar.
    +// Updates any of stone, alex, and eddie's attributes that may have
    +// changed over the years.
    +
    +

    collection.reset(records, options?)

    +

    Replace the collection's content with the new records. More efficient than collection.set, but does not send record-level events.

    +

    Calling collection.reset() without passing any records as arguments will empty the entire collection.

    +
      +
    1. Trigger event reset(collection, options).
    2. +
    3. Trigger event changes(collection, options).
    4. +
    +

    collection.pluck(attribute)

    +

    Pluck an attribute from each model in the collection. Equivalent to calling map and returning a single attribute from the iterator.

    +
    const users = new UserCollection([
    +  {name: "Curly"},
    +  {name: "Larry"},
    +  {name: "Moe"}
    +]);
    +
    +const names = users.pluck("name");
    +
    +alert(JSON.stringify(names));
    +
    +

    Sorting

    +

    Type-R implements BackboneJS Collection sorting API with some extensions.

    +

    collection.sort(options?)

    +

    Force a collection to re-sort itself. You don't need to call this under normal circumstances, as a collection with a comparator will sort itself whenever a record is added. To disable sorting when adding a record, pass {sort: false} to add. Calling sort triggers a "sort" event on the collection.

    +

    By default, there is no comparator for a collection. If you define a comparator, it will be used to maintain the collection in sorted order. This means that as records are added, they are inserted at the correct index in collection.models.

    +

    Note that Type-R depends on the arity of your comparator function to determine between the two styles, so be careful if your comparator function is bound.

    +

    Collections with a comparator will not automatically re-sort if you later change record attributes, so you may wish to call sort after changing record attributes that would affect the order.

    +

    static comparator = 'attrName'

    +

    Maintain the collection in sorted order by the given record's attribute.

    +

    static comparator = x => number | string

    +

    Maintain the collection in sorted order according to the "sortBy" comparator function.

    +

    "sortBy" comparator functions take a record and return a numeric or string value by which the record should be ordered relative to others.

    +

    static comparator = (x, y) => -1 | 0 | 1

    +

    Maintain the collection in sorted order according to the "sort" comparator function.

    +

    "sort" comparator functions take two records and return -1 if the first record should come before the second, 0 if they are of the same rank and 1 if the first record should come after.

    +

    Note how even though all of the chapters in this example are added backward, they come out in the proper order:

    +
    @define class Chapter extends Record {
    +    static attributes = {
    +        page : Number,
    +        title : String
    +    }
    +}
    +
    +var chapters = new Chapter.Collection();
    +
    +chapters.comparator = 'page';
    +
    +chapters.add({page: 9, title: "The End"});
    +chapters.add({page: 5, title: "The Middle"});
    +chapters.add({page: 1, title: "The Beginning"});
    +
    +alert(chapters.map( x => x.title ));
    +
    +

    Other methods

    +

    CollectionClass.from( models, options? )

    +

    Create CollectionClass from the array of models. Similar to direct collection creation, but supports additional option for strict data validation. +If { strict : true } option is passed the validation will be performed and an exception will be thrown in case of an error.

    +

    Please note, that Type-R always performs type checks on assignments, convert types, and reject improper updates reporting it as an error. It won't, however, execute custom validation +rules on every update as they are evaluated lazily. strict option will invoke custom validators and will throw on every error or warning instead of reporting them and continue.

    +
    // Validate the body of an incoming HTTP request.
    +// Throw an exception if validation fails.
    +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true });
    +
    +
    // Validate the body of an incoming HTTP request.
    +// Throw an exception if validation fails.
    +const body = MyRequestBody.from( ctx.request.body, { parse : true, strict : true });
    +
    +

    collection.createSubset( records?, options? )

    +

    Create the collection which is a subset of a source collection serializable as an array of record ids. Takes the same arguments as the collection's constructor.

    +

    The created collection is an instance of subsetOf( sourceCollection, CollectionCtor ) attribute type (non-aggregating serializable collection).

    + + +

    collection.assignFrom( otherCollection )

    +

    Synchronize the state of the collection and its aggregation tree with other collection of the same type. Updates existing objects in place. Record in the collection is considered to be "existing" if it has the same id.

    +

    Equivalent to collection.set( otherCollection.models, { merge : true } ) and triggers similar events on change.

    +

    collection.dispose()

    +

    Dispose of the collection. An aggregating collection will recursively dispose of its records.

    +

    Observable Changes

    +

    Overview

    +

    Type-R implements deeply observable changes on the object graph constructed of records and collection.

    +

    All of the record and collection updates happens in a scope of the transaction followed by the change event. Every record or collection update operation opens implicit transaction. Several update operations can be groped to the single explicit transaction if executed in the scope of the obj.transaction() or col.updateEach() call.

    +
    @define class Author extends Record {
    +    static attributes = {
    +        name : ''
    +    }
    +}
    +
    +@define class Book extends Record {
    +    static attributes = {
    +        name : '',
    +        datePublished : Date,
    +        author : Author
    +    }
    +}
    +
    +const book = new Book();
    +book.on( 'change', () => console.log( 'Book is changed') );
    +
    +// Implicit transaction, prints to the console
    +book.author.name = 'John Smith';
    +
    +

    Record

    +

    Events mixin methods (7)

    +

    Record implements Events mixin.

    +

    event "change" ( record )

    +

    Triggered by the record at the end of the attributes update transaction in case if there were any changes applied.

    +

    event "change:attrName" ( record, value )

    +

    Triggered by the record during the attributes update transaction for every changed attribute.

    +

    attrDef : type( Type ).watcher( watcher )

    +

    Attach change:attr event listener to the particular record's attribute. watcher can either be the record's method name or the function ( newValue, attr ) => void. Watcher is always executed in the context of the record.

    +
    @define class User extends Record {
    +    static attributes = {
    +        name : type( String ).watcher( 'onNameChange' ),
    +        isAdmin : Boolean,
    +    }
    +
    +    onNameChange(){
    +        // Cruel. But we need it for the purpose of the example.
    +        this.isAdmin = this.name.indexOf( 'Admin' ) >= 0;
    +    }
    +}
    +
    +

    attrDef : type( Type ).changeEvents( false )

    +

    Turn off changes observation for nested records or collections.

    +

    Record automatically listens to change events of all nested records and collections, triggering appropriate change events for its attributes. This declaration turns it off for the specific attribute.

    +

    attrDef : type( Type ).events({ eventName : handler, ... })

    +

    Automatically manage custom event subscription for the attribute. handler is either the method name or the handler function.

    +

    record.changed

    +

    The changed property is the internal hash containing all the attributes that have changed during its last transaction. +Please do not update changed directly since its state is internally maintained by set(). +A copy of changed can be acquired from changedAttributes().

    +

    record.changedAttributes( attrs? )

    +

    Retrieve a hash of only the record's attributes that have changed during the last transaction, +or false if there are none. Optionally, an external attributes hash can be passed in, +returning the attributes in that hash which differ from the record. +This can be used to figure out which portions of a view should be updated, +or what calls need to be made to sync the changes to the server.

    +

    record.previous( attr )

    +

    During a "change" event, this method can be used to get the previous value of a changed attribute.

    +
    @define class Person extends Record{
    +    static attributes = {
    +        name: ''
    +    }
    +}
    +
    +const bill = new Person({
    +  name: "Bill Smith"
    +});
    +
    +bill.on("change:name", ( record, name ) => {
    +  alert( `Changed name from ${ bill.previous('name') } to ${ name }`);
    +});
    +
    +bill.name = "Bill Jones";
    +
    +

    record.previousAttributes()

    +

    Return a copy of the record's previous attributes. Useful for getting a diff between versions of a record, or getting back to a valid state after an error occurs.

    +

    Collection

    +

    All changes in the records cause change events in the collections they are contained in.

    +

    Subset collections is an exception; they don't observe changes of its elements by default.

    +

    Events mixin methods (7)

    +

    Collection implements Events mixin.

    +

    collection.transaction( fun )

    +

    Execute the sequence of updates in fun function in the scope of the transaction.

    +

    All collection updates occurs in the scope of transactions. Transaction is the sequence of changes which results in a single changes event.

    +

    Transaction can be opened either manually or implicitly with calling any of collection update methods. +Any additional changes made to the collection or its items in event handlers will be executed in the scope of the original transaction, and won't trigger an additional changes events.

    +

    collection.updateEach( iteratee : ( val : Record, index ) => void, context? )

    +

    Similar to the collection.each, but wraps an iteration in a transaction. The single changes event will be emitted for the group of changes to the records made in updateEach.

    +

    static itemEvents = { eventName : handler, ... }

    +

    Subscribe for events from records. The hander is either the collection's method name, the handler function, or true.

    +

    When true is passed as a handler, the corresponding event will be triggered on the collection.

    +

    event "changes" (collection, options)

    +

    When collection has changed. Single event triggered when the collection has been changed.

    +

    event "reset" (collection, options)

    +

    When the collection's entire contents have been reset (reset() method was called).

    +

    event "update" (collection, options)

    +

    Single event triggered after any number of records have been added or removed from a collection.

    +

    event "sort" (collection, options)

    +

    When the collection has been re-sorted.

    +

    event "add" (record, collection, options)

    +

    When a record is added to a collection.

    +

    event "remove" (record, collection, options)

    +

    When a record is removed from a collection.

    +

    event "change" (record, options)

    +

    When a record inside of the collection is changed.

    +

    Events mixin

    +

    Type-R uses an efficient synchronous events implementation which is backward compatible with Backbone 1.1 Events API but is about twice faster in all major browsers. It comes in form of Events mixin and the Messenger base class.

    +

    Events is a mixin giving the object the ability to bind and trigger custom named events. Events do not have to be declared before they are bound, and may take passed arguments.

    +

    Both source and listener mentioned in method signatures must implement Events methods.

    +
    import { mixins, Events } from 'type-r'
    +
    +@mixins( Events )
    +class EventfulClass {
    +    ...
    +}
    +
    + + +

    source.trigger(event, arg1, arg2, ... )

    +

    Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks.

    +

    listener.listenTo(source, event, callback)

    +

    Tell an object to listen to a particular event on an other object. The advantage of using this form, instead of other.on(event, callback, object), is that listenTo allows the object to keep track of the events, and they can be removed all at once later on. The callback will always be called with object as context.

    +
        view.listenTo(record, 'change', view.render );
    +
    + + +

    listener.stopListening([source], [event], [callback])

    +

    Tell an object to stop listening to events. Either call stopListening with no arguments to have the object remove all of its registered callbacks ... or be more precise by telling it to remove just the events it's listening to on a specific object, or a specific event, or just a specific callback.

    +
        view.stopListening(); // Unsubscribe from all events
    +
    +    view.stopListening(record); // Unsubscribe from all events from the record
    +
    + + +

    listener.listenToOnce(source, event, callback)

    +

    Just like listenTo(), but causes the bound callback to fire only once before being automatically removed.

    +

    source.on(event, callback, [context])

    +

    Bind a callback function to an object. The callback will be invoked whenever the event is fired. If you have a large number of different events on a page, the convention is to use colons to namespace them: poll:start, or change:selection. The event string may also be a space-delimited list of several events...

    +
        book.on("change:title change:author", ...);
    +
    +

    Callbacks bound to the special "all" event will be triggered when any event occurs, and are passed the name of the event as the first argument. For example, to proxy all events from one object to another:

    +
        proxy.on("all", function(eventName) {
    +        object.trigger(eventName);
    +    });
    +
    +

    All event methods also support an event map syntax, as an alternative to positional arguments:

    +
        book.on({
    +        "change:author": authorPane.update,
    +        "change:title change:subtitle": titleView.update,
    +        "destroy": bookView.remove
    +    });
    +
    +

    To supply a context value for this when the callback is invoked, pass the optional last argument: record.on('change', this.render, this) or record.on({change: this.render}, this).

    + + +

    source.off([event], [callback], [context])

    +

    Remove a previously bound callback function from an object. If no context is specified, all of the versions of the callback with different contexts will be removed. If no callback is specified, all callbacks for the event will be removed. If no event is specified, callbacks for all events will be removed.

    +
        // Removes just the `onChange` callback.
    +    object.off("change", onChange);
    +
    +    // Removes all "change" callbacks.
    +    object.off("change");
    +
    +    // Removes the `onChange` callback for all events.
    +    object.off(null, onChange);
    +
    +    // Removes all callbacks for `context` for all events.
    +    object.off(null, null, context);
    +
    +    // Removes all callbacks on `object`.
    +    object.off();
    +
    +

    Note that calling record.off(), for example, will indeed remove all events on the record — including events that Backbone uses for internal bookkeeping.

    +

    source.once(event, callback, [context])

    +

    Just like on(), but causes the bound callback to fire only once before being removed. Handy for saying "the next time that X happens, do this". When multiple events are passed in using the space separated syntax, the event will fire once for every event you passed in, not once for a combination of all events

    +

    Built-in events

    +

    All Type-R objects implement Events mixin and use events to notify listeners on changes.

    +

    Record and Store change events:

    + + + + + + + + + + + + + + + + + + + + +
    Event nameHandler argumentsWhen triggered
    change(record, options)At the end of any changes.
    change:attrName(record, value, options)The record's attribute has been changed.
    +

    Collection change events:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Event nameHandler argumentsWhen triggered
    changes(collection, options)At the end of any changes.
    reset(collection, options)reset() method was called.
    update(collection, options)Any records added or removed.
    sort(collection, options)Order of records is changed.
    add(record, collection, options)The record is added to a collection.
    remove(record, collection, options)The record is removed from a collection.
    change(record, options)The record is changed inside of collection.
    +

    Messenger class

    +

    Messenger is an abstract base class implementing Events mixin and some convenience methods.

    +
    import { define, Messenger } from 'type-r'
    +
    +class MyMessenger extends Messenger {
    +
    +}
    +
    +

    Events mixin methods (7)

    +

    Messenger implements Events mixin.

    +

    messenger.cid

    +

    Unique run-time only messenger instance id (string).

    +

    callback messenger.initialize()

    +

    Callback which is called at the end of the constructor.

    +

    messenger.dispose()

    +

    Executes messenger.stopListening() and messenger.off().

    +

    Objects must be disposed to prevent memory leaks caused by subscribing for events from singletons.

    +

    Type Safety and Validation

    +

    Type-R records and collections are dynamically type safe. It's guaranteed that Type-R data structures will always conform to the declared shape. +Records and collections convert values to the declared types on assignment, and reject an update (logging an error in a console) if it cannot be done.

    +

    In addition to that, Type-R supports validation API allowing developer to attach custom validation rules to attributes, records, and collections. Type-R validation mechanics based on following principles:

    +
      +
    • Validation happens transparently on the first access to the validation error. There's no special API to trigger the validation.
    • +
    • Validation is performed recursively on the aggregation tree formed by nested records and collections. If an element at the bottom of the tree is not valid, the whole object tree is not valid.
    • +
    • Validation results are cached across the aggregation tree, thus consequent validation error reads are cheap. Only changed parts of aggregation tree will be revalidated when necessary.
    • +
    +

    Attribute-level checks

    +

    attrDef : type( Type ).check( predicate, errorMsg? )

    +

    Attribute-level validator.

    +
      +
    • predicate : value => boolean is the function taking attribute's value and returning true whenever the value is valid.
    • +
    • optional errorMsg is the error message which will be passed in case if the validation fail.
    • +
    +

    If errorMsg is omitted, error message will be taken from predicate.error. It makes possible to define reusable validation functions.

    +
    function isAge( years ){
    +    return years >= 0 && years < 200;
    +}
    +
    +isAge.error = "Age must be between 0 and 200";
    +
    +

    Attribute may have any number of checks attached which are being executed in a sequence. Validation stops when first check in sequence fails. +It can be used to define reusable attribute types as demonstrated below:

    +
    // Define new attribute metatypes encapsulating validation checks.
    +const Age = type( Number )
    +                .check( x => x == null || x >= 0, 'I guess you are a bit older' )
    +                .check( x => x == null || x < 200, 'No way man can be that old' );
    +
    +const Word = type( String ).check( x => indexOf( ' ' ) < 0, 'No spaces allowed' );
    +
    +@define class Person extends Record {
    +    static attributes = {
    +        firstName : Word,
    +        lastName : Word,
    +        age : Age
    +    }
    +}
    +
    +

    attrDef : type( Type ).required

    +

    The special case of attribute-level check cutting out empty values. Attribute value must be truthy to pass, "Required" is used as validation error.

    +

    isRequired is the first validator to check, no matter in which order validators were attached.

    +

    Record

    +

    rec.isValid( attrName )

    +

    Returns true if the specified record's attribute is valid.

    +

    rec.getValidationError( attrName )

    +

    Return the validation error for the given attribute or null if it's valid.

    +

    Record and Collection

    +

    Record and Collection share the same validation API. key is the attribute name for the record and record's id/cid for the collection.

    +

    callback obj.validate()

    +

    Override this method in subclass to define object-level validation rules. Whatever is returned from validate() is treated as validation error.

    + + +

    obj.isValid()

    +

    Returns true if the object is valid. Has same effect as !object.validationError.

    +

    obj.isValid( key )

    +

    Returns true if the specified record's attribute or collection element is valid. key is an attribute's name for the record or record's id/cid for the collection.

    +

    obj.validationError

    +

    null if an object is valid, or the the ValidationError object with detailed information on validation results.

    +

    ValidationError object has following shape:

    +
    {
    +    error : /* as returned from collection.validate() */,
    +
    +    // Members validation errors.
    +    nested : {
    +        // key is an attrName for the record, and record.cid for the collcation
    +        key : validationError,
    +        ...
    +    }
    +}
    +
    +

    obj.getValidationError( key )

    +

    Return the validation error for the given attribute or collection's item. +key is an attribute's name for the record or record's id/cid for the collection.

    +

    obj.eachValidationError( iteratee : ( error, key, obj ) => void )

    +

    Recursively traverse aggregation tree validation errors. key is null for the object-level validation error returned by obj.validate(). +obj is the reference to the current object.

    +

    I/O and Serialization

    +

    Overview

    +

    Type-R implements generalized IO on top of the IOEndpoint interface, with JSON serialization handled by Record and Collection classes.

    +

    IOEndpoint defines the set of CRUD + list methods operating on raw JSON. +Attachment of an endpoint to the record or collection enables I/O API. There are few endpoints bundled with Type-R, for instance memoryIO() which can be used for mock testing.

    +
    @define class User extends Record {
    +    static endpoint = memoryIO();
    +
    +    static attributes = {
    +        name : '',
    +        email : ''
    +    }
    +}
    +
    +const users = new User.Collection();
    +users
    +    .add({ name : 'John' })
    +    .save()
    +    .then( () => console.log( user.id );
    +
    +

    I/O API

    +

    static endpoint

    +

    I/O endpoint declaration which should be used in Record or Collection definition to enable I/O API.

    +

    If an endpoint is defined for the MyRecord, it's automatically defined for the corresponding MyRecord.Collection as well.

    +

    attrDef : type( Type ).endpoint( endpoint )

    +

    Override or define an I/O endpoint for the specific record's attribute.

    +

    obj.getEndpoint()

    +

    Returns an object's IO endpoint. Normally, this is an endpoint which is defined in object's static endpoint = ... declaration, but it might be overridden by the parent's record using type( Type ).endpoint( ... ) attribute declaration.

    +
    @define class User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +    ...
    +}
    +
    +@define class UserRole extends Record {
    +    static endpoint = restfulIO( '/api/roles' );
    +    static attributes = {
    +        // Use the relative path '/api/roles/:id/users'
    +        users : type( User.Collection ).endpoint( restfulIO( './users' ) ),
    +        ...
    +    }
    +}
    +
    +

    record.fetch( options? )

    +

    Asynchronously fetch the record using endpoint.read() method. Returns an abortable ES6 promise.

    +

    An endpoint must be defined for the record in order to use that method.

    +

    record.save( options? )

    +

    Asynchronously save the record using endpoint.create() (if there are no id) or endpoint.update() (if id is present) method. Returns an abortable ES6 promise.

    +

    An endpoint must be defined for the record in order to use that method.

    +

    record.destroy( options? )

    +

    Asynchronously destroy the record using endpoint.destroy() method. Returns an abortable ES6 promise. The record is removed from the aggregating collection upon the completion of the I/O request.

    +

    An endpoint must be defined for the record in order to use that method.

    +

    collection.fetch( options? )

    +

    Fetch the collection. Returns an abortable promise.

    +

    options accepts an optional liveUpdates parameter. When true, collection subscribes for the live updates when I/O is finished.

    +

    collection.liveUpdates( true | false )

    +

    Subscribe for the live data updates if an I/O endpoint supports it (subscribe()/unsubscribe() IOEndpoint methods).

    + + +

    obj.hasPendingIO()

    +

    Returns an abortable promise if there's any I/O pending with the object, or null otherwise.

    +

    Can be used to check for active I/O in progress or to abort pending I/O operation. Please note, that all pending I/O is aborted automatically when new I/O operation is started or an object is disposed. When I/O is aborted, the promise is rejected.

    +
    const promise = users.hasPendingIO();
    +if( promise && promise.abort ) promise.abort();
    +
    +

    I/O endpoints

    +

    restfulIO( url, options? )

    +

    HTTP REST client endpoint. Requires window.fetch available natively or through the polyfill. Implements standard BackboneJS REST semantic.

    +

    All I/O methods append an optional options.params object to the URL parameters translating them to string with JSON.stringify().

    +
      +
    • record.save() makes:
        +
      • POST url, if the model has no id. Expects to receive { id : recordId }.
      • +
      • PUT url/:id, if the model has an id.
      • +
      +
    • +
    • collection.fetch() makes GET url.
    • +
    • record.destroy() makes DELETE url.
    • +
    +

    Supports URI relative to owner (./relative/url resolves as /owner/:id/relative/url/:id ).

    +
    import { restfulIO } from 'type-r/endpoints/restful'
    +
    +@define class Role extends Record {
    +    static endpoint = restfulIO( '/api/roles' );
    +    ...
    +}
    +
    +@define class User extends Record {
    +    static endpoint = restfulIO( '/api/users' );
    +
    +    static attributes = {
    +        // Roles collection here has relative url /api/users/:user_id/roles/
    +        roles : type( Role.Collection ).endpoint( restfulIO( './roles' ) ), 
    +        ...
    +    }
    +}
    +
    +

    memoryIO( mockData?, delay? )

    +

    Endpoint for mock testing. Takes optional array with mock data, and optional delay parameter which is the simulated I/O delay in milliseconds.

    +
    import { memoryIO } from 'type-r/endpoints/memory'
    +
    +@define class User extends Record {
    +    static endpoint = memoryIO();
    +    ...
    +}
    +
    +

    localStorageIO( key )

    +

    Endpoint for localStorage. Takes key parameter which must be unique for the persistent record's collection.

    +
    import { localStorageIO } from 'type-r/endpoints/localStorage'
    +
    +@define class User extends Record {
    +    static endpoint = localStorageIO( '/users' );
    +    ...
    +}
    +
    +

    attributesIO()

    +

    Endpoint for I/O composition. Redirects record's fetch() request to its attributes and returns the combined abortable promise. Does not enable any other I/O methods and can be used with record.fetch() only.

    +

    It's common pattern to use attributesIO endpoint in conjunction with Store to fetch all the data required by SPA page.

    +
    import { localStorageIO } from 'type-r/endpoints/attributes'
    +
    +@define class PageStore extends Store {
    +    static endpoint = attributesIO();
    +    static attributes = {
    +        users : User.Collection,
    +        roles : UserRole.Collection,
    +    }
    +}
    +...
    +const store = new PageStore();
    +store.fetch().then( () => renderUI() );
    +
    +

    proxyIO( RecordCtor )

    +

    Create IO endpoint from the Record class. This endpoint is designed for use on the server side with a data layer managed by Type-R.

    +

    Assuming that you have Type-R records with endpoints working with the database, you can create an endpoint which will use +an existing Record subclass as a transport. This endpoint can be connected to the RESTful endpoint API on the server side which will serve JSON to the restfulIO endpoint on the client.

    +

    An advantage of this approach is that JSON schema will be transparently validated on the server side by the Type-R.

    +
        import { proxyIO } from 'type-r/endpoint/proxy'
    +
    +    ...
    +
    +    const usersIO = proxyIO( User );
    +
    +

    IOEndpoint Interface

    +

    An IO endpoint is an "plug-in" abstraction representing the persistent collection of JSON objects, which is required to enable records and collections I/O API. There are several pre-defined endpoints included in Type-R package which can be used for HTTP REST I/O, mock testing, working with localStorage, and IO composition.

    +

    You will need to define custom endpoint if you would like to implement or customize serialization transport for Type-R objects. Use built-in endpoints as an example and the starting boilerplate.

    +

    All IOEndpoint methods might return standard Promises or abortable promises (created with createIOPromise()). An IOEndpoint instance is shared by all of the class instances it's attached to and therefore it's normally must be stateless.

    +

    endpoint.read( id, options, record )

    +

    Reads an object with a given id. Used by record.fetch() method. Must return JSON wrapped in abortable promise.

    +

    endpoint.update( id, json, options, record )

    +

    Updates or creates an object with a given id. Used by record.save() method when record already has an id. Must return abortable promise.

    +

    endpoint.create( json, options, record )

    +

    Creates an object. Used by record.save() method when record does not have an id. Must return abortable promise.

    +

    endpoint.destroy( id, options, record )

    +

    Destroys the object with the given id. Used by record.destroy() method. Must return abortable promise.

    +

    endpoint.list( options, collection )

    +

    Fetch an array of objects. Used by collection.fetch() method. Must returns abortable promise.

    +

    endpoint.subscribe( callbacks, collection )

    +

    Optional method to enable the live updates subscription. Used by collection.liveUpdates( true ) method. Must returns abortable promise.

    +

    Method callbacks argument is an object of the following shape:

    +
    {
    +    // Endpoint must call it when an object is created or updated.
    +    updated( json ){}
    +
    +    // Endpoint must call it when an object is removed.
    +    removed( json ){}
    +}
    +
    +

    endpoint.unsubscribe( callbacks, collection )

    +

    Unsubscribe from the live updates. Used by collection.liveUpdates( false ) method. Takes the same callbacks object as subscribe().

    +

    createIOPromise( init )

    +

    Service function to create an abortable version of ES6 promise (with promise.abort() which meant to stop pending I/O and reject the promise).

    +

    init function takes the third onAbort argument to register an optional abort handler. If no handler is registered, the default implementation of promise.abort() will just reject the promise.

    +
    import { createIOPromise } from 'type-r'
    +
    +const abortablePromise = createIOPromise( ( resolve, reject, onAbort ) =>{
    +    ...
    +    onAbort( () => {
    +        reject( 'I/O Aborted' );
    +    });
    +});
    +
    +

    Serialization

    +

    Record and Collection has a portion of common API related to the I/O and serialization.

    +

    obj.toJSON( options? )

    +

    Serialize record or collection to JSON. Used internally by I/O methods. Can be overridden to customize serialization.

    +

    Produces the JSON for the given record or collection and its aggregated members. Aggregation tree is serialized as nested JSON. Record corresponds to an object in JSON, while the collection is represented as an array of objects.

    +

    If you override toJSON(), it usually means that you must override parse() as well, and vice versa.

    + + +
    @define class Comment extends Record {
    +    static attributes = {
    +        body : ''
    +    }
    +}
    +
    +@define class BlogPost extends Record {
    +    static attributes = {
    +        title : '',
    +        body : '',
    +        comments : Comment.Collection
    +    }
    +}
    +
    +const post = new BlogPost({
    +    title: "Type-R is cool!",
    +    comments : [ { body : "Agree" }]
    +});
    +
    +const rawJSON = post.toJSON()
    +// { title : "Type-R is cool!", body : "", comments : [{ body : "Agree" }] }
    +
    +

    option { parse : true }

    +

    obj.set() and constructor's option to force parsing of the raw JSON. Is used internally by I/O methods to parse the data received from the server.

    +
    // Another way of doing the bestSeller.clone()
    +// Amazingly, this is guaranteed to work by default.
    +const book = new Book();
    +book.set( bestSeller.toJSON(), { parse : true } );
    +
    +

    callback obj.parse( json )

    +

    Optional hook called to transform the JSON when it's passes to the record or collection with set( json, { parse : true }) call. Used internally by I/O methods.

    +

    If you override toJSON(), it usually means that you must override parse() as well, and vice versa.

    + + +

    attrDef : type( Type ).toJSON( false )

    +

    Do not serialize the specific attribute.

    +

    attrDef : type( Type ).toJSON( ( value, name, options ) => json )

    +

    Override the default serialization for the specific record's attribute.

    +

    Attribute is not serialized when the function return undefined.

    +

    attrDef : type( Type ).parse( ( json, name ) => value )

    +

    Transform the data before it will be assigned to the record's attribute.

    +

    Invoked when the { parse : true } option is set.

    +
    // Define custom boolean attribute type which is serialized as 0 or 1.
    +const MyWeirdBool = type( Boolean )
    +                      .parse( x => x === 1 )
    +                      .toJSON( x => x ? 1 : 0 );
    +
    +

    static create( attrs, options )

    +

    Static factory function used internally by Type-R to create instances of the record.

    +

    May be redefined in the abstract Record base class to make it serializable type.

    +
    @define class Widget extends Record {
    +    static attributes = {
    +        type : String
    +    }
    +
    +    static create( attrs, options ){
    +        switch( attrs.type ){
    +            case "typeA" : return new TypeA( attrs, options );
    +            case "typeB" : return new TypeB( attrs, options );
    +        }
    +    }
    +}
    +
    +@define class TypeA extends Widget {
    +    static attributes = {
    +        type : "typeA",
    +        ...
    +    }
    +}
    +
    +@define class TypeB extends Widget {
    +    static attributes = {
    +        type : "typeB",
    +        ...
    +    }
    +}
    +
    +

    Normalized data

    +

    Type-R has first-class support for working with normalized data represented as a set of collections with cross-references by record id. References are represented as record ids in JSON, and being transparently resolved to record instances on the first access.

    +

    Store class is the special record class which serves as a placeholder for the set of interlinked collections of normalized records. Id-references are defined as record attributes of the special type representing the serializable reference to the records from the specified master collection.

    +

    attrDef : memberOf( sourceCollection )

    +

    Serializable reference to the record from the particular collection. +Initialized as null and serialized as record.id. Is not recursively cloned, validated, or disposed. Used to model one-to-many relationships.

    +

    Changes in shared record are not detected.

    +

    sourceCollection may be:

    +
      +
    • the JS variable pointing to the collection singleton;
    • +
    • the function returning the collection;
    • +
    • the string with the dot-separated relative object path to the collection. It is resolved dynamically relative to the record's this. Following shortcuts may be used in path:
        +
      • owner.path (or ^path) works as () => this.getOwner().path.
      • +
      • store.path (or ~path) works as () => this.getStore().path.
      • +
      +
    • +
    +
        @define class State extends Record {
    +        static attributes = {
    +            items : Item.Collection,
    +            selected : memberOf( 'items' ) // Will resolve to `this.items`
    +        }
    +    }
    +
    +
        @define class State extends Record {
    +        @type( Item.Collection ).as items : Collection<Item>;
    +        @memberOf( 'items' ).as selected : Item
    +    }
    +
    + + +

    attrDef : subsetOf( sourceCollection, CollectionCtor? )

    +

    Serializable non-aggregating collection which is the subset of the existing collection. Serialized as an array of record ids. Used to model many-to-many relationships. CollectionCtor argument may be omitted unless you need it to be a sublass of the particular collection type.

    +

    The collection object itself is recursively created and cloned. However, its records are not aggregated by the collection thus they are not recursively cloned, validated, or disposed.

    +

    sourceCollection is the same reference as used by memberOf( sourceCollection ).

    +
    @define class Role extends Record {
    +    static attributes = {
    +        name : String,
    +        ...
    +    }
    +}
    +
    +@define class User extends Record {
    +    static attributes = {
    +        name : String,
    +        roles : subsetOf( '~roles', Role.Collection )
    +    }
    +}
    +
    +@define class UsersDirectory extends Store {
    +    static attributes = {
    +        roles : Role.Collection,
    +        users : User.Collection // `~roles` references will be resolved against this.roles
    +    }
    +}
    +
    +

    sourceCollection.createSubset( records?, options? )

    +

    Create an instance of subsetOf( sourceCollection, CollectionCtor ) type (non-aggregating serializable collection) which is the subset of the given collection. Takes the same arguments as the collection's constructor.

    + + +

    class Store

    +

    Store is the special kind of record which serves as a root for id references.

    +

    For all records inside of the store's aggregation tree ~attrName will resolve to the attribute of the store class found with record.getStore() method. If there are no such an attribute in the store, the next available store upper in aggregation tree will be used (as regular records stores can be nested), or the default store if there are no one.

    + + +

    Store is the subclass of the Record. It's defined extending the Store abstract base class. It behaves as a regular record in most aspects.

    +

    store._defaultStore

    +

    Reference to the master store used for lookups if the current store doesn't have the required attribute and there are no other store found upper in the ownership chain.

    +

    Defaults to the Store.global. May be explicitly defined to create custom store lookup chains across the ownership hierarchy.

    +

    static Store.global

    +

    The default singleton store class. Is always the last store to lookup when resolving ~reference.

    +

    Use the default store for the globally shared data only. Each application page must have its local store.

    +
    @define class MyStore extends Store {
    +    static attributes = {
    +        users : User.Collection,
    +        roles : Role.Collection
    +    }
    +}
    +
    +Store.global = new MyStore();
    +
    +// Now the reference '~users` will point to users collection from the MyStore.
    +
    +

    recordOrCollection.getStore()

    +

    Return the closest store. Used internally to resolve symbolic ~reference relative to the store.

    +

    Method looks for the Store subclass traversing the ownership chain of current aggregation tree upwards. If there are no store found this way, default Store from Store.global is returned.

    +

    recordOrCollection.clone({ pinStore : true })

    +

    Make the cloned object to preserve the reference to its original store.

    +

    Cloned objects don't have an owner by default, thus they loose the reference to their store as no ownership chain can be traversed. pinStore option should be used in such a cases.

    +

    Tools

    +

    Logging

    +

    Type-r doesn't attempt to manage logs. Instead, it treat logs as an event stream and uses the logger singleton as a log router.

    +

    By default, the logger has the default listener writing events to the console.

    +

    log( level, topic, msg, props? )

    +

    Method used to trigger the log event. Same as logger.trigger( level, topic, msg, props? ).

    +

    The level corresponds to the logging methods of the console object: error, warn, info, log, debug.

    +

    topic is the short string used to denote the log source source and functional area. Type-R topics are prefixed with TR, and looks like TR:TypeError. +If you want to use Type-R

    +
    import { log } from 'type-r'
    +
    +log( 'error', 'client-api:users', 'No user with the given id', { user } );
    +
    +

    logger.off()

    +
    import { logger } from 'type-r'
    +
    +// Remove all the listeners
    +logger.off();
    +
    +// Remove specific log level listeners (corresponds to the console methods, like console.log, console.warn, etc)
    +logger.off( 'warn' );
    +
    +

    logger.throwOn( level )

    +

    Sometimes (for instance, in a test suite) developer would like Type-R to throw exceptions on type errors instead of the console warnings.

    +
    import { logger } from 'type-r'
    +
    +logger.off().throwOn( 'error' ).throwOn( 'warn' );
    +
    +

    Or, there might be a need to throw exceptions on error in the specific situation (e.g. throw if the incoming HTTP request is not valid to respond with 500 HTTP code).

    +
    import { Logger } from 'type-r'
    +
    +async function processRequest( ... ){
    +    // Create an empty logger
    +    const logger = new Logger();
    +
    +    // Tell it to throw exceptions.
    +    logger.throwOn( 'error' ).throwOn( 'warn' );
    +
    +    // Override the default logger with option. Constructor will throw on error or warning.
    +    const request = new RequestBody( json, { parse : true, logger });
    +    ...
    +}
    +
    +

    logger.on( level, handler )

    +

    Type-R log message is the regular event. It's easy to attach custom listeners to integrate third-party log management libraries.

    +
    import { logger } from 'type-r'
    +
    +logger
    +    .off()
    +    .on( 'error', ( topic, msg, props ) => {
    +        // Log errors with bunyan
    +    } );
    +
    +

    Class Definitions

    +

    Type-R mechanic is based on class transformations at the moment of module load. These transformations are controlled by definitions in static class members.

    +

    decorator @definitions({ propName : rule, ... })

    +

    Treat specified static class members as definitions. When @define decorator is being called, definitions are extracted from static class members and mixins and passed as an argument to the Class.onDefine( definition ).

    +

    Class definitions are intended to use in the abstract base classes and they are inherited by subclasses. You don't need to add any new definitions to existing Type-R classes unless you want to extend the library, which you're welcome to do.

    +

    rule mixinRules.value

    +

    Merge rule used to mark class definitions. The same rule is also applied to all mixin members if other rule is not specified.

    +
    @define
    +@definitions({
    +    urlRoot : mixinRules.value
    +})
    +class X {
    +    static urlRoot = '/api';
    +
    +    static onDefine( definition ){
    +        this.prototype.urlRoot = definition.urlRoot;
    +    }
    +}
    +
    +

    rule mixinRules.protoValue

    +

    Same as mixinRules.value, but the value is being assigned to the class prototype.

    +
    @define
    +@definitions({
    +    urlRoot : mixinRules.protoValue
    +})
    +class X {
    +    static urlRoot = '/api';
    +}
    +
    +assert( X.prototype.urlRoot === '/api' );
    +
    +

    rule mixinRules.merge

    +

    Assume the property to be the key-value hash. Properties with the same name from mixins are merged.

    +
    const M = {
    +    attributes : {
    +        b : 1
    +    }
    +};
    +
    +@define
    +@mixins( M )
    +@definitions({
    +    attributes : mixinRules.merge
    +})
    +class X {
    +    static attributes = {
    +        a : 1
    +    };
    +
    +    onDefine( definitions ){
    +        const { attributes } = definitions;
    +        assert( attributes.a === attributes.b === 1 );
    +    }
    +}
    +
    +

    decorator @define

    +

    Extract class definitions, call class definition hooks, and apply mixin merge rules to inherited class members.

    +
      +
    1. Call static onExtend( BaseClass ) hook.
    2. +
    3. Extract definitions from static class members and all the mixins applied, and pass them to onDefine( definitions, BaseClass ) hook.
    4. +
    5. Apply merge rules for overriden class methods.
    6. +
    +

    All Type-R class definitions must be precedeed with the @define (or @predefine) decorator.

    +
    @define
    +@definitions({
    +    attributes : mixinRules.merge
    +})
    +class Record {
    +    static onDefine( definitions, BaseClass ){
    +        definitions.attributes && console.log( JSON.stringify( definitions.attributes ) );
    +    }
    +}
    +
    +// Will print "{ "a" : 1 }"
    +@define class A extends Record {
    +    static attributes = {
    +        a : 1
    +    }
    +}
    +
    +// Will print "{ "b" : 1 }"
    +@define class B extends Record {
    +    static attributes = {
    +        b : 1
    +    }
    +}
    +
    +

    decorator @define( mixin )

    +

    When called with an argument, @define decorator applies the given mixin as if it would be the first mixin applied. +In other aspects, it behaves the same as the @default decorator without argument.

    +

    static Class.onExtend( BaseClass )

    +

    Called from the @predefine or as the first action of the @define. Takes base class constructor as an argument.

    +

    static Class.onDefine( definition, BaseClass )

    +

    Called from the @define or Class.define() method. Takes class definition (see the @definitions decorator) as the first argument.

    +

    decorator @predefine

    +

    The sequence of @predefine with the following Class.define() call is equivalent to @define decorator. It should be used in the case if the class definition must reference itself, or multiple definitions contain circular dependency.

    +

    It calls static onExtend( BaseClass ) function if it's defined. It assumes that the Class.define( definitions ) method will be called later, and attaches Class.define method to the class if it was not defined.

    +

    static Class.define( definitions? )

    +

    Finalized the class definition started with @predefine decorator. Has the same effect as the @define decorator excepts it assumes that Class.onExtend() static function was called already.

    +

    Mixins

    +

    decorator @mixins( mixinA, mixinB, ... ) class X ...

    +

    Merge specified mixins to the class definition. Both plain JS object and class constructor may be used as mixin. In the case of the class constructor, missing static members will copied over as well.

    +
        import { mixins, Events } from 'type-r'
    +    ...
    +
    +    @define
    +    @mixins( Events, plainObject, MyClass, ... )
    +    class X {
    +        ...
    +    }
    +
    +

    static Class.mixins

    +

    Class member holding the state of the class mixins.

    + + +

    Merge rules

    +

    decorator @mixinRules({ propName : rule, ... })

    +

    The rule is the reducer function which is applied when there are several values for the particular class members are defined in different mixins or the class, or if the class member is overriden by the subclass.

    + + +

    rule mixinRules.classFirst

    +

    Assume the property to be the function. Call functions from mixins in sequence: f1.apply( this, arguments ); f2.apply( this, arguments );...

    +

    rule mixinRules.classLast

    +

    Same as sequence, but functions are called in the reverse sequence.

    +
    @define
    +@mixinRules({
    +    componentWillMount : mixinRules.classLast
    +})
    +class Component {
    +    componentWillMount(){
    +        console.log( 1 );
    +    }
    +}
    +
    +const M = {
    +    componentWillMount(){
    +        console.log( 2 );
    +    }
    +}
    +
    +@define
    +@mixins( M )
    +class X extends Component {
    +    componentWillMount(){
    +        console.log( 3 );
    +    }
    +}
    +
    +const x = new X();
    +x.componentWillMount();
    +// Will print 1, 2, 3
    +
    +
    +

    rule mixinRules.pipe

    +

    Assume the property to be the function with a signature ( x : T ) => T. Join functions from mixins in a pipe: f1( f2( f3( x ) ) ).

    +

    rule mixinRules.defaults

    +

    Assume the property to be the function returning object. Merge objects returned by functions from mixins, executing them in sequence.

    +

    rule mixinRules.every

    +

    Assume property to be the function returning boolean. Return true if all functions from mixins return truthy values.

    +

    rule mixinRules.some

    +

    Same as every, but return true when at least one function from mixins returns true.

    +

    Release Notes

    +

    3.0.0

    +

    Breaking changes

    +

    Changed semantic which needs to be refactored:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    2.x3.x
    Typeless attributevalue(x)type(null).value(x)
    Infer type from the valuex (except functions)value(x), or x (except functions)
    record.parse() overriderecord._parse(json)no such a method, remove it
    record attributes iterationrecord.forEachAttr(obj, iteratee)record.forEach(iteratee)
    Shared objectUser.sharedshared( User )
    one-to-many relationshipRecordClass.from( ref )memberOf( ref )
    many-to-many relationshipCollectionClass.from( ref )subsetOf( ref, CollectionClass? )
    construct from object/array-RecordOrCollectionClass.from( json, options? )
    +

    New attribute definition notation

    +

    Starting from version 3.X, Type-R does not modify built-in global JS objects. New type(T) attribute definition notation is introduced to replace T.has.

    +

    There's type-r/globals package for compatibility with version 2.x which must be imported once with import 'type-r/globals'. +If this package is not used, the code must be refactored according to the rules below.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    2.x3.x
    UNIX TimestampDate.timestampimport { Timestamp } from 'type-r/ext-types'
    Microsoft dateDate.microsoftimport { MicrosoftDate } from 'type-r/ext-types'
    IntegerInteger and Number.integerimport { Integer } from 'type-r/ext-types'
    Create metatype from constructorCtor.hastype(Ctor)
    Typed attribute with default valueCtor.value(default)type(Ctor).value(default)
    Attribute "Required" checkCtor.isRequiredtype(Ctor).required
    +

    First-class TypeScript support

    +
      +
    • Infer<typeof Metatype> infers TypeScript type from the Type-R attribute metatype.
    • +
    • InferAttrs<typeof attributes> infers TypeScript type for the Type-R attributes definitions.
    • +
    • attributes({ attrDefs }) returns the properly typed TypeScript Record class.
    • +
    +

    TypeScript attributes definitions:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    2.x3.x
    Extract Type-R type with Reflect.metadata@attr name : T@auto name : T
    Extract Type-R type & specify the default valuenot possible@auto(default) name : T
    Explicitly specify the type@attr(T) name : T@type(T).as name : T
    Infer Type-R type from default value@attr(default) name : T@value(default).as name : T
    Specify type and default value@attr(T.value(default)) name : T@type(T).value(default).as name : T
    +

    Other improvements

    +
      +
    • Collection class now proxies ES6 Array methods
    • +
    • New logger API which easy to override or turn off.
    • +
    • Improved error messages.
    • +
    • Type.from( json, options? ) method to restore object from JSON with a strict type check and validation.
    • +
    +
    @define class User extends Record {
    +    // There's an HTTP REST enpoint for users.
    +    static endpoint = restfulIO( '/api/users' );
    +
    +    @auto name : string
    +
    +    // Collection of Role records represented as an array of role.id in JSON.
    +    // When the "roles" attribute will be accessed for the first time,
    +    // User will look-up for a 'roles' attribute of the nearest store to resolve ids to actual Users.
    +    @subsetOf( '~roles' ).as roles : Collection<Role>
    +}
    +
    +@define class Role extends Record {
    +    static endpoint = restfulIO( '/api/roles' );
    +    @auto name : string
    +}
    +
    +// Store is the regular Record, nothing special.
    +@define class UsersDirectory extends Store {
    +    // When this record is fetched, fetch all the attributes instead.
    +    static endpoint = attributesIO();
    +
    +    // '~roles' references from all aggregated collections
    +    // will point to here, because this is the nearest store.
    +    @type( User.Collection ).as users : Collection<User>
    +    @type( Role.Collection ).as roles : Collection<Role>
    +}
    +
    +const directory = new UsersDirectory();
    +await directory.fetch();
    +
    +for( let user of directory.users ){
    +    assert( user.roles.first().users.first() instanceOf User );
    +}
    +
    +

    2.1.0

    +

    This release adds long-awaited HTTP REST endpoint.

    + +

    2.0.0

    +

    This release brings new features which fixes problems with component's inheritance in React bindings and implements long-awaited generic IO implementation based on ES6 promises.

    +

    There shouldn't be breaking changes unless you're using custom logger or React bindings (formerly known as React-MVx, with a name changed to React-R in new release).

    +

    Generic IO support

    +

    New IOEndpoint concept is introduced, making it easy to create IO abstractions. To enable Record and Collection IO API, you need to assign IO endpoint in the class definition.

    +

    Endpoint is the class defining CRUD and list operations on JSON data, as well as the methods to subscribe for the data changes. There are two endpoints included with 2.0 release, memoryIO which is suitable for mock testing and localStorageIO which could be used in demos and prototypes. They can be used as a references as starting points to define your own IO endpoints.

    +
    @define class User extends Record {
    +    static endpoint = memoryIO();
    +    static attributes = {
    +        name : String,
    +        ...
    +    }
    +}
    +
    +

    There are three Record IO methods (save(), fetch(), and destroy()) and two collection IO method (fetch() and liveUpdates()) ). All IO methods returns ES6 promises, so you either must have the runtime supporting ES6 or use the ES6 promise polyfill. The promises are modified to be abortable (all of them have abort() method).

    +
    const user = new User({ name : 'John' });
    +user.save().then( () => {
    +    console.log( `new user is added ${ user.id }` )
    +});
    +
    +

    There's the special attributesIO() endpoint to fetch all of attributes independently and return the combined promise. This is the recommended way of fetching the data required by SPA page.

    +
    @define class PageStore extends Store {
    +    static endpoint = attributesIO();
    +    static attributes = {
    +        users : User.Collection,
    +        roles : UserRole.Collection,
    +        ...
    +    }
    +}
    +
    +const store = new PageStore();
    +store.fetch().then( () =>{
    +    // render your page
    +});
    +
    +

    It's possible to define or override the defined endpoint for the nested model or collection using type().endpoint() type-R attribute annotation.

    +
    @define class PageStore extends Store {
    +    static endpoint = attributesIO();
    +    static attributes = {
    +        users : type( User.Collection ).endpoint( restful( '/api/users' ) ),
    +        roles : type( UserRole.Collection ).endpoint( restful( '/api/userroles' ) ),
    +        ...
    +    }
    +}
    +
    + + +

    New mixins engine

    +

    Type-R metaprogramming system built on powerful mixins composition with configurable member merge rules. In 2.0 release, mixins engine was rewritten to properly apply merge rules on inheritance. This feature is heavily used in Type-R React's bindings and is crucial to prevent errors when extending the React.Component subclasses.

    +

    An example illustrating the principle:

    +
    @define
    +// Define the class with 
    +@mixinRules({
    +    componentWillMount : mixinRules.classLast,
    +    componentWillUnmount : mixinRules.classFirst
    +})
    +class Component {
    +    componentWillMount(){
    +        console.log( 1 );
    +    }
    +
    +    componentWillUnmount(){
    +        console.log( 3 );
    +    }
    +}
    +
    +@define
    +@mixins({
    +    componentWillMount(){
    +        console.log( 2 );
    +    },
    +
    +    componentWillUnmount(){
    +        console.log( 2 );
    +    }
    +})
    +class MyBaseComponent extends Component {
    +    componentWillMount(){
    +        console.log( 3 );
    +    }
    +
    +    componentWillUnmount(){
    +        console.log( 1 );
    +    }
    +}
    +
    +

    In this example, all of the methods defined in the mixin, base class, and subclass will be called in the order specified in the console.log.

    +

    Other changes

    +
      +
    • Update pipeline was rewritten to improve record's initialization speed (collection's fetch speed is improved by 30%).
    • +
    • Fixed bug causing dynamic type checks to be disabled in records constructors.
    • +
    • New implementation of the Collection.subsetOf which both fixes some edge case bugs and is more efficient.
    • +
    • New logger handling NODE_ENV variable setting.
    • +
    +
    +
    + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..43640901 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,248 @@ +--- +title: Type-R 3.0 API Reference + +language_tabs: + - javascript + - typescript + +logoTitle: Type-R 3.0 + +toc_footers: + - GitHub repository + - Report the bug + - Ask the question + - Supported by + +includes: + - record + - collection + - observable + - validation + - io + - tools + - releasenotes + +search: true +--- + +# Getting started + +## Overview + +Type-R is the TypeScript and JavaScript model framework helping to define and manage the complex application state as a combination of reusable parts. Type-R cover the needs of business logic and data layers in 3-tier application architecture, providing the presentation layer with the unified technique to handle the UI and domain state. Type-R data structures look and feel (and, in some aspects, behaves) more like classes in the statically typed languages. + +Type-R in unopinionated on the way how an application state should be managed ("single source of truth" or "distributed state"). It can support all approaches equally well being not dependent on singletons and having powerful capabilities for state synchronization. + +![overview](images/3-layer-client.png) + +A state is defined as a superposition of typed records and collections. A record is a class with a known set of attributes of predefined types possibly holding other records and collections in its attributes, describing the data structure of arbitrary complexity. Record with its attributes forms an aggregation tree with deeply observable attributes changes. Attribute types are checked on assignments and invalid changes are being rejected, therefore it is guaranteed that the application state will preserve the valid shape. + +Application state defined with Type-R is serializable to JSON by default. Aggregation tree of records and collections is mapped in JSON as a tree of plain objects and arrays. Normalized data represented as a set of collections of records cross-referencing each other are supported as first-class serialization scenario. + +A record may have an associated IOEndpont representing the I/O protocol for CRUD and collection fetch operations which enables the persistence API for the particular record/collection class pair. Some useful endpoints (`restfulIO`, `localStorageIO`, etc) are provided by `type-r/endpoints/*` packages, and developers can define their own I/O endpoints implementing any particular persistence transport or API. + +Record attributes may have custom validation rules attached to them. Validation is being triggered transparently on demand and its result is cached across the record/collection aggregation tree, making subsequent calls to the validation API extremely cheap. + +All aspects of record behavior including serialization and validation can be controlled on attribute level with declarative definitions combining attribute types with metadata. Attribute definitions ("metatypes") can be reused across different models forming the domain-specific language of model declarations. Some useful attribute metatypes (`Email`, `Url`, `MicrosoftDate`, etc) are provided by `type-r/ext-types` package. + +## How Type-R compares to X? + +Type-R (former "NestedTypes") project was started in 2014 in Volicon as a modern successor to BackboneJS models, which would match Ember Data in its capabilities to work with a complex state while retaining the BackboneJS simplicity, modularity, and some degree of backward API compatibility. It replaced BackboneJS in the model layer of Volicon products, and it became the key technology in Volicon's strategy to gradually move from BackboneJS Views to React in the view layer. + +[Ember Data](https://guides.emberjs.com/v2.2.0/models/) is the closest thing to Type-R by its capabilities, with [BackboneJS models and collections](http://backbonejs.org/#Model) being the closest thing by the API, and [mobx](https://github.com/mobxjs/mobx) being pretty close in the way how the UI state is managed. + +Type-R, however, takes a very different approach to all of them: + +- Type-R models look and feel more like classes in a statically typed language with the majority of features being controlled by attribute metadata. +- Type-R is built around the concept of _aggregation trees_ formed by nested records and collections and it knows how to clone, serialize, and validate complex objects with cross-references properly. +- In contrast to BackboneJS, Record is _not an object hash_ but the class with statically typed and dynamically checked attributes. +- In contrast to mobx, Type-R detects _deeply nested changes_. +- In contrast to Ember Data, Type-R doesn't require the singleton global store. In Type-R, stores are a special kind of records and there might be as many dynamically created and disposed of stores as you need, starting with no stores at all. + +Feature | Type-R | Backbone Models | Ember Data | mobx +-|-|-|-|- +Observable changes in object graph | ✓ | - | - | ✓ +JSON Serialization | ✓ | ✓ | ✓ | - +Validation | ✓ | ✓ | ✓ | - +Dynamic Type Safety | ✓ | - | for serialization only | - +Aggregation | ✓ | - | - | - +Relations by id | ✓ | - | ✓ | - +Generalized I/O | ✓ | sync function | ✓ | - + +## Features by example + +Here's the brief overview of features groped by application purpose. + +### Persistent domain state + +The basic building block is the `Record` class. To fetch data from the server, a developer creates the subclass of the `Record` describing its attribute types and attaches the `restfulIO` endpoint. It enables the persistence API allowing the developer to fetch the collection from the server. `restfulIO` expects the server to implement the standard RESTful API expected by BackboneJS models. + +- `GET /api/users` - fetch all the users +- `POST /api/users` - create the user +- `GET /api/users/:id` - fetch the user with a given id +- `PUT /api/users/:id` - update the user with a given id +- `DELETE /api/users/:id` - delete the user with a given id + +Record and collection are serializable to and can be parsed from JSON with no additional effort. A mapping to JSON can be customized for collections, records, and individual attributes. The Record validates all updates casting attribute values to declared attribute types to protect the state structure from the protocol incompatibilities and improper assignments. + +```javascript +@define User extends Record { + static endpoint = restfulIO( '/api/users' ); + static attributes = { + name : String, + email : String, + createdAt : Date + } +} + +const users = new User.Collection(); +await users.fetch(); + +expect( users.first().createdAt ).toBeInstanceOf( Date ); +expect( typeof users.toJSON()[ 0 ].createdAt ).toBe( "string" ); +``` + +```typescript +@define User extends Record { + static endpoint = restfulIO( '/api/users' ); + + // Type-R can infer attribute types from TypeScript type annotations. + @auto name : string + @auto email : string + @auto createdAt : Date +} + +const users : Collection = new User.Collection(); +await users.fetch(); + +expect( users.first().createdAt ).toBeInstanceOf( Date ); +expect( typeof users.toJSON()[ 0 ].createdAt ).toBe( "string" ); +``` + +### UI state and observable changes + +Type-R provides the universal technique to working with the UI and domain state. To define the UI state, a developer creates the subclass of the `Record` with attributes holding all the necessary state data possibly along with the persistent data which can become the part of the same local UI state. The UI state itself can be a part of some particular view or UI component, it can be managed as a singleton ("single source of truth"), or both at the same time. Type-R is unopinionated on the application state structure leaving this decision to the developer. + +Records and collections form an aggregation tree with deeply observable changes, so it's enough to subscribe to the single `change` event from the `UIState` to get updates on both data arrival and local changes of the state attributes. Records and collections can be indefinitely nested to describe a state of arbitrary complexity. The developer can attach reactions on changes to the records, their individual attributes, and collections. Additional changes made in reactions will be executed in the scope of the same "change transaction" and won't trigger additional change events. + + +```javascript +@define UIState extends Record { + static attributes = { + users : User.Collection, + selectedUser : from( 'users' ) + } +} + +const uiState = new UIState(); + +uiState.on( 'change', () => { + console.log( 'Something is changed' ); + updateUI(); +}); + +uiState.users.fetch(); +``` + +```typescript +@define UIState extends Record { + // For collections and more complex types attribute type must be provided explicitly + @type( User.Collection ).as users : Collection + + @from( 'users' ).as selectedUser : User +} + +const uiState = new UIState(); + +uiState.on( 'change', () => { + console.log( 'Something is changed' ); + updateUI(); +}); + +uiState.users.fetch(); +``` + +### Validation + +Type-R supports validation as attribute-level checks attached to attribute definitions as metadata. Attribute type together with checks forms an "attribute metatype", which can be defined separately and reused across multiple record definitions. + +Validation rules are evaluated recursively on the aggregation tree on first access to the validation API, and validations results are cached in records and collections across the tree till the next update. The validation is automatic, subsequent calls to the validation API are cheap, and the developer doesn't need to manually trigger the validation on data changes. + +The majority of checks in a real application will be a part of attribute "metatypes", while the custom validation can be also defined on the `Record` and `Collection` level to check data integrity and cross-attributes dependencies. + +```javascript +const Email = type( String ) + .check( x => !x || x.indexOf( '@' ) >= 0, "Doesn't look like an email" ); + +@define User extends Record { + static endpoint = restfulIO( '/api/users' ); + static attributes = { + name : type( String ).required, + email : type( Email ).required, + createdAt : type( Date ).check( x => x.getTime() <= Date.now() ) + } +} + +const users = new User.Collection(); +users.add({ email : 'john' }); +expect( users.isValid() ).toBe( false ); +expect( users.first().isValid() ).toBe( false ); + +users.first().name = "John"; +users.first().email = "john@ny.com"; +expect( users.isValid() ).toBe( true ); +``` + +```typescript +const Email = type( String ) + .check( x => !x || x.indexOf( '@' ) >= 0, "Doesn't look like an email" ); + +@define User extends Record { + static endpoint = restfulIO( '/api/users' ); + + // @type(...).as converts Type-R attribute type definition to the TypeScript decorator. + @type( String ).required.as + name : string + + @type( Email ).required.as + email : string + + @type( Date ).check( x => x.getTime() <= Date.now() ).as + createdAt : Date +} + +const users = new User.Collection(); +users.add({ email : 'john' }); +expect( users.isValid() ).toBe( false ); +expect( users.first().isValid() ).toBe( false ); + +users.first().name = "John"; +users.first().email = "john@ny.com"; +expect( users.isValid() ).toBe( true ); +``` + +## Installation and requirements + +Is packed as UMD and ES6 module. No peer dependencies are required. + +`npm install type-r --save-dev` + + + + + +## ReactJS bindings + +[React-MVx](https://volicon.github.io/React-MVx/) is a glue framework which uses Type-R to manage the UI state in React and the [NestedLink](https://github.com/Volicon/NestedLink) library to implement two-way data binding. React-MVx provides the complete MVVM solution on top of ReactJS, featuring: + +- Type-R [Record](https://volicon.github.io/Type-R/#record) to manage the local [component's state](https://volicon.github.io/React-MVx/#state). +- [two-way data binding](https://volicon.github.io/React-MVx/#link) for UI and domain state. +- Hassle-free form validation (due to the combination of features of Type-R and NestedLink). +- [Type-R type annotation](https://volicon.github.io/Type-R/#definition) used to define component [props](https://volicon.github.io/React-MVx/#props) and [context](https://volicon.github.io/React-MVx/#context). + +## Usage with NodeJS + +Type-R can be used at the server side to build the business logic layer by defining the custom I/O endpoints to store data in a database. Type-R dynamic type safety features are particularly advantageous when schema-less JSON databases (like Couchbase) are being used. + +![server](images/3-layer-server.png) + diff --git a/docs/lib/fonts/icomoon.eot b/docs/lib/fonts/icomoon.eot new file mode 100644 index 00000000..212835a5 Binary files /dev/null and b/docs/lib/fonts/icomoon.eot differ diff --git a/docs/lib/fonts/icomoon.svg b/docs/lib/fonts/icomoon.svg new file mode 100644 index 00000000..24493587 --- /dev/null +++ b/docs/lib/fonts/icomoon.svg @@ -0,0 +1,18 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/lib/fonts/icomoon.ttf b/docs/lib/fonts/icomoon.ttf new file mode 100644 index 00000000..360b3d75 Binary files /dev/null and b/docs/lib/fonts/icomoon.ttf differ diff --git a/docs/lib/fonts/icomoon.woff b/docs/lib/fonts/icomoon.woff new file mode 100644 index 00000000..9a213b11 Binary files /dev/null and b/docs/lib/fonts/icomoon.woff differ diff --git a/docs/lib/fonts/slate.eot b/docs/lib/fonts/slate.eot new file mode 100644 index 00000000..13c4839a Binary files /dev/null and b/docs/lib/fonts/slate.eot differ diff --git a/docs/lib/fonts/slate.svg b/docs/lib/fonts/slate.svg new file mode 100644 index 00000000..5f349823 --- /dev/null +++ b/docs/lib/fonts/slate.svg @@ -0,0 +1,14 @@ + + + +Generated by IcoMoon + + + + + + + + + + diff --git a/docs/lib/fonts/slate.ttf b/docs/lib/fonts/slate.ttf new file mode 100644 index 00000000..ace9a46a Binary files /dev/null and b/docs/lib/fonts/slate.ttf differ diff --git a/docs/lib/fonts/slate.woff b/docs/lib/fonts/slate.woff new file mode 100644 index 00000000..1e72e0ee Binary files /dev/null and b/docs/lib/fonts/slate.woff differ diff --git a/docs/lib/fonts/slate.woff2 b/docs/lib/fonts/slate.woff2 new file mode 100644 index 00000000..7c585a72 Binary files /dev/null and b/docs/lib/fonts/slate.woff2 differ diff --git a/docs/lib/javascripts/all.js b/docs/lib/javascripts/all.js new file mode 100644 index 00000000..a19b7589 --- /dev/null +++ b/docs/lib/javascripts/all.js @@ -0,0 +1,128 @@ +!function(){if("ontouchstart"in window){var t,e,i,n,o,s,r={};t=function(t,e){return Math.abs(t[0]-e[0])>5||Math.abs(t[1]-e[1])>5},e=function(t){this.startXY=[t.touches[0].clientX,t.touches[0].clientY],this.threshold=!1},i=function(e){return this.threshold?!1:void(this.threshold=t(this.startXY,[e.touches[0].clientX,e.touches[0].clientY]))},n=function(e){if(!this.threshold&&!t(this.startXY,[e.changedTouches[0].clientX,e.changedTouches[0].clientY])){var i=e.changedTouches[0],n=document.createEvent("MouseEvents");n.initMouseEvent("click",!0,!0,window,0,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),n.simulated=!0,e.target.dispatchEvent(n)}},o=function(t){var e=Date.now(),i=e-r.time,n=t.clientX,o=t.clientY,a=[Math.abs(r.x-n),Math.abs(r.y-o)],l=s(t.target,"A")||t.target,c=l.nodeName,h="A"===c,u=window.navigator.standalone&&h&&t.target.getAttribute("href");return r.time=e,r.x=n,r.y=o,(!t.simulated&&(500>i||1500>i&&a[0]<50&&a[1]<50)||u)&&(t.preventDefault(),t.stopPropagation(),!u)?!1:(u&&(window.location=l.getAttribute("href")),void(l&&l.classList&&(l.classList.add("energize-focus"),window.setTimeout(function(){l.classList.remove("energize-focus")},150))))},s=function(t,e){for(var i=t;i!==document.body;){if(!i||i.nodeName===e)return i;i=i.parentNode}return null},document.addEventListener("touchstart",e,!1),document.addEventListener("touchmove",i,!1),document.addEventListener("touchend",n,!1),document.addEventListener("click",o,!0)}}(),/* + * jQuery Highlight plugin + * + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * + * Code a little bit refactored and cleaned (in my humble opinion). + * Most important changes: + * - has an option to highlight only entire words (wordsOnly - false by default), + * - has an option to be case sensitive (caseSensitive - false by default) + * - highlight element tag and class names can be specified in options + * + * Usage: + * // wrap every occurrance of text 'lorem' in content + * // with (default options) + * $('#content').highlight('lorem'); + * + * // search for and highlight more terms at once + * // so you can save some time on traversing DOM + * $('#content').highlight(['lorem', 'ipsum']); + * $('#content').highlight('lorem ipsum'); + * + * // search only for entire word 'lorem' + * $('#content').highlight('lorem', { wordsOnly: true }); + * + * // don't ignore case during search of term 'lorem' + * $('#content').highlight('lorem', { caseSensitive: true }); + * + * // wrap every occurrance of term 'ipsum' in content + * // with + * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); + * + * // remove default highlight + * $('#content').unhighlight(); + * + * // remove custom highlight + * $('#content').unhighlight({ element: 'em', className: 'important' }); + * + * + * Copyright (c) 2009 Bartek Szopka + * + * Licensed under MIT license. + * + */ +jQuery.extend({highlight:function(t,e,i,n){if(3===t.nodeType){var o=t.data.match(e);if(o){var s=document.createElement(i||"span");s.className=n||"highlight";var r=t.splitText(o.index);r.splitText(o[0].length);var a=r.cloneNode(!0);return s.appendChild(a),r.parentNode.replaceChild(s,r),1}}else if(1===t.nodeType&&t.childNodes&&!/(script|style)/i.test(t.tagName)&&(t.tagName!==i.toUpperCase()||t.className!==n))for(var l=0;la;a++)for(o in r[a])s=r[a][o],r[a].hasOwnProperty(o)&&s!==e&&(i[o]=t.isPlainObject(s)?t.isPlainObject(i[o])?t.widget.extend({},i[o],s):t.widget.extend({},s):s);return i},t.widget.bridge=function(i,o){var s=o.prototype.widgetFullName||i;t.fn[i]=function(r){var a="string"==typeof r,l=n.call(arguments,1),c=this;return r=!a&&l.length?t.widget.extend.apply(null,[r].concat(l)):r,this.each(a?function(){var n,o=t.data(this,s);return o?t.isFunction(o[r])&&"_"!==r.charAt(0)?(n=o[r].apply(o,l),n!==o&&n!==e?(c=n&&n.jquery?c.pushStack(n.get()):n,!1):e):t.error("no such method '"+r+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; attempted to call method '"+r+"'")}:function(){var e=t.data(this,s);e?e.option(r||{})._init():t.data(this,s,new o(r,this))}),c}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(e,n){n=t(n||this.defaultElement||this)[0],this.element=t(n),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),n!==this&&(t.data(n,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===n&&this.destroy()}}),this.document=t(n.style?n.ownerDocument:n.document||n),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,n){var o,s,r,a=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(a={},o=i.split("."),i=o.shift(),o.length){for(s=a[i]=t.widget.extend({},this.options[i]),r=0;o.length-1>r;r++)s[o[r]]=s[o[r]]||{},s=s[o[r]];if(i=o.pop(),n===e)return s[i]===e?null:s[i];s[i]=n}else{if(n===e)return this.options[i]===e?null:this.options[i];a[i]=n}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,n,o){var s,r=this;"boolean"!=typeof i&&(o=n,n=i,i=!1),o?(n=s=t(n),this.bindings=this.bindings.add(n)):(o=n,n=this.element,s=this.widget()),t.each(o,function(o,a){function l(){return i||r.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?r[a]:a).apply(r,arguments):e}"string"!=typeof a&&(l.guid=a.guid=a.guid||l.guid||t.guid++);var c=o.match(/^(\w+)\s*(.*)$/),h=c[1]+r.eventNamespace,u=c[2];u?s.delegate(u,h,l):n.bind(h,l)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?n[t]:t).apply(n,arguments)}var n=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,n){var o,s,r=this.options[e];if(n=n||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],s=i.originalEvent)for(o in s)o in i||(i[o]=s[o]);return this.element.trigger(i,n),!(t.isFunction(r)&&r.apply(this.element[0],[i].concat(n))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(n,o,s){"string"==typeof o&&(o={effect:o});var r,a=o?o===!0||"number"==typeof o?i:o.effect||i:e;o=o||{},"number"==typeof o&&(o={duration:o}),r=!t.isEmptyObject(o),o.complete=s,o.delay&&n.delay(o.delay),r&&t.effects&&t.effects.effect[a]?n[e](o):a!==e&&n[a]?n[a](o.duration,o.easing,s):n.queue(function(i){t(this)[e](),s&&s.call(n[0]),i()})}})}(jQuery),/* jquery Tocify - v1.8.0 - 2013-09-16 +* http://www.gregfranko.com/jquery.tocify.js/ +* Copyright (c) 2013 Greg Franko; Licensed MIT +* Modified lightly by Robert Lord to fix a bug I found, +* and also so it adds ids to headers +* also because I want height caching, since the +* height lookup for h1s and h2s was causing serious +* lag spikes below 30 fps */ +function(t){"use strict";t(window.jQuery,window,document)}(function(t,e,i,n){"use strict";var o="tocify",s="tocify-focus",r="tocify-hover",a="tocify-hide",l="tocify-header",c="."+l,h="tocify-subheader",u="."+h,d="tocify-item",f="."+d,p="tocify-extend-page",g="."+p;t.widget("toc.tocify",{version:"1.8.0",options:{context:"body",ignoreSelector:null,selectors:"h1, h2, h3",showAndHide:!0,showEffect:"slideDown",showEffectSpeed:"medium",hideEffect:"slideUp",hideEffectSpeed:"medium",smoothScroll:!0,smoothScrollSpeed:"medium",scrollTo:0,showAndHideOnScroll:!0,highlightOnScroll:!0,highlightOffset:40,theme:"bootstrap",extendPage:!0,extendPageOffset:100,history:!0,scrollHistory:!1,hashGenerator:"compact",highlightDefault:!0},_create:function(){var i=this;i.tocifyWrapper=t(".tocify-wrapper"),i.extendPageScroll=!0,i.items=[],i._generateToc(),i.cachedHeights=[],i.cachedAnchors=[],i._addCSSClasses(),i.webkit=function(){for(var t in e)if(t&&-1!==t.toLowerCase().indexOf("webkit"))return!0;return!1}(),i._setEventHandlers(),t(e).load(function(){i._setActiveElement(!0),t("html, body").promise().done(function(){setTimeout(function(){i.extendPageScroll=!1},0)})})},_generateToc:function(){var e,i,n=this,s=n.options.ignoreSelector;return e=t(this.options.context).find(-1!==this.options.selectors.indexOf(",")?this.options.selectors.replace(/ /g,"").substr(0,this.options.selectors.indexOf(",")):this.options.selectors.replace(/ /g,"")),e.length?(n.element.addClass(o),void e.each(function(e){t(this).is(s)||(i=t("