Nickolay - KiokuJS-0.01

Documentation | Source
Class('KiokuJS.Reference', {
    
    has : {
        ID          : { required : true },
        
        type        : null
    },
    
    
    methods : {
        toString : function () {
            return '{"$ref":' + this.ID + '}'
        }
    }
    
});
Class('KiokuJS.Exception', {
    
    has : {
        nativeEx        : null,
        
        message         : { is : 'rw' },
        description     : 'Unknown exception'
    },
    
    
    methods : {
        
        toString : function () {
            return this.meta.name + ': ' + this.description + ', ' + this.getMessage()
        }
    }
})
;
Class('KiokuJS.Exception.Network', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        description : 'Network failure'
    }
})
;
Class('KiokuJS.Exception.Format', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        description : 'Wrong serialization format'
    }
})
;
Class('KiokuJS.Exception.Overwrite', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        id          : { required : true },
        oldValue    : '',
        newValue    : '',
        
        description : 'Overwrite attempt occured'
    },
    
    methods : {
        
        getMesage : function () {
            return "Attempt to overwrite entry with ID = [" + this.id + "], value = [" + this.oldValue + "], with [" + this.newValue + "]"
        }
    }
})
;
Class('KiokuJS.Exception.Update', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        description : 'The entry being updated missed in the storage'
    }
})
;
Class('KiokuJS.Exception.Remove', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        description : 'Failed `remove` operation'
    }
})
;
Class('KiokuJS.Exception.LookUp', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        id          : { required : true },
        backendName : { required : true },
        
        description : 'Failed lookup attempt'
    },
    
    methods : {
        
        getMesage : function () {
            return 'ID [' + this.id + '] not found in the backend [' + this.backendName + ']'
        }
    }
})
;
Class('KiokuJS.Exception.Conflict', {
    
    isa     : 'KiokuJS.Exception',
    
    has : {
        description : 'Revisioning or consistency conflict'
    }
})
;
Role('KiokuJS.Aspect.AfterCollapse', {
    
    
    methods : {
        
        afterCollapse : function () {
        }
    }
    
})
;
Role('KiokuJS.Aspect.AfterExpand', {
    
    
    methods : {
        
        afterExpand : function () {
        }
    }
    
})
;
Role('KiokuJS.Feature.Attribute.Intrinsic', {
    
    does    : 'KiokuJS.Aspect.AfterCollapse',
    
    
    
    after : {
        
        
        afterCollapse : function (instance, value) {
            
            if (value instanceof KiokuJS.Node) value.intrinsic = true
        }
    }
    
})
;
Role('KiokuJS.Feature.Attribute.Extrinsic', {
    
    does    : 'KiokuJS.Aspect.AfterCollapse',
    
    
    
    after : {
        
        
        afterCollapse : function (instance, value) {
            
            if (value instanceof KiokuJS.Node) value.extrinsic = true
        }
    }
    
})
;
Role('KiokuJS.Feature.Attribute.Skip')
;
// XXX implement auto-registration of such attributes
// XXX proxy will currently only work, when linking with the `shallowLevel` == 0

Role('KiokuJS.Feature.Attribute.Proxy')
;
Role('KiokuJS.Feature.Attribute.Lazy', {
    
    does    : 'KiokuJS.Aspect.AfterCollapse',
    
    
    use     : 'JooseX.CPS',
    
    
    after : {
        
        afterCollapse : function (instance, value) {
            
            if (value instanceof KiokuJS.Node) value.lazy = true
        },
        
        
        initialize : function () {
            this.readable = this.hasGetter = true
        }
    },
    
    
    override : {
        
        getGetter : function () {
            var original    = this.SUPER()
            
            var me          = this
            
            return function (scope) {
                var value   = original.call(this)
                var self    = this
                
                var cont = Joose.top.__GLOBAL_CNT__ || new JooseX.CPS.Continuation()
                
                if (value instanceof KiokuJS.Reference && value.type == 'lazy') {
                    
                    var ID = value.ID
                    
                    if (!scope) throw "No scope provided to fetch the lazy reference in. Reference ID [" + ID + "]"
                    
                    return cont.TRY(function () {
                        
                        scope.lookUp(ID).andThen(function (obj) {
                            
                            me.setRawValueTo(self, obj)
                            
                            this.CONT.CONTINUE(obj)
                        }, self)
                    }, self)
                }
                
                return cont.TRY(function () { 
                    this.CONT.CONTINUE(value) 
                }, self)    
            }
        }
    }
})
;
Role('KiokuJS.Feature.Class.Intrinsic', {
    
    does    : 'KiokuJS.Aspect.AfterCollapse',
    
    
    
    after : {
        
        
        afterCollapse : function (node) {
            node.intrinsic = true
        }
    }
    
})
;
Role('KiokuJS.Feature.Class.OwnID', {
    
    requires : [ 'acquireID' ]

})
;
Role('KiokuJS.Feature.Class.OwnUUID', {
    
    use         : 'Data.UUID',
    
    does        : 'KiokuJS.Feature.Class.OwnID',
    
    
    has : {
        uuid    : {
            is      : 'rw',
            init    : function () { return Data.UUID.uuid() }
        }
    },
    
    
    methods : {
        
        acquireID : function () {
            return this.getUuid()
        }
    }
})
;
Role('KiokuJS.Feature.Class.Immutable')
;
Role('KiokuJS.Role.Serializer', {
    
    my : {
        requires : [ 'serialize', 'deserialize' ]
    }
})

