octonom
Version:
Object Document Mapper for any database
191 lines • 8.05 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 lodash_1 = require("lodash");
const errors_1 = require("../errors");
const schema_1 = require("./schema");
// populate an object and instanceMap simultaneously
function populateObject(obj, instanceMap, schemaMap, populateReference) {
return __awaiter(this, void 0, void 0, function* () {
const newObj = {};
yield Promise.all(Object.keys(populateReference).map((key) => __awaiter(this, void 0, void 0, function* () {
const schema = schemaMap[key];
if (!schema) {
throw new Error(`Key ${key} not found in schema.`);
}
if (!schema.populate) {
throw new Error(`Key ${key} is not populatable.`);
}
const instance = instanceMap[key];
if (instance === undefined) {
return;
}
newObj[key] = yield schema.populate(instance, populateReference[key]);
})));
Object.assign(obj, newObj);
return obj;
});
}
exports.populateObject = populateObject;
// proxify an object to sync changes to an instanceMap
function proxifyObject(obj, instanceMap, parentInstance, schemaMap) {
return new Proxy(obj, {
set(target, key, value, receiver) {
if (typeof key !== 'symbol' && schemaMap[key]) {
parentInstance.beforeChange([], { [key]: value }, parentInstance);
const oldInstance = instanceMap[key];
if (oldInstance) {
delete oldInstance.parent;
}
instanceMap[key] = schemaMap[key].create(value, { parent: { instance: parentInstance, path: key } });
target[key] = instanceMap[key].value;
parentInstance.afterChange([], { [key]: value }, parentInstance);
}
else {
target[key] = value;
}
return true;
},
deleteProperty(target, key) {
if (typeof key !== 'symbol' && schemaMap[key]) {
parentInstance.beforeChange([], { [key]: undefined }, parentInstance);
const oldInstance = instanceMap[key];
if (oldInstance) {
delete oldInstance.parent;
}
delete instanceMap[key];
delete target[key];
parentInstance.afterChange([], { [key]: undefined }, parentInstance);
}
else {
delete target[key];
}
return true;
},
});
}
exports.proxifyObject = proxifyObject;
// set an object and instanceMap simultaneously
function setObject(data, obj, instanceMap, parentInstance, schemaMap, sanitizeOptions) {
const dataKeys = Object.keys(data);
const schemaKeys = Object.keys(schemaMap);
const disallowedKeys = lodash_1.difference(dataKeys, schemaKeys);
if (disallowedKeys.length > 0) {
throw new errors_1.SanitizationError(`Key ${disallowedKeys[0]} not found in schema.`, 'key-not-in-schema', sanitizeOptions.parent);
}
const newInstanceMap = {};
// sanitize all values before setting
schemaKeys.forEach((key) => {
if (data[key] === undefined && !sanitizeOptions.defaults) {
return;
}
newInstanceMap[key] = schemaMap[key].create(data[key], Object.assign({}, sanitizeOptions, { parent: { instance: parentInstance, path: key } }));
});
schemaKeys.forEach((key) => {
if (sanitizeOptions.replace || key in data) {
if (instanceMap[key]) {
delete instanceMap[key].parent;
}
delete instanceMap[key];
delete obj[key];
}
if (newInstanceMap[key]) {
instanceMap[key] = newInstanceMap[key];
obj[key] = newInstanceMap[key].value;
}
});
}
exports.setObject = setObject;
function toObject(instanceMap, options) {
const newObj = {};
Object.keys(instanceMap).forEach((key) => {
const instance = instanceMap[key];
newObj[key] = instance.schema.toObject(instance, options);
});
return newObj;
}
exports.toObject = toObject;
function validateObject(instanceMap, parentInstance, schemaMap) {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(Object.keys(schemaMap).map((key) => __awaiter(this, void 0, void 0, function* () {
const schema = schemaMap[key];
const instance = instanceMap[key];
if (schema.options.required && instance === undefined) {
throw new errors_1.ValidationError(`Key ${key} is required.`, 'required', { instance: parentInstance, path: key });
}
if (instance) {
yield schema.validate(instance);
}
})));
});
}
exports.validateObject = validateObject;
class ObjectSchema {
constructor(options) {
this.options = options;
}
create(value, sanitizeOptions = {}) {
const sanitizedValue = this.sanitize(value, sanitizeOptions);
if (sanitizedValue === undefined) {
return undefined;
}
const instance = {
instanceMap: {},
parent: sanitizeOptions.parent,
schema: this,
value: undefined,
beforeChange: (path, newValue, oldInstance) => {
if (this.options.callParentHooks !== false && instance.parent) {
instance.parent.instance.beforeChange([instance.parent.path].concat(path), newValue, oldInstance);
}
},
afterChange: (path, newValue, newInstance) => {
if (this.options.callParentHooks !== false && instance.parent) {
instance.parent.instance.afterChange([instance.parent.path].concat(path), newValue, newInstance);
}
},
};
const obj = {};
setObject(sanitizedValue, obj, instance.instanceMap, instance, this.options.schemaMap, sanitizeOptions);
instance.value = proxifyObject(obj, instance.instanceMap, instance, this.options.schemaMap);
return instance;
}
populate(instance, populateReference) {
return __awaiter(this, void 0, void 0, function* () {
if (populateReference === true) {
throw new Error('populateReference must be an object.');
}
return populateObject(instance.value, instance.instanceMap, this.options.schemaMap, populateReference);
});
}
toObject(instance, options = {}) {
return toObject(instance.instanceMap, options);
}
validate(instance) {
return __awaiter(this, void 0, void 0, function* () {
yield validateObject(instance.instanceMap, instance, this.options.schemaMap);
yield schema_1.validate(this.options, instance);
});
}
sanitize(value, sanitizeOptions) {
if (value === undefined) {
if (this.options.required && sanitizeOptions.defaults) {
return {};
}
return undefined;
}
if (typeof value !== 'object') {
throw new errors_1.SanitizationError('Value is not an object.', 'no-object', sanitizeOptions.parent);
}
return value;
}
}
exports.ObjectSchema = ObjectSchema;
//# sourceMappingURL=object.js.map