model-layer
Version:
391 lines • 12.8 kB
JavaScript
"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