octonom
Version:
Object Document Mapper for any database
238 lines • 12.5 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 schema_1 = require("./schema");
/** Creates a proxy array that a user can interact with.
* All change operations on the array are intercepted for reflecting the changes
* in instanceArray and calling the beforeChange/afterChange handlers.
* Note: this is a bit lengthy since we need to cover the full js array API.
*/
function proxifyArray(array, instanceArray, parentInstance, elementSchema) {
return new Proxy(array, {
get(target, key, receiver) {
switch (key) {
/* istanbul ignore next */
case 'copyWithin':
throw new Error('not yet implemented');
/* istanbul ignore next */
case 'fill':
throw new Error('not yet implemented');
case 'pop':
return () => {
const clone = target.slice();
clone.pop();
parentInstance.beforeChange([], clone, parentInstance);
const removedInstance = instanceArray.pop();
delete removedInstance.parent;
const result = target.pop();
parentInstance.afterChange([], clone, parentInstance);
return result;
};
case 'push':
return (...args) => {
const clone = target.slice();
clone.push(...args);
parentInstance.beforeChange([], clone, parentInstance);
const newInstances = args.map((arg, index) => {
return elementSchema.create(arg, { parent: { instance: parentInstance, path: target.length + index } });
});
instanceArray.push(...newInstances);
const result = target.push(...newInstances.map(el => el.value));
parentInstance.afterChange([], clone, parentInstance);
return result;
};
case 'reverse':
return () => {
const clone = target.slice();
clone.reverse();
parentInstance.beforeChange([], clone, parentInstance);
instanceArray.reverse();
instanceArray.forEach((instance, index) => instance.parent.path = index);
target.reverse();
parentInstance.afterChange([], clone, parentInstance);
return receiver;
};
case 'sort':
return (compare) => {
const mapped = instanceArray.map((instance, index) => ({ instance, index }));
mapped.sort((a, b) => {
if (compare) {
return compare(a.instance.value, b.instance.value);
}
else {
if (a.instance.value < b.instance.value) {
return -1;
}
if (a.instance.value > b.instance.value) {
return 1;
}
return 0;
}
});
const clone = mapped.map(el => el.instance.value);
parentInstance.beforeChange([], clone, parentInstance);
instanceArray.splice(0, instanceArray.length, ...mapped.map((el, index) => {
el.instance.parent.path = index;
return el.instance;
}));
target.splice(0, target.length, ...mapped.map(el => el.instance.value));
parentInstance.afterChange([], clone, parentInstance);
return receiver;
};
case 'splice':
return (start, deleteCount, ...args) => {
if (deleteCount === undefined || deleteCount > target.length - start) {
deleteCount = target.length - start;
}
const clone = instanceArray.map(el => el.value);
clone.splice(start, deleteCount, ...args);
parentInstance.beforeChange([], clone, parentInstance);
const newInstances = args.map((arg, index) => {
return elementSchema.create(arg, { parent: { instance: parentInstance, path: start + index } });
});
const removedInstances = instanceArray.splice(start, deleteCount, ...newInstances);
removedInstances.forEach(instance => delete instance.parent);
instanceArray.slice(start + newInstances.length).forEach((instance, index) => {
instance.parent.path = start + newInstances.length + index;
});
const result = target.splice(start, deleteCount, ...newInstances.map(el => el.value));
parentInstance.afterChange([], clone, parentInstance);
return result;
};
// case 'toString':
// return () => target.map(el => el.value).toString();
case 'unshift':
return (...args) => {
const clone = instanceArray.map(el => el.value);
clone.unshift(...args);
parentInstance.beforeChange([], clone, parentInstance);
const newInstances = args.map((arg, index) => {
return elementSchema.create(arg, { parent: { instance: parentInstance, path: index } });
});
instanceArray.unshift(...newInstances);
instanceArray.slice(newInstances.length).forEach((instance, index) => {
instance.parent.path = newInstances.length + index;
});
const result = target.unshift(...newInstances.map(el => el.value));
parentInstance.afterChange([], clone, parentInstance);
return result;
};
}
return target[key];
},
set(target, key, value, receiver) {
if (typeof key === 'number' || typeof key === 'string' && /^\d$/.test(key)) {
const numKey = typeof key === 'number' ? key : parseInt(key, 10);
const oldInstance = instanceArray[numKey];
parentInstance.beforeChange([numKey], value, oldInstance);
const newInstance = elementSchema.create(value, { parent: { instance: parentInstance, path: numKey } });
if (oldInstance) {
delete oldInstance.parent;
}
instanceArray[numKey] = newInstance;
target[numKey] = newInstance ? newInstance.value : undefined;
parentInstance.afterChange([numKey], value, newInstance);
}
else {
target[key] = value;
}
return true;
},
deleteProperty: (target, key) => {
if (typeof key === 'number' || typeof key === 'string' && /^\d$/.test(key)) {
const numKey = typeof key === 'number' ? key : parseInt(key, 10);
const oldInstance = instanceArray[numKey];
parentInstance.beforeChange([numKey], undefined, oldInstance);
if (oldInstance) {
delete oldInstance.parent;
}
delete instanceArray[numKey];
delete target[numKey];
parentInstance.afterChange([numKey], undefined, undefined);
}
else {
delete target[key];
}
return true;
},
});
}
exports.proxifyArray = proxifyArray;
class ArraySchema {
constructor(options) {
this.options = options;
}
create(value, sanitizeOptions = {}) {
const sanitizedValue = this.sanitize(value, sanitizeOptions);
if (sanitizedValue === undefined) {
return undefined;
}
const instance = {
instanceArray: undefined,
rawArray: undefined,
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);
}
},
};
instance.instanceArray = sanitizedValue.map((element, index) => this.options.elementSchema.create(element, Object.assign({}, sanitizeOptions, { parent: { instance, path: index } })));
instance.rawArray = instance.instanceArray.map(element => element.value);
instance.value = proxifyArray(instance.rawArray, instance.instanceArray, instance, this.options.elementSchema);
return instance;
}
populate(instance, populateReference) {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(instance.instanceArray.map((element, index) => __awaiter(this, void 0, void 0, function* () {
instance.rawArray[index] = yield this.options.elementSchema.populate(element, populateReference);
})));
return instance.value;
});
}
toObject(instance, options = {}) {
return instance.instanceArray.map(element => element ? element.schema.toObject(element, options) : undefined);
}
validate(instance) {
return __awaiter(this, void 0, void 0, function* () {
if (this.options.minLength !== undefined && instance.value.length < this.options.minLength) {
throw new errors_1.ValidationError(`Array must have at least ${this.options.minLength} elements.`, 'array-min-length', instance.parent);
}
if (this.options.maxLength !== undefined && instance.value.length > this.options.maxLength) {
throw new errors_1.ValidationError(`Array must have at most ${this.options.maxLength} elements.`, 'array-max-length', instance.parent);
}
// validate all elements
yield Promise.all(instance.instanceArray.map(element => this.options.elementSchema.validate(element)));
yield schema_1.validate(this.options, instance);
});
}
sanitize(value, sanitizeOptions) {
if (value === undefined) {
if (this.options.required && sanitizeOptions.defaults) {
return [];
}
return undefined;
}
if (!(value instanceof Array)) {
throw new errors_1.SanitizationError('Value is not an array.', 'no-array', sanitizeOptions.parent);
}
return value;
}
}
exports.ArraySchema = ArraySchema;
//# sourceMappingURL=array.js.map