octonom
Version:
Object Document Mapper for any database
129 lines • 6.15 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("./errors");
const hooks_1 = require("./hooks");
const model_1 = require("./schema/model");
const object_1 = require("./schema/object");
function Hook(name, handler) {
return (constructor) => {
constructor.hooks = new hooks_1.Hooks(constructor.hooks);
constructor.hooks.register(name, handler);
};
}
exports.Hook = Hook;
const shadowInstanceProperty = Symbol('shadowInstanceProperty');
exports.getShadowInstance = Symbol('getShadowInstance');
const setShadowInstance = Symbol('setShadowInstance');
// TODO: think about model instances
function sanitize(value, sanitizeOptions) {
if (value === undefined) {
return {};
}
if (typeof value !== 'object') {
throw new errors_1.SanitizationError('Value is not an object.', 'no-object', sanitizeOptions.parent);
}
return value;
}
// TODO: Model can be made an abstract class but the type Constructor<Model>
// doesn't work anymore (e.g., used in mixins)
class Model {
// TODO: ideally we'd also use Partial<this> as the type for data
constructor(value, sanitizeOptions = { defaults: value === undefined }) {
const constructor = this.constructor;
const schemaMap = constructor.schemaMap;
// The shadow property holds data that is required to allow methods to operate on both
// the model instance and the instanceMap that is hidden from the user.
const shadowInstance = {
instanceMap: {},
parent: sanitizeOptions.parent,
schema: new model_1.ModelSchema({ model: constructor }),
value: undefined,
rawObject: this,
beforeChange: (path, newValue, oldInstance) => {
constructor.hooks.run('beforeChange', { path, value: newValue, modelInstance: this, schemaInstance: oldInstance });
if (shadowInstance.schema.options.callParentHooks !== false && shadowInstance.parent) {
shadowInstance.parent.instance.beforeChange([shadowInstance.parent.path].concat(path), newValue, oldInstance);
}
},
afterChange: (path, newValue, newInstance) => {
constructor.hooks.run('afterChange', { path, value: newValue, modelInstance: this, schemaInstance: newInstance });
if (shadowInstance.schema.options.callParentHooks !== false && shadowInstance.parent) {
shadowInstance.parent.instance.afterChange([shadowInstance.parent.path].concat(path), newValue, newInstance);
}
},
};
shadowInstance.value = object_1.proxifyObject(this, shadowInstance.instanceMap, shadowInstance, schemaMap);
this[setShadowInstance](shadowInstance);
const sanitizedValue = sanitize(value, sanitizeOptions);
shadowInstance.beforeChange([], sanitizedValue, shadowInstance);
object_1.setObject(sanitizedValue, this, shadowInstance.instanceMap, shadowInstance, schemaMap, sanitizeOptions);
shadowInstance.afterChange([], sanitizedValue, shadowInstance);
return shadowInstance.value;
}
static setSchema(key, schema) {
if (this.schemaMap[key]) {
throw new Error(`Key ${key} is already set.`);
}
this.schemaMap = Object.assign({}, this.schemaMap);
this.schemaMap[key] = schema;
}
inspect() {
return this.toObject();
}
populate(populateMap) {
return __awaiter(this, void 0, void 0, function* () {
const constructor = this.constructor;
const schemaMap = constructor.schemaMap;
const shadowInstance = this[exports.getShadowInstance]();
yield object_1.populateObject(shadowInstance.rawObject, shadowInstance.instanceMap, schemaMap, populateMap);
return this;
});
}
// note: set runs with `this` bound to the unproxied instance, therefore
// the hooks will be called with the unproxied instance as well.
// This prevents recursion when handlers set properties.
// The set hooks are called by the proxy.
set(data, options = {}, runHooks = true) {
const constructor = this.constructor;
const schemaMap = constructor.schemaMap;
const shadowInstance = this[exports.getShadowInstance]();
if (runHooks) {
shadowInstance.beforeChange([], data, shadowInstance);
}
object_1.setObject(data, shadowInstance.rawObject, shadowInstance.instanceMap, shadowInstance, schemaMap, options);
if (runHooks) {
shadowInstance.afterChange([], data, shadowInstance);
}
}
toObject(options) {
const shadowInstance = this[exports.getShadowInstance]();
return object_1.toObject(shadowInstance.instanceMap, options);
}
toJSON() {
return this.toObject();
}
validate() {
return __awaiter(this, void 0, void 0, function* () {
const shadowInstance = this[exports.getShadowInstance]();
yield object_1.validateObject(shadowInstance.instanceMap, shadowInstance, shadowInstance.schema.options.model.schemaMap);
});
}
[setShadowInstance](instance) {
this[shadowInstanceProperty] = instance;
}
[exports.getShadowInstance]() {
return this[shadowInstanceProperty];
}
}
Model.schemaMap = {};
Model.hooks = new hooks_1.Hooks();
exports.Model = Model;
//# sourceMappingURL=model.js.map