UNPKG

ngn-data

Version:

Data modeling, stores, proxies, and utilities for NGN

1,244 lines (1,017 loc) 32.4 kB
/** * v0.1.11 generated on: Sat Jun 24 2017 01:20:07 GMT+0000 (UTC) * Copyright (c) 2014-2017, Ecor Ventures LLC. All Rights Reserved. See LICENSE (BSD3). */ 'use strict' class NgnDataModel extends NGN.EventEmitter { constructor (config) { config = config || {} super() Object.defineProperties(this, { idAttribute: NGN.privateconst(config.idAttribute || 'id'), fields: NGN.private(config.fields || { id: { required: true, type: String, 'default': config.id || null } } ), hiddenFields: NGN.private(NGN.coalesce(config.metaFields)), joins: NGN.private(config.relationships || {}), virtuals: NGN.private(config.virtuals || {}), validators: NGN.private({}), validation: NGN.public(NGN.coalesce(config.validation, true)), isNew: NGN.private(true), isRecordDestroyed: NGN.private(false), oid: NGN.private(config[this.idAttribute] || null), autoid: NGN.public(NGN.coalesce(config.autoid, false)), autoidPrefix: NGN.public(NGN.coalesce(config.autoidPrefix)), autoidPostfix: NGN.public(NGN.coalesce(config.autoidPostfix)), benchmark: NGN.private(null), expiration: NGN.private(null), expirationTimeout: NGN.private(null), hasExpired: NGN.private(false), ignoreTTL: NGN.private(false), createDate: NGN.privateconst(Date.now()), setUnmodified: NGN.privateconst(function () { this.benchmark = this.checksum this.changelog = [] }), allowInvalidSave: NGN.private(NGN.coalesce(config.allowInvalidSave, false)), disableDataValidation: NGN.private(NGN.coalesce(config.disableDataValidation, false)), invalidDataAttributes: NGN.private([]), initialDataAttributes: NGN.private([]), changelog: NGN.private([]), _nativeValidators: NGN.privateconst({ min: function (minimum, value) { if (NGN.typeof(value) === 'array') { return value.length >= minimum } if (NGN.typeof(value) === 'number') { return value >= minimum } if (NGN.typeof(value) === 'string') { return value.trim().length >= minimum } if (NGN.typeof(value) === 'date') { return value.parse() >= minimum.parse() } return false }, max: function (maximum, value) { if (NGN.typeof(value) === 'array') { return value.length <= maximum } if (NGN.typeof(value) === 'number') { return value <= maximum } if (NGN.typeof(value) === 'string') { return value.trim().length <= maximum } if (NGN.typeof(value) === 'date') { return value.parse() <= maximum.parse() } return false }, enum: function (valid, value) { return valid.indexOf(value) >= 0 }, required: (field, value) => { return this.hasOwnProperty(field) && this[value] !== null } }), _dataMap: NGN.private(config.dataMap || null), _reverseDataMap: NGN.public(null), raw: NGN.private({}), rawjoins: NGN.private({}), _store: NGN.private(null), _proxy: NGN.private(config.proxy || null), proxyignore: NGN.private(NGN.coalesce(config.proxyignore, false)) }) if (NGN.coalesce(this.idAttribute, '') !== 'id' && !this.fields.hasOwnProperty(this.idAttribute)) { console.warn(this.idAttribute + ' is specified as the ID, but it is not defined in the fields.') } if (config.proxy) { if (this._proxy instanceof NGN.DATA.Proxy) { this._proxy.init(this) } else { throw new Error('Invalid proxy configuration.') } } let allfields = this.datafields.concat(this.virtualdatafields).concat(this.relationships).filter(function (key, i, a) { return a.indexOf(key) !== i }) if (allfields.length > 0) { throw new Error('Duplicate field names exist: ' + allfields.join(', ') + '. Unique fieldnames are required for data fields, virtuals, and relationship fields.') } if (!this.fields.hasOwnProperty('id')) { config.fields.id = { required: true, type: String, 'default': config.id || null } } Object.keys(this.fields).forEach((field) => { if (typeof this.fields[field] !== 'object' && this.fields[field] !== null) { this.fields[field] = { required: true, type: this.fields[field], default: null, name: field } } this.addField(field, true) }) if (this.hiddenFields) { Object.keys(this.hiddenFields).forEach((field) => { this.addMetaField(field, NGN.coalesce(this.hiddenFields[field], null), true) }) } Object.keys(this.virtuals).forEach((v) => { Object.defineProperty(this, v, NGN.get(() => { return this.virtuals[v].apply(this) })) }) Object.keys(this.joins).forEach((field) => { this.addRelationshipField(field, this.joins[field], true) }) let events = [ 'field.update', 'field.create', 'field.remove', 'field.invalid', 'validator.add', 'validator.remove', 'relationship.create', 'relationship.remove', 'expired', 'deleted', 'reset', 'load' ] if (NGN.BUS) { events.forEach((eventName) => { this.on(eventName, function () { let args = NGN.slice(arguments) args.push(this) args.unshift(eventName) NGN.BUS.emit.apply(NGN.BUS, args) }) }) } if (config.hasOwnProperty('expires')) { this.expires = config.expires } this.on('changelog.append', (delta) => { delta.id = NGN.DATA.util.GUID() this.changelog.push(delta) this.emit('append.changelog', delta) }) this.on('changelog.remove', (idList) => { idList = Array.isArray(idList) ? idList : [idList] this.emit('remove.changelog', idList) }) } get proxy () { return this._proxy } set proxy (value) { if (!this._proxy && value instanceof NGN.DATA.Proxy) { this._proxy = value this._proxy.init(this) } } get deleted () { return this.isRecordDestroyed } set isDestroyed (value) { if (typeof value !== 'boolean') { console.warn(NGN.stack) throw new Error('Invalid data type. isDestroyed must be a boolean. Received ' + (typeof value)) } this.isRecordDestroyed = value if (value) { this.emit('deleted') } } get expires () { return this.expiration } set expires (value) { if (NGN.typeof(value) !== 'date' && NGN.typeof(value) !== 'number') { try { const source = NGN.stack.pop() console.warn('Expiration could not be set at %c' + source.path + '%c (Invalid data type. Must be a Date or number).', NGN.css, '') } catch (e) { console.warn('Expiration could not be set (Invalid data type. Must be a Date or number).') } return } clearTimeout(this.expirationTimeout) if (NGN.typeof(value) === 'number') { if (value < 0) { this.ignoreTTL = true return } const currentDate = new Date() value = new Date ( currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds(), currentDate.getMilliseconds() + value ) } this.ignoreTTL = false this.expiration = value if (Date.now() >= this.expiration.getTime()) { this.expire() return } this.hasExpired = false let waitPeriod = this.expiration.getTime() - Date.now() this.expirationTimeout = setTimeout(() => { this.expire() }, waitPeriod) } get expired () { if (this.ignoreTTL) { return false } return this.hasExpired } get modified () { return this.checksum !== this.benchmark } get id () { return this.oid } set id (value) { this.oid = value } get checksum () { return NGN.DATA.util.checksum(JSON.stringify(this.data)) } get dataMap () { return this._dataMap } set dataMap (value) { this._dataMap = value this._reverseDataMap = null } get datastore () { return this._store } get valid () { this.validate() return this.invalidDataAttributes.length === 0 } get datafields () { if (!this.hiddenFields) { return Object.keys(this.fields) } let hidden = NGN.coalesce(this.hiddenFields, {}) return Object.keys(this.fields).filter((fieldname) => { return !hidden.hasOwnProperty(fieldname) }) } get relationships () { return Object.keys(this.joins) } get virtualdatafields () { return Object.keys(this.virtuals) } get reverseMap () { if (this.dataMap !== null) { if (this._reverseDataMap !== null) { return this._reverseDataMap } let rmap = {} const me = this Object.keys(this._dataMap).forEach(function (attr) { rmap[me._dataMap[attr]] = attr }) this._reverseDataMap = rmap return rmap } return null } get data () { let d = this.serialize() if (!d.hasOwnProperty(this.idAttribute) && this.autoid) { d[this.idAttribute] = this[this.idAttribute] } if (this.dataMap) { const me = this Object.keys(this.dataMap).forEach(function (key) { if (d.hasOwnProperty(key)) { if (d[key] instanceof NGN.DATA.Model) { d[me.dataMap[key]] = d[key].data } else { d[me.dataMap[key]] = d[key] } delete d[key] } }) } return d } get representation () { let data = this.data Object.keys(this.rawjoins).forEach((join) => { data[join] = this.rawjoins[join].representation }) Object.keys(this.virtuals).forEach((attribute) => { let val = this[attribute] if (val instanceof NGN.DATA.Entity || val instanceof NGN.DATA.Store) { data[attribute] = this[attribute].representation } else if (typeof val !== 'function') { data[attribute] = this[attribute] } }) return data } get history () { return this.changelog.reverse() } expire (duration) { if (this.expired) { return } if (duration) { this.expires = duration return } if (this.ignoreTTL) { return } this.hasExpired = true clearTimeout(this.expirationTimeout) this.emit('expired', this) } disableExpiration () { this.expires = -1 } addValidator (property, validator) { if (!this.hasOwnProperty(property)) { console.warn('No validator could be create for %c' + property + '%c. It is not an attribute of %c' + this.type + '%c.', NGN.css, '', NGN.css, '') return } switch (typeof validator) { case 'function': this.validators[property] = this.validators[property] || [] this.validators[property].push(validator) this.emit('validator.add', property) break case 'object': if (Array.isArray(validator)) { this.validators[property] = this.validators[property] || [] this.validators[property].push(function (value) { return validator.indexOf(value) >= 0 }) this.emit('validator.add', property) } else if (validator.test) { this.validators[property] = this.validators[property] || [] this.validators[property].push(function (value) { return validator.test(value) }) this.emit('validator.add', property) } else { console.warn('No validator could be created for %c' + property + '%c. The validator appears to be invalid.', NGN.css, '') } break case 'string': case 'number': case 'date': this.validators[property] = this.validators[property] || [] this.validators[property].push(function (value) { return value === validator }) this.emit('validator.add', property) break default: console.warn('No validator could be create for %c' + property + '%c. The validator appears to be invalid.', NGN.css, '') } } removeValidator (attribute) { if (this.validators.hasOwnProperty(attribute)) { delete this.validators[attribute] this.emit('validator.remove', attribute) } } validate (attribute) { if (!this.validation) { return true } const me = this if (attribute) { if (this.validators.hasOwnProperty(attribute)) { for (let i = 0; i < this.validators[attribute].length; i++) { if (!me.validators[attribute][i].apply(me, [me[attribute]])) { me.invalidDataAttributes.indexOf(attribute) < 0 && me.invalidDataAttributes.push(attribute) return false } else { me.invalidDataAttributes = me.invalidDataAttributes.filter(function (attr) { return attribute !== attr }) } } if (!this.validateDataType(attribute)) { this.invalidDataAttributes.push(attribute) return false } } return true } this.datafields.forEach(function (field) { me.validate(field) }) } validateDataType (field) { const fieldType = NGN.typeof(this[field]) const expectedType = NGN.typeof(this.fields[field].type) if (fieldType !== 'null') { return fieldType === expectedType } if (this[field] === null && this.fields[field].required) { if (this.autoid && field === this.idAttribute) { return true } return false } return true } getRelationshipField (fieldname) { return this.joins[fieldname] } hasRelationship (fieldname) { return this.joins.hasOwnProperty(fieldname) } getDataField (fieldname) { return this.fields[fieldname] } getMappedFieldName (fieldname) { if (!this.dataMap || !this.dataMap.hasOwnProperty(fieldname)) { return fieldname } return this.dataMap[fieldname] } getReverseMappedFieldName (fieldname) { if (!this.reverseMap || !this.reverseMap.hasOwnProperty(fieldname)) { return fieldname } return this.reverseMap[fieldname] } hasDataField (fieldname) { return this.fields.hasOwnProperty(fieldname) } hasMetaField (fieldname) { if (!this.has(fieldname)) { return false } return this.getDataField(fieldname).hidden } has (attribute, includeVirtuals = true) { if (this.idAttribute === attribute || this.hasDataField(attribute) || this.hasRelationship(attribute)) { return true } if (includeVirtuals && this.hasVirtualField(attribute)) { return true } return false } serialize (obj, ignoreID = false) { let _obj = obj || this.raw let rtn = {} for (let key in _obj) { _obj.nonEnumerableProperties = _obj.nonEnumerableProperties || '' if (this.fields.hasOwnProperty(key) && !this.hasMetaField(key)) { key = key === 'id' ? this.idAttribute : key if ((_obj.hasOwnProperty(key) && (_obj.nonEnumerableProperties.indexOf(key) < 0 && /^[a-z0-9 ]$/.test(key.substr(0, 1)))) || (_obj[key] !== undefined && _obj.enumerableProperties.indexOf(key) >= 0)) { let dsc = Object.getOwnPropertyDescriptor(_obj, key) if (!dsc.set) { switch (typeof dsc.value) { case 'function': if (dsc.value.name === 'Date') { rtn[key] = _obj[key].refs.toJSON() } else if (dsc.value.name === 'RegExp') { rtn[key] = dsc.value() } break case 'object': if (_obj[key] instanceof Array && !Array.isArray(_obj[key])) { _obj[key] = _obj[key].slice(0) } rtn[key] = _obj[key] break default: rtn[key] = NGN.coalesce(_obj[key], this.fields[key].default, null) break } } } } } const me = this this.relationships.forEach(function (r) { rtn[r] = me.rawjoins[r].data }) if ((rtn.hasOwnProperty(this.idAttribute) && ignoreID) || (rtn.hasOwnProperty(this.idAttribute) && rtn[this.idAttribute] === undefined)) { delete rtn[this.idAttribute] } return rtn } addField (field, fieldcfg = null, suppressEvents = false) { if (typeof fieldcfg === 'boolean') { suppressEvents = fieldcfg fieldcfg = null } const me = this let cfg = null if (field.toLowerCase() !== 'id') { if (typeof field === 'object') { if (!field.name) { throw new Error('Cannot create data field. The supplied configuration does not contain a unique data field name.') } cfg = field field = cfg.name delete cfg.name } if (me[field] !== undefined) { try { const source = NGN.stack.pop() console.warn('%c' + field + '%c data field defined multiple times (at %c' + source.path + '%c). Only the last defintion will be used.', NGN.css, '', NGN.css, '') } catch (e) { console.warn('%c' + field + '%c data field defined multiple times. Only the last definition will be used.', NGN.css, '', NGN.css, '') } delete me[field] } me.fields[field] = NGN.coalesce(cfg, fieldcfg, me.fields[field], {}) me.fields[field].required = NGN.coalesce(me.fields[field].required, false) if (!me.fields[field].hasOwnProperty('type')) { if (me.fields[field].hasOwnProperty('default')) { let type = NGN.typeof(me.fields[field].default) type = type.charAt(0).toUpperCase() + type.slice(1) me.fields[field].type = eval(type) } } me.fields[field].type = NGN.coalesce(me.fields[field].type, String) if (field === me.idAttribute && me.autoid === true) { me.fields[field].type = String me.fields[field]['default'] = NGN.coalesce(me.autoidPrefix, '') + NGN.DATA.util.GUID() + NGN.coalesce(me.autoidPostfix, '') } else { me.fields[field]['default'] = NGN.coalesce(me.fields[field]['default']) } me.fields[field].hidden = NGN.coalesce(me.fields[field].hidden, false) me.raw[field] = me.fields[field]['default'] me[field] = me.raw[field] Object.defineProperty(me, field, { get: function () { return NGN.coalesce(me.raw[field], me.fields[field].default, null) }, set: function (value) { let old = me.raw[field] if (old === value) { return } const wasInvalid = !me.validate(field) me.raw[field] = value let c = { action: 'update', field: field, old: old, new: me.raw[field] } this.emit('changelog.append', c) this.emit('field.update', c) this.emit('field.update.' + field, c) if (!me.validate(field)) { me.emit('field.invalid', { field: field }) } else if (wasInvalid) { me.emit('field.valid', { field: field }) } } }) if (!suppressEvents) { let c = { action: 'create', field: field } this.emit('changelog.append', c) this.emit('field.create', c) } if (me.fields.hasOwnProperty(field)) { if (me.fields[field].hasOwnProperty('pattern')) { me.addValidator(field, me.fields[field].pattern) } ['min', 'max', 'enum'].forEach(function (v) { if (me.fields[field].hasOwnProperty(v)) { me.addValidator(field, function (val) { return me._nativeValidators[v](me.fields[field][v], val) }) } }) if (me.fields[field].hasOwnProperty('required')) { if (me.fields[field].required) { me.addValidator(field, function (val) { return me._nativeValidators.required(field, val) }) } } if (me.fields[field].hasOwnProperty('validate')) { if (typeof me.fields[field].validate === 'function') { me.addValidator(field, function (val) { return me.fields[field].validate.apply(me, [val]) }) } else { const source = NGN.stack.pop() console.warn('Invalid custom validation function (in %c' + source.path + '%c). The value passed to the validate attribute must be a function.', NGN.css, '') } } } } else if (me.id === null && me.autoid) { me.id = NGN.coalesce(me.autoidPrefix, '') + NGN.DATA.util.GUID() + NGN.coalesce(me.autoidPostfix, '') } } addMetaField (fieldname, fieldcfg = null) { if (!this.has(fieldname)) { this.hiddenFields = NGN.coalesce(this.hiddenFields, {}) this.hiddenFields[fieldname] = fieldcfg this.addField.apply(this, arguments) this.fields[fieldname].hidden = true } } addVirtual (name, fn) { const me = this Object.defineProperty(this, name, { get: function () { return fn.apply(me) } }) } addRelationshipField (name, cfg, suppressEvents) { suppressEvents = suppressEvents !== undefined ? suppressEvents : false if (this.rawjoins.hasOwnProperty(name) || this.fields.hasOwnProperty(name) || this.hasOwnProperty(name)) { throw new Error(name + ' already exists. It cannot be added to the model again.') } if (typeof cfg === 'function' || typeof cfg === 'object' && !cfg.hasOwnProperty('type')) { cfg = { type: cfg } } if (!cfg.type) { throw new Error('Configuration has no reference! The reference must be an NGN.DATA.Model or NGN.DATA.Store.') } cfg.required = NGN.coalesce(cfg.required, true) cfg.default = cfg.default || null if (!this.joins.hasOwnProperty(name)) { this.joins[name] = cfg } const me = this let entityType = 'model' if (cfg.type instanceof NGN.DATA.Store) { entityType = 'store' } else if (NGN.typeof(cfg.type) === 'array') { if (cfg.type.length === 0) { throw new Error(name + ' cannot be an empty store. A model must be provided.') } entityType = 'collection' } else if (typeof cfg.type === 'object') { if (cfg.type.model) { entityType = 'store' } } if (entityType === 'store') { let storeCfg = {} if (cfg.type instanceof NGN.DATA.Store) { this.rawjoins[name] = cfg.type storeCfg = null } else if (cfg.type.model) { storeCfg = cfg.type } else { throw new Error('Nested store configuration is invalid or was not recognized.') } if (storeCfg !== null) { this.rawjoins[name] = new NGN.DATA.Store(storeCfg) } this.applyStoreMonitor(name) } else if (entityType === 'collection') { this.rawjoins[name] = new NGN.DATA.Store({ model: cfg.type[0] }) this.applyStoreMonitor(name) } else if (!cfg.type.data) { this.rawjoins[name] = cfg.default !== null ? new cfg.type(cfg.default) : new cfg.type() this.applyModelMonitor(name) } else if (cfg.type.data) { this.rawjoins[name] = cfg.type this.applyStoreMonitor(name) } else { throw new Error('Nested store configuration is invalid or was not recognized.') } Object.defineProperty(this, name, { enumerable: true, get: function () { return me.rawjoins[name] } }) if (!suppressEvents) { let c = { action: 'create', field: name } this.emit('changelog.append', c) this.emit('relationship.create', c) } } applyModelMonitor (name) { const model = this.rawjoins[name] const me = this let oldData = model.data model.on('load', () => { let payload = { action: 'update', field: name, old: NGN.coalesce(oldData), new: model.data, join: true, originalEvent: { event: 'load', record: model } } oldData = model.data me.emit('field.update', payload) me.emit('field.update.' + name, payload) }) model.on('field.update', function (delta) { let payload = { action: 'update', field: name + '.' + delta.field, old: delta.old, new: delta.new, join: true, originalEvent: { event: 'field.update', record: model } } me.emit('field.update', payload) me.emit('field.update.' + name + '.' + delta.field, payload) }) model.on('field.create', function (delta) { let payload = { action: 'update', field: name + '.' + delta.field, old: null, new: null, join: true, originalEvent: { event: 'field.create', record: model } } me.emit('field.update', payload) me.emit('field.update.' + name + '.' + delta.field, payload) }) model.on('field.remove', function (delta) { let payload = { action: 'update', field: name + '.' + delta.field, old: delta.value, new: null, join: true, originalEvent: { event: 'field.remove', record: model } } me.emit('field.update', payload) me.emit('field.update.' + name + '.' + delta.field, payload) }) model.on('field.invalid', function (data) { me.emit('field.invalid') me.emit('field.invalid.' + name + '.' + data.field) }) model.on('field.valid', function (data) { me.emit('field.valid') me.emit('field.valid.' + name + '.' + data.field) }) } applyStoreMonitor (name) { if (!this.rawjoins.hasOwnProperty(name)) { return } if (this.rawjoins[name] instanceof NGN.DATA.Store) { const me = this this.rawjoins[name].on('record.create', function (record) { let old = me[name].data old.pop() let c = { action: 'update', field: name, join: true, old: old, new: me[name].data, originalEvent: { event: 'record.create', record: record } } me.emit('field.update', c) me.emit('field.update.' + name, c) }) this.rawjoins[name].on('record.update', function (record, delta) { if (!delta) { return } let c = { action: 'update', field: name + '.' + delta.field, join: true, old: delta.old, new: delta.new, originalEvent: { event: 'record.update', record: record } } me.emit('field.update', c) me.emit('field.update.' + name + '.' + delta.field, c) }) this.rawjoins[name].on('record.delete', function (record) { let old = me[name].data old.push(record.data) let c = { action: 'update', field: name, join: true, old: old, new: me[name].data, originalEvent: { event: 'record.delete', record: record } } me.emit('field.update', c) me.emit('field.update.' + name, c) }) this.rawjoins[name].on('record.invalid', function (data) { me.emit('field.invalid', data.field) me.emit('field.invalid.' + name, data.field) }) this.rawjoins[name].on('record.valid', function (data) { me.emit('field.valid', data.field) me.emit('field.valid.' + name, data.field) }) } } removeField (name) { if (this.raw.hasOwnProperty(name)) { let val = this.raw[name] delete this[name] delete this.fields[name] delete this.raw[name] if (this.invalidDataAttributes.indexOf(name) >= 0) { this.invalidDataAttributes.splice(this.invalidDataAttributes.indexOf(name), 1) } let c = { action: 'delete', field: name, value: val } this.emit('field.remove', c) this.emit('changelog.append', c) } } removeVirtual (name) { delete this[name] } removeRelationshipField (name, suppressEvents) { suppressEvents = suppressEvents !== undefined ? suppressEvents : false if (this.joins.hasOwnProperty(name)) { let val = this.rawjoins[name] delete this.rawjoins[name] delete this[name] delete this.joins[name] if (!suppressEvents) { let c = { action: 'delete', field: name, old: val, join: true } this.emit('changelog.append', c) this.emit('relationship.remove', c) } } } hasVirtualField (fieldname) { return this.virtuals.hasOwnProperty(fieldname) } setSilent (fieldname, value) { if (fieldname === this.idAttribute) { this.id = value return } if (this.hasRelationship(fieldname)) { if (this[fieldname] instanceof NGN.DATA.Store) { this[fieldname].clear() this[fieldname].bulk(null, value) return } this[fieldname].load(value) return } this.raw[fieldname] = value } undo (back = 1) { let old = this.changelog.splice(this.changelog.length - back, back) const me = this old.reverse().forEach(function (change) { if (!(typeof change.join === 'boolean' ? change.join : false)) { switch (change.action) { case 'update': me[change.field] = change.old break case 'create': me.removeField(change.field) break case 'delete': me.addField(change.field) me[change.field] = me.old break } } else { switch (change.action) { case 'create': me.removeRelationshipField(change.field) break case 'delete': me.addRelationshipField(change.field) me[change.field] = change.old break } } }) this.emit('changelog.remove', old.map((item) => { return item.id })) } load (data) { data = data || {} if (this._dataMap !== null) { Object.keys(this.reverseMap).forEach((key) => { if (data.hasOwnProperty(key)) { data[this.reverseMap[key]] = data[key] delete data[key] } }) } Object.keys(data).forEach((key) => { if (this.hasDataField(key)) { if (this.raw.hasOwnProperty(key)) { this.raw[key] = data[key] } else if (key === this.idAttribute) { this.id = data[key] } } else if (this.hasRelationship(key)) { this.rawjoins[key].load(data[key]) } else if (key !== this.idAttribute && !this.hasMetaField(key)) { try { const source = NGN.stack.pop() console.warn('%c' + key + '%c specified in %c' + source.path + '%c as a data field but is not defined in the model.', NGN.css, '', NGN.css, '') } catch (e) { console.warn('%c' + key + '%c specified as a data field but is not defined in the model.', NGN.css, '') } } }) this.setUnmodified() this.emit('load') } } NGN.DATA = NGN.DATA || {} Object.defineProperties(NGN.DATA, { Model: NGN.const(function (cfg) { const ModelLoader = function (data) { let model = new NgnDataModel(cfg) if (data) { model.load(data) } return model } return ModelLoader }), Entity: NGN.private(NgnDataModel) }) if (NGN.nodelike) { module.exports = NGN.DATA }