UNPKG

octonom

Version:

Object Document Mapper for any database

238 lines 12.5 kB
"use strict"; 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