;
Class('KiokuJS.Serializer.JSON', {
    
    does        : 'KiokuJS.Role.Serializer',
    
    use         : [ 'JSON2', 'KiokuJS.Exception.Format' ],
    
    my : {
        
        methods : {
            
            serialize   : function (data) {
                try {
                    return JSON2.stringify(data)
                } catch (e) {
                    throw new KiokuJS.Exception.Format({ message : 'Invalid JSON: ' + data })
                }
            },
            
            
            deserialize : function (string) {
                try {
                    return JSON2.parse(string)
                } catch (e) {
                    
                    throw new KiokuJS.Exception.Format({ message : 'Invalid JSON: ' + string })
                }
            }
        } 
    }
})
;
Role('KiokuJS.TypeMap.Role.NoDeps', {
        
    methods : {
        
        getRequiredClasses : function () {
            return []
        }
    }
})
;
Class('KiokuJS.TypeMap', {
    
    use     : 'Data.UUID',
    
    
    has : {
        inherit     : false,
        
        intrinsic   : false,

        // this flag will be set for native ( [], {} ) data structures which can be passed through w/o own entries 
        passThrough : false,      
        
        forClass    : {
            required    : true
        },
        
        classVersion    : null,
        isVersionExact  : true 
    },
    
    
    methods : {
        
        getRequiredClasses : function () {
            if (this.classVersion) {
                var obj = {}
                
                obj[ this.forClass ] = this.classVersion
                
                return [ obj ]
            }
            
            return [ this.forClass ]
        },
        
        
        // XXX add versions check
        canHandle : function (className) {
            if (className == this.forClass) return true
            
            if (this.inherit) {
                
                var classConstructor        = eval(className)
                var forClass                = eval(this.forClass)
                
                if (classConstructor.meta) return classConstructor.meta.isa(forClass)
            }
            
            return false
        },
        
        
        acquireID : function (node, desiredId) {
            return desiredId != null ? desiredId : Data.UUID.uuid()
        },
        
        
        collapse : function (node, collapser) {
            throw "Abstract method 'collapse' called for " + this
        },
        
        
        clearInstance : function (node) {
            throw "Abstract method 'clear' called for " + this
        },
        
        
        createEmptyInstance : function (node) {
            throw "Abstract method 'createEmptyInstance' called for " + this
        },
        
        
        populate : function (node, expander) {
            throw "Abstract method 'expand' called for " + this
        }
    }
})
;
Class('KiokuJS.TypeMap.Date', {
    
    isa     : 'KiokuJS.TypeMap',
    
    does    : 'KiokuJS.TypeMap.Role.NoDeps',
    
    
    has : {
        forClass    : 'Date'
    },
    
        
    methods : {
        
        canHandle : function (className) {
            return className == 'Date'
        },
        
        
        collapse : function (node, collapser) {
            return node.object.getTime()
        },
        
        
        clearInstance : function (node) {
        },
        
        
        createEmptyInstance : function (node) {
            return new Date()
        },
        
        
        populate : function (node, expander) {
            var instance = node.object
            
            instance.setTime(node.data)
        }
    }

})
;
Class('KiokuJS.TypeMap.Function', {
    
    isa     : 'KiokuJS.TypeMap',
    
    does    : 'KiokuJS.TypeMap.Role.NoDeps',
    
    
    has : {
        forClass    : 'Function'
    },
    
        
    methods : {
        
        canHandle : function (className) {
            return className == 'Function'
        },
        
        
        collapse : function (node, collapser) {
            var props = {}
            
            Joose.O.eachOwn(node.object, function (value, name) {
                props[ name ] = collapser.visit(value)
            })
            
            return {
                source  : Function.prototype.toString.call(node.object),
                
                props   : props
            }
        },
        
        
        clearInstance : function (node) {
            var func = node.object
            
            Joose.O.eachOwn(func, function (value, name) {
                
                delete func[ name ]
            })
            
            delete node.objectData.action
        },
        
        
        createEmptyInstance : function (node) {
            var closure = node.objectData = {
                action : null
            }
            
            return function () {
                return closure.action.apply(this, arguments)
            }
        },
        
        
        populate : function (node, expander) {
            
            var func = node.object
            
            Joose.O.each(node.data.props, function (value, name) {
                
                func[ name ] = expander.visit(value)
            })
            
            node.objectData.action = eval('(' + node.data.source + ')')
        }
    }

})
;
Class('KiokuJS.TypeMap.Array', {
    
    isa     : 'KiokuJS.TypeMap',
    
    does    : 'KiokuJS.TypeMap.Role.NoDeps',
    
    
    has : {
        forClass    : 'Array',
        passThrough : true
    },
    
        
    methods : {
        
        canHandle : function (className) {
            return className == 'Array'
        },
        
        
        collapse : function (node, collapser) {
            
            return Joose.A.map(node.object, function (value) {
                return collapser.visit(value)
            })
        },

        
        clearInstance : function (node) {
            var instance = node.object
            
            if (instance.length) instance.splice(0, instance.length)
        },
        
        
        createEmptyInstance : function (node) {
            return []
        },
        
        
        populate : function (node, expander) {
            var instance = node.object
            
            Joose.A.map(node.data, function (value) {
                
                instance.push(expander.visit(value))
            })
        }
    }

})
;
Class('KiokuJS.TypeMap.Object', {
    
    isa     : 'KiokuJS.TypeMap',
    
    does    : 'KiokuJS.TypeMap.Role.NoDeps',
    
    
    has : {
        forClass    : 'Object',
        passThrough : true
    },
    
        
    methods : {
        
        canHandle : function (className) {
            return className == 'Object'
        },
        
        
        collapse : function (node, collapser) {
            var data = {}
            
            Joose.O.eachOwn(node.object, function (value, name) {
                data[ name ] = collapser.visit(value)
            })
            
            return data
        },
        
        
        clearInstance : function (node) {
            
            Joose.O.eachOwn(node.object, function (value, name) {
                delete instance[ name ]
            })
        },
        
        
        createEmptyInstance : function (node) {
            return {}
        },
        
        
        populate : function (node, expander) {
            var instance = node.object
            
            Joose.O.each(node.data, function (value, name) {
                
                instance[ name ] = expander.visit(value)
            })
        }
    }

})
;
Class('KiokuJS.TypeMap.Joose', {
    
    isa     : 'KiokuJS.TypeMap',
    
    use     : [ 
        'KiokuJS.Aspect.AfterCollapse', 
        'KiokuJS.Feature.Attribute.Skip',
        'KiokuJS.Feature.Class.Immutable'
    ],
    
    
    has : {
        forClass    : 'Joose.Proto.Object',
        inherit     : false
    },
    
        
    methods : {
        
        acquireID : function (node, desiredId) {
            var instance        = node.object
            
            if (instance.meta.does('KiokuJS.Feature.Class.OwnID')) return instance.acquireID(desiredId, node)
            
            return this.SUPER(node, desiredId)
        },
        
        
        eachAttribute : function (instance, func, scope) {
            var meta            = instance.meta
            
            // XXX only store Joose.Managed.Attribute and Joose.Managed.Class?
            var scanAttribute = function (attribute, name) {
                var attributeLevel = attribute instanceof Joose.Managed.Attribute ? 2 : attribute instanceof Joose.Managed.Property.Attribute ? 1 : 0
                
                if (attributeLevel == 2 && attribute.meta.does(KiokuJS.Feature.Attribute.Skip)) return
                
                func.call(scope || null, attribute, name, attributeLevel)
            }
            
            if (meta instanceof Joose.Managed.Class)
                meta.getAttributes().each(scanAttribute)
            else
                Joose.O.each(meta.attributes, scanAttribute)
        },
        
        
        collapse : function (node, collapser) {
            var instance        = node.object
            var meta            = instance.meta
            var isManagedClass  = meta instanceof Joose.Managed.Class
            var data            = {}
            
            // if node already has `data`, then either it is being collapsed for the 2nd time or were loaded from the backend
            // in both cases, if node represents an instance of immutable class, it becomes also immutable
            // and will be skipped from all `store/update/insert` commands 
            if (node.data && isManagedClass && meta.does(KiokuJS.Feature.Class.Immutable)) {
                node.immutable = true
                
                return node.data
            }
            
            this.eachAttribute(instance, function (attribute, name, attributeLevel) {
                
                if (attribute.hasValue(instance))
                    if (attributeLevel) {
                        data[ name ] = collapser.visit(attribute.getRawValueFrom(instance))
                        
                        if (attributeLevel == 2 && attribute.meta.does(KiokuJS.Aspect.AfterCollapse)) attribute.afterCollapse(instance, data[ name ], node, collapser, attribute)
                        
                    } else
                        // Joose.Proto.Class attributes - just raw values
                        data[ name ] = collapser.visit(instance[ name ])
            })
            
            
            // instance has traits
            if (meta.isDetached) node.objTraits = Joose.A.map(meta.getRoles(), function (trait) {
                var traitName = trait.meta.name
                
                if (!traitName) throw "Can't serialize instance [" + instance + "] - it contains an anonymous trait"
                
                return trait.meta.VERSION ? {
                    type    : 'joose',
                    token   : traitName,
                    version : trait.meta.VERSION
                } : traitName
            })
            
            node.classVersion = meta.VERSION
            
            if (isManagedClass && meta.does(KiokuJS.Aspect.AfterCollapse)) instance.afterCollapse(node, collapser)
            
            return data
        },
        
        
        clearInstance : function (node) {
            var instance        = node.object
            
            this.eachAttribute(instance, function (attribute, name, attributeLevel) {
                
                if (attributeLevel)
                    delete instance[ attribute.slot ]
                else
                    // Joose.Proto.Class attributes - just raw values
                    delete instance[ attribute.name ]
            })
        },
        
        
        createEmptyInstance : function (node) {
            var constructor     = node.getClass()
            
            var classVersion = constructor.meta.VERSION
            
            if (this.isVersionExact && classVersion && classVersion != node.classVersion) 
                throw "Typemap [" + this + "] handles only exact version [" + classVersion + "] of class [" + node.className + ']'
            
            if (node.objTraits) {
                var traits = Joose.A.map(node.objTraits, function (traitOrDesc) {
                    if (typeof traitOrDesc == 'string') return eval(traitOrDesc)
                    
                    return eval(traitOrDesc.token)
                })
                
                constructor = constructor.meta.subClass({
                    does : traits 
                }, node.className)
                
                constructor.meta.isDetached = true
            }
            
            var f               = function () {}
            f.prototype         = constructor.prototype
            
            return new f()
        },
        
        
        populate : function (node, expander) {
            var instance        = node.object
            var data            = node.data
            
            // now that instance for `node.ID` is already pinned and we can assign its attributes (which can contain 
            // self-references for example)
            this.eachAttribute(instance, function (attribute, name, attributeLevel) {
                
                if (data.hasOwnProperty(name))
                    if (attributeLevel)
                        attribute.setRawValueTo(instance, expander.visit(data[ name ]))
                    else
                        // Joose.Proto.Class attributes - just raw values
                        instance[ attribute.name ] = expander.visit(data[ name ])
            })
        }
    }

})
;
Class('KiokuJS.Resolver', {
    
    trait   : 'JooseX.CPS',
    
    
    has : {
        entries             : Joose.I.Array,
        
        parent              : null,
        
        cache               : Joose.I.Object,
        
        classesFetched      : false
    },
    
    
    methods : {
        
        BUILD   : function (param) {
            if (param instanceof Array) return {
                entries : param
            }
            
            return this.SUPERARG(arguments)
        },
        
        
        initialize : function () {
            
            var entries     = this.entries
            
            Joose.A.each(entries, function (entry, index) {
                    
                entries[ index ] = this.prepareEntry(entry)
                
            }, this)
        },
        
        
        prepareEntry : function (entry) {
            if (!entry) throw "Can't add empty entry to resolver : " + this
            
            if (!Joose.O.isInstance(entry)) {
                var entryClass = eval(entry.meta)
                delete entry.meta
                
                entry = new entryClass(entry)
            }
            
            if (entry instanceof KiokuJS.Resolver) entry.parent = this 
            
            return entry
        },
        

        //XXX implement full CRUD for entries
        addEntry : function (entry) {
            this.entries.push(this.prepareEntry(entry))
            
            this.discardCache()
        },
        
        
        getEntryAt : function (index) {
            return this.entries[ index ]
        },
        
        
        discardCache : function () {
            this.cache              = {}
            this.classesFetched     = false
            
            if (this.parent) this.parent.discardCache()
        },
        
        
        each : function (func, scope) {
            scope = scope || this
            
            return Joose.A.each(this.entries, function (entry) {
                
                if (entry instanceof KiokuJS.Resolver) 
                    return entry.each(func, scope)
                else 
                    return func.call(scope, entry)
            })
        },
        
        
        resolveSingle : function (className) {
            var cache = this.cache
            
            if (cache[ className ]) return cache[ className ]
            
            var typeMap
            
            this.each(function (entry) {
                
                if (entry instanceof KiokuJS.Resolver) { 
                    typeMap = entry.resolveSingle(className)
                    
                    if (typeMap) return false
                } else 
                    if (entry instanceof KiokuJS.TypeMap) { 
                        
                        if (entry.canHandle(className)) {
                            typeMap = entry
                            
                            return false
                        }
                    } else
                        throw "Invalid entry [" + entry + "] in resolver + [" + this + "]"
            })
            
            if (typeMap) return cache[ className ] = typeMap
        },
        
        
        resolveMulti : function (classNames) {
            return Joose.A.map(classNames, this.resolveSingle, this)
        }
        
    },
    
    
    continued : {
        
        methods : {
        

            fetchClasses : function () {
                if (this.classesFetched) {
                    this.CONTINUE()
                    
                    return
                }
                
                
                var classes = []
                
                this.each(function (entry) {
                    classes.push.apply(classes, entry.getRequiredClasses())
                })

                
                var me      = this
                var CONT    = this.CONT
                
                use(classes, function () {
                    me.classesFetched = true
                    
                    CONT.CONTINUE()
                })
            },

            
            resolve : function (classNames) {
                (this.classesFetched ? this : this.fetchClasses()).andTHEN(function () {
                    
                    this.CONTINUE(this.resolveMulti(classNames))
                })
            }
        }
    }

})
;
Class('KiokuJS.Resolver.Standard', {
    
    isa         : 'KiokuJS.Resolver',
    
    
    use         : [
        'KiokuJS.TypeMap.Role.NoDeps',
    
        'KiokuJS.TypeMap.Joose',
        
        'KiokuJS.TypeMap.Object',
        'KiokuJS.TypeMap.Array',
        
        'KiokuJS.TypeMap.Function',
        'KiokuJS.TypeMap.Date'
    ],
    

    
    after : {
        
        initialize : function () {
            
            // the order matter
            
            this.addEntry(new KiokuJS.TypeMap.Joose({
                trait   : KiokuJS.TypeMap.Role.NoDeps,
                
                inherit : true
            }))
            
            this.addEntry(new KiokuJS.TypeMap.Object())
            
            this.addEntry(new KiokuJS.TypeMap.Array())
            
            this.addEntry(new KiokuJS.TypeMap.Function())
            
            this.addEntry(new KiokuJS.TypeMap.Date())
        }
    }

})
;
Role('KiokuJS.Role.Resolvable', {
    
    requires : [ 'getResolver' ],
    
        
    methods : {
        
        getClassNameFor : function (object) {
            if (Joose.O.isInstance(object))      return object.meta.name
            
            return Object.prototype.toString.call(object).replace(/^\[object /, '').replace(/\]$/, '')
        },
        
        
        getTypeMapFor : function (className) {
            if (typeof className != 'string') className = this.getClassNameFor(className)
            
            var typeMap = this.getResolver().resolveSingle(className)
            
            if (!typeMap) throw "Can't find TypeMap entry for className = [" + className + "]"
            
            return typeMap
        }
    }
})
;
Class('KiokuJS.Node', {
    
    use     : 'Data.Visitor',
    
    does    : 'KiokuJS.Role.Resolvable',
    
    
    has : {
        // stored attributes
        ID              : null,
        
        className       : undefined,
        classVersion    : undefined,
        objTraits       : undefined,
        
        isRoot          : undefined,
        data            : null,
        
        // run-time attributes
        object      : {
            is  : 'rw'
        },
        
        // arbitrary data about object
        objectData  : null,
        
        typeMap     : {
            is      : 'ro',
            lazy    : function () { return this.getTypeMapFor(this.className) }
        },
        
        resolver    : {
            is          : 'rw',
            required    : true
        },
        
        entry       : {
            is      : 'ro',
            lazy    : 'this.buildEntry'
        },
        
        
        selfReference   : {
            is      : 'ro',
            lazy    : 'this.buildSelfReference'
        },
        
        // node will produce separate first-class (extrinisic) entry unless it (or its typemap) has `intrinsic` flag set
        extrinsic   : false,
        
        // node will produce intrinsic entries
        intrinsic   : false,
        
        // node will be skipped from the collapse result (effectively from all store operations)
        immutable   : false,
        
        // node will contain a lazy reference
        lazy        : false
    },
    

    methods : {
        
        initialize : function () {
            var object      = this.object
            var className   = this.className
            
            if (!className && !object) throw "Either `object` or `className` with `data` must be supplied during instantiation of node [" + this + "]"
            
            if (!className) this.className = this.getClassNameFor(object)
        },
        
        
        isLive : function () {
            return this.object != null 
        },
        
        
        isFirstClass : function () {
            return this.ID != null
        },
        
        
        isIntrinsic : function () {
            return this.intrinsic || this.getTypeMap().intrinsic
        },
        
        
        getClass : function () {
            return eval(this.className)
        },
        
        
        acquireID : function (desiredId) {
            var ID      = this.ID
            
            if (ID) {
                if (desiredId != null && ID != desiredId) throw "Attempt to redefine the ID of node [" + this + "] from [" + ID + "] to [" + desiredId + "]"
                
                return
            }
            
            this.ID = this.getTypeMap().acquireID(this, desiredId)
        },
        
        
        // XXX implement clear/predicate for attribute
        clearEntry : function () {
            delete this.entry
        },
        
        
        clearInstance : function () {
            if (!this.object) throw "Node [" + this + "] doesn't contain an object instance to clear"
            
            this.getTypeMap().clearInstance(this)
        },
        
        
        buildEntry   : function () {
            
            var entry = {
                className       : this.className,
                classVersion    : this.classVersion,
                objTraits       : this.objTraits,
                
                isRoot          : this.isRoot,
                data            : this.data
            }
            
            if (this.ID != null) entry.ID = this.ID
            
            return entry
        },
        
        
        buildSelfReference : function () {
            var ref = {
                $ref    : this.ID
            }
            
            if (this.lazy) ref.type = 'lazy'
            
            return ref
        },
        

        collapse : function (collapser) {
            this.data = this.getTypeMap().collapse(this, collapser)
        },
        
        
        createEmptyInstance : function () {
            if (this.object) throw "Node [" + this + "] already contain an object instance"
            
            return this.object = this.getTypeMap().createEmptyInstance(this)
        },
        
        
        populate : function (expander) {
            if (!this.object) throw "Node [" + this + "] doesn't contain the object - can't be expanded"
            
            this.getTypeMap().populate(this, expander)
            
            return this.object
        },
        
        
        consumeOldNode : function (oldNode) {
            this.object = oldNode.object
            
            // this.isRoot = oldNode.isRoot seems this will be returned from DB in entry anyway
            
            // XXX also copy `intrinsic, extrinsic, immutable and lazy` ?
            // seems it will be recalculated during collapsing anyway?
        },
        
        
        consumeEntry : function (entry) {
            this.clearEntry()
        }
        
    },
    
    
    my : {
        
        has : {
            HOST    : null
        },
        
        
        methods : {
        
            newFromEntry : function (entry, resolver) {
                entry.resolver   = resolver
                
                return new this.HOST(entry)
            },
            
            
            newFromObject : function (object, resolver) {
                
                Data.Visitor.assignRefAdrTo(object)
                
                return new this.HOST({
                    object      : object,
                    
                    resolver    : resolver
                })
            }
        }
    }
});
Class('KiokuJS.Collapser.Encoder', {
    
    isa         : 'Data.Visitor',
    
    
    use         : [ 'KiokuJS.Reference', 'KiokuJS.Node' ],
    

    has         : {
        reservedKeys : /^\$ref$|^\$entry$/
    },
    
    
    methods : {
        
        encodeEntry : function (entry, node) {
            
            if (node.isFirstClass() || !node.getTypeMap().passThrough) {
                entry = this.visit(entry)
                
                entry.$entry = true
                 
                return entry
            }

            //passthrough the entries from non-firstclass nodes with native typemaps: [], {} 
            return this.visit(entry.data)
        },
        
        
        visitNode : function (node, needEntry) {
            if (node.isFirstClass() && !needEntry) return node.getSelfReference()
            
            return this.encodeEntry(node.getEntry(), node)
        },
        
        
        visitJooseInstance : function (node, className) {
            if (node instanceof KiokuJS.Node) return this.visitNode(node)

            throw "Invalid Joose instance [" + node + "] encountered during inlining - only `KiokuJS.Node` allowed"
        },
        
        
        // a bit faster visiting of array
        visitArray  : function (array, className) {
            return Joose.A.map(array, function (value) {
                
                return this.visit(value)
                
            }, this)
        },
        
        
        visitObject : function (object, className) {
            var res = {}
            
            Joose.O.eachOwn(object, function (value, key) {
                
                if (this.reservedKeys.test(key)) key = 'public:' + key
                
                res[ key ] = this.visit(value)
            }, this)
            
            return res
        }
    },
    
    
    my : {

        methods : {
            
            encodeNodes : function (nodes) {
                
                var instance = new this.HOST()
                
                return Joose.A.map(nodes, function (node) {
                    
                    return instance.visitNode(node, true)
                })
            }
        }                    
    }
})
;
Class('KiokuJS.Linker.Decoder', {
    
    isa         : 'Data.Visitor',
    
    use         : 'KiokuJS.Reference',
    

    has         : {
        backend         : { required : true },
        
        reservedKeys    : /^\$ref$|^\$entry$/
    },
    
    
    methods : {
        
        visitJooseInstance : function (node, className) {
            throw "Joose instance [" + node + "] encountered during decoding - data should contain only native structures"
        },
        
        
        // a bit faster visiting of array
        visitArray  : function (array, className) {
            return Joose.A.map(array, function (value) {
                
                return this.visit(value)
                
            }, this)
        },
        
        
        visitObject : function (object, className) {
            var refID = object.$ref
            
            if (refID)
                return new KiokuJS.Reference({
                    ID      : refID,
                    type    : object.type
                })
                
            var decodedObject = this.visitNativeObject(object, className)
            
            if (object.$entry) return this.backend.createNodeFromEntry(decodedObject)
            
            return decodedObject
        },
        
        
        visitNativeObject : function (object, className) {
            var res = {}
            
            Joose.O.eachOwn(object, function (value, key) {
                
                // ignore `$entry` mark from entries
                if (key == '$entry') return
                
                if (/^public:/.test(key)) {
                    var reservedKey = key.replace(/^public:/, '')
                    
                    if (this.reservedKeys.test(reservedKey)) key = reservedKey
                }
                
                res[ key ] = this.visit(value)
                
            }, this)
            
            return res
        }
    },
    
    
    my : {

        methods : {
            
            decodeEntries : function (entries, backend) {
                var instance = new this.HOST({
                    backend : backend
                })
                
                return instance.visit(entries)
            }
        }                    
    }
})
;
Class('KiokuJS.Linker.RefGatherer', {
    
    isa         : 'Data.Visitor',
    
    use         : 'KiokuJS.Reference',
    
    
    has : {
        references      : Joose.I.Object
    },    
    
    
    methods : {
        
        visitObject : function (object, className) {
            if (object.$ref && object.type != 'lazy') this.references[ object.$ref ] = true
            
            return this.SUPERARG(arguments)
        }
    },
    
    
    my : {
        
        methods : {
            
            gatherReferences   : function (data) {
                
                var instance = new this.HOST()
                
                instance.visit(data)
                
                var uniqueRefs = []
                
                Joose.O.each(instance.references, function (value, ref) {
                    uniqueRefs.push(ref)
                })
                
                return uniqueRefs
            }
        }                    
    }
})
;
Class('KiokuJS.Linker.Expander', {
    
    isa         : 'Data.Visitor',
    
    use         : 'KiokuJS.Reference',
    
    
    has         : {
        scope           : { required : true },
        nodes           : { required : true }
    },
    
    
    methods : {
        
        visitNode : function (node) {
            var scope   = this.scope
            
            var oldNode = scope.idToNode(node.ID)
            
            if (oldNode) node.consumeOldNode(oldNode)
            
            if (node.isLive())
                node.clearInstance()
            else 
                // newly created instance need to has the __REF_ADR__ as this property
                // is being extensively used internally
                this.assignRefAdrTo(node.createEmptyInstance())
            
            return node.populate(this)
        },
        
        
        visitReference : function (reference) {
            
            var refID   = reference.ID
            
            var refNode = this.nodes[ refID ] || this.scope.idToNode(refID)
            
            if (!refNode) 
                if (reference.type != 'lazy') 
                    throw new KiokuJS.Exception.LookUp({ id : refID, backendName : "Expander working set" })
                else
                    return reference
            
            
            if (refNode.isLive()) {
                var instance = refNode.getObject()
                
                this.assignRefAdrTo(instance)
                
                return instance
            }
            
            // `visit` and not(!) `visitNode` to utilize the `seen` cache for already processed nodes
            return this.visit(refNode)
        },
        
        
        visitJooseInstance : function (node, className) {
            if (node instanceof KiokuJS.Node) return this.visitNode(node)

            if (node instanceof KiokuJS.Reference) return this.visitReference(node)

            throw "Invalid Joose instance [" + node + "] encountered during inlining - only `KiokuJS.Node` and `KiokuJS.Reference` allowed"
        },
        
        
        visitArray  : function (array, className) {
            return Joose.A.map(array, function (value, index) {
                
                return this.visit(value)
            }, this)
        },
        
        
        visitObject : function (object, className) {
            var res = {}
            
            Joose.O.eachOwn(object, function (value, key) {
                
                res[ key ] = this.visit(value)
            }, this)
            
            return res
        }
    },
    
    
    my : {
        
        methods : {
            
            expandNodes   : function (nodes, scope) {
                
                var instance = new this.HOST({
                    scope       : scope,
                    nodes       : nodes
                })
                
                return instance.visit(nodes)
            }
        }                    
    }
})
;
Role('KiokuJS.Backend.Role.SkipFixture', {
    
    requires : [ 'skipFixtures' ]

})

