UNPKG

model-layer

Version:
391 lines 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Model = void 0; const events_1 = require("events"); const EqualStack_1 = __importDefault(require("./EqualStack")); const Type_1 = require("./type/Type"); const utils_1 = require("./utils"); const Walker_1 = __importDefault(require("./Walker")); const errors_1 = require("./errors"); class Model extends events_1.EventEmitter { constructor(inputData) { super(); this.prepareStructure(); const defaultRow = {}; for (const propKey in this.properties) { const key = propKey; if (key === "*") { continue; } const description = this.properties[key]; // default value is null, or something from description let defaultValue = description.default(); // default can be invalid defaultValue = description.prepare(defaultValue, key, this); defaultRow[key] = defaultValue; } this.row = Object.freeze(defaultRow); this.isInit = true; // do not check const this.set(inputData || {}); delete this.isInit; } get(key) { return this.row[key]; } set(row, options) { options = options || { onlyValidate: false }; const newData = {}; const oldData = this.row; // clone old values in oldData for (const key in oldData) { newData[key] = oldData[key]; } const anyKeyDescription = this.properties["*"]; for (const key in row) { let description = this.properties[key]; if (!description) { if (anyKeyDescription) { description = anyKeyDescription; const isValidKey = description.validateKey(key); if (!isValidKey) { throw new errors_1.InvalidKeyError({ key }); } } else { throw new errors_1.UnknownPropertyError({ propertyName: key }); } } let value = row[key]; // cast input value to expected format value = description.prepare(value, key, this); // validate by params const isValid = description.validate(value, key); if (!isValid) { const valueAsString = utils_1.invalidValuesAsString(value); throw new errors_1.InvalidValueError({ key, value: valueAsString }); } newData[key] = value; } // modify by reference // because it conveniently this.prepare(newData); const changes = {}; for (const key in newData) { const anyKey = key; let description = this.properties[anyKey]; if (!description) { description = anyKeyDescription; } let newValue = newData[key]; const oldValue = oldData[key]; // if field has type string, // then he must be string or null in anyway! if (this.prepare !== Model.prototype.prepare) { newValue = description.prepare(newValue, key, this); } if (oldValue !== newValue) { if (description.const) { if (!this.isInit) { throw new errors_1.ConstValueError({ key }); } } } if (description.required) { if (newValue == null) { throw new errors_1.RequiredError({ key }); } } if (newValue !== oldValue) { changes[key] = newValue; newData[key] = newValue; } } const hasChanges = Object.keys(changes).length > 0; if (!hasChanges) { return; } // juniors love use model.row for set // stick on his hands Object.freeze(newData); this.validate(newData); // do not call emit and set newData if (options.onlyValidate) { return; } this.row = newData; if (this.primaryKey) { const primaryValue = this.row[this.primaryKey]; this[this.primaryKey] = primaryValue; this.primaryValue = primaryValue; } for (const key in changes) { this.emit("change:" + key, { prev: oldData, changes }, options); } this.emit("change", { prev: oldData, changes }, options); } isValid(row) { if (!utils_1.isObject(row)) { throw new errors_1.DataShouldBeObjectError({}); } try { this.set(row, { onlyValidate: true }); return true; } catch (err) { return false; } } hasProperty(key) { return this.row.hasOwnProperty(key); } getDescription(key) { const iKey = key; return this.properties[iKey] || this.properties["*"]; } hasValue(key) { const value = this.row[key]; if (value == null) { return false; } else { return true; } } walk(iteration, stack) { stack = stack || []; for (const key in this.row) { const value = this.row[key]; let elements = [value]; const isModelsArray = (Array.isArray(value) && value[0] instanceof Model); const isCollection = (value && Array.isArray(value.models) && value.models[0] instanceof Model); if (isModelsArray) { elements = value; } else if (isCollection) { elements = value.models; } for (let i = 0, n = elements.length; i < n; i++) { const element = elements[i]; if (element instanceof Model) { const model = element; // stop circular recursion if (stack.includes(model)) { continue; } stack.push(model); // api for stop and skip elements const walker = new Walker_1.default(); // callback iteration(model, walker); // inside iteration we call walker.exit(); if (walker.isExited()) { return; } // inside iteration we call walker.continue(); if (walker.isContinued()) { continue; } // recursion model.walk(iteration, stack); } } } } findChild(iteration) { let child; this.walk((model, walker) => { const result = iteration(model); if (result) { child = model; walker.exit(); } }); return child; } filterChildren(iteration) { const children = []; this.walk((model) => { const result = iteration(model); if (result) { children.push(model); } }); return children; } filterChildrenByInstance(SomeModel) { return this.filterChildren((model) => model instanceof SomeModel); } findParent(iteration, stack) { stack = stack || []; let parent = this.parent; while (parent) { // stop circular recursion if (stack.includes(parent)) { return; } stack.push(parent); const result = iteration(parent); if (result) { return parent; } parent = parent.parent; } } filterParents(iteration) { const parents = []; let parent = this.parent; while (parent) { const result = iteration(parent); if (result) { parents.push(parent); } parent = parent.parent; } return parents; } findParentInstance(SomeModel) { return this.findParent((model) => model instanceof SomeModel); } toJSON(stack = []) { const json = {}; for (const key in this.row) { const description = this.getDescription(key); let value = this.row[key]; if (value != null) { value = description.toJSON(value, [...stack]); } json[key] = value; } this.prepareJSON(json); return json; } clone(stack) { stack = stack || new EqualStack_1.default(); // circular reference const existsClone = stack.get(this); if (existsClone) { return existsClone; } const clone = Object.create(this.constructor.prototype); stack.add(this, clone); const cloneData = {}; for (const key in this.row) { const description = this.getDescription(key); let value = this.row[key]; if (value != null) { value = description.clone(value, stack, clone); } cloneData[key] = value; } clone.row = Object.freeze(cloneData); return clone; } equal(otherModel, stack) { stack = stack || new EqualStack_1.default(); for (const key in this.row) { const anyKey = key; const description = this.getDescription(key); const selfValue = this.row[anyKey]; const otherValue = (otherModel instanceof Model ? otherModel.row[anyKey] : otherModel[anyKey]); const isEqual = description.equal(selfValue, otherValue, stack); if (!isEqual) { return false; } } // check additional keys from other model const otherData = (otherModel instanceof Model ? otherModel.row : otherModel); for (const key in otherData) { if (key in this.row) { continue; } // exists unknown property for self model return false; } return true; } validate(row) { // for invalid row throw error here } prepare(row) { // any calculations with row by reference } prepareJSON(json) { // any calculations with json by reference } on(eventName, keyOrListener, listener) { if (typeof keyOrListener === "string") { const key = keyOrListener; const description = this.getDescription(key); if (!description) { throw new errors_1.UnknownPropertyError({ propertyName: key }); } super.on(eventName + ":" + key, listener); } else { listener = keyOrListener; super.on(eventName, listener); } return this; } prepareStructure() { if (this.constructor.prototype.hasOwnProperty("properties")) { return; } const constructor = this.constructor; const properties = constructor.prototype.structure(); // for speedup constructor, saving structure to prototype this.constructor.prototype.properties = properties; for (const key in this.properties) { const description = this.properties[key]; this.properties[key] = Type_1.Type.create(description, key); if (description.primary) { this.constructor.prototype.primaryKey = key; } } // structure must be static... really static Object.freeze(properties); } } exports.Model = Model; Model.Type = Type_1.Type; // for js Model.prototype.structure = function () { throw new errors_1.ModelWithoutStructureError({ className: this.constructor.name }); }; Type_1.Type.Model = Model; //# sourceMappingURL=Model.js.map