changeset 0:fc2517695ee1

Initial commit, draft import of existing work
author cin
date Thu, 01 Jun 2017 13:20:03 +0300
parents
children 93fb6c09f2e1
files .eslintrc.json src/implab/Uri.js src/implab/Uuid.js src/implab/components/ActivationController.js src/implab/components/ConsoleLogChannel.js src/implab/components/StateMachine.js src/implab/components/_ActivatableMixin.js src/implab/components/_LogMixin.js src/implab/data/DataContext.js src/implab/data/MapSchema.js src/implab/data/ObjectStore.js src/implab/data/RestStore.js src/implab/data/StatefullStoreAdapter.js src/implab/data/StoreAdapter.js src/implab/data/_ModelBase.js src/implab/data/_StatefulModelMixin.js src/implab/data/declare-model.js src/implab/declare.js src/implab/declare/_load.js src/implab/declare/override.js src/implab/di/ActivationContext.js src/implab/di/ActivationError.js src/implab/di/Container.js src/implab/di/Descriptor.js src/implab/di/ReferenceDescriptor.js src/implab/di/ServiceDescriptor.js src/implab/di/ValueDescriptor.js src/implab/guard.js src/implab/messaging/Client.js src/implab/messaging/Destination.js src/implab/messaging/Listener.js src/implab/messaging/Session.js src/implab/safe.js src/implab/text/format-compile.js src/implab/text/format.js src/implab/text/template-compile.js
diffstat 36 files changed, 3618 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.eslintrc.json	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,27 @@
+{
+    "env": {
+        "browser": true,
+        "commonjs": true,
+        "amd": true,
+        "node": true
+    },
+    "parserOptions": {
+        "ecmaFeatures": {
+            "jsx": true
+        },
+        "sourceType": "module"
+    },
+    "extends": "eslint:recommended",
+    "rules": {
+        "no-const-assign": "warn",
+        "no-this-before-super": "warn",
+        "no-undef": "error",
+        "no-unreachable": "warn",
+        "no-unused-vars": "warn",
+        "constructor-super": "warn",
+        "valid-typeof": "warn",
+        "semi" : "warn",
+        "no-invalid-this" : "error",
+        "no-console": "off" 
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/Uri.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,232 @@
+define(
+    [ "dojo/_base/declare" ],
+    function(declare) {
+        function parseURI(uri) {
+            var schema, host, port, path, query, hash, i;
+            if (typeof (uri) == "string") {
+                if ((i = uri.indexOf(":")) >= 0 &&
+                    uri.substr(0, i).match(/^\w+$/)) {
+                    schema = uri.substr(0, i);
+                    uri = uri.substr(i + 1);
+                }
+
+                if (uri.indexOf("//") === 0) {
+                    uri = uri.substr(2);
+                    if ((i = uri.indexOf("/")) >= 0) {
+                        host = uri.substr(0, i);
+                        uri = uri.substr(i);
+                    } else {
+                        host = uri;
+                        uri = "";
+                    }
+                }
+
+                if ((i = uri.indexOf("?")) >= 0) {
+                    path = uri.substr(0, i);
+                    uri = uri.substr(i + 1);
+
+                } else {
+                    path = uri;
+                    uri = "";
+
+                    if ((i = path.indexOf("#")) >= 0) {
+                        hash = path.substr(i + 1);
+                        path = path.substr(0, i);
+                    }
+                }
+
+                if ((i = uri.indexOf("#")) >= 0) {
+                    query = uri.substr(0, i);
+                    hash = uri.substr(i + 1);
+                } else {
+                    query = uri;
+                }
+            }
+
+            if (host && (i = host.lastIndexOf(":")) >= 0) {
+                port = host.substr(i + 1);
+                host = host.substr(0, i);
+            }
+
+            return {
+                schema : schema,
+                host : host,
+                port : port,
+                path : path,
+                query : query,
+                hash : hash
+            };
+        }
+
+        function makeURI(options) {
+            var uri = [];
+
+            if (options.schema)
+                uri.push(options.schema, ":");
+            if (options.host)
+                uri.push("//", options.host);
+            if (options.host && options.port)
+                uri.push(":", options.port);
+
+            if (options.path) {
+                if (options.host && options.path[0] != "/")
+                    uri.push("/");
+                uri.push(options.path);
+            } else if (options.host) {
+                uri.push("/");
+            }
+
+            if (options.query)
+                uri.push("?", options.query);
+            if (options.hash)
+                uri.push("#", options.hash);
+
+            return uri.join("");
+        }
+
+        function reducePath(parts) {
+            var balance = 0, result = [], isRoot;
+
+            for (var i = 0; i < parts.length; i++) {
+                var part = parts[i];
+                switch (part) {
+                case "..":
+                    if (balance > 0) {
+                        result.pop();
+                    } else {
+                        if (isRoot)
+                            throw new Error("Unbalanced path: " + parts);
+
+                        result.push(part);
+                    }
+                    balance--;
+                    break;
+                case ".":
+                    break;
+                case "":
+                    if (i === 0) {
+                        isRoot = true;
+                        result.push(part);
+                    }
+                    break;
+                default:
+                    result.push(part);
+                    balance++;
+                    break;
+                }
+            }
+
+            return result.join("/");
+        }
+
+        var meta = {
+            schema : null,
+            host : null,
+            port : null,
+            path : null,
+            query : null,
+            hash : null
+        };
+
+        var URI = declare(null, {
+            constructor : function(opts) {
+                if (typeof (opts) == "string")
+                    opts = parseURI(opts);
+                for ( var p in meta)
+                    if (p in opts)
+                        this[p] = opts[p];
+            },
+
+            clone : function() {
+                return new URI(this);
+            },
+
+            combine : function(rel) {
+                var me = this;
+
+                if (typeof (rel) === "string")
+                    rel = new URI(rel);
+                else
+                    rel = rel.clone();
+
+                // //some.host:123/path?q=a#123
+                if (rel.host)
+                    return rel;
+
+                // /abs/path?q=a#123
+                if (rel.path && rel.path[0] == "/") {
+                    if (me.host) {
+                        rel.schema = me.schema;
+                        rel.host = me.host;
+                        rel.port = me.port;
+                    }
+                    return rel;
+                }
+
+                var base = me.clone();
+
+                // rel/path?a=b#cd
+                if (rel.path) {
+                    var segments = base.getSegments();
+                    segments.pop();
+                    segments.push.apply(segments, rel.getSegments());
+
+                    base.path = reducePath(segments);
+                }
+
+                // ?q=a#123
+                if (rel.query)
+                    base.query = rel.query;
+                if (rel.hash)
+                    base.hase = rel.hash;
+
+                return base;
+            },
+
+            optimize : function() {
+                this.path = reducePath(this.getSegments());
+            },
+
+            getSegments : function() {
+                if (typeof (this.path) === "string")
+                    return this.path.split("/");
+                else
+                    return [];
+            },
+
+            toString : function() {
+                var uri = [], me = this;
+
+                if (me.schema)
+                    uri.push(me.schema, ":");
+                if (me.host)
+                    uri.push("//", me.host);
+                if (me.host && me.port)
+                    uri.push(":", me.port);
+
+                if (me.path) {
+                    if (me.host && me.path[0] != "/")
+                        uri.push("/");
+                    uri.push(me.path);
+                } else if (me.host) {
+                    uri.push("/");
+                }
+
+                if (me.query)
+                    uri.push("?", me.query);
+                if (me.hash)
+                    uri.push("#", me.hash);
+
+                return uri.join("");
+            }
+
+        });
+
+        URI.combine = function(base, rel) {
+            if (typeof (base) === "string")
+                base = new URI(base);
+            return base.combine(rel).toString();
+        };
+
+        return URI;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/Uuid.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,280 @@
+//     uuid.js
+//
+//     Copyright (c) 2010-2012 Robert Kieffer
+//     MIT License - http://opensource.org/licenses/mit-license.php
+define(
+    [],
+    function () {
+        'use strict';
+
+        var _window = 'undefined' !== typeof window ? window : null;
+
+        // Unique ID creation requires a high quality random # generator. We
+        // feature
+        // detect to determine the best RNG source, normalizing to a function
+        // that
+        // returns 128-bits of randomness, since that's what's usually required
+        var _rng, _mathRNG, _nodeRNG, _whatwgRNG, _previousRoot;
+
+        function setupBrowser() {
+            // Allow for MSIE11 msCrypto
+            var _crypto = _window.crypto || _window.msCrypto;
+
+            if (!_rng && _crypto && _crypto.getRandomValues) {
+                // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
+                //
+                // Moderately fast, high quality
+                try {
+                    var _rnds8 = new Uint8Array(16);
+                    _whatwgRNG = _rng = function whatwgRNG() {
+                        _crypto.getRandomValues(_rnds8);
+                        return _rnds8;
+                    };
+                    _rng();
+                } catch (e) {}
+            }
+
+            if (!_rng) {
+                // Math.random()-based (RNG)
+                //
+                // If all else fails, use Math.random(). It's fast, but is of
+                // unspecified
+                // quality.
+                var _rnds = new Array(16);
+                _mathRNG = _rng = function () {
+                    for (var i = 0, r; i < 16; i++) {
+                        if ((i & 0x03) === 0) {
+                            r = Math.random() * 0x100000000;
+                        }
+                        _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
+                    }
+
+                    return _rnds;
+                };
+                if ('undefined' !== typeof console && console.warn) {
+                    console
+                        .warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()");
+                }
+            }
+        }
+
+        function setupNode() {
+            // Node.js crypto-based RNG -
+            // http://nodejs.org/docs/v0.6.2/api/crypto.html
+            //
+            // Moderately fast, high quality
+            if ('function' === typeof require) {
+                try {
+                    var _rb = require('crypto').randomBytes;
+                    _nodeRNG = _rng = _rb && function () {
+                        return _rb(16);
+                    };
+                    _rng();
+                } catch (e) {}
+            }
+        }
+
+        if (_window) {
+            setupBrowser();
+        } else {
+            setupNode();
+        }
+
+        // Buffer class to use
+        var BufferClass = ('function' === typeof Buffer) ? Buffer : Array;
+
+        // Maps for number <-> hex string conversion
+        var _byteToHex = [];
+        var _hexToByte = {};
+        for (var i = 0; i < 256; i++) {
+            _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+            _hexToByte[_byteToHex[i]] = i;
+        }
+
+        // **`parse()` - Parse a UUID into it's component bytes**
+        function parse(s, buf, offset) {
+            var i = (buf && offset) || 0,
+                ii = 0;
+
+            buf = buf || [];
+            s.toLowerCase().replace(/[0-9a-f]{2}/g, function (oct) {
+                if (ii < 16) { // Don't overflow!
+                    buf[i + ii++] = _hexToByte[oct];
+                }
+            });
+
+            // Zero out remaining bytes if string was short
+            while (ii < 16) {
+                buf[i + ii++] = 0;
+            }
+
+            return buf;
+        }
+
+        // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
+        function unparse(buf, offset) {
+            var i = offset || 0,
+                bth = _byteToHex;
+            return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] +
+                bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' +
+                bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] +
+                bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] +
+                bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]];
+        }
+
+        // **`v1()` - Generate time-based UUID**
+        //
+        // Inspired by https://github.com/LiosK/UUID.js
+        // and http://docs.python.org/library/uuid.html
+
+        // random #'s we need to init node and clockseq
+        var _seedBytes = _rng();
+
+        // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit =
+        // 1)
+        var _nodeId = [
+            _seedBytes[0] | 0x01,
+            _seedBytes[1],
+            _seedBytes[2],
+            _seedBytes[3],
+            _seedBytes[4],
+            _seedBytes[5]
+        ];
+
+        // Per 4.2.2, randomize (14 bit) clockseq
+        var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
+
+        // Previous uuid creation time
+        var _lastMSecs = 0,
+            _lastNSecs = 0;
+
+        // See https://github.com/broofa/node-uuid for API details
+        function v1(options, buf, offset) {
+            var i = buf && offset || 0;
+            var b = buf || [];
+
+            options = options || {};
+
+            var clockseq = (options.clockseq != null) ? options.clockseq : _clockseq;
+
+            // UUID timestamps are 100 nano-second units since the Gregorian
+            // epoch,
+            // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
+            // time is handled internally as 'msecs' (integer milliseconds) and
+            // 'nsecs'
+            // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01
+            // 00:00.
+            var msecs = (options.msecs != null) ? options.msecs : new Date()
+                .getTime();
+
+            // Per 4.2.1.2, use count of uuid's generated during the current
+            // clock
+            // cycle to simulate higher resolution clock
+            var nsecs = (options.nsecs != null) ? options.nsecs : _lastNSecs + 1;
+
+            // Time since last uuid creation (in msecs)
+            var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000;
+
+            // Per 4.2.1.2, Bump clockseq on clock regression
+            if (dt < 0 && options.clockseq == null) {
+                clockseq = clockseq + 1 & 0x3fff;
+            }
+
+            // Reset nsecs if clock regresses (new clockseq) or we've moved onto
+            // a new
+            // time interval
+            if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
+                nsecs = 0;
+            }
+
+            // Per 4.2.1.2 Throw error if too many uuids are requested
+            if (nsecs >= 10000) {
+                throw new Error(
+                    'uuid.v1(): Can\'t create more than 10M uuids/sec');
+            }
+
+            _lastMSecs = msecs;
+            _lastNSecs = nsecs;
+            _clockseq = clockseq;
+
+            // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
+            msecs += 12219292800000;
+
+            // `time_low`
+            var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
+            b[i++] = tl >>> 24 & 0xff;
+            b[i++] = tl >>> 16 & 0xff;
+            b[i++] = tl >>> 8 & 0xff;
+            b[i++] = tl & 0xff;
+
+            // `time_mid`
+            var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
+            b[i++] = tmh >>> 8 & 0xff;
+            b[i++] = tmh & 0xff;
+
+            // `time_high_and_version`
+            b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
+            b[i++] = tmh >>> 16 & 0xff;
+
+            // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
+            b[i++] = clockseq >>> 8 | 0x80;
+
+            // `clock_seq_low`
+            b[i++] = clockseq & 0xff;
+
+            // `node`
+            var node = options.node || _nodeId;
+            for (var n = 0; n < 6; n++) {
+                b[i + n] = node[n];
+            }
+
+            return buf ? buf : unparse(b);
+        }
+
+        // **`v4()` - Generate random UUID**
+
+        // See https://github.com/broofa/node-uuid for API details
+        function v4(options, buf, offset) {
+            // Deprecated - 'format' argument, as supported in v1.2
+            var i = buf && offset || 0;
+
+            if (typeof (options) === 'string') {
+                buf = (options === 'binary') ? new BufferClass(16) : null;
+                options = null;
+            }
+            options = options || {};
+
+            var rnds = options.random || (options.rng || _rng)();
+
+            // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+            rnds[6] = (rnds[6] & 0x0f) | 0x40;
+            rnds[8] = (rnds[8] & 0x3f) | 0x80;
+
+            // Copy bytes to buffer, if provided
+            if (buf) {
+                for (var ii = 0; ii < 16; ii++) {
+                    buf[i + ii] = rnds[ii];
+                }
+            }
+
+            return buf || unparse(rnds);
+        }
+
+        // Export public API
+        var uuid = function() {
+            return new String(v4());
+        };
+        uuid.v1 = v1;
+        uuid.v4 = v4;
+        uuid.create = v4;
+        uuid.empty = "00000000-0000-0000-0000-000000000000";
+        uuid.parse = parse;
+        uuid.unparse = unparse;
+        uuid.BufferClass = BufferClass;
+        uuid._rng = _rng;
+        uuid._mathRNG = _mathRNG;
+        uuid._nodeRNG = _nodeRNG;
+        uuid._whatwgRNG = _whatwgRNG;
+
+        return uuid;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/components/ActivationController.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,126 @@
+define([ "dojo/_base/declare", "./guard", "./safe", "./_LogMixin" ],
+
+function(declare, guard, safe, _LogMixin) {
+	"use strict";
+	return declare([ _LogMixin ], {
+
+		_current : null,
+
+		_pending : false,
+
+		getCurrent : function() {
+			return this._current;
+		},
+
+		_start : function() {
+			if (this._pending)
+				throw new Error("The activation/decativation is already pending");
+			this._pending = true;
+		},
+
+		_await : function(d) {
+			var me = this;
+			return d.then(function(x) {
+				me._pending = false;
+				return x;
+			}, function(e) {
+				me._pending = false;
+				throw e;
+			});
+		},
+
+		activate : function(component) {
+			safe.argumentNotNull(component, "component");
+			var me = this;
+			if (component.getController() !== this)
+				throw new Error("The specified component doesn't belong to this controller");
+
+			return me._await(guard(me, "_start").then(function() {
+				me._activate(component);
+			}));
+		},
+
+		_activate : function(component) {
+			var me = this;
+			if (me._current === component)
+				return guard(false);
+
+			// before activation hook
+			return guard(me, "onActivating", [ component ]).then(function() {
+				// deactivate curent
+				if (me._current)
+					return me._current.deactivate(true).then(function() {
+						try {
+							me._current.onDeactivated();
+						} catch (err) {
+							me.error(err);
+						}
+						// HACK raise deactivated event
+						try {
+							me.onDeactivated(me._current, component);
+						} catch (err) {
+							// deactivated shouldn't affect the process
+							me.error(err);
+						}
+						me._current = null;
+
+					});
+			}).then(function() {
+				return component.activate(true);
+			}).then(function() {
+				me._current = component;
+				try {
+					me.onActivated(component);
+				} catch (err) {
+					me.error(err);
+				}
+
+			});
+
+		},
+
+		/**
+		 * Деактивирует текущую компоненту.
+		 * 
+		 * @async
+		 * @returns true - компонента была деактивирована, либо нет активной
+		 *          компоненты. false - запрос на деактивацию - отклонен.
+		 */
+		deactivate : function() {
+			var me = this;
+			return me._await(guard(me,"_start").then(function() {
+				return me._deactivate();
+			}));
+		},
+
+		_deactivate : function() {
+			var me = this;
+			if (!me._current)
+				return guard(false);
+
+			return guard(me, "onDeactivating").then(function() {
+				return me._current.deactivate(true);
+			}).then(function() {
+				// HACK raise deactivated event
+				try {
+					me.onDeactivated(me._current);
+				} catch (err) {
+					me.error(err);
+				}
+				me._current = null;
+			});
+		},
+
+		onActivating : function(component) {
+		},
+
+		onDeactivating : function(component) {
+		},
+
+		onDeactivated : function(component, next) {
+		},
+
+		onActivated : function(component) {
+		}
+	});
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/components/ConsoleLogChannel.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,30 @@
+define(
+    [ "dojo/_base/declare", "./format" ],
+    function(declare, format) {
+        return declare(
+            null,
+            {
+                name : null,
+
+                constructor : function(name) {
+                    this.name = name;
+                },
+
+                log : function() {
+                    console.log(this._makeMsg(arguments));
+                },
+
+                warn : function() {
+                    console.warn(this._makeMsg(arguments));
+                },
+
+                error : function() {
+                    console.error(this._makeMsg(arguments));
+                },
+
+                _makeMsg : function(args) {
+                    return this.name ? this.name + " " +
+                        format.apply(null, args) : format.apply(null, args);
+                }
+            });
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/components/StateMachine.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,34 @@
+define([ "dojo/_base/declare", "./safe", "./format" ], function(declare, safe, format) {
+    return declare(null, {
+        states : null,
+        
+        current : null,
+
+        constructor : function(opts) {
+            safe.argumentNotNull(opts, "opts");
+            safe.argumentNotNull(opts.states, "opts.states");
+            safe.argumentNotNull(opts.initial, "opts.initial");
+            
+            this.states = opts.states;
+            this.current =  opts.initial;
+            
+            if (safe.isNull(this.states[this.current]))
+                throw new Error("Invalid initial state " + this.current);
+        },
+        
+        move : function(input, noThrow) {
+            safe.argumentNotNull(input, "input");
+            
+            var next = this.states[this.current][input];
+            if(safe.isNull(next)) {
+                if (noThrow)
+                    return false;
+                else
+                    throw new Error(format("Invalid transition {0}-{1}->?", this.current, input));
+            } else {
+                this.current = next;
+                return true;
+            }
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/components/_ActivatableMixin.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,159 @@
+define([ "dojo/_base/declare", "./guard", "./StateMachine", "./_LogMixin", ],
+
+function(declare, guard, StateMachine, _LogMixin) {
+
+	var states = {
+		inactive : {
+			activate : "activating"
+		},
+		activating : {
+			success : "active",
+			failed : "inactive"
+		},
+		active : {
+			deactivate : "deactivating"
+		},
+		deactivating : {
+			success : "inactive",
+			failed : "active"
+		}
+	};
+
+	return declare([ _LogMixin ], {
+		_controller : null,
+
+		_active : null,
+
+		constructor : function() {
+			this._active = new StateMachine({
+				states : states,
+				initial : "inactive"
+			});
+		},
+
+		/**
+		 * @returns {Object} контроллер для активации текущей компоненты
+		 */
+		getController : function() {
+			return this._controller;
+		},
+
+		/**
+		 * @param {Object}
+		 *            v Контроллер для активации текущей компоненты
+		 */
+		setController : function(v) {
+			this._controller = v;
+		},
+
+		/**
+		 * @returns {Boolean} текущая компонента активна
+		 */
+		isActive : function() {
+			return this._active.current == "active";
+		},
+
+		assertActive : function() {
+			if (!this.isActive())
+				throw new Error("The object must be active to perform the operation");
+		},
+
+		/**
+		 * Активирует текущую компоненту, если у текущей компоненты задан
+		 * контроллер, то активация будет осуществляться через него
+		 * 
+		 * @async
+		 * @param{Boolean}
+		 *            direct вызов должен осуществится напрямую, без участия
+		 *            контроллера.
+		 * @return{Boolean} успешно/неуспешно
+		 */
+		activate : function(direct) {
+			var me = this;
+			if (!direct && this._controller)
+				return me._controller.activate(me).then(function() {
+					me.onActivated();
+				});
+
+			me._active.move("activate");
+			return guard(me, "onActivating").then(function() {
+				me.log("Activated");
+				me._active.move("success");
+				if (!me._controller)
+					me.onActivated();
+			}, function(err) {
+				console.error(err);
+				me.error("Activation failed: {0}", err);
+				me._active.move("failed");
+				throw err;
+			});
+		},
+
+		/**
+		 * Деактивирует текущую компоненту, если у компоненты задан контроллер,
+		 * то деактивация будет осуществляться через него.
+		 * 
+		 * @async
+		 * @param{Boolean} direct вызов должен осуществится напрямую, без
+		 *                 участия контроллера.
+		 * 
+		 */
+		deactivate : function(direct) {
+			var me = this;
+			if (!direct && me._controller)
+				return me._controller.deactivate(me).then(function() {
+					me.onDeactivated();
+				});
+
+			me._active.move("deactivate");
+			return guard(me, "onDeactivating").then(function() {
+				me.log("Deactivated");
+				me._active.move("success");
+				if (!me._controller)
+					me.onDeactivated();
+			}, function(err) {
+				console.error(err);
+				me.error("Deactivation failed: {0}", err);
+				me.move("failed");
+				throw err;
+			});
+
+		},
+
+		toogleActive : function() {
+			var me = this;
+			return (me.isActive() ? me.deactivate() : me.activate()).then(function() {
+				return me.isActive();
+			});
+		},
+
+		/**
+		 * Событие вызывается перед активацией текущей компоненты
+		 * 
+		 * @returns{Boolean|undefined} если false - активация будет отменена
+		 */
+		onActivating : function() {
+		},
+
+		/**
+		 * Событие вызывается перед деактивацией текущей компоненты
+		 * 
+		 * @returns {Boolean|undefined} если false - деактивация будет отменена
+		 */
+		onDeactivating : function() {
+		},
+
+		/**
+		 * Событие вызывается после активации текущей компоненты
+		 */
+		onActivated : function() {
+		},
+
+		/**
+		 * Событие вызывается после деактивации текущей компоненты
+		 */
+		onDeactivated : function() {
+		}
+
+	});
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/components/_LogMixin.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,67 @@
+define([ "dojo/_base/declare" ],
+
+function(declare) {
+    var cls = declare(null, {
+        _logChannel : null,
+
+        _logLevel : 1,
+
+        constructor : function(opts) {
+            if (typeof opts == "object") {
+                if ("logChannel" in opts)
+                    this._logChannel = opts.logChannel;
+                if ("logLevel" in opts)
+                    this._logLevel = opts.logLevel;
+            }
+        },
+
+        getLogChannel : function() {
+            return this._logChannel;
+        },
+
+        setLogChannel : function(v) {
+            this._logChannel = v;
+        },
+
+        getLogLevel : function() {
+            return this._logLevel;
+        },
+
+        setLogLevel : function(v) {
+            this._logLevel = v;
+        },
+
+        log : function(format) {
+            if (this._logChannel && this._logLevel > 2)
+                this._logChannel.log.apply(this._logChannel, arguments);
+        },
+        warn : function(format) {
+            if (this._logChannel && this._logLevel > 1)
+                this._logChannel.warn.apply(this._logChannel, arguments);
+        },
+        error : function(format) {
+            if (this._logChannel && this._logLevel > 0)
+                this._logChannel.error.apply(this._logChannel, arguments);
+        },
+
+        /**
+         * Used to by widgets
+         */
+        startup : function() {
+            var me = this, parent;
+            if (!me.getLogChannel()) {
+                parent = me;
+                while (parent = parent.getParent()) {
+                    if (parent.getLogChannel) {
+                        me.setLogChannel(parent.getLogChannel());
+                        if(parent.getLogLevel)
+                            me.setLogLevel(parent.getLogLevel());
+                        break;
+                    }
+                }
+            }
+            this.inherited(arguments);
+        }
+    });
+    return cls;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/DataContext.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,45 @@
+define([ "dojo/_base/declare", "../safe" ], function(declare, safe) {
+    return declare(
+        null,
+        {
+            _params : null,
+
+            _repositories : null,
+
+            constructor : function(opts) {
+                this._params = opts || {};
+                this._repositories = {};
+            },
+
+            getRepository : function(name) {
+                safe.argumentNotEmptyString(name, "name");
+                var repo = this._repositories[name];
+                if (!repo) {
+                    repo = this._params[name];
+                    if (!repo)
+                        throw new Error("The repository '" + name +
+                            "' isn't found");
+                    if (repo instanceof Function)
+                        repo = new repo(); // factory method or constructor
+                    if (repo.initialize) {
+                        repo.initialize({
+                            dataContext : this
+                        });
+                    } else if (repo.setDataContext) {
+                        repo.setDataContext(this);
+                    }
+                    this._repositories[name] = repo;
+                }
+
+                return repo;
+            },
+
+            dispose : function() {
+                for( var name in this._repositories) {
+                    var r = this._repositories[name];
+                    if (r.dispose)
+                        r.dispose();
+                }
+            }
+        });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/MapSchema.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,67 @@
+define([ "dojo/_base/declare", "../safe" ], function(declare, safe) {
+    return declare(null, {
+        /**
+         * Отображение одного типа объектов в другой.
+         * 
+         * @remarks Отображения являются односторонними, т.е. позволяют
+         *          перенести часть содержимого одного объекта в другой. Каждая
+         *          схема отображения строится из набора примитивных
+         *          отображений, которые будут применены в произвольном порядке.
+         */
+        _schema : null,
+
+        constructor : function(schema) {
+            this._schema = schema;
+        },
+
+        /**
+         * Осуществляет отображение одного объекта в другой
+         * 
+         * @src{Object} Исходный объект из которого будут взяты данные
+         * @dst{Object}
+         */
+        map : function(src, dst, ctx) {
+            safe.argumentNotNull(src, "src");
+            safe.argumentNotNull(dst, "dst");
+
+            for ( var p in this._schema) {
+                var mapper = this._schema[p];
+                if (mapper instanceof Function) {
+                    dst[p] = mapper(src[p]);
+                } else if (mapper && mapper.map) {
+                    mapper.map(src, dst, p, ctx);
+                } else {
+                    this._defaultMapper(src, dst, p, mapper, ctx);
+                }
+            }
+        },
+
+        _defaultMapper : function(src, dst, prop, opts) {
+            if (typeof (opts) == "string") {
+                if (opts in src)
+                    dst[prop] = src[opts];
+            } else if (opts && opts.type instanceof Function) {
+                if (src[prop] instanceof opts.type)
+                    dst[prop] = src[prop];
+                else
+                    dst[prop] = this._isPrimitiveType(opts.type) ? opts.type
+                        .call(null, src[prop]) : new opts.type(src[prop]);
+
+            } else {
+                if (!(prop in src))
+                    if (opts && opts.required)
+                        throw new Error("The " + prop + "is missing");
+                    else
+                        return;
+                dst[prop] = src[prop];
+            }
+        },
+
+        _isPrimitiveType : function(type) {
+            return (type === String || type === Number || type === Boolean
+                || type === Number || type === Date);
+        }
+
+    });
+
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/ObjectStore.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,174 @@
+define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array",
+    "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare,
+    lang, array, safe, when, Deferred, QueryResults) {
+    /**
+     * @module core/data/RestStore
+     * 
+     * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и
+     * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования
+     * данных.
+     */
+    return declare(null, {
+
+        model: null,
+
+        mapping: null,
+
+        _dataContext: null,
+        
+        _store : null, // backing store
+        
+        _cache : null,
+
+        constructor : function(options) {
+            options = options || {};
+
+            if (options.store)
+                this._store = options.store;
+            
+            if (options.dataContext) {
+                this._dataContext = options.dataContext;
+            }
+
+            if (options.cache === false) {
+                // no cache at all
+            } else if (options.cache === "string" && options.dataContext) {
+                this._cache = this._dataContext.getCache(options.cache);
+            } else {
+                this._cache = {};
+            }
+        },
+        
+        getDataContext : function() {
+            return this._dataContext;
+        },
+        
+        // READ
+        get : function(id) {
+            var me = this;
+            var cache = me.getCacheEntry(id);
+            if (cache)
+                return cache;
+            else
+                return when(me._store.get(id), function(data) {
+                    return me._mapToObject(id, data);
+                });
+        },
+
+        query : function(query, options) {
+            var me = this;
+            var d = me._store.query(query, options);
+            var result = QueryResults(when(d, function(data) {
+                return array.map(data, function(item) {
+                    return me._mapToObject(me._store.getIdentity(item), item);
+                });
+            }));
+            result.total = d.total;
+            return result;
+        },
+
+        getIdentity : function(object) {
+            return object.getId();
+        },
+
+        // UPDATE
+        put : function(object, directives) {
+            return this._store.put(this._mapFromObject(object), directives);
+        },
+
+        // INSERT
+        add : function(object, directives) {
+            var me = this;
+            // добавляем в хранилище данные, сохраняем в кеше объект с
+            // полученным идентификатором
+            return when(
+                me._store.add(this._mapFromObject(object), directives),
+                function(id) {
+                    object.attach(id, me);
+                    me.storeCacheEntry(id, object);
+                    return id;
+                });
+        },
+
+        // DELETE
+        remove : function(id) {
+            var me = this;
+            return when(me._store.remove(id), function() {
+                me.removeCacheEntry(id);
+            });
+        },
+
+        _mapToObject : function(id, data) {
+            var instance = this.createInstance(id);
+            this.populateInstance(instance, data);
+            return instance;
+        },
+
+        _mapFromObject : function(object) {
+            return this.serializeInstance(object);
+        },
+
+        getCacheEntry : function(id) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+
+            return this._cache[id];
+        },
+
+        storeCacheEntry : function(id, object) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+            
+            this._cache[id] = object;
+        },
+
+        removeCacheEntry : function(id) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+            delete this._cache[id];
+        },
+
+        /** Создает экземпляр сущности с указанным идентификатором, либо извлекает из кеша, если таковая уже имеется.
+         * @remarks
+         * Технически сюда можно было бы дополнительно передать данные для ининциализации объекта,
+         * но концептуально это не верно, поскольку процесс чтения объекта состоит из двух этапов:
+         *  1. Создание пустого объекта (createInstance)
+         *  2. Заполнение объекта при помощи схемы отображения (populateInstance)
+         * при этом первый этап может быть выполнен за долго до второго, например,
+         * при создании заглушек в процессе установления ссылок между объектами.
+         */
+        createInstance : function(id) {
+            var instance = this.getCacheEntry(id);
+            if (!instance) {
+                instance = this.createInstanceImpl(id);
+                this.storeCacheEntry(id, instance);
+            }
+            return instance;
+        },
+
+        /** Непосредственно создает экземпляр сущнсти, т.е. является фабричным методом.
+         * @param {String} id идентификатор создаваемого экземпляра.
+         */
+        createInstanceImpl : function(id) {
+            var opts = {
+                dataContext : this.getDataContext(),
+                id : id
+            };
+
+            return new this.itemsType(opts);
+        },
+
+        populateInstance : function(instance, data) {
+            this.mapping.readData(instance, data,this.getDataContext());
+            if (instance.onPopulate)
+                instance.onPopulate();
+        },
+
+        serializeInstance : function(instance) {
+            var data = {};
+            this.mapping.writeData(instance, data, this.getDataContext());
+            return data;
+        }
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/RestStore.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,166 @@
+define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array",
+    "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare,
+    lang, array, safe, when, Deferred, QueryResults) {
+    /**
+     * @module core/data/RestStore
+     * 
+     * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и
+     * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования
+     * данных.
+     */
+    return declare(null, {
+
+        itemsType : null,
+
+        _dataContext : null,
+        
+        _store : null, // backing store
+        _cache : null,
+
+        constructor : function(options) {
+            options = options || {};
+
+            this._cache = {};
+            if (options.store)
+                this._store = options.store;
+            if (options.dataContext)
+                this._dataContext = options.dataContext;
+        },
+        
+        setDataContext : function(v) {
+          this._dataContext = v;
+        },
+        
+        getDataContext : function() {
+            return this._dataContext;
+        },
+        
+        // READ
+        get : function(id) {
+            var me = this;
+            var cache = me.getCacheEntry(id);
+            if (cache)
+                return cache;
+            else
+                return when(me._store.get(id), function(data) {
+                    return me._mapToObject(id, data);
+                });
+        },
+
+        query : function(query, options) {
+            var me = this;
+            var d = me._store.query(query, options);
+            var result = QueryResults(when(d, function(data) {
+                return array.map(data, function(item) {
+                    return me._mapToObject(me._store.getIdentity(item), item);
+                });
+            }));
+            result.total = d.total;
+            return result;
+        },
+
+        getIdentity : function(object) {
+            return object.getId();
+        },
+
+        // UPDATE
+        put : function(object, directives) {
+            return this._store.put(this._mapFromObject(object), directives);
+        },
+
+        // INSERT
+        add : function(object, directives) {
+            var me = this;
+            // добавляем в хранилище данные, сохраняем в кеше объект с
+            // полученным идентификатором
+            return when(
+                me._store.add(this._mapFromObject(object), directives),
+                function(id) {
+                    object.attach(id, me);
+                    me.storeCacheEntry(id, object);
+                    return id;
+                });
+        },
+
+        // DELETE
+        remove : function(id) {
+            var me = this;
+            return when(me._store.remove(id), function() {
+                me.removeCacheEntry(id);
+            });
+        },
+
+        _mapToObject : function(id, data) {
+            var instance = this.createInstance(id);
+            this.populateInstance(instance, data);
+            return instance;
+        },
+
+        _mapFromObject : function(object) {
+            return this.serializeInstance(object);
+        },
+
+        getCacheEntry : function(id) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+
+            return this._cache[id];
+        },
+
+        storeCacheEntry : function(id, object) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+            
+            this._cache[id] = object;
+        },
+
+        removeCacheEntry : function(id) {
+            safe.argumentNotNull(id, "id");
+            id = id.toString();
+            delete this._cache[id];
+        },
+
+        /** Создает экземпляр сущности с указанным идентификатором, либо извлекает из кеша, если таковая уже имеется.
+         * @remarks
+         * Технически сюда можно было бы дополнительно передать данные для ининциализации объекта,
+         * но концептуально это не верно, поскольку процесс чтения объекта состоит из двух этапов:
+         *  1. Создание пустого объекта (createInstance)
+         *  2. Заполнение объекта при помощи схемы отображения (populateInstance)
+         * при этом первый этап может быть выполнен за долго до второго, например,
+         * при создании заглушек в процессе установления ссылок между объектами.
+         */
+        createInstance : function(id) {
+            var instance = this.getCacheEntry(id);
+            if (!instance) {
+                instance = this.createInstanceImpl(id);
+                this.storeCacheEntry(id, instance);
+            }
+            return instance;
+        },
+
+        /** Непосредственно создает экземпляр сущнсти, т.е. является фабричным методом.
+         * @param {String} id идентификатор создаваемого экземпляра.
+         */
+        createInstanceImpl : function(id) {
+            var opts = {
+                dataContext : this.getDataContext(),
+                id : id
+            };
+
+            return new this.itemsType(opts);
+        },
+
+        populateInstance : function(instance, data) {
+            this.itemsType.readData(instance, data,this.getDataContext());
+            if (instance.onPopulate)
+                instance.onPopulate();
+        },
+
+        serializeInstance : function(instance) {
+            var data = {};
+            this.itemsType.writeData(instance, data, this.getDataContext());
+            return data;
+        }
+
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/StatefullStoreAdapter.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,19 @@
+define(["dojo/_base/declare", "dojo/_base/array", "core/safe", "./StoreAdapter"], function(declare, array, safe ,AdapterStore){
+    return declare([AdapterStore], {
+        _attrs : null,
+        
+        constructor : function(opts) {
+            safe.argumentNotEmptyArray(opts.attrs, "opts.attrs");
+            this._attrs = opts.attrs;
+        },
+        
+        mapItem : function(item) {
+            var result = {};
+            array.forEach(this._attrs, function(p) {
+                result[p] = item.get(p);
+            });
+            return result;
+        }
+    });
+    
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/StoreAdapter.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,110 @@
+define([
+    "dojo/_base/declare",
+    "core/safe",
+    "dojo/when",
+    "dojo/store/util/QueryResults" ],
+
+function(declare, safe, when, QueryResults) {
+
+    "use strict";
+
+    /**
+     * Обертка вокруг произвольного хранилища, только для чтения. Используется
+     * для преобразования данных, например, отображения в списках элементов
+     * пространственных данных.
+     */
+    return declare(null, {
+        /**
+         * @type{String} Свойство, хранящее идентификатор
+         */
+        idProperty : null,
+
+        _store : null,
+
+        /**
+         * @param{String} opts.idProperty Имя свойства, в которое будет записан
+         *                идентификатор, если не указан, то идентификатор будет
+         *                взят из родительского хранилища или использоваться
+         *                строка <code>id</code>
+         * @param{dojo.store} opts.store Родительское хранилище
+         */
+        constructor : function(opts) {
+            safe.argumentNotNull(opts, "opts");
+            safe.argumentNotNull(opts.store, "opts.store");            
+
+            this._store = opts.store;
+            delete opts.store;
+            declare.safeMixin(this, opts);
+            this.idProperty = opts.idProperty || this._store.idProperty || "id";
+        },
+
+        getParentStore : function() {
+            return this._store;
+        },
+
+        get : function(id) {
+            var me = this;
+            return when(me._store.get(id), function(x) {
+                var m = me.mapItem(x);
+                if (!(me.idProperty in m))
+                    m[me.idProperty] = id;
+                return m;
+            });
+        },
+
+        /**
+         * Выполняет запрос в родительском хранилище, для этого используется
+         * <code>translateQuery</code> для подготовки запроса, затем,
+         * <code>mapItem</code> для преобразования результатов.
+         */
+        query : function(q, options) {
+            var me = this, store = this._store;
+            return when(store.query(me.translateQuery(q), me
+                .translateOptions(options)), function(res) {
+                var total = res.total;
+                var mapped = res.map(function(x) {
+                    var m = me.mapItem(x);
+                    if (!(me.idProperty in m))
+                        m[me.idProperty] = store.getIdentity &&
+                            store.getIdentity(x);
+                    return m;
+                });
+                mapped.total = total;
+                var results = new QueryResults(mapped);
+                console.log(results);
+                return results;
+            });
+        },
+
+        getIdentity : function(obj) {
+            return obj && obj[this.idProperty];
+        },
+
+        /**
+         * Преобразование запроса в формат родительского хранилища.
+         * 
+         * @param{Object} q Запрос в формате текущего хранилища
+         * @returns{Object} Запрос в формате родительского хранилища
+         */
+        translateQuery : function(q) {
+            return q;
+        },
+
+        translateOptions : function(options) {
+            return options;
+        },
+
+        /**
+         * Преобразование объекта из родительского хранилища. При преобразовании
+         * в объекте можно задать идентификатор, иначе идентификатор будет
+         * автоматически получен и присвоен из родительского хранилища
+         * 
+         * @param{Object} item Объект из родительского хранилища
+         * @returns{Object} результат преобразования
+         */
+        mapItem : function(item) {
+            return item;
+        }
+    });
+
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/_ModelBase.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,37 @@
+define(["dojo/_base/declare"], function(declare) {
+    
+    return declare(null, {
+        dataContext : null,
+        idField : "id",
+        loaded : false,
+        
+        constructor : function(opts){
+            if (opts) {
+                if(opts.dataContext)
+                    this.dataContext = opts.dataContext;
+                if(opts.id)
+                    this[this.idField] = opts.id;
+            }
+        },
+        
+        getId : function() {
+            return this[this.idField];
+        },
+        
+        attach : function(id, dc) {
+            if (this.dataContext)
+                throw new Error("The object is already attached");
+            this[this.idField] = id;
+            this.dataContext = dc;
+        },
+        
+        isAttached : function() {
+            return this.dataContext ? true : false; 
+        },
+        
+        onPopulate : function() {
+            this.loaded = true;
+        }
+        
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/_StatefulModelMixin.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,5 @@
+define(["dojo/_base/declare", "dojo/Stateful"], function(declare, Stateful) {
+    return declare([Stateful], {
+        
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/data/declare-model.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,72 @@
+define([ "dojo/_base/declare", "./_ModelBase", "./MapSchema" ], function(
+    declare, _ModelBase, MapSchema) {
+    /**
+     * Создает новый класс, унаследованный от ./ModelBase, с указанной схемой
+     * отображения данных.
+     * 
+     * @details Модель представляет собой объект, живущий в рамках контекста
+     *          данных, также имеющий две схемы отображения: из модели хранения
+     *          в источнике данных (toObjectMap) и наооборот в модель хранения в
+     *          источнике данных (fromObjectMap).
+     * 
+     * Описание схемы выглядит следующим образом
+     * <pre>
+     * {
+     *      name : null, // отображение в обе стороны без преобразования
+     *      
+     *      age : Number,   // при преобразоваении к объекту поле будет преобразовано dst.age = Number(src.age)
+     *                      // обратное преобразование отсутстсвует
+     *      
+     *      age : [Number, null] // тоже самое что и age : Number
+     *      
+     *      date : [Date, function(v) { return v.toString() }] // указывается преобразование в одну и в другую сторону
+     * }
+     * <pre>
+     */
+    return function(schema, mixins, opts) {
+        var fromObjectSchema = {}, toObjectSchema = {};
+        if (schema !== null && schema !== undefined) {
+            for ( var p in schema) {
+                var mapper = schema[p];
+
+                if (mapper instanceof Array) {
+                    toObjectSchema[p] = mapper[0];
+                    fromObjectSchema[p] = mapper[1];
+                } else {
+                    toObjectSchema[p] = mapper;
+                    fromObjectSchema[p] = null;
+                }
+            }
+        }
+
+        if (arguments.length < 3) {
+            opts = mixins;
+            mixins = undefined;
+        }
+
+        var base = [ _ModelBase ];
+        if (mixins) {
+            if (mixins instanceof Array)
+                base = base.concat(mixins);
+            else
+                base.push(mixins);
+        }
+
+        var model = declare(base, opts);
+
+        model.toObjectMap = new MapSchema(toObjectSchema);
+
+        model.fromObjectMap = new MapSchema(fromObjectSchema);
+
+        model.readData = function(that, data, context) {
+            model.toObjectMap.map(data, that, context);
+        };
+
+        model.writeData = function(that, data, context) {
+            data = data || {};
+            model.fromObjectMap.map(that, data, context);
+        };
+
+        return model;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/declare.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,6 @@
+define([
+    './declare/_load!'
+], function(declare) {
+    'use strict';
+    return declare;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/declare/_load.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,12 @@
+define([], function () {
+    'use strict';
+
+    return {
+        load: function (id, require, callback) {
+            require(['dojo/_base/declare'], function (declare) {
+                callback(declare);
+            });
+        }
+    };
+
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/declare/override.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,24 @@
+"use strict";
+define([], function () {
+    var slice = Array.prototype.slice;
+    return function (method) {
+        var proxy;
+
+        /** @this target object */
+        proxy = function () {
+            var me = this;
+            var inherited = (this.getInherited && this.getInherited(proxy.nom, {
+                callee: proxy
+            })) || function () {};
+
+            method.apply(me, [function () {
+                return inherited.apply(me, arguments);
+            }].concat(slice.apply(arguments)));
+        };
+
+        proxy.method = method;
+        proxy.overrides = true;
+
+        return proxy;
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ActivationContext.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,139 @@
+define([
+        "../declare",
+        "../safe",
+        "./Descriptor",
+        "./ValueDescriptor"
+    ],
+
+    function (declare, safe, Descriptor, Value) {
+        var Context = declare(null, {
+
+            _cache: null,
+
+            _services: null,
+
+            _stack: null,
+
+            _visited: null,
+
+            container: null,
+
+            _trace: false,
+
+            constructor: function (container, services, cache, visited) {
+                safe.argumentNotNull(container, "container");
+                safe.argumentNotNull(services, "services");
+
+                this._visited = visited || {};
+                this._stack = [];
+                this._cache = cache || {};
+                this._services = services;
+                this.container = container;
+            },
+
+            getService: function (name, def) {
+                var d = this._services[name];
+
+                if (!d)
+                    if (arguments.length > 1)
+                        return def;
+                    else
+                        throw new Error("Service '" + name + "' not found");
+
+                return d.activate(this, name);
+            },
+
+            /**
+             * registers services local to the the activation context
+             * 
+             * @name{string} the name of the service
+             * @service{string} the service descriptor to register
+             */
+            register: function (name, service) {
+                safe.argumentNotEmptyString(name, "name");
+
+                if (!(service instanceof Descriptor))
+                    service = new Value(service, true);
+                this._services[name] = service;
+            },
+
+            clone: function () {
+                return new Context(
+                    this.container,
+                    safe.create(this._services),
+                    this._cache,
+                    this._visited
+                );
+
+            },
+
+            has: function (id) {
+                return id in this._cache;
+            },
+
+            get: function (id) {
+                return this._cache[id];
+            },
+
+            store: function (id, value) {
+                return (this._cache[id] = value);
+            },
+
+            parse: function (data, name) {
+                var me = this;
+                if (safe.isPrimitive(data))
+                    return data;
+
+                if (data instanceof Descriptor) {
+                    return data.activate(this, name);
+                } else if (data instanceof Array) {
+                    me.enter(name);
+                    var v = data.map(function (x, i) {
+                        return me.parse(x, "." + i);
+                    });
+                    me.leave();
+                    return v;
+                } else {
+                    me.enter(name);
+                    var result = {};
+                    for (var p in data)
+                        result[p] = me.parse(data[p], "." + p);
+                    me.leave();
+                    return result;
+                }
+            },
+
+            visit: function (id) {
+                var count = this._visited[id] || 0;
+                this._visited[id] = count + 1;
+                return count;
+            },
+
+            getStack: function () {
+                return this._stack.slice().reverse();
+            },
+
+            enter: function (name, d, localize) {
+                if (this._trace)
+                    console.log("enter " + name + " " + (d || "") +
+                        (localize ? " localize" : ""));
+                this._stack.push({
+                    name: name,
+                    service: d,
+                    scope: this._services
+                });
+                if (localize)
+                    this._services = safe.create(this._services);
+            },
+
+            leave: function () {
+                var ctx = this._stack.pop();
+                this._services = ctx.scope;
+
+                if (this._trace)
+                    console.log("leave " + ctx.name + " " + (ctx.service || ""));
+            }
+        });
+
+        return Context;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ActivationError.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,39 @@
+define([
+    "../declare"
+], function (declare) {
+    return declare(null, {
+        activationStack: null,
+
+        service: null,
+
+        innerException: null,
+
+        message: null,
+
+        constructor: function (service, activationStack, innerException) {
+            this.message = "Failed to activate the service";
+            this.activationStack = activationStack;
+            this.service = service;
+            this.innerException = innerException;
+        },
+
+        toString: function () {
+            var parts = [this.message];
+            if (this.service)
+                parts.push("when activating: " + this.service.toString());
+
+            if (this.innerException)
+                parts.push("caused by: " + this.innerException.toString());
+
+            if (this.activationStack) {
+                parts.push("at");
+                this.activationStack.forEach(function (x) {
+                    parts.push("    " + x.name + " " +
+                        (x.service ? x.service.toString() : ""));
+                });
+            }
+
+            return parts.join("\n");
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/Container.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,299 @@
+define([
+    "../declare",
+    "../safe",
+    "../UUID",
+    "dojo/Deferred",
+    "./ActivationContext",
+    "./Descriptor",
+    "./ValueDescriptor",
+    "./ReferenceDescriptor",
+    "./ServiceDescriptor",
+    "./ActivationError"
+], function (
+    declare,
+    array,
+    safe,
+    UUID,
+    Deferred,
+    ActivationContext,
+    Descriptor,
+    Value,
+    Reference,
+    Service,
+    ActivationError) {
+    var Container = declare(null, {
+        _services: null,
+        _cache: null,
+        _cleanup: null,
+        _root: null,
+        _parent: null,
+
+        constructor: function (parent) {
+            this._parent = parent;
+            this._services = parent ? safe.create(parent._services) : {};
+            this._cache = {};
+            this._cleanup = [];
+            this._root = parent ? parent.getRootContainer() : this;
+            this._services.container = new Value(this, true);
+        },
+
+        getRootContainer: function () {
+            return this._root;
+        },
+
+        getParent: function () {
+            return this._parent;
+        },
+
+        getService: function (name, def) {
+            var d = this._services[name];
+            if (!d)
+                if (arguments.length > 1)
+                    return def;
+                else
+                    throw new Error("Service '" + name + "' isn't found");
+            if (d.isInstanceCreated())
+                return d.getInstance();
+
+            var context = new ActivationContext(this, this._services);
+
+            try {
+                return d.activate(context, name);
+            } catch (error) {
+                throw new ActivationError(name, context.getStack(), error);
+            }
+        },
+
+        register: function (name, service) {
+            if (arguments.length == 1) {
+                var data = name;
+                for (name in data)
+                    this.register(name, data[name]);
+            } else {
+                if (!(service instanceof Descriptor))
+                    service = new Value(service, true);
+                this._services[name] = service;
+            }
+            return this;
+        },
+
+        onDispose: function (callback) {
+            if (!(callback instanceof Function))
+                throw new Error("The callback must be a function");
+            this._cleanup.push(callback);
+        },
+
+        dispose: function () {
+            if (this._cleanup) {
+                for (var i = 0; i < this._cleanup.length; i++)
+                    this._cleanup[i].call(null);
+                this._cleanup = null;
+            }
+        },
+
+        /**
+         * @param{String|Object} config
+         *  The configuration of the contaier. Can be either a string or an object,
+         *  if the configuration is an object it's treated as a collection of
+         *  services which will be registed in the contaier.
+         * 
+         * @param{Function} opts.contextRequire
+         *  The function which will be used to load a configuration or types for services.
+         *  
+         */
+        configure: function (config, opts) {
+            var p, me = this,
+                contextRequire = (opts && opts.contextRequire);
+
+            if (typeof (config) === "string") {
+                p = new Deferred();
+                if (!contextRequire) {
+                    var shim = config + "-" + UUID();
+                    define(shim, ["require", config], function (ctx, data) {
+                        p.resolve([data, {
+                            contextRequire: ctx
+                        }]);
+                    });
+                    require([shim]);
+                } else {
+                    // TODO how to get correct contextRequire for the relative config module?
+                    contextRequire([config], function (data) {
+                        p.resolve([data, {
+                            contextRequire: contextRequire
+                        }]);
+                    });
+                }
+
+                return p.then(function (args) {
+                    return me._configure.apply(me, args);
+                });
+            } else {
+                return me._configure(config, opts);
+            }
+        },
+
+        createChildContainer: function () {
+            return new Container(this);
+        },
+
+        has: function (id) {
+            return id in this._cache;
+        },
+
+        get: function (id) {
+            return this._cache[id];
+        },
+
+        store: function (id, value) {
+            return (this._cache[id] = value);
+        },
+
+        _configure: function (data, opts) {
+            var typemap = {},
+                d = new Deferred(),
+                me = this,
+                p,
+                contextRequire = (opts && opts.contextRequire) || require;
+
+            var services = {};
+
+            for (p in data) {
+                var service = me._parse(data[p], typemap);
+                if (!(service instanceof Descriptor))
+                    service = new Value(service, false);
+                services[p] = service;
+            }
+
+            me.register(services);
+
+            var names = [];
+
+            for (p in typemap)
+                names.push(p);
+
+            if (names.length) {
+                contextRequire(names, function () {
+                    for (var i = 0; i < names.length; i++)
+                        typemap[names[i]] = arguments[i];
+                    d.resolve(me);
+                });
+            } else {
+                d.resolve(me);
+            }
+            return d.promise;
+        },
+
+        _parse: function (data, typemap) {
+            if (safe.isPrimitive(data) || data instanceof Descriptor)
+                return data;
+            if (data.$dependency)
+                return new Reference(
+                    data.$dependency,
+                    data.lazy,
+                    data.optional,
+                    data["default"],
+                    data.services && this._parseObject(data.services, typemap));
+            if (data.$value) {
+                var raw = !data.parse;
+                return new Value(raw ? data.$value : this._parse(
+                    data.$value,
+                    typemap), raw);
+            }
+            if (data.$type || data.$factory)
+                return this._parseService(data, typemap);
+            if (data instanceof Array)
+                return this._parseArray(data, typemap);
+
+            return this._parseObject(data, typemap);
+        },
+
+        _parseService: function (data, typemap) {
+            var me = this,
+                opts = {
+                    owner: this
+                };
+            if (data.$type) {
+
+                opts.type = data.$type;
+
+                if (typeof (data.$type) === "string") {
+                    typemap[data.$type] = null;
+                    opts.typeMap = typemap;
+                }
+            }
+
+            if (data.$factory)
+                opts.factory = data.$factory;
+
+            if (data.services)
+                opts.services = me._parseObject(data.services, typemap);
+            if (data.inject)
+                opts.inject = data.inject instanceof Array ? array.map(
+                    data.inject,
+                    function (x) {
+                        return me._parseObject(x, typemap);
+                    }) : me._parseObject(data.inject, typemap);
+            if (data.params)
+                opts.params = me._parse(data.params, typemap);
+
+            if (data.activation) {
+                if (typeof (data.activation) === "string") {
+                    switch (data.activation.toLowerCase()) {
+                        case "singleton":
+                            opts.activation = Service.SINGLETON;
+                            break;
+                        case "container":
+                            opts.activation = Service.CONTAINER;
+                            break;
+                        case "hierarchy":
+                            opts.activation = Service.HIERARCHY;
+                            break;
+                        case "context":
+                            opts.activation = Service.CONTEXT;
+                            break;
+                        case "call":
+                            opts.activation = Service.CALL;
+                            break;
+                        default:
+                            throw new Error("Unknown activation type: " +
+                                data.activation);
+                    }
+                } else {
+                    opts.activation = Number(data.activation);
+                }
+            }
+
+            if (data.cleanup)
+                opts.cleanup = data.cleanup;
+
+            return new Service(opts);
+        },
+
+        _parseObject: function (data, typemap) {
+            if (data.constructor &&
+                data.constructor.prototype !== Object.prototype)
+                return new Value(data, true);
+
+            var o = {};
+
+            for (var p in data)
+                o[p] = this._parse(data[p], typemap);
+
+            return o;
+        },
+
+        _parseArray: function (data, typemap) {
+            if (data.constructor &&
+                data.constructor.prototype !== Array.prototype)
+                return new Value(data, true);
+
+            var me = this;
+            return array.map(data, function (x) {
+                return me._parse(x, typemap);
+            });
+        }
+
+    });
+
+    return Container;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/Descriptor.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,4 @@
+define([], function() {
+    // abstract base type for descriptros
+    return function() {};
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ReferenceDescriptor.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,90 @@
+define([
+    "dojo/_base/declare", "../safe", "./Descriptor", "./ActivationError", "./ValueDescriptor"
+],
+
+function(declare, safe, Descriptor, ActivationError, Value) {
+    return declare(Descriptor, {
+        _name : null,
+        _lazy : false,
+        _optional : false,
+        _default : undefined,
+
+        constructor : function(name, lazy, optional, def, services) {
+            safe.argumentNotEmptyString(name, "name");
+            this._name = name;
+            this._lazy = Boolean(lazy);
+            this._optional = Boolean(optional);
+            this._default = def;
+            this._services = services;
+        },
+
+        activate : function(context, name) {
+            var me = this;
+
+            context.enter(name, this, true);
+
+            // добавляем сервисы
+            if (me._services) {
+                for ( var p in me._services) {
+                    var sv = me._services[p];
+                    context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false));
+                }
+            }
+
+            if (me._lazy) {
+                // сохраняем контекст активации
+                context = context.clone();
+                return function(cfg) {
+                    // защищаем контекст на случай исключения в процессе
+                    // активации
+                    var ct = context.clone();
+                    try {
+                        if (cfg)
+                            safe.each(cfg, function(v, k) {
+                                ct.register(k, v instanceof Descriptor ? v : new Value(v, false));
+                            });
+                        return me._optional ? ct.getService(me._name, me._default) : ct
+                            .getService(me._name);
+                    } catch (error) {
+                        throw new ActivationError(me._name, ct.getStack(), error);
+                    }
+                };
+            }
+
+            var v = me._optional ? context.getService(me._name, me._default) : context
+                .getService(me._name);
+            context.leave(me);
+            return v;
+        },
+
+        isInstanceCreated : function() {
+            return false;
+        },
+
+        toString : function() {
+            var opts = [];
+            if (this._optional)
+                opts.push("optional");
+            if (this._lazy)
+                opts.push("lazy");
+
+            var parts = [
+                "@ref "
+            ];
+            if (opts.length) {
+                parts.push("{");
+                parts.push(opts.join());
+                parts.push("} ");
+            }
+
+            parts.push(this._name);
+
+            if (!safe.isNull(this._default)) {
+                parts.push(" = ");
+                parts.push(this._default);
+            }
+
+            return parts.join("");
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ServiceDescriptor.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,291 @@
+define(
+    [
+        "dojo/_base/declare",
+        "../safe",
+        "dojo/_base/lang",
+        "dojo/_base/array",
+        "./Descriptor",
+        "./ValueDescriptor"
+    ],
+
+    function (declare, safe, lang, array, Descriptor, Value) {
+        var SINGLETON_ACTIVATION = 1,
+            CONTAINER_ACTIVATION = 2,
+            CONTEXT_ACTIVATION = 3,
+            CALL_ACTIVATION = 4,
+            HIERARCHY_ACTIVATION = 5;
+
+        var injectMethod = function (target, method, context, args) {
+            var m = target[method];
+            if (!m)
+                throw new Error("Method '" + method + "' not found");
+
+            if (args instanceof Array)
+                m.apply(target, context.parse(args, "." + method));
+            else
+                m.call(target, context.parse(args, "." + method));
+        };
+
+        var makeClenupCallback = function (target, method) {
+            if (typeof (method) === "string") {
+                return function () {
+                    target[method]();
+                };
+            } else {
+                return function () {
+                    method(target);
+                };
+            }
+        };
+
+        var cacheId = 0;
+
+        var cls = declare(
+            Descriptor, {
+                _instance: null,
+                _hasInstance: false,
+                _activationType: CALL_ACTIVATION,
+                _services: null,
+                _type: null,
+                _typeMap: null,
+                _factory: null,
+                _params: undefined,
+                _inject: null,
+                _cleanup: null,
+                _cacheId: null,
+                _owner: null,
+
+                constructor: function (opts) {
+                    safe.argumentNotNull(opts, "opts");
+                    safe.argumentNotNull(opts.owner, "opts.owner");
+
+                    this._owner = opts.owner;
+
+                    if (!(opts.type || opts.factory))
+                        throw new Error(
+                            "Either a type or a factory must be specified");
+
+                    if (typeof (opts.type) === "string" && !opts.typeMap)
+                        throw new Error(
+                            "The typeMap is required when the type is specified by its name");
+
+                    if (opts.activation)
+                        this._activationType = opts.activation;
+                    if (opts.type)
+                        this._type = opts.type;
+                    if (opts.params)
+                        this._params = opts.params;
+                    if (opts.inject)
+                        this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject];
+                    if (opts.services)
+                        this._services = opts.services;
+                    if (opts.factory)
+                        this._factory = opts.factory;
+                    if (opts.typeMap)
+                        this._typeMap = opts.typeMap;
+                    if (opts.cleanup) {
+                        if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
+                            throw new Error(
+                                "The cleanup parameter must be either a function or a function name");
+
+                        this._cleanup = opts.cleanup;
+                    }
+
+                    this._cacheId = ++cacheId;
+                },
+
+                activate: function (context, name) {
+
+                    // if we have a local service records, register them first
+
+                    var instance;
+
+                    switch (this._activationType) {
+                        case 1: // SINGLETON
+                            // if the value is cached return it
+                            if (this._hasInstance)
+                                return this._instance;
+
+                            var tof = this._type || this._factory;
+
+                            // create the persistent cache identifier for the type
+                            if (safe.isPrimitive(tof))
+                                this._cacheId = this._type;
+                            else
+                                this._cacheId = safe.oid(tof);
+
+                            // singletons are bound to the root container
+                            var container = context.container.getRootContainer();
+
+                            if (container.has(this._cacheId)) {
+                                instance = container.get(this._cacheId);
+                            } else {
+                                instance = this._create(context, name);
+                                container.store(this._cacheId, instance);
+                                if (this._cleanup)
+                                    container.onDispose(
+                                        makeClenupCallback(instance, this._cleanup));
+                            }
+
+                            this._hasInstance = true;
+                            return (this._instance = instance);
+
+                        case 2: // CONTAINER
+                            //return a cached value
+                            if (this._hasInstance)
+                                return this._instance;
+
+                            // create an instance
+                            instance = this._create(context, name);
+
+                            // the instance is bound to the container
+                            if (this._cleanup)
+                                this._owner.onDispose(
+                                    makeClenupCallback(instance, this._cleanup));
+
+                            // cache and return the instance
+                            this._hasInstance = true;
+                            return (this._instance = instance);
+                        case 3: // CONTEXT
+                            //return a cached value if one exists
+                            if (context.has(this._cacheId))
+                                return context.get(this._cacheId);
+                            // context context activated instances are controlled by callers  
+                            return context.store(this._cacheId, this._create(
+                                context,
+                                name));
+                        case 4: // CALL
+                            // per-call created instances are controlled by callers
+                            return this._create(context, name);
+                        case 5: // HIERARCHY
+                            // hierarchy activated instances are behave much like container activated
+                            // except they are created and bound to the child container
+
+                            // return a cached value
+                            if (context.container.has(this._cacheId))
+                                return context.container.get(this._cacheId);
+
+                            instance = this._create(context, name);
+
+                            if (this._cleanup)
+                                context.container.onDispose(makeClenupCallback(
+                                    instance,
+                                    this._cleanup));
+
+                            return context.container.store(this._cacheId, instance);
+                        default:
+                            throw "Invalid activation type: " + this._activationType;
+                    }
+                },
+
+                isInstanceCreated: function () {
+                    return this._hasInstance;
+                },
+
+                getInstance: function () {
+                    return this._instance;
+                },
+
+                _create: function (context, name) {
+                    context.enter(name, this, Boolean(this._services));
+
+                    if (this._activationType != CALL_ACTIVATION &&
+                        context.visit(this._cacheId) > 0)
+                        throw new Error("Recursion detected");
+
+                    if (this._services) {
+                        for (var p in this._services) {
+                            var sv = this._services[p];
+                            context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false));
+                        }
+                    }
+
+                    var instance;
+
+                    if (!this._factory) {
+                        var ctor, type = this._type;
+
+                        if (typeof (type) === "string") {
+                            ctor = this._typeMap[type];
+                            if (!ctor)
+                                throw new Error("Failed to resolve the type '" +
+                                    type + "'");
+                        } else {
+                            ctor = type;
+                        }
+
+                        if (this._params === undefined) {
+                            this._factory = function () {
+                                return new ctor();
+                            };
+                        } else if (this._params instanceof Array) {
+                            this._factory = function () {
+                                var inst = lang.delegate(ctor.prototype);
+                                var ret = ctor.apply(inst, arguments);
+                                return typeof (ret) === "object" ? ret : inst;
+                            };
+                        } else {
+                            this._factory = function (param) {
+                                return new ctor(param);
+                            };
+                        }
+                    }
+
+                    if (this._params === undefined) {
+                        instance = this._factory();
+                    } else if (this._params instanceof Array) {
+                        instance = this._factory.apply(this, context.parse(
+                            this._params,
+                            ".params"));
+                    } else {
+                        instance = this._factory(context.parse(
+                            this._params,
+                            ".params"));
+                    }
+
+                    if (this._inject) {
+                        array.forEach(this._inject, function (spec) {
+                            for (var m in spec)
+                                injectMethod(instance, m, context, spec[m]);
+                        });
+                    }
+
+                    context.leave();
+
+                    return instance;
+                },
+
+                // @constructor {singleton} foo/bar/Baz
+                // @factory {singleton}
+                toString: function () {
+                    var parts = [];
+
+                    parts.push(this._type ? "@constructor" : "@factory");
+
+                    parts.push(activationNames[this._activationType]);
+
+                    if (typeof (this._type) === "string")
+                        parts.push(this._type);
+
+                    return parts.join(" ");
+                }
+
+            });
+
+        cls.SINGLETON = SINGLETON_ACTIVATION;
+        cls.CONTAINER = CONTAINER_ACTIVATION;
+        cls.CONTEXT = CONTEXT_ACTIVATION;
+        cls.CALL = CALL_ACTIVATION;
+        cls.HIERARCHY = HIERARCHY_ACTIVATION;
+
+        var activationNames = [
+            "",
+            "{singleton}",
+            "{container}",
+            "{context}",
+            "{call}",
+            "{hierarchy}"
+        ];
+
+        return cls;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/di/ValueDescriptor.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,38 @@
+define([ "dojo/_base/declare", "./Descriptor", "../safe" ],
+
+function(declare, Descriptor, safe) {
+    return declare(Descriptor, {
+        _value : undefined,
+        _raw : false,
+        constructor : function(value, raw) {
+            this._value = value;
+            this._raw = Boolean(raw);
+        },
+
+        activate : function(context, name) {
+            context.enter(name, this);
+            var v = this._raw ? this._value : context.parse(
+                this._value,
+                ".params");
+            context.leave(this);
+            return v;
+        },
+
+        isInstanceCreated : function() {
+            return this._raw;
+        },
+
+        getInstance : function() {
+            if (!this._raw)
+                throw new Error("The instance isn't constructed");
+            return this._value;
+        },
+
+        toString : function() {
+            if (this._raw)
+                return "@value {raw}";
+            else
+                return safe.isNull(this._value) ? "@value <null>" : "@value";
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/guard.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,54 @@
+define([ "dojo/Deferred" ], function(Deferred) {
+    var toPromise = function(d) {
+        if (d && d.then)
+            return d;
+        var d2 = new Deferred();
+        d2.resolve(d);
+        return d2;
+    };
+
+    /**
+     * функция для асинхронного выполнения метода
+     * 
+     * @async
+     * @param{Object} o Объект, который будет использован в качестве
+     *                <code>this</code>, если не указан, будет
+     *                <code>null</code>
+     * @param{Function|String} m Функция или имя метода, обязательный параметр.
+     *                         Если указано имя, тогда параметр <code>o</code>
+     *                         также должен быть задан
+     * @param{Array} args Параметры для вызова метода, не обязательно.
+     * @returns{dojo/promise}
+     */
+    return function(o, m, args) {
+        if (arguments.length == 1) {
+            m = o;
+            o = null;
+        } else if (arguments.length == 2 && o instanceof Function &&
+            m instanceof Array) {
+            args = m;
+            m = o;
+            o = null;
+        }
+
+        try {
+            if (!(m instanceof Function)) {
+                if (o)
+                    m = o[m];
+                else if (arguments.length == 1)
+                    return toPromise(m);
+                else
+                    throw new Error("The target object must be specified");
+            }
+
+            if (!m)
+                throw new Error("Method not found");
+
+            return toPromise(m.apply(o, args));
+        } catch (err) {
+            var d = new Deferred();
+            d.reject(err);
+            return d;
+        }
+    };
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/messaging/Client.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,61 @@
+define(
+    [ "dojo/_base/declare", "dojo/_base/lang", "dojo/Evented", "../_LogMixin" ],
+
+    function(declare, lang, Evented, _LogMixin) {
+        return declare([ Evented, _LogMixin ], {
+            _session : null,
+            _destination : null,
+            _id : null,
+
+            constructor : function(session, destination, options) {
+                this._destination = destination;
+                this._session = session;
+            },
+
+            getDestination : function() {
+                return this._destination;
+            },
+
+            start : function() {
+                var me = this;
+                return me._session.createClient(me.prepareOptions({})).then(
+                    function(id) {
+                        me._id = id;
+                        return me;
+                    });
+            },
+
+            prepareOptions : function(options) {
+                var me = this;
+                options.mode = me.getMode();
+                options.destination = me.getDestination();
+                options.client = function(msg) {
+                    me.process(msg);
+                };
+                return options;
+            },
+
+            process : function(msg) {
+                this.warn("Messages are not acceped by this client");
+            },
+
+            stop : function() {
+                var me = this;
+                if (me._id) {
+                    me.log("stop");
+                    return me._session.deleteClient({'clientId': me._id}).then(function() {
+                        me._id = null;
+                        return me;
+                    });
+                }
+            },
+
+            toString : function() {
+                return "["
+                    + [
+                        this.getMode().toUpperCase(),
+                        this.getDestination(),
+                        this._id ].join(',') + "]";
+            }
+        });
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/messaging/Destination.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,34 @@
+define([ "dojo/_base/declare", "./Listener" ],
+
+function(declare, Listener) {
+    return declare(null, {
+        _session : null,
+        _destination : null,
+        _listenerClass : null,
+
+        constructor : function(session, destination, options) {
+            if (!session)
+                throw new Error("A session is required");
+            if (!destination)
+                throw new Error("A destination is required");
+
+            this._session = session;
+            this._destination = destination;
+            if (options) {
+                if (options.listenerClass)
+                    this._listenerClass = options.listenerClass;
+            }
+        },
+
+        listen : function(callback) {
+            var factory = this._listenerClass || Listener;
+            var listener = new factory(this._session, this._destination, {
+                listener : callback
+            });
+            listener.start();
+
+            return listener;
+        }
+
+    });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/messaging/Listener.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,64 @@
+define([ "dojo/_base/declare", "dojo/_base/lang", "./Client" ],
+
+function(declare, lang, Client) {
+    return declare([ Client ], {
+        _listener : null,
+
+        constructor : function(session, destination, options) {
+            if (!options || !options.listener)
+                throw new Error("A listener is required");
+            this._listener = options.listener;
+            if (options.transform)
+                this._transform = options.transform;
+        },
+
+        getMode : function() {
+            return "listener";
+        },
+
+        process : function(result) {
+            switch (result.type) {
+            case "message":
+                try {
+                    this._handleMessage(result.message);
+                } catch (ex) {
+                    var err = new Error("Failed to handle message");
+                    err.envelope = result.message;
+                    err.innerException = ex;
+                    this._handleError(err);
+                }
+                break;
+            case "error":
+                this._handleError(result.error);
+                break;
+            }
+
+        },
+
+        _transform : function(envelope) {
+            return envelope;
+        },
+
+        _handleMessage : function(envelope) {
+            this.log(
+                "MESSAGE type = ${0}, headers = ${2}: ${1}",
+                envelope.bodyType,
+                envelope.body,
+                JSON.stringify(envelope.headers));
+            var data = this._transform(envelope);
+            this._listener(data);
+            this.emit("message", data);
+        },
+
+        _handleError : function(ex) {
+            if (ex.innerException)
+                this.error(
+                    "ERROR: ${0} -> ${1}",
+                    ex.message,
+                    ex.innerException.message);
+            else
+                this.error("ERROR: ${0}", ex.message);
+            this.emit("error", ex);
+        }
+    });
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/messaging/Session.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,217 @@
+define(
+    [
+        "dojo/_base/declare",
+        "dojo/_base/lang",
+        "dojo/request",
+        "./Destination",
+        "dojo/Evented",
+        "dojo/Deferred",
+        "../_LogMixin" ],
+
+    function(declare, lang, request, Destination, Evented, Deferred, _LogMixin) {
+
+        var cls = declare(
+            [ Evented, _LogMixin ],
+            {
+                _id : null,
+                _baseUrl : null,
+                _destinations : null,
+                _timeout : 100000,
+                _clients : null,
+                _started : null,
+                _starting : false,
+
+                constructor : function(baseUrl, options) {
+                    if (!baseUrl)
+                        throw new Error("baseUrl is required");
+                    options = options || {};
+
+                    this._baseUrl = baseUrl.replace(/\/*$/, "");
+                    this._destinations = {};
+                    this._pending = [];
+                    this._clients = {};
+                    if (options.timeout)
+                        this._timeout = options.timeout;
+
+                    this._started = new Deferred();
+                },
+
+                start : function() {
+                    if (this._starting)
+                        return this._started;
+                    this._starting = true;
+
+                    var me = this;
+                    me.log("START");
+                    request(this._baseUrl, {
+                        method : "POST",
+                        handleAs : "json"
+                    }).then(function(result) {
+                        me._id = result;
+                        me._emitConnected();
+                        me._poll();
+                        me._started.resolve(me);
+                    }, function(error) {
+                        me._emitError(error);
+                        me._started.reject(me);
+                    });
+                    return me._started.promise;
+                },
+
+                createClient : function(options) {
+                    if (!options || !options.destination || !options.mode)
+                        throw new Error("Invalid argument");
+
+                    var me = this;
+
+                    return me._started
+                        .then(function() {
+                            var url = me._makeUrl(me._id);
+                            me.log(
+                                "CREATE mode=${0}, destination=${1}",
+                                options.mode,
+                                options.destination);
+
+                            return request(url, {
+                                method : "POST",
+                                data : {
+                                    mode : options.mode,
+                                    destination : options.destination
+                                },
+                                handleAs : 'json'
+                            })
+                                .then(
+                                    function(id) {
+                                        me
+                                            .log(
+                                                "CLIENT id=${0}, mode=${1}, destination=${2}",
+                                                id,
+                                                options.mode,
+                                                options.destination);
+                                        me._clients[id] = options.client
+                                            ? options.client
+                                            : function(msg) {
+                                                me
+                                                    .warn(
+                                                        "The client id=${0}, mode=${1}, destination=${2} isn't accepting mesages",
+                                                        id,
+                                                        options.mode,
+                                                        options.destination);
+                                            };
+                                        return id;
+                                    });
+                        });
+
+                },
+
+                deleteClient : function(options) {
+                    if (!options || !options.clientId)
+                        throw new Error("Invalid argument");
+
+                    var me = this, id = options.clientId;
+
+                    return me._started.then(function() {
+                        var url = me._makeUrl(me._id, options.clientId);
+
+                        me.log("DELETE CLIENT ${0}", options.clientId);
+
+                        return request(url, {
+                            method : "DELETE",
+                            handleAs : 'json'
+                        }).then(function() {
+                            me.log("CLIENT DELETED ${0}", options.clientId);
+                            me._clients[id] = undefined;
+                        });
+                    });
+                },
+
+                _poll : function() {
+                    var me = this, url = this._makeUrl(this._id);
+                    me.log("POLL timeout=${0}", me._timeout);
+                    request(url, {
+                        method : "GET",
+                        handleAs : "json",
+                        query : {
+                            timeout : me._timeout
+                        }
+                    }).then(function(response) {
+                        me._handlePoll(response);
+                        me._poll();
+                    }, function(err) {
+                        me.error("POLL faield with ${0}", err);
+                        me._emitError(err);
+                    });
+                },
+
+                _handlePoll : function(response) {
+                    if (!response) {
+                        this.log("POLL response undefined, looks like a bug");
+                        return;
+                    }
+                    if (!response.results || !response.results.length) {
+                        this.log("POLL response is empty");
+                        return;
+                    }
+
+                    var results = response.results;
+                    this.log("POLL got ${0} results", results.length);
+
+                    for (var i = 0; i < results.length; i++) {
+                        var result = results[i];
+                        var client = this._clients[result.clientId];
+                        if (!client) {
+                            // TODO this could happen due to client isn't
+                            // registered yet
+                            this.error("Unknown client ${0}", result.clientId);
+                            continue;
+                        }
+                        client.call(this, result);
+                    }
+                },
+
+                _emitError : function(err) {
+                    this.emit("error", err);
+                },
+
+                _emitConnected : function() {
+                    var me = this;
+                    me.log("CONNECTED");
+                    me.emit("connected");
+                },
+
+                _makeUrl : function() {
+                    var parts = [ this._baseUrl ];
+                    for (var i = 0; i < arguments.length; i++)
+                        parts.push(arguments[i].replace(/\/*$/, ""));
+                    return parts.join('/');
+                },
+
+                queue : function(name) {
+                    return this._getDestination("queue://" + name);
+                },
+
+                topic : function(name) {
+                    return this._getDestination("topic://" + name);
+                },
+
+                _getDestination : function(uri) {
+                    if (uri in this._destinations)
+                        return this._destinations[uri];
+
+                    var dest = new Destination(this, uri);
+                    this._destinations[uri] = dest;
+                    return dest;
+                },
+
+                toString : function() {
+                    return [ "[", "SESSION ", this._id, "]" ].join(" ");
+                }
+            });
+
+        cls.connect = function(url, options) {
+            var session = new cls(url, options);
+            return session.start();
+        };
+
+        return cls;
+    });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/safe.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,356 @@
+define([],
+
+    function () {
+        var id = 0,
+            OID_FIELD = "__core_safe_oid_field",
+            _create, _defineProperty, _keys;
+
+        if (!Object.keys) {
+            _defineProperty = function (obj, prop, d) {
+                if (!d)
+                    throw new Error("Invalid descriptor");
+                obj[prop] = d.value;
+            };
+        } else {
+            _defineProperty = Object.defineProperty;
+        }
+
+        if (!Object.create) {
+            var tctor = function () {};
+            _create = function (proto) {
+                if (arguments.length > 1)
+                    throw new Error("The two arguments for isn't supported");
+
+                tctor.prototype = proto;
+                return new tctor();
+            };
+        } else {
+            _create = Object.create;
+        }
+
+        if (!Object.keys) {
+            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#Polyfill
+            _keys = (function () {
+                'use strict';
+                var hasOwnProperty = Object.prototype.hasOwnProperty,
+                    hasDontEnumBug = !({
+                        toString: null
+                    }).propertyIsEnumerable('toString'),
+                    dontEnums = [
+                        'toString',
+                        'toLocaleString',
+                        'valueOf',
+                        'hasOwnProperty',
+                        'isPrototypeOf',
+                        'propertyIsEnumerable',
+                        'constructor'
+                    ],
+                    dontEnumsLength = dontEnums.length;
+
+                return function (obj) {
+                    if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
+                        throw new TypeError('Object.keys called on non-object');
+                    }
+
+                    var result = [],
+                        prop, i;
+
+                    for (prop in obj) {
+                        if (prop != OID_FIELD && hasOwnProperty.call(obj, prop)) {
+                            result.push(prop);
+                        }
+                    }
+
+                    if (hasDontEnumBug) {
+                        for (i = 0; i < dontEnumsLength; i++) {
+                            if (hasOwnProperty.call(obj, dontEnums[i])) {
+                                result.push(dontEnums[i]);
+                            }
+                        }
+                    }
+                    return result;
+                };
+            }());
+        } else {
+            _keys = Object.keys;
+        }
+
+        var safe = null;
+        safe = {
+            OID_FIELD: OID_FIELD,
+
+            argumentNotNull: function (arg, name) {
+                if (arg === null || arg === undefined)
+                    throw new Error("The argument " + name + " can't be null or undefined");
+            },
+
+            argumentNotEmptyString: function (arg, name) {
+                if (typeof (arg) !== "string" || !arg.length)
+                    throw new Error("The argument '" + name + "' must be a not empty string");
+            },
+
+            argumentNotEmptyArray: function (arg, name) {
+                if (!(arg instanceof Array) || !arg.length)
+                    throw new Error("The argument '" + name + "' must be a not empty array");
+            },
+
+            argumentOfType: function (arg, type, name) {
+                if (!(arg instanceof type))
+                    throw new Error("The argument '" + name + "' type doesn't match");
+            },
+
+            isNull: function (arg) {
+                return (arg === null || arg === undefined);
+            },
+
+            isPrimitive: function (arg) {
+                return (arg === null || arg === undefined || typeof (arg) === "string" ||
+                    typeof (arg) === "number" || typeof (arg) === "boolean");
+            },
+
+            isInteger: function (arg) {
+                return parseInt(arg) === arg;
+            },
+
+            isNumber: function (arg) {
+                return parseFloat(arg) === arg;
+            },
+
+            isString: function (val) {
+                return typeof (val) == "string" || val instanceof String;
+            },
+
+            isNullOrEmptyString: function (str) {
+                if (str === null || str === undefined ||
+                    ((typeof (str) == "string" || str instanceof String) && str.length === 0))
+                    return true;
+            },
+
+            isNotEmptyArray: function (arg) {
+                return (arg instanceof Array && arg.length > 0);
+            },
+
+            /**
+             * Выполняет метод для каждого элемента массива, останавливается, когда
+             * либо достигнут конец массива, либо функция <c>cb</c> вернула
+             * значение.
+             * 
+             * @param{Array | Object} obj массив элементов для просмотра
+             * @param{Function} cb функция, вызываемая для каждого элемента
+             * @param{Object} thisArg значение, которое будет передано в качестве
+             *                <c>this</c> в <c>cb</c>.
+             * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
+             *          если достигнут конец массива.
+             */
+            each: function (obj, cb, thisArg) {
+                safe.argumentNotNull(cb, "cb");
+                var i, x;
+                if (obj instanceof Array) {
+                    for (i = 0; i < obj.length; i++) {
+                        x = cb.call(thisArg, obj[i], i);
+                        if (x !== undefined)
+                            return x;
+                    }
+                } else {
+                    var keys = _keys(obj);
+                    for (i = 0; i < keys.length; i++) {
+                        var k = keys[i];
+                        x = cb.call(thisArg, obj[k], k);
+                        if (x !== undefined)
+                            return x;
+                    }
+                }
+            },
+
+            oid: function (obj) {
+                return this.isPrimitive(obj) ? undefined : obj[OID_FIELD] ||
+                    (_defineProperty(obj, OID_FIELD, {
+                        value: ++id,
+                        enumerable: false
+                    }) && id);
+            },
+
+            /**
+             * Копирует свойства одного объекта в другой.
+             * 
+             * @param{Any} dest объект в который нужно скопировать значения
+             * @param{Any} src источник из которого будут копироваться значения
+             * @tmpl{Object|Array} tmpl шаблон по которому будет происходить
+             *                     копирование. Если шаблон является массивом
+             *                     (список свойств), тогда значения этого массива
+             *                     являются именами свойсвт которые будут
+             *                     скопированы. Если шаблон является объектом (карта
+             *                     преобразования имен свойств src->dst), тогда
+             *                     копирование будет осуществляться только
+             *                     собственных свойств источника, присутсвующих в
+             *                     шаблоне, при этом значение свойства шаблона
+             *                     является именем свойства в которое будет
+             *                     произведено коприрование
+             */
+            mixin: function (dest, src, tmpl) {
+                safe.argumentNotNull(dest, "dest");
+                if (!src)
+                    return dest;
+
+                var keys, i, p;
+                if (arguments.length < 3) {
+                    keys = _keys(src);
+                    for (i = 0; i < keys.length; i++) {
+                        p = keys[i];
+                        dest[p] = src[p];
+                    }
+                } else {
+                    if (tmpl instanceof Array) {
+                        for (i = 0; i < tmpl.length; i++) {
+                            p = tmpl[i];
+                            if (p in src)
+                                dest[p] = src[p];
+                        }
+
+                    } else {
+                        keys = _keys(src);
+                        for (i = 0; i < keys.length; i++) {
+                            p = keys[i];
+                            if (p in tmpl)
+                                dest[tmpl[p]] = src[p];
+                        }
+                    }
+                }
+                return dest;
+            },
+
+            create: _create,
+
+            delegate: function (target, method) {
+                if (!(method instanceof Function)) {
+                    this.argumentNotNull(target, "target");
+                    method = target[method];
+                }
+
+                if (!(method instanceof Function))
+                    throw new Error("'method' argument must be a Function or a method name");
+
+                return function () {
+                    return method.apply(target, arguments);
+                };
+            },
+
+            /**
+             * Для каждого элемента массива вызывает указанную функцию и сохраняет
+             * возвращенное значение в массиве результатов.
+             * 
+             * @remarks cb может выполняться асинхронно, при этом одновременно будет
+             *          только одна операция.
+             * 
+             * @async
+             */
+            pmap: function (items, cb) {
+                safe.argumentNotNull(cb, "cb");
+
+                if (items && items.then instanceof Function)
+                    return items.then(function (data) {
+                        return safe.pmap(data, cb);
+                    });
+
+                if (safe.isNull(items) || !items.length)
+                    return items;
+
+                var i = 0,
+                    result = [];
+
+                function next() {
+                    var r, ri;
+
+                    function chain(x) {
+                        result[ri] = x;
+                        return next();
+                    }
+
+                    while (i < items.length) {
+                        r = cb(items[i], i);
+                        ri = i;
+                        i++;
+                        if (r && r.then) {
+                            return r.then(chain);
+                        } else {
+                            result[ri] = r;
+                        }
+                    }
+                    return result;
+                }
+
+                return next();
+            },
+
+            /**
+             * Для каждого элемента массива вызывает указанную функцию, результаты
+             * не сохраняются
+             * 
+             * @remarks cb может выполняться асинхронно, при этом одновременно будет
+             *          только одна операция.
+             * @async
+             */
+            pfor: function (items, cb) {
+                safe.argumentNotNull(cb, "cb");
+
+                if (items && items.then instanceof Function)
+                    return items.then(function (data) {
+                        return safe.pmap(data, cb);
+                    });
+
+                if (safe.isNull(items) || !items.length)
+                    return items;
+
+                var i = 0;
+
+                function next() {
+                    while (i < items.length) {
+                        var r = cb(items[i], i);
+                        i++;
+                        if (r && r.then)
+                            return r.then(next);
+                    }
+                }
+
+                return next();
+            },
+
+            /**
+             * Выбирает первый элемент из последовательности, или обещания, если в
+             * качестве параметра используется обещание, оно должно вернуть массив.
+             * 
+             * @param{Function} cb обработчик результата, ему будет передан первый
+             *                  элемент последовательности в случае успеха
+             * @param{Fucntion} err обработчик исключения, если массив пустой, либо
+             *                  не массив
+             * 
+             * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
+             *          обещание, либо первый элемент.
+             * @async
+             */
+            first: function (sequence, cb, err) {
+                if (sequence) {
+                    if (sequence.then instanceof Function) {
+                        return sequence.then(function (res) {
+                            return safe.first(res, cb, err);
+                        }, err);
+                    } else if (sequence && "length" in sequence) {
+                        if (sequence.length === 0) {
+                            if (err)
+                                return err(new Error("The sequence is empty"));
+                            else
+                                throw new Error("The sequence is empty");
+                        }
+                        return cb ? cb(sequence[0]) : sequence[0];
+                    }
+                }
+                
+                if (err)
+                    return err(new Error("The sequence is required"));
+                else
+                    throw new Error("The sequence is required");
+            }
+        };
+
+        return safe;
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/text/format-compile.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,101 @@
+define(
+    [],
+    function() {
+        var map = {
+            "\\{" : "&curlopen;",
+            "\\}" : "&curlclose;",
+            "&" : "&amp;",
+            "\\:" : "&colon;"
+        };
+
+        var rev = {
+            curlopen : "{",
+            curlclose : "}",
+            amp : "&",
+            colon : ":"
+        };
+
+        var espaceString = function(s) {
+            if (!s)
+                return s;
+            return "'" + s.replace(/('|\\)/g, "\\$1") + "'";
+        };
+
+        var encode = function(s) {
+            if (!s)
+                return s;
+            return s.replace(/\\{|\\}|&|\\:/g, function(m) {
+                return map[m] || m;
+            });
+        };
+
+        var decode = function(s) {
+            if (!s)
+                return s;
+            return s.replace(/&(\w+);/g, function(m, $1) {
+                return rev[$1] || m;
+            });
+        };
+
+        var subst = function(s) {
+            var i = s.indexOf(":"), name, pattern;
+            if (i >= 0) {
+                name = s.substr(0, i);
+                pattern = s.substr(i + 1);
+            } else {
+                name = s;
+            }
+
+            if (pattern)
+                return [
+                    espaceString(decode(name)),
+                    espaceString(decode(pattern)) ];
+            else
+                return [ espaceString(decode(name)) ];
+        };
+
+        var compile = function(str) {
+            if (!str)
+                return function() {};
+
+            var chunks = encode(str).split("{"), chunk;
+
+            var code = [ "var result=[];" ];
+
+            for (var i = 0; i < chunks.length; i++) {
+                chunk = chunks[i];
+
+                if (i === 0) {
+                    if (chunk)
+                        code.push("result.push(" + espaceString(decode(chunk)) +
+                            ");");
+                } else {
+                    var len = chunk.indexOf("}");
+                    if (len < 0)
+                        throw new Error("Unbalanced substitution #" + i);
+
+                    code.push("result.push(subst(" +
+                        subst(chunk.substr(0, len)).join(",") + "));");
+                    if (chunk.length > len + 1)
+                        code.push("result.push(" +
+                            espaceString(decode(chunk.substr(len + 1))) + ");");
+                }
+            }
+
+            code.push("return result.join('');");
+
+            /* jshint -W054 */
+            return new Function("subst", code.join("\n"));
+        };
+
+        var cache = {};
+
+        return function(template) {
+            var compiled = cache[template];
+            if (!compiled) {
+                compiled = compile(template);
+                cache[template] = compiled;
+            }
+            return compiled;
+        };
+    });
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/text/format.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,85 @@
+define([
+    "./safe",
+    "./format-compile",
+    "dojo/number",
+    "dojo/date/locale",
+    "dojo/_base/array" ], function(safe, compile, number, date, array) {
+
+    // {short,medium,full,long}-{date,time}
+    var convert = function(value, pattern) {
+        if (!pattern)
+            return value.toString();
+
+        if (pattern.toLocaleLowerCase() == "json") {
+            var cache = [];
+            return JSON.stringify(value, function(k, v) {
+                if (!safe.isPrimitive(v)) {
+                    var id = array.indexOf(cache, v);
+                    if (id >= 0)
+                        return "@ref-" + id;
+                    else
+                        return v;
+                } else {
+                    return v;
+                }
+            });
+        }
+
+        if (safe.isNumber(value)) {
+            var nopt = {};
+            if (pattern.indexOf("!") === 0) {
+                nopt.round = -1;
+                pattern = pattern.substr(1);
+            }
+            nopt.pattern = pattern;
+            return number.format(value, nopt);
+        } else if (value instanceof Date) {
+            var m = pattern.match(/^(\w+)-(\w+)$/);
+            if (m)
+                return date.format(value, {
+                    selector : m[2],
+                    formatLength : m[1]
+                });
+            else
+                return date.format(value, {
+                    selector : "date",
+                    datePattern : pattern
+                });
+        } else {
+            return value.toString(pattern);
+        }
+    };
+
+    function formatter(format) {
+        var data;
+
+        if (arguments.length <= 1)
+            return format;
+
+        data = Array.prototype.slice.call(arguments, 1);
+
+        var template = compile(format);
+
+        return template(function(name, pattern) {
+            var value = data[name];
+            return !safe.isNull(value) ? convert(value, pattern) : "";
+        });
+    }
+
+    formatter.compile = function(format) {
+        var template = compile(format);
+
+        return function() {
+            var data = arguments;
+
+            return template(function(name, pattern) {
+                var value = data[name];
+                return !safe.isNull(value) ? convert(value, pattern) : "";
+            });
+        };
+    };
+
+    formatter.convert = convert;
+
+    return formatter;
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/text/template-compile.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,54 @@
+define(
+    [ "dojo/request", "./format" ],
+    function(request, format) {
+        var compile = function(str) {
+            var code = "var p=[],print=function(){p.push(foramt.apply(null,arguments));};" +
+                // Introduce the data as local variables using with(){}
+                "with(obj){p.push('" +
+                // Convert the template into pure JavaScript
+                str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace(
+                    /(^|%>)[^\t]*/g,
+                    function(x) {
+                        return x.replace(/('|\\)/g, "\\$1");
+                    }).replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');")
+                    .split("%>").join("p.push('") + "');}return p.join('');";
+            /* jshint -W054 */
+            try {
+                var compiled = new Function("obj, format, nls", code);
+
+                /**
+                 * Функция форматирования по шаблону
+                 * 
+                 * @type{Function}
+                 * @param{Object} obj объект с параметрами для подстановки
+                 */
+                return function(obj) {
+                    return compiled(obj || {}, format);
+                };
+            } catch (e) {
+                if (console && console.error) {
+                    console.error(code);
+                }
+            }
+        };
+
+        var cache = {};
+
+        compile.load = function(id, require, callback) {
+            var url = require.toUrl(id);
+            if (url in cache) {
+                callback(cache[url]);
+            } else {
+                request(url).then(compile).then(function(tc) {
+                    callback(cache[url] = tc);
+                }, function(err) {
+                    require.signal("error", [ {
+                        error : err,
+                        src : 'implab/template-compile'
+                    } ]);
+                });
+            }
+        };
+
+        return compile;
+    });
\ No newline at end of file