;
// Backend can detect overwrite attempts

Role('KiokuJS.Backend.Feature.Overwrite')
;
// Backend can detect incorrect updates (w/o corresponding entry in the storage)

Role('KiokuJS.Backend.Feature.Update')
;
Class('KiokuJS.Backend', {
    
    trait   : 'JooseX.CPS',
    
    use     : [
        'KiokuJS.Resolver', 
        'KiokuJS.Resolver.Standard', 
    
        'KiokuJS.Serializer.JSON',
        
        'KiokuJS.Scope',
        'KiokuJS.Node' 
    ],
    
    
    has : {
        nodeClass       : Joose.I.FutureClass('KiokuJS.Node'),
        scopeClass      : Joose.I.FutureClass('KiokuJS.Scope'),
        
        resolver        : null,
        
        serializer      : {
            handles     : [ 'serialize', 'deserialize' ],
            init        : Joose.I.FutureClass('KiokuJS.Serializer.JSON')
        }
    },
    
        
    methods : {
        
        initialize : function () {
            var resolver    = this.resolver
            
            // wrapping the possibly passed resolver with another one, containig the standard resolver as the lowest-priority
            this.resolver = new KiokuJS.Resolver( 
                (resolver ? [ resolver ] : []).concat( new KiokuJS.Resolver.Standard() )
            )
        },
        
        
        newScope    : function (options) {
            return new this.scopeClass(Joose.O.copy({
                backend     : this,
                resolver    : this.resolver
            }, options))
        },
        
        
        createNodeFromEntry : function (entry) {
            return this.nodeClass.newFromEntry(entry, this.resolver)
        },
        
        
        createNodeFromObject : function (object) {
            return this.nodeClass.newFromObject(object, this.resolver)
        },
        
        
        decodePacket : function (packet) {
            var scope = this.newScope()
            
            var linker = new KiokuJS.Linker({
                scope       : scope,
                
                entries     : packet.entries
            })
            
            linker.animateNodes()
            
            var objects = {}
            
            Joose.A.each(packet.customIDs, function (id) { 
                objects[ id ] = scope.idToObject(id)
            })
                
            return [ objects, Joose.A.map(packet.IDs, scope.idToObject, scope) ]
        },
        
        
        encodePacket : function (wIDs, woIDs) {
            var scope = this.newScope()
            
            return scope.includeNewObjects(wIDs, woIDs)
        }
    },
    
    
    continued : {
        
        methods : {
            
            get     : function (idsToGet, mode) {
                throw "Abstract method 'get' called for " + this
            },
            
            
            insert  : function (entriesToInsert, mode) {
                throw "Abstract method 'insert' called for " + this
            },
            
            
            remove  : function (idsOrEntriesToRemove) {
                throw "Abstract method 'remove' called for " + this
            },
            
            
            exists  : function (idsToCheck) {
                throw "Abstract method 'exists' called for " + this
            },
            
            
            search : function (scope, arguments) {
                throw "Abstract method 'search' called for " + this
            }
        }
    }

})

// placing this override here, since backend is always required

// XXX need to keep in sync with original `Joose.O.each`
Joose.O.each = function (object, func, scope) {
    scope = scope || this
    
    for (var i in object) 
        if (i != '__REFADR__')
            if (func.call(scope, object[i], i) === false) return false
    
    if (Joose.is_IE) 
        return Joose.A.each([ 'toString', 'constructor', 'hasOwnProperty' ], function (el) {
            
            if (object.hasOwnProperty(el)) return func.call(scope, object[el], el)
        })
}


Joose.O.isEmpty = function (object) {
    for (var i in object) if (object.hasOwnProperty(i) && i != '__REFADR__') return false
    
    return true
}
;
Class('KiokuJS.Collapser', {
    
    isa         : 'Data.Visitor',
    
    
    has : {
        refCounts           : Joose.I.Object,
        nodes               : Joose.I.Object, //vertexes, addressed by __REFADR__
        
        backend             : null,
        scope               : { required : true },
        
        isShallow           : false,
        setRoot             : true,
        
        rootObjects         : Joose.I.Object
    },
    
        
    
    before : {
        
        markSeenAs    : function (object) {
            this.refCounts[ object.__REFADR__ ] = 1
        }
        
    },
    
    
    methods : {
        
        initialize : function () {
            this.backend = this.scope.getBackend()
        },
        
        
        visitSeen : function (object, seen) {
            var refAdr = object.__REFADR__
            
            this.refCounts[ refAdr ]++
            
            // return either the node from `this.nodes` - for nodes being collapsed
            // or "seen" node - for nodes which are skipped during shallow collapsing
            return this.nodes[ refAdr ] || seen
        },
        
        
        // calls `func` with (object, desiredID) signature for each passed argument
        eachArgument : function (wIDs, woIDs, func, scope) {
            scope = scope || this
            
            Joose.O.each(wIDs, func, scope)
            
            Joose.A.each(woIDs, function (argument) {
                func.call(scope, argument)
            })
        },
        
        
        collapse : function (wIDs, woIDs) {
            
            // sanity checks
            
            this.eachArgument(wIDs, woIDs, function (argument) {
                if (argument == null || (typeof argument != 'object' && typeof argument != 'function') ) throw "Invalid argument [" + argument + "]  to 'collapse'. Can only collapse objects, not values."
                
                var refAdr = this.assignRefAdrTo(argument)
                
                this.rootObjects[ refAdr ] = true
            })
            
            
            // recurse through the graph, accumulating nodes and counting refs
            
            this.visit(wIDs, woIDs)
            

            // all objects are from root set, so we need to acquire IDs for them
            
            var nodes               = this.nodes
            
            this.eachArgument(wIDs, woIDs, function (argument, desiredId) {
                var node = nodes[ argument.__REFADR__ ]
                
                node.acquireID(desiredId)
                if (this.setRoot) node.isRoot = true
            })


            // also marks shared nodes (refCount > 1) and Joose instances (unless intrinsic) as first class 
            
            var refCounts           = this.refCounts
            var firstClassNodes     = []
            var me                  = this
            
            Joose.O.each(nodes, function (node, refadr) {
                
                var object      = node.object
                var isExtrinsic = (refCounts[ refadr ] > 1 || Joose.O.isInstance(object) || node.extrinsic || node.lazy) && !node.isIntrinsic()
                
                if ( me.belongsToRootSet(object) || isExtrinsic) {
                    // this makes this node `firstClass` (but not root)
                    if (!node.isFirstClass()) node.acquireID()
                    
                    if (!node.immutable) firstClassNodes.push(node)
                }
            })
            
            
            return firstClassNodes
        },
     
        
        visitArray : function (instance, className) {
            return this.visitObject(instance, className)
        },
        
        
        belongsToRootSet : function (object) {
            return this.rootObjects[ object.__REFADR__ ]
        },
        
        
        visitObject : function (instance, className) {
            var scope           = this.scope
            var nodes           = this.nodes
            
            var node            = scope.objectToNode(instance)
            var refAdr          = instance.__REFADR__
            
            // if this is a shallow collapsing, and we found the instance which already has the node
            // then stop collapsing at this point and do not recurse
            // also do not add the node into `this.nodes` to prevent it from returning as a result of `collapse`
            if (node && (this.isShallow && !this.belongsToRootSet(instance) || node.immutable)) return node
                
            if (node) {
                node.clearEntry()
                
                nodes[ refAdr ] = node
            } else
                // need to create the node before recursing through the instance's data, to handle circular-references correctly
                nodes[ refAdr ] = node = this.backend.createNodeFromObject(instance)
            
            // now that node is already in the `this.nodes` we can collect the data
            node.collapse(this)
            
            return node
        }
    }

})


;
Class('KiokuJS.Linker', {
    
    trait   : 'JooseX.CPS',
    
    
    use     : 'KiokuJS.Linker.Expander',
    
    
    has : {
        scope           : { 
            required        : true,
            handles         : 'decodeEntries'
        },
        
        entries         : Joose.I.Object,
        
        nodes           : {
            lazy    : function () {
                return this.decodeEntries(this.entries)
            }
        }
    },
    

    methods : {
        
        BUILD : function (config) {
            var entries = config.entries
            
            if (entries instanceof Array) {
                var entriesByID = {}
                
                Joose.A.each(entries, function (entry) {
                    entriesByID[ entry.ID ] = entry
                })
                
                config.entries = entriesByID
            }
            
            return config
        },
        
        
        animateNodes : function () {
            var scope   = this.scope
            var nodes   = this.getNodes()
            
            KiokuJS.Linker.Expander.expandNodes(nodes, this.scope)
            
            Joose.O.each(nodes, function (node, id) {
                
                if (node.isFirstClass()) scope.pinNode(node)
            })
        }
    },
    
    
    continued : {
        
        methods : {
            
            materialize : function (ids, shallowLevel) {
                var me          = this
                var scope       = this.scope
                var backend     = scope.getBackend()
                
                var idsToFetch  = []
                
                Joose.A.each(ids, function (id) {
                    if (!scope.idPinned(id) || shallowLevel > 0) idsToFetch.push(id)
                })
                

                backend.get(idsToFetch, scope).andThen(function (entries) {
                    
                    var newEntries        = []
                    
                    // filter the entries returned from backend to only the new ones
                    // (which don't already have corresponding object in the scope)
                    // this should allow backends to pre-fetch references (potentially
                    // with extra entries)
                    Joose.A.each(entries, function (entry) {
                        
                        var entryID      = entry.ID
                        
                        // some entry was returned repeatedly
                        if (me.entries[ entryID ]) return
                        
                        if (!scope.idPinned(entryID) || shallowLevel > 0) {

                            me.entries[ entryID ] = entry
                            
                            newEntries.push(entry)
                        }
                    })
                    
                    var notFetchedIds   = []
                    
                    Joose.A.each(scope.gatherReferences(newEntries), function (refID) {
                        if (me.entries[ refID ]) return
                        
                        if (!scope.idPinned(refID) || shallowLevel == 2) notFetchedIds.push(refID)
                    })
                    
                    if (notFetchedIds.length)
                        me.materialize(notFetchedIds, shallowLevel == 2 ? 2 : 0).now()
                    else 
                        me.prefetchClasses(me.getNodes()).andThen(function () {
                            
                            me.animateNodes()
                            
                            this.CONTINUE()
                        })
                })
            },
            
            
            prefetchClasses : function (nodes) {
                // gathering classes of the nodes, which needs to be loaded
                var classDescriptors = []
                
                //XXX extract required classes from typemap instead of directly from node (node.getRequiredClasses())
                Joose.O.each(nodes, function (node, id) {
                    
                    var className = node.className
                    
                    if (className == 'Object' || className == 'Array') return
                    
                    if (node.classVersion) 
                        classDescriptors.push({ type : 'joose', token : className, version : node.classVersion })
                    else
                        classDescriptors.push(className)
                        
                    if (node.objTraits) classDescriptors.push.apply(classDescriptors, node.objTraits)
                })
                
                use(classDescriptors, this.getCONTINUE())
            },
            
            
            link : function (ids, shallowLevel) {
                // fetching resolver's classes (in case it has been mutated)
                this.scope.getResolver().fetchClasses().andThen(function () {
                    
                    this.materialize(ids, shallowLevel).now()
                    
                }, this)
            }
        }
    }
    

})


;
Class('KiokuJS.Scope', {
    
    trait   : 'JooseX.CPS',
    
    use     : [ 
        'KiokuJS.Collapser', 
        'KiokuJS.Exception.LookUp',
        
        'KiokuJS.Collapser.Encoder', 
        'KiokuJS.Linker.Decoder', 
        'KiokuJS.Linker.RefGatherer' 
    ],
    
    
    has : {
        backend         : {
            is          : 'ro',
            required    : true
        },
        
//        parent          : null,        
        
        // only store the first class nodes (with ID) & live ones
        nodesByREFADR   : Joose.I.Object,
        nodesByID       : Joose.I.Object,
        
        encoder         : {
            handles     : 'encodeNodes',
            init        : Joose.I.FutureClass('KiokuJS.Collapser.Encoder')
        },
        decoder         : Joose.I.FutureClass('KiokuJS.Linker.Decoder'),
        
        gatherer        : {
            handles     : 'gatherReferences',
            init        : Joose.I.FutureClass('KiokuJS.Linker.RefGatherer')
        }
    },
    
        
    methods : {
        
        getResolver : function () {
            return this.backend.resolver
        },
        
        
        registerProxy : function (object, ID) {
            
            var node = this.backend.createNodeFromObject(object)
            
            // XXX proxy will currently only work, when linking with the `shallowLevel` == 0
            node.immutable  = true
            
            if (!node.isFirstClass()) node.acquireID(ID)
            
            this.pinNode(node)
        },
        
        
//        deriveChild : function (options) {
//            return new this.constructor(Joose.O.copy({
//                parent          : this,
//                
//                nodesByREFADR   : Joose.O.getMutableCopy(this.nodesByREFADR),
//                nodesByID       : Joose.O.getMutableCopy(this.nodesByID)
//            }, options))
//        },        
        
        
        // node *must* be live
        pinNode : function (node) {
            var nodeID = node.ID
            
//            if (!this.hasID(nodeID) || this.hasOwnID(nodeID)) {
                
                if (!node.isLive()) throw "Can pin only live nodes"
                
                this.nodesByID[ nodeID ] = node
                
                var object  = node.getObject()
                
                this.nodesByREFADR[ object.__REFADR__ ] = node
//            } else
//                // XXX no proto inheritance already
//                this.parent.pinNode(node)
        },
        
        
        unpinNode : function (node) {
            var nodeID = node.ID
            
//            if (this.hasOwnID( nodeID )) {
            
                if (!node.isLive()) throw "Can unpin only live node"
                
                delete this.nodesByID[ nodeID ]
                
                var REFADR  = node.getObject().__REFADR__
                
                delete this.nodesByREFADR[ REFADR ]
//            } else
//                // XXX no proto inheritance already
//                this.parent.unpinNode(node)
        },
        

        unpinID : function (id) {
            var node = this.idToNode(id)
            
            if (node) 
                this.unpinNode(node)
            else
                throw "ID [" + id + "] is not in scope - can't unpin it"
        },
        
        
        objectToNode : function (obj) {
            return this.nodesByREFADR[ obj.__REFADR__ ]
        },
        
        
        nodeToObject : function (node) {
            var ownNode     = this.nodesByID[ node.ID ]
            
            return ownNode && ownNode.getObject()
        },
        
        
        objectToId : function (obj) {
            return obj.__REFADR__ && this.nodesByREFADR[ obj.__REFADR__ ].ID || null
        },
        
        
        idToObject : function (id) {
            var node = this.nodesByID[ id ]
            
            return node && node.getObject()
        },
        
        
        idToNode : function (id) {
            return this.nodesByID[ id ]
        },
        
        
        idPinned : function (id) {
            return this.nodesByID[ id ] != null
        },
        
        
        nodePinned : function (node) {
            return node.ID && this.nodesByID[ node.ID ] != null
        },
        
        
        objectPinned : function (object) {
            return object.__REFADR__ && this.nodesByREFADR[ object.__REFADR__ ] != null || false
        },
        
        
//        getOwnNodes : function () {
//            return Joose.O.copyOwn(this.nodesByID)
//        },
//        
//        
//        
//        hasID : function (id) {
//            return this.nodesByID[ id ] != null
//        },
//        
//        
//        hasOwnID : function (id) {
//            return this.nodesByID.hasOwnProperty( id )
//        },
        
        
        collapse : function (wIDs, woIDs, options) {
            options             = options || {}
            
            options.backend     = this.getBackend()
            options.scope       = this
            
            return new KiokuJS.Collapser(options).collapse(wIDs, woIDs)
        },
        
        
        encodeNode : function (node) {
            return this.encoder.encodeNodes([ node ])[ 0 ]
        },
        
        
        decodeEntry : function (entry) {
            return this.decoder.decodeEntries([ entry ], this.getBackend())[ 0 ]
        },
        
        
        decodeEntries : function (entries) {
            return this.decoder.decodeEntries(entries, this.getBackend())
        },
        
        
        includeNewObjects : function (wIDs, woIDs) {
            var nodes   = this.collapse(wIDs, woIDs, { isShallow : true })
            
            Joose.A.each(nodes, this.pinNode, this)
            
            var customIDs = []
            
            Joose.O.each(wIDs, function (object, id) {
                customIDs.push(id)
            })
            
            return {
                entries     : this.encodeNodes(nodes),
                customIDs   : customIDs,
                IDs         : Joose.A.map(woIDs, this.objectToId, this)
            }
        }
    },
    
    
    continued : {
        
        methods : {
            
            store : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'store',
                    shallow     : false
                    
                }).now()
            },
            
            
            storeAs : function () {
                var woIDs = Array.prototype.slice.call(arguments)
                
                this.storeObjects({
                    
                    wIDs        : woIDs.shift(),
                    woIDs       : woIDs,
                    
                    mode        : 'store',
                    shallow     : false
                    
                }).now()
            },
            
            
            update : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'update',
                    shallow     : true
                    
                }).now()
            },
            
            
            deepUpdate : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'update',
                    shallow     : false
                    
                }).now()
            },
            
            
            insert : function () {
                this.storeObjects({
                    
                    wIDs        : {},
                    woIDs       : Array.prototype.slice.call(arguments),
                    
                    mode        : 'insert',
                    shallow     : true
                    
                }).now()
            },
            
            
            insertAs : function () {
                var woIDs = Array.prototype.slice.call(arguments)
                
                this.storeObjects({
                    
                    wIDs        : woIDs.shift(),
                    woIDs       : woIDs,
                    
                    mode        : 'insert',
                    shallow     : true
                    
                }).now()
            },
            
            
            
            storeObjects : function (args) {
                var wIDs        = args.wIDs     || {}
                var woIDs       = args.woIDs    || []
                var mode        = args.mode     || 'store'    
                var shallow     = args.shallow  || false
                var setRoot     = args.setRoot
                
                
                var resolver    = this.getResolver()
                var backend     = this.getBackend()
                var self        = this
                
                resolver.fetchClasses().andThen(function () {
                    
                    var firstClassNodes = self.collapse(wIDs, woIDs, {
                        isShallow           : shallow,
                        setRoot             : setRoot != null ? setRoot : true
                    })
                    
                    // saving only first-class nodes - by design they'll contain a description of the whole graph
                    backend.insert(self.encodeNodes(firstClassNodes), mode).andThen(function (entries) {
                        
                        // pin nodes only after successfull insert and only those not pinned yet 
                        Joose.A.each(firstClassNodes, function (node, index) {
                            
                            node.consumeEntry(entries[ index ])
                            
                            if (!self.nodePinned(node)) self.pinNode(node)
                        })
                        
                        this.CONTINUE.apply(this, Joose.A.map(woIDs, self.objectToId, self))
                    })
                })
            },
            
            
            remove : function () {
                var me          = this
                
                var entriesOrIds  = Joose.A.map(arguments, function (arg) {
                    // id
                    // trying to replace an ID with entry where possible, as it has more information attached
                    if (typeof arg == 'string') return me.idPinned(arg) ? me.idToNode(arg).getEntry() : arg
                    
                    // object
                    if (!me.objectPinned(arg)) 
                        throw new KiokuJS.Exception.Remove({
                            message : "Can't remove object [" + arg + "] - its not in the scope"
                        })
                    
                    return me.objectToNode(arg).getEntry()
                })
                
                
                var backend  = this.getBackend()
                
                backend.remove(entriesOrIds).andThen(function () {
                    
                    Joose.A.each(entriesOrIds, function (entryOrId) {
                        
                        me.unpinID(typeof entryOrId == 'string' ? entryOrId : entryOrId.ID)
                    })
                    
                    this.CONTINUE()
                })
            },
            
            
            animatePacket : function (packet) {
                var me          = this
                
                var linker = new KiokuJS.Linker({
                    scope       : this,
                    
                    entries     : packet.entries // entries will be converted from Array to Object (by ID)
                })
                
                var entries         = linker.entries
                var notFetchedRefs  = []
                
                Joose.A.each(this.gatherReferences(entries), function (refID) {
                    if (entries[ refID ]) return
                    
                    if (!me.idPinned(refID)) notFetchedRefs.push(refID)
                })
                
                
                linker.link(notFetchedRefs, 0).andThen(function () {
                    
                    var objects = {}
                    
                    Joose.A.each(packet.customIDs, function (id) { 
                        objects[ id ] = me.idToObject(id)
                    })
                    
                    this.CONTINUE.apply(this, [ objects, Joose.A.map(packet.IDs, me.idToObject, me) ])
                })
            },            
            
            
            // `idsToFetch` - array of ids to fetch and materialize from backend 
            // `shallowLevel == 0` - means stop fetching at the nodes already in scope 
            // `shallowLevel == 1` - means fetching the passed `idsToFetch` anyway, but stop on further references 
            // `shallowLevel == 2` - full deep refresh 
            fetch : function (idsToFetch, shallowLevel) {
                var me          = this
                
                // linker will have an access to backend through the scope
                var linker = new KiokuJS.Linker({
                    scope       : this
                })
                
                linker.link(idsToFetch, shallowLevel).andThen(function () {
                    
                    this.CONTINUE.apply(this, Joose.A.map(idsToFetch, me.idToObject, me))
                })
            },
            
            
            lookUp : function () {
                this.fetch(Array.prototype.slice.call(arguments), 0).now()
            },
            
            
            refresh : function () {
                var me              = this
                var idsToFetch      = []
                
                Joose.A.each(arguments, function (object) {
                    if (!me.objectPinned(object)) throw "Can only refresh objects in scope" 
                    
                    idsToFetch.push(me.objectToId(object))
                })
                
                this.fetch(idsToFetch, 1).now()
            },
            
            
            deepRefresh : function () {
                var me              = this
                var idsToFetch      = []
                
                Joose.A.each(arguments, function (object) {
                    if (!me.objectPinned(object)) throw "Can only refresh objects in scope" 
                    
                    idsToFetch.push(me.objectToId(object))
                })
                
                this.fetch(idsToFetch, 2).now()
            },
            
            
            search : function () {
                var backend = this.getBackend()
                
                backend.search(this, arguments).now()
            }
        }
    }

})


;
Class('KiokuJS', {
    
//    my : {
//        
//        methods : {
//        
//            connect : function (config) {
//            }
//        }
//    }

})
;
Class('KiokuJS.Backend.Hash', {
    
    isa   : 'KiokuJS.Backend',
    
    
    has : {
        docs         : Joose.I.Object
    },
    
    
    does    : [ 'KiokuJS.Backend.Feature.Overwrite', 'KiokuJS.Backend.Feature.Update' ],
    
    
    use     : [ 'KiokuJS.Exception.Overwrite', 'KiokuJS.Exception.Update' ],
    
        
    continued : {
        
        methods : {
            
            get     : function (idsToGet, mode) {
                var docs = this.docs
                
                var strings = Joose.A.map(idsToGet, function (id) {
                    
                    if (docs[ id ]) return docs[ id ]
                    
                    throw new KiokuJS.Exception.LookUp({ id : id, backendName : this.meta.name })
                    
                }, this)
                
                
                var entries     = Joose.A.map(strings, this.deserialize, this)
                
                var CONTINUE    = this.getCONTINUE()
                
                setTimeout(function () {
                    CONTINUE(entries)
                }, 0)
            },
            
            
            insert  : function (entries, mode) {
                var docs        = this.docs
                
                var strings     = Joose.A.map(entries, this.serialize, this)
                
                Joose.A.each(entries, function (entry, index) {
                    
                    var ID  = entry.ID
                    
                    if (mode == 'insert' && docs[ ID ]) 
                        throw new KiokuJS.Exception.Overwrite({
                            id          : ID,
                            oldValue    : docs[ ID ],
                            newValue    : strings[ index ]
                        })
                    
                    if (mode == 'update' && !docs[ ID ]) 
                        throw new KiokuJS.Exception.Update({
                            message : "Attempt to update entry with ID = [" + ID + "], value = [" + strings[ index ] + "], but no such entry in storage"
                        })
                        
                        
                    docs[ ID ] = strings[ index ]
                    
                    return ID
                })
                
                var CONTINUE = this.getCONTINUE()
                
                setTimeout(function () {
                    CONTINUE(entries)
                }, 0)
            },
            
            
            remove  : function (idsOrEntriesToRemove) {
                var docs = this.docs
                
                Joose.A.each(idsOrEntriesToRemove, function (id) {
                    
                    // if `id` is an entry
                    if (id === Object(id)) id = id.ID
                    
                    delete docs[ id ]
                })
                
                setTimeout(this.getCONTINUE(), 0)
            },
            
            
            exists  : function (idsToCheck) {
                var docs = this.docs
                
                var checks = Joose.A.map(idsToCheck, function (id) {
                    return docs[ id ] != null
                })
                
                var CONTINUE = this.getCONTINUE()
                
                setTimeout(function () {
                    CONTINUE(checks)
                }, 0)
                
            }
        }
    }

})
;
Class('KiokuJS.Test.Person', {
    
    has : {
        name    : null,
        
        self    : null,
        
        spouse  : {
            is  : 'rwc'
        },
        
        father  : null,
        mother  : null,
        
        children : Joose.I.Array,
        
        mood    : {
            is : 'rw'
        },
        
        age     : 0,
        
        task    : null
    },
    
    
    methods : {
        
        initialize : function () {
            this.self = this
        }
    }
})
;
Role('KiokuJS.Test.Person.Hobby', {
    
    requires : [ 'setMood' ],
    
    
    has : {
        hobbyName       : null
    },
    
    
    methods : {
        
        doHobby : function () {
            this.setMood('good')
        }
    }
})
;
Class('KiokuJS.Test.Vertex', {
    
    has : {
        ID          : null,
        
        checkSum    : Joose.I.Array,
        
        ref0        : null,
        ref1        : null,
        ref2        : null,
        ref3        : null,
        ref4        : null,
        ref5        : null,
        ref6        : null,
        ref7        : null,
        ref8        : null,
        ref9        : null
    },
    
    
    methods : {
        
        initialize : function () {
            this.ID = this.my.ID++
        },
        
        
        addRef : function (object) {
            var index = this.checkSum.length
            
            
            this[ 'ref' + index ]   = object
            
            this.checkSum[ index ]  = object.ID
        },
        
        
        verifyIntegrity : function () {
            var ok = true
            
            Joose.A.each(this.checkSum, function (ID, index) {
                
                if (this[ 'ref' + index ].ID != ID) { 
                    ok = false
                    
                    return false
                }
                
            }, this)
            
            return ok
        }
    },
    
    
    my : {
        
        has : {
            ID      : 1,
            
            HOST    : null
        },
        
        
        methods : {
            
            createVertex : function (level, maxRefs) {
                
                var vertex      = new this.HOST()
                
                if (level) {
                
                    var refsNum     = 1 + Math.floor(Math.random() * maxRefs)
                    
                    for (var i = 1; i <= refsNum; i++) vertex.addRef(this.createVertex(level - 1, maxRefs))
                }
                
                return vertex
            },
            
            
            createGeneration : function (levels, maxRefs) {
                return this.createVertex(levels, maxRefs)
            }
        }
        
    }
})


;
Class('KiokuJS.Test.Fixture', {
    
    use     : 'KiokuJS.Backend.Role.SkipFixture',
    
    trait   : 'JooseX.CPS',
    
    
    has : {
        connect                 : { required : true },
        cleanup                 : null,
        
        skipCleanup             : false,
        
        t                       : { required : true },
        
        requiredBackendRoles    : Joose.I.Array,
        
        sort                    : 1
    },
    
        
    methods : {
        
        checkSkipReason : function (handle) {
            
            var backend = handle
            
            if (backend.meta.does(KiokuJS.Backend.Role.SkipFixture)) {
                var shouldSkip = backend.skipFixtures()
                
                if (Joose.A.exists(shouldSkip, this.meta.name)) return "Backend [" + backend + "] doesn't support functionality from the [" + this + "]"
            }
            
            var notImplementedRole 
            
            Joose.A.each(this.requiredBackendRoles, function (requiredRole) {
                requiredRole = eval(requiredRole)
                
                if (typeof requiredRole != 'function' || !requiredRole.meta) throw "Unknown value used for required backend role: [" + requiredRole + "], fixture [" + this + "]"
                
                if (!backend.meta.does(requiredRole)) {
                    notImplementedRole = requiredRole
                    
                    return false
                }
            }, this)
            
            if (notImplementedRole) return "Backend [" + backend + "] don't implement the role [" + notImplementedRole.meta.name + "], required for fixture [" + this + "]"
        }
        
    },
    
    
    continued : {
        
        methods : {
            
            run : function () {
                this.createHandler().andThen(function (handle) {
                    var t               = this.t
                    
                    this.TRY(function () {
                        
                        var skipReason      = this.checkSkipReason(handle)
                        
                        if (skipReason) {
                            t.skip(skipReason)
                            
                            this.cleanupHandler(handle, t).now()
                            
                            return
                        }
                    
                        this.populate(handle, t)
                            
                        this.verify(handle, t)
                                
                        this.cleanupHandler(handle, t).now()
                            
                    }).CATCH(function (e) {
                            
                        t.fail('Exception [' + e + '] caught when running fixture: [' + this + ']')
                        
                        this.CONTINUE()
                            
                    }).now()
                })
            },
            
        
            populate : function (handle, t) {
                this.CONTINUE()
            },
            
            
            verify : function (handle, t) {
                this.CONTINUE()
            },
            
            
            createHandler : function () {
                var connect = this.connect
                
                this.TRY(connect, this).now()
            },
            
            
            cleanupHandler : function (handle, t) {
                var cleanup = this.cleanup
                
                if (cleanup && !this.skipCleanup) 
                    this.TRY(cleanup, this, [ handle, t ]).now()
                else
                    this.CONTINUE()
            }
        }
    }

})

;
Class('KiokuJS.Test.Fixture.ObjectGraph', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Person',
    
    has : {
        sort                    : 10,
        
        originalHomer           : null,
        homerID                 : null
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.ObjectGraph - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = this.originalHomer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson',
                    
                    task    : function () { return "Beer" }
                })
                
                var margesTask = function () { return "Children" }
                
                var Marge = new KiokuJS.Test.Person({
                    name    : 'Marge Simpson',
                    
                    task    : margesTask
                })
                
                var Bart = new KiokuJS.Test.Person({
                    name    : 'Bart Simpson'
                })
                
                var Lisa = new KiokuJS.Test.Person({
                    name    : 'Lisa Simpson'
                })
                
                t.ok(Homer.self == Homer, 'Self-reference established')
                
                Homer.spouse(Marge)
                Marge.spouse(Homer)
                
                Bart.father     = Lisa.father  = Homer
                Bart.mother     = Lisa.mother  = Marge
                
                var kids = [ Bart, Lisa ]
                
                Homer.children = Marge.children = margesTask.children = kids


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.store(Homer).andThen(function (homerID) {
                    
                    this.homerID = homerID
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving live object from the same scope')
                
                    scope.lookUp(homerID).andThen(function (homer2) {
                        
                        t.ok(homer2 === Homer, 'Retrieved the Homer object from live objects')
                        
                        this.CONTINUE()
                    })
                    
                }, this)
            },
            
            
            verify : function (handle, t) {
                
                var newScope = handle.newScope()
                

                newScope.lookUp(this.homerID).andThen(function (homer3) {
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving from backend')
                
                    t.ok(homer3 !== this.originalHomer, 'Retrieved Homer is another instance this time')
                    
                    t.ok(homer3.name == 'Homer Simpson', 'But it has a correct name')
                    t.ok(homer3.self === homer3, 'Self-reference was reflected correctly #1')
                    
                    t.ok(homer3.task() == 'Beer', 'Correctly de-serialized function #1')
                    
                    
                    var marge3 = homer3.spouse()
                    
                    t.ok(marge3.self = marge3, 'Self-reference was reflected correctly #2')
                    
                    t.ok(marge3 instanceof KiokuJS.Test.Person, 'Marge2 isa Person')
                    t.ok(marge3.name == 'Marge Simpson', 'Marge has a correct name')
                    
                    t.ok(marge3.task() == 'Children', 'Correctly de-serialized function #2')
                    
                    t.ok(marge3.spouse() === homer3, 'Marge2&Homer2 are spouses')
                    
                    t.ok(marge3.children === homer3.children, 'Marge2&Homer2 have correct kids')
                    t.ok(marge3.children === marge3.task.children, 'Marge2 also has children array attached to its `task`')
                    
                    
                    
                    var kids = marge3.children
                    
                    t.ok(kids.length == 2, 'we forgot Maggy..')
                    
                    t.ok((kids[0] instanceof KiokuJS.Test.Person) && (kids[1] instanceof KiokuJS.Test.Person), 'Both kids are Persons')
                    
                    var bart3 = kids[0]
                    var lisa3 = kids[1]
                    
                    t.ok(bart3.name == 'Bart Simpson', 'First kid in array is Bart')
                    t.ok(lisa3.name == 'Lisa Simpson', 'Second kid in array is Lisa')
                    
                    t.ok(bart3.father == homer3 && bart3.mother == marge3, 'Bart3 has correct parents')
                    t.ok(lisa3.father == homer3 && lisa3.mother == marge3, 'Lisa3 has correct parents')
                    

                    //======================================================================================================================================================================================================================================================
                    t.diag('Examining scope')
                    
                    t.ok(newScope.objectPinned(homer3), 'Homer is in scope')
                    t.ok(newScope.objectPinned(marge3), 'Marge is in scope')
                    
                    t.ok(newScope.objectPinned(marge3.children), 'Kids are in scope')
                    
                    t.ok(newScope.objectPinned(bart3), 'Bart is in scope')
                    t.ok(newScope.objectPinned(lisa3), 'Lisa is in scope')
                    
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving unknown key')
                    
                    var thrown = false
                    
                    newScope.lookUp('foobar').except(function (e) {
                        
                        thrown = true
                        
                        t.isaOk(e, KiokuJS.Exception.LookUp, 'Correct exception thrown')
                        
                        this.CONTINUE()
                        
                    }).andThen(function () {
                        
                        t.ok(thrown, 'Exception thrown')
                        
                        this.CONTINUE()
                    })
                    
                }, this)
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Update', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Person',
    
    has : {
        sort                    : 10,
        
        homerID                 : null,
        
        requiredBackendRoles    : { 
            init : [ 'KiokuJS.Backend.Feature.Update' ] 
        }
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Update - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson'
                })
                
                var Marge = new KiokuJS.Test.Person({
                    name    : 'Marge Simpson'
                })
                
                var Bart = new KiokuJS.Test.Person({
                    name    : 'Bart Simpson'
                })
                
                var Lisa = new KiokuJS.Test.Person({
                    name    : 'Lisa Simpson'
                })
                
                t.ok(Homer.self == Homer, 'Self-reference established')
                
                Homer.spouse(Marge)
                Marge.spouse(Homer)
                
                Bart.father     = Lisa.father  = Homer
                Bart.mother     = Lisa.mother  = Marge
                
                var kids = [ Bart, Lisa ]
                
                Homer.children = Marge.children = kids


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.store(Homer).andThen(function (homerID) {
                    
                    this.homerID = homerID
                    
                    this.CONTINUE()
                    
                }, this)
            },
            
            
            verify : function (handle, t) {
                
                var homerID     = this.homerID
                var newScope    = handle.newScope()
                
                newScope.lookUp(homerID).andThen(function (homer3) {
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving from backend')
                
                    
                    var marge3 = homer3.spouse()
                    
                    homer3.name = 'Homer Simpson the 3rd'
                    marge3.name = 'Marge Simpson the 3rd'
                    
                    
                    newScope.update(homer3).andThen(function () {
                        
                        var cleanScope = handle.newScope()
                        
                        cleanScope.lookUp(homerID).andThen(function (homer4) {
                            
                            //======================================================================================================================================================================================================================================================
                            t.diag('Examining changes')
                            

                            t.ok(homer4.name == 'Homer Simpson the 3rd', 'Homer was updated')
                            
                            var marge4 = homer4.spouse()
                            
                            t.ok(marge4.name == 'Marge Simpson', 'But Marge not, as it was not a deep update')
                            
                            
                            //======================================================================================================================================================================================================================================================
                            t.diag('Deep update')

                            homer4.name = 'Homer Simpson the 4th'
                            marge4.name = 'Marge Simpson the 4th'
                            
                            
                            cleanScope.deepUpdate(homer4).andThen(function () {
                                
                                var cleanScope2 = handle.newScope()
                                
                                var CONTINUE = this.getCONTINUE()
                                
                                
                                // XXX this prevents a very weird behavior of FF 3.6.8 for Ubuntu
                                // when 2 GET requests are somehow skipped
                                // all other browsers (even FF 3.6.8 for Windows) aren't affected
                                setTimeout(function () {
                                    
                                    cleanScope2.lookUp(homerID).andThen(function (homer5) {
                                        
                                        //======================================================================================================================================================================================================================================================
                                        t.diag('Examining changes')
                                        
            
                                        t.ok(homer5.name == 'Homer Simpson the 4th', 'Homer was updated')
                                        
                                        var marge5 = homer5.spouse()
                                        
                                        t.ok(marge5.name == 'Marge Simpson the 4th', 'And Marge also, as it was a deep update')
                                        
                                        
                                        CONTINUE()
                                    })
                                    
                                }, 100)
                                
                            })
                        })
                    })
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Remove', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Person',
    
    has : {
        sort                    : 10,
        
        originalHomer           : null,
        homerID                 : null
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Remove - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = this.originalHomer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson'
                })
                
                var Marge = new KiokuJS.Test.Person({
                    name    : 'Marge Simpson'
                })
                
                var Bart = new KiokuJS.Test.Person({
                    name    : 'Bart Simpson'
                })
                
                var Lisa = new KiokuJS.Test.Person({
                    name    : 'Lisa Simpson'
                })
                
                t.ok(Homer.self == Homer, 'Self-reference established')
                
                Homer.spouse(Marge)
                Marge.spouse(Homer)
                
                Bart.father     = Lisa.father  = Homer
                Bart.mother     = Lisa.mother  = Marge
                
                var kids = [ Bart, Lisa ]
                
                Homer.children = Marge.children = kids


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.store(Homer).andThen(function (homerID) {
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Removing')
                    
                    scope.remove(homerID, Marge).andThen(function () {
                        
                        //======================================================================================================================================================================================================================================================
                        t.diag('Examining scope')
                        
                        t.ok(!scope.objectPinned(Homer), 'Homer is not in scope now')
                        t.ok(!scope.objectPinned(Marge), 'Marge is not in scope now')
                        
                        t.ok(scope.objectPinned(Marge.children), 'Kids are in scope')
                        
                        t.ok(scope.objectPinned(Bart), 'Bart is in scope')
                        t.ok(scope.objectPinned(Lisa), 'Lisa is in scope')
                        
                        
                        //======================================================================================================================================================================================================================================================
                        t.diag('Retrieving removed key')
                        
                        var thrown = false
                        
                        handle.newScope().lookUp(homerID).except(function (e) {
                            
                            thrown = true
                            
                            t.isaOk(e, KiokuJS.Exception.LookUp, 'Correct exception thrown')
                            
                            this.CONTINUE()
                            
                        }).andThen(function () {
                            
                            t.ok(thrown, 'Exception thrown')
                            
                            this.CONTINUE()
                        })
                    })
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Refresh', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Person',
    
    has : {
        sort                    : 150
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Refresh - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = this.originalHomer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson',
                    
                    task    : function () { return 'Beer' }
                })
                
                var Marge = new KiokuJS.Test.Person({
                    name    : 'Marge Simpson'
                })
                
                var Bart = new KiokuJS.Test.Person({
                    name    : 'Bart Simpson'
                })
                
                var Lisa = new KiokuJS.Test.Person({
                    name    : 'Lisa Simpson'
                })
                
                t.ok(Homer.self == Homer, 'Self-reference established')
                
                Homer.spouse(Marge)
                Marge.spouse(Homer)
                
                Bart.father     = Lisa.father  = Homer
                Bart.mother     = Lisa.mother  = Marge
                
                var kids = [ Bart, Lisa ]
                
                Homer.children = Marge.children = kids


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope1 = handle.newScope()
                var scope2 = handle.newScope()
                
                scope1.store(Homer).andThen(function (homerID) {
                    
                    scope2.lookUp(homerID).andThen(function (homer2) {
                        
                        t.ok(homer2 != Homer, 'Retrieved another copy of Homer')
                        
                        //======================================================================================================================================================================================================================================================
                        t.diag('Changing values deeply in graph')
                
                        homer2.name             = 'Homer Simpson the 2nd'
                        homer2.task             = function () { return 'Bible' }
                        homer2.spouse().name    = 'Marge Simpson the 2nd'
                        homer2.children[0].name = 'Bart Simpson the 2nd'
                        homer2.children[1].name = 'Lisa Simpson the 2nd'
                        

                        scope2.store(homer2).andThen(function () {
                            
                            scope1.refresh(Homer).andThen(function () {
                                
                                t.ok(Homer.name == 'Homer Simpson the 2nd', 'Correct name for refreshed Homer')
                                t.ok(Homer.task() == 'Bible', 'Homer goes religious')
                                t.ok(Marge.name == 'Marge Simpson', 'But Marge name is still the same, as it was not deep update')
                                
                                scope1.deepRefresh(Homer).andThen(function () {
                                    
                                    t.ok(Homer.name == 'Homer Simpson the 2nd', 'Correct name for refreshed Homer')
                                    t.ok(Marge.name == 'Marge Simpson the 2nd', 'Marge name is now also refreshed')
                                    
                                    t.ok(Homer.children[0].name == 'Bart Simpson the 2nd', '... as well as Bart')
                                    t.ok(Homer.children[1].name == 'Lisa Simpson the 2nd', '... as well as Lisa')
                                    
                                    this.CONTINUE()
                                })
                            })
                        })
                    })
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Traits', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Person',
    
    has : {
        sort                    : 10,
        
        homerID                 : null
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Traits - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
                t.ok(KiokuJS.Test.Person.Hobby, "'KiokuJS.Test.Person.Hobby' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson',
                    
                    trait   : KiokuJS.Test.Person.Hobby
                })
                
                var Marge = new KiokuJS.Test.Person({
                    name    : 'Marge Simpson'
                })
                
                var Bart = new KiokuJS.Test.Person({
                    name    : 'Bart Simpson'
                })
                
                var Lisa = new KiokuJS.Test.Person({
                    name    : 'Lisa Simpson',
                    
                    trait   : KiokuJS.Test.Person.Hobby
                })
                
                t.ok(Homer.self == Homer, 'Self-reference established')
                
                Homer.spouse(Marge)
                Marge.spouse(Homer)
                
                Bart.father     = Lisa.father  = Homer
                Bart.mother     = Lisa.mother  = Marge
                
                var kids = [ Bart, Lisa ]
                
                Homer.children = Marge.children = kids


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.store(Homer).andThen(function (homerID) {
                    
                    this.homerID = homerID
                    
                    this.CONTINUE()
                    
                }, this)
            },
            
            
            verify : function (handle, t) {
                
                var newScope = handle.newScope()
                
                newScope.lookUp(this.homerID).andThen(function (homer3) {
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving from backend')
                
                    t.ok(homer3.meta.isDetached, 'Retrieved Homer has traits')
                    
                    t.ok(homer3.meta.does(KiokuJS.Test.Person.Hobby), 'Retrieved Homer has hobby')
                    
                    
                    homer3.doHobby()
                    
                    t.is(homer3.getMood(), 'good', 'Doing hobby is fun #1')
                    
                    
                    var lisa3 = homer3.children[1]
                    
                    t.ok(lisa3.meta.isDetached, 'Retrieved Lisa has traits')
                    
                    t.ok(lisa3.meta.does(KiokuJS.Test.Person.Hobby), 'Retrieved Lisa has hobby')
                    

                    lisa3.doHobby()
                    
                    t.is(lisa3.getMood(), 'good', 'Doing hobby is fun #2')
                    
                    

                    this.CONTINUE()
                    
                }, this)
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Intrinsic', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    
    has : {
        sort                    : 10
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Intrinsic - Sanity')
                
                
                t.ok(KiokuJS.Feature.Class.Intrinsic, 'KiokuJS.Feature.Class.Intrinsic is here')
                t.ok(KiokuJS.Feature.Attribute.Intrinsic, 'KiokuJS.Feature.Attribute.Intrinsic is here')
                
                
                Class('Test.Wrapper', {
                    
                    has : {
                        slot            : null,
                        
                        intrinsicSlot   : {
                            trait : KiokuJS.Feature.Attribute.Intrinsic
                        }
                    }
                })
        

                Class('Test.Value.Intrinsic', {
                    
                    does : KiokuJS.Feature.Class.Intrinsic,
                    
                    has : {
                        value : null
                    }
                })
                
                t.ok(Test.Wrapper, 'Test.Wrapper is here')
                t.ok(Test.Value.Intrinsic, 'Test.Value.Intrinsic is here')
                
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var sharedValue1 = new Test.Value.Intrinsic({ value : 'value' })
                var sharedValue2 = [ 'foo' ]
                
                
                var wrapper1 = new Test.Wrapper({ slot : sharedValue1, intrinsicSlot : sharedValue2 })
                var wrapper2 = new Test.Wrapper({ slot : sharedValue1, intrinsicSlot : sharedValue2 })
                var wrapper3 = new Test.Wrapper({ slot : sharedValue1, intrinsicSlot : sharedValue2 })


                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.storeAs({
                    
                    wrapper1 : wrapper1,
                    wrapper2 : wrapper2,
                    wrapper3 : wrapper3
                    
                }).now()
            },
            
            
            verify : function (handle, t) {
                
                var newScope = handle.newScope()
                
                newScope.lookUp('wrapper1', 'wrapper2', 'wrapper3').andThen(function (wrapper1, wrapper2, wrapper3) {
                    
                    t.ok(wrapper1.slot != wrapper2.slot && wrapper2.slot != wrapper3.slot && wrapper1.slot != wrapper3.slot, 'All `slot` attributes are now different object')
                    
                    t.ok(wrapper1.intrinsicSlot != wrapper2.intrinsicSlot && wrapper2.intrinsicSlot != wrapper3.intrinsicSlot && wrapper1.intrinsicSlot != wrapper3.intrinsicSlot, 'All `intrinsicSlot` attributes are now different object')
                    
                    Joose.A.each(arguments, function (wrapper) {
                        t.ok(wrapper.slot.value == 'value', 'All `slot` has correct values though')
                        
                        t.ok(wrapper.intrinsicSlot[ 0 ] == 'foo', 'All `intrinsicSlot` has correct values though')
                    })
                    
                    this.CONTINUE()
                    
                }, this)
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Immutable', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    
    has : {
        sort                    : 10
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Immutable - Sanity')
                

                t.ok(KiokuJS.Feature.Class.Immutable, 'KiokuJS.Feature.Class.Immutable is here')
                
                
                Class('Test.Immutable', {
                    
                    does : KiokuJS.Feature.Class.Immutable,
                    
                    has : {
                        slot1           : null,
                        slot2           : null
                    }
                })
        

                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var mutable     = [ 'foo' ]
                
                var immutable   = new Test.Immutable({ 
                    slot1 : 'foo1', 
                    slot2 : mutable 
                })
                
                

                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.storeAs({
                    
                    immutable   : immutable
                    
                }).now()
            },
            
            
            verify : function (handle, t) {
                
                var newScope = handle.newScope()
                
                newScope.lookUp('immutable').andThen(function (immutable) {
                    
                    t.ok(immutable.slot1 == 'foo1', 'Correct value for `slot1` of `immutable`')
                    t.ok(immutable.slot2[ 0 ] == 'foo' && immutable.slot2.length == 1, 'Correct value for `slot2` of `immutable`')
                    
                    immutable.slot2.push('baz')
                    
                    newScope.store(immutable).andThen(function (immutableID) {
                        
                        t.ok(immutableID == 'immutable', 'Correct ID passed after `store`')
                        
                        
                        var cleanScope1 = handle.newScope()
                        
                        cleanScope1.lookUp('immutable').andThen(function (immutable) {
                            t.ok(immutable.slot1 == 'foo1', 'Correct value for `slot1` of `immutable`')
                            t.ok(immutable.slot2[ 0 ] == 'foo' && immutable.slot2.length == 1, 'Immutable object does not update referenced objects when stored 2nd time')
                            
                            
                            cleanScope1.insertAs({ immutable : immutable }).except(function (e) {
                                
                                t.fail('Exception during repeated storage of immutable object')
                                
                                this.CONTINUE()
                            
                            }).now()
                            
                        })
                    })
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Proxy', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    
    has : {
        ID                      : null,
        
        sort                    : 10
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Proxy - Sanity')
                
                Class('Proxy2', {
                    
                    does    : KiokuJS.Feature.Class.OwnID,
                    
                    methods : {
                        
                        acquireID   : function () {
                            return 'proxy2'
                        }
                    }
                })
                
                
                Class('With.Proxy', {
                    
                    has : {
                        outerProxy      : null,
                        attr            : null,
                        
                        proxy2          : null
                    }
                })
        

                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var proxy       = { 'bar' : 'baz' }
                var proxy2      = new Proxy2()
                
                var withproxy   = new With.Proxy({ 
                    outerProxy  : proxy, 
                    attr        : [ 'foo', proxy ],
                    
                    proxy2      : proxy2
                })
                
                

                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.registerProxy(proxy, 'proxy')
                scope.registerProxy(proxy2)
                
                scope.store(withproxy).andThen(function (ID) {
                    
                    this.ID = ID
                    
                    this.CONTINUE()
                    
                }, this)
            },
            
            
            verify : function (handle, t) {
                
                var newScope    = handle.newScope()
                var proxy       = {}
                var proxy2      = {}
                
                newScope.registerProxy(proxy, 'proxy')
                newScope.registerProxy(proxy2, 'proxy2')
                
                newScope.lookUp(this.ID).andThen(function (withproxy) {
                    
                    t.ok(withproxy.attr[0] == 'foo', '`withproxy` has been correctly restored from DB')
                    t.ok(withproxy.attr[1] == proxy, 'Proxy has been correctly set from the scope #1')
                    
                    t.ok(withproxy.outerProxy == proxy, 'Proxy has been correctly set from the scope #2')
                    
                    t.ok(withproxy.proxy2 == proxy2, 'Proxy2 has been correctly set from the scope')
                    
                    this.CONTINUE()
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.AnimatePacket', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    
    has : {
        test1               : null,
        
        packet              : null
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.AnimatePacket - Sanity')
                
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                Class('Test.Class', {
                    
                    has : {
                        attr1       : null,
                        attr2       : null
                    }
                })
                
                var test1 = new Test.Class({
                    attr1   : 'foo',
                    attr2   : 'bar'
                })
                
                this.test1 = test1

                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.store(test1).andThen(function (test1ID) {
                    
                    var test2 = new Test.Class({
                        attr1   : test1
                    })
                    
                    var test3 = new Test.Class({
                        attr1   : test1,
                        attr2   : test2
                    })
                    
                    
                    this.packet = scope.includeNewObjects({ test3 : test3 }, [ test2 ])
                    
                    this.CONTINUE()
                }, this)
            },
            
            
            verify : function (handle, t) {
                
                var newScope    = handle.newScope()
                
                //======================================================================================================================================================================================================================================================
                t.diag('Animating packet')
                
                newScope.animatePacket(this.packet).andThen(function (customIDs, IDs) {
                    
                    var test3 = customIDs.test3
                    
                    t.ok(test3, 'Something animated as the test3')
                    t.ok(IDs.length == 1, 'Something animated as the object w/o ids')
                    
                    var test21 = IDs[0]
                    var test22 = test3.attr2
                    
                    t.ok(test21 == test22, 'Correct relationships in animated graph #1')
                    
                    t.ok(test21.attr1 == test3.attr1, 'Correct relationships in animated graph #2')
                    
                    var test1 = test21.attr1
                    
                    t.ok(test1.attr1 == 'foo' && test1.attr2 == 'bar', 'Correctly fetched `test1` instance')
                    
                    t.ok(test1 != this.test1, 'Test1 is copy, not the same object')
                    
                    this.CONTINUE()
                }, this)
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.Lazy', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    has : {
        sort                    : 10
    },

    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.Lazy - Sanity')
                
                t.ok(KiokuJS.Feature.Attribute.Lazy, 'KiokuJS.Feature.Attribute.Lazy is here')
                
                
                Class('TestClass', {
                    
                    has : {
                        
                        lazyAttr1 : {
                            trait   : KiokuJS.Feature.Attribute.Lazy
                        },
                        
                        
                        lazyAttr2 : {
                            trait   : KiokuJS.Feature.Attribute.Lazy
                        },
                        
                        
                        lazyAttr3 : {
                            trait   : KiokuJS.Feature.Attribute.Lazy
                        },
                        
                        usualAttr   : 'foo'
                    }
                })
                
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var bigArray    = [ 1, 2, 3 ]
                
                var instance = new TestClass({
                    lazyAttr1   : bigArray,
                    lazyAttr2   : bigArray,
                    
                    lazyAttr3   : 'yo'
                })
                
                t.ok(instance, 'Instance with lazy attributes has been instantiated successfully')
                
                t.ok(instance.getLazyAttr1() instanceof JooseX.CPS.Continuation, 'Correct result from getter #1')
                
                instance.getLazyAttr1().andThen(function (value) {
                    t.ok(value == bigArray, 'Correct result from getter #2')
                    
                    t.ok(this == instance, 'Scope of getter is the instance itself #1')
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Populating')
                    
                    var scope = handle.newScope()
                    
                    scope.storeAs({ instance : instance }).now()
                })
            },
            
            
            verify : function (handle, t) {
                
                var newScope = handle.newScope()

                newScope.lookUp('instance').andThen(function (instance) {
                    
                    //======================================================================================================================================================================================================================================================
                    t.diag('Retrieving lazy attributes')
                    
                    t.ok(instance && instance.usualAttr == 'foo', 'Instance seems to be restored correctly #1')
                    
                    instance.getLazyAttr3().andThen(function (value) {
                        t.ok(value == 'yo', 'Instance seems to be restored correctly #2')
                        
                        t.ok(this == instance, 'Scope of getter is the instance itself #2')
                        
                        
                        instance.getLazyAttr1(newScope).andThen(function (value1) {
                            t.isDeeply(value1, [ 1, 2, 3 ], 'Value of `lazyAttr1` is correct')
                            
                            instance.getLazyAttr2(newScope).andThen(function (value2) {
                                t.isDeeply(value2, [ 1, 2, 3 ], 'Value of `lazyAttr2` is correct')
                                
                                t.ok(value1 == value2, 'Lazy attribute has been correctly pulled from scope')
                                
                                this.CONT.CONTINUE()
                            })
                        })
                    })
                })
            }
        }
    }

})
;
Class('KiokuJS.Test.Fixture.BackendFeature.Overwrite', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : [ 'KiokuJS.Test.Person', 'KiokuJS.Backend.Feature.Overwrite' ],
    
    
    has : {
        sort                    : 20,
        
        requiredBackendRoles    : { 
            init : [ 'KiokuJS.Backend.Feature.Overwrite' ] 
        }
    },

    
    continued : {
        
        methods : {
            
            // XXX add tests for content-addressed objects
            
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.BackendFeature.Overwrite - Sanity')
                
                t.ok(KiokuJS.Test.Person, "'KiokuJS.Test.Person' is here")
        
                
                //======================================================================================================================================================================================================================================================
                t.diag('Graph setup')
                
                var Homer = new KiokuJS.Test.Person({
                    name    : 'Homer Simpson'
                })
                

                //======================================================================================================================================================================================================================================================
                t.diag('Populating')
                
                var scope = handle.newScope()
                
                scope.insertAs({ 'homer' : Homer }).now()
            },
            
            
            
            verify : function (handle, t) {

                var exceptionThrown = false

                
                var newScope            = handle.newScope()
                
                newScope.insertAs({ 'homer' : {} }).except(function (ex) {
                    
                    t.isaOk(ex, KiokuJS.Exception.Overwrite, 'Correct exception thrown')
                    
                    exceptionThrown = true
                    
                    this.CONTINUE()
                
                }).andThen(function () {
                    
                    t.ok(exceptionThrown, 'Exception thrown on overwrite attempt')
                    
                    this.CONTINUE()
                })
            }
            
        }
    }

})
;
Class('KiokuJS.Test.Fixture.StressLoad.Tree', {
    
    isa     : 'KiokuJS.Test.Fixture',
    
    use     : 'KiokuJS.Test.Vertex',
    
    
    has : {
        sort                    : 100
    },
    
    
    continued : {
        
        methods : {
            
            populate : function (handle, t) {
                //======================================================================================================================================================================================================================================================
                t.diag('KiokuJS.Test.Fixture.StressLoad.Tree - Sanity')
                
                t.ok(KiokuJS.Test.Vertex, 'KiokuJS.Test.Vertex is here')
                
                var CONT    = this.CONT
                var scope   = handle.newScope()
                
                for (var i = 1; i <= 10; i++) CONT.AND(function () {
                    
                    var CONTINUE = this.getCONTINUE()
                    
                    setTimeout(function () {
                        
                        scope.store(KiokuJS.Test.Vertex.createGeneration(3, 3)).andThen(function (genID) {
                            
                            var cleanScope = handle.newScope()
                            
                            cleanScope.lookUp(genID).andThen(function (vertex) {
                                
                                t.ok(vertex.verifyIntegrity(), 'Generation integrity is ok')
                                
                                CONTINUE()
                            })  
                        })
                        
                        
                    }, Math.floor(Math.random() * 3000))
                })
        
                
                CONT.now()
            }
            
        }
        // eof methods
    }
    // eof continued

})
;
Class('KiokuJS.Test', {
    
    trait   : 'JooseX.CPS',
    
    
    has : {
        t               : { required : true },
        
        connect         : { required : true },
        cleanup         : null,
        
        skipCleanup     : false,
        
        fixtures        : {
            init : [
                'ObjectGraph',
                'Refresh',
                'Update',
                'Remove',
                'Traits',
                'Intrinsic',
                'Immutable',
                'Proxy',
                'AnimatePacket',
                'Lazy',
                'BackendFeature.Overwrite',
                'StressLoad.Tree'
            ]
        }
    },
    
    
    methods : {
        
        expandFixturesNames : function () {
            return Joose.A.map(this.fixtures, function (fixture) {
                if (!/^=/.test(fixture)) 
                    fixture = 'KiokuJS.Test.Fixture.' + fixture
                else
                    fixture = fixture.replace(/^=/, '')
                
                return fixture
            })
        }
    },
    
        
    continued : {
        
        methods : {
            
            runAllFixtures : function () {
                this.loadFixtures().andThen(function () {
                    
                    var fixtures = Joose.A.map(this.expandFixturesNames(), function (fixtureName) {
                        var constructor = eval(fixtureName)
                        
                        return new constructor({
                            t           : this.t,
                            
                            connect     : this.connect,
                            cleanup     : this.cleanup,
                            
                            skipCleanup : this.skipCleanup
                        })
                    }, this)
                    
                    fixtures.sort(function (a, b) {
                        
                        return a.sort - b.sort
                    })
                    
                    this.runFixtures(fixtures).now()
                })
            },
            
            
            runFixtures : function (fixtures) {
                var me = this
                
                Joose.A.each(fixtures, function (fixture) {
                    
                    me.THEN(function () {
                        
                        fixture.run().now()
                    })
                })
                
                if (fixtures.length)
                    this.NOW()
                else
                    this.CONTINUE()
            },
            
            
            loadFixtures : function () {
                use(this.expandFixturesNames(), this.getCONTINUE())
            }
        }
    }

})

;