UNPKG

objecture

Version:

❂ Objecture ⏣ Object Watcher, Property Manager ⊚ Capture property changes for object, array mutator methods. ⊚ Schematize and validate object, array properties. ⊚ Browser, NodeJS compatible. ⁘ Uses Core-Plex - Event Listener Manage

1,448 lines (1,414 loc) 98.8 kB
import Core from 'core-plex'; import { typedObjectLiteral as typedObjectLiteral$1, assign as assign$3, variables, typeOf as typeOf$1, impandTree, regularExpressions } from 'recourse'; const Primitives = { 'string': String, 'number': Number, 'boolean': Boolean, 'bigint': BigInt, 'undefined': undefined, 'null': null, }; Object.values(Primitives); const Objects = { 'object': Object, 'array': Array, }; Object.values(Objects); const Types = Object.assign({}, Primitives, Objects); Object.values(Types); [ Primitives.String, Primitives.Number, Primitives.Boolean, Objects.Object, Objects.Array ]; var typeOf = ($data) => Object .prototype .toString .call($data).slice(8, -1).toLowerCase(); function typedObjectLiteral($value) { let _typedObjectLiteral; const typeOfValue = typeOf($value); if(typeOfValue === 'string') { const value = $value.toLowerCase(); if(value === 'object') { _typedObjectLiteral = {}; } else if(value === 'array') { _typedObjectLiteral = []; } } else { if(typeOfValue === 'object') { _typedObjectLiteral = {}; } else if(typeOfValue === 'array') { _typedObjectLiteral = []; } } return _typedObjectLiteral } var isArrayLike = ($source) => { let isArrayLike; const typeOfSource = typeOf($source); if(typeOfSource === 'array') { isArrayLike = true; } else if( typeOfSource === 'object' && Number.isInteger($source.length) && $source.length >= 0 ) { iterateSourceKeys: for(const $sourceKey of Object.keys( Object.getOwnPropertyDescriptors($source) )) { if($sourceKey === 'length') { continue iterateSourceKeys } isArrayLike = !isNaN($sourceKey); if(!isArrayLike) { break iterateSourceKeys } } } else { isArrayLike = false; } return isArrayLike }; function assign$2($target, ...$sources) { if(!$target) { return $target} iterateSources: for(const $source of $sources) { if(!$source) continue iterateSources for(const [ $sourcePropertyKey, $sourcePropertyValue ] of Object.entries($source)) { const typeOfTargetPropertyValue = typeOf($target[$sourcePropertyKey]); const typeOfSourcePropertyValue = typeOf($sourcePropertyValue); if( typeOfTargetPropertyValue === 'object' && typeOfSourcePropertyValue === 'object' ) { $target[$sourcePropertyKey] = assign$2($target[$sourcePropertyKey], $sourcePropertyValue); } else { $target[$sourcePropertyKey] = $sourcePropertyValue; } } } return $target } var Options$1$1 = { ancestors: [], delimiter: '.', depth: 0, frozen: false, maxDepth: 10, nonenumerable: true, path: false, sealed: false, type: false, }; function getOwnPropertyDescriptor($properties, $propertyKey, $options) { const options = Object.assign({}, Options$1$1, $options, { ancestors: Object.assign([], $options.ancestors) }); const propertyDescriptor = Object.getOwnPropertyDescriptor($properties, $propertyKey); if(!options.nonenumerable && !propertyDescriptor.enumerable) { return } if(!options.ancestors.includes($properties)) { options.ancestors.unshift($properties); } if(options.ancestors.includes(propertyDescriptor.value)) { return } if(options.path) { options.path = (typeOf(options.path) === 'string') ? [options.path, $propertyKey].join(options.delimiter) : $propertyKey; propertyDescriptor.path = options.path; } if(options.type) { propertyDescriptor.type = typeOf(propertyDescriptor.value); } if(options.frozen) { propertyDescriptor.frozen = Object.isFrozen(propertyDescriptor.value); } if(options.sealed) { propertyDescriptor.sealed = Object.isSealed(propertyDescriptor.value); } if(['array', 'object'].includes(typeOf(propertyDescriptor.value))) { propertyDescriptor.value = getOwnPropertyDescriptors(propertyDescriptor.value, options); } return propertyDescriptor } function getOwnPropertyDescriptors($properties, $options) { const propertyDescriptors = {}; const options = Object.assign({}, Options$1$1, $options); if(options.depth >= options.maxDepth) { return propertyDescriptors } else { options.depth++; } for(const [$propertyKey, $propertyDescriptor] of Object.entries(Object.getOwnPropertyDescriptors($properties))) { const propertyDescriptor = getOwnPropertyDescriptor($properties, $propertyKey, options); if(propertyDescriptor !== undefined) { propertyDescriptors[$propertyKey] = propertyDescriptor; } } return propertyDescriptors } var Options$2 = { typeCoercion: false, }; function defineProperty$1($target, $propertyKey, $propertyDescriptor, $options) { const propertyDescriptor = Object.assign({}, $propertyDescriptor); const options = Object.assign({}, Options$2, $options); const typeOfPropertyValue = typeOf(propertyDescriptor.value); if(['array', 'object'].includes(typeOfPropertyValue)) { const propertyValue = isArrayLike(Object.defineProperties( typedObjectLiteral(typeOfPropertyValue), propertyDescriptor.value )) ? [] : {}; propertyDescriptor.value = defineProperties$1(propertyValue, propertyDescriptor.value, options); } else if( options.typeCoercion && Object.getOwnPropertyDescriptor(propertyDescriptor, 'type') !== undefined && !['undefined', 'null'].includes(typeOfPropertyValue) ) { propertyDescriptor.value = Primitives[propertyDescriptor.type](propertyDescriptor.value); } Object.defineProperty($target, $propertyKey, propertyDescriptor); if($propertyDescriptor.sealed) { Object.seal($target[$propertyKey]); } if($propertyDescriptor.frozen) { Object.freeze($target[$propertyKey]); } return $target } function defineProperties$1($target, $propertyDescriptors, $options) { const options = Object.assign({}, Options$2, $options); for(const [ $propertyKey, $propertyDescriptor ] of Object.entries($propertyDescriptors)) { defineProperty$1($target, $propertyKey, $propertyDescriptor, options); } return $target } var Options$1$2 = ($options) => { const options = assign$2({ propertyDescriptors: false, defineProperties: false, replacers: [], revivers: [], }, $options); if(options.propertyDescriptors?.type) { options.replacers.push(function BigintReplacer($key, $value) { if(typeOf($value) === 'bigint') { return String($value) } else { return $value } }); } return options }; function JSONMiddlewares($middlewares, $key, $value) { let value = $value; for(const $middleware of $middlewares) { value = $middleware($key, $value); } return value } class LocalStorageRoute extends EventTarget { constructor($path, $options) { super(); if(!$path) return null const options = Options$1$2($options); const db = localStorage; Object.defineProperties(this, { 'path': { value: $path }, 'raw': { value: function raw() { return db.getItem(this.path) } }, 'get': { value: function get() { const { path } = this; const raw = db.getItem(this.path); if(['undefined', undefined].includes(raw)) { return } const propertyDescriptors = JSON.parse(raw, JSONMiddlewares.bind(null, options.revivers)); const dataTypedObjectLiteral = typedObjectLiteral(propertyDescriptors); const data = (options.propertyDescriptors) ? defineProperties$1( dataTypedObjectLiteral, propertyDescriptors, options.defineProperties ) : propertyDescriptors; this.dispatchEvent(new CustomEvent('get', { detail: { path, raw, data } })); return data } }, 'set': { value: function set($data) { const data = $data; const { path } = this; let raw = (options.propertyDescriptors) ? JSON.stringify( getOwnPropertyDescriptors(data, options.propertyDescriptors), JSONMiddlewares.bind(null, options.replacers) ) : JSON.stringify( data, JSONMiddlewares.bind(null, options.replacers) ); db.setItem(this.path, raw); this.dispatchEvent(new CustomEvent('set', { detail: { path, raw, data } })); return } }, 'remove': { value: function remove() { const { path } = this; const raw = this.raw(); const data = this.get(); db.removeItem(this.path); this.dispatchEvent(new CustomEvent('remove', { detail: { path, raw, data } })); return } }, }); } } class Verification extends EventTarget { constructor($settings) { super(); const settings = Object.assign({}, $settings); Object.defineProperties(this, { 'type': { value: settings.type }, 'key': { value: settings.key }, 'value': { value: settings.value }, 'message': { configurable: true, get() { let message; if(this.pass !== undefined) { message = settings.messages[String(this.pass)](this); Object.defineProperty(this, 'message', { value: message }); } return message } }, 'pass': { writable: true, set pass($pass) { Object.defineProperty(this, 'pass', { value: $pass }); }, }, }); } } const Messages$1 = { 'true': ($validation) => `${$validation.valid}`, 'false': ($validation) => `${$validation.valid}`, }; function report($format = "expand", $prevalidation) { const prevalidation = $prevalidation || this; const schema = prevalidation.schema; const validations = [].concat( prevalidation.advance, prevalidation.deadvance, prevalidation.unadvance ); if($format === "expand") { const _report = typedObjectLiteral$1(schema.type); for(const $validation of validations) { const verifications = [].concat( $validation.advance, $validation.deadvance, $validation.unadvance ); _report[$validation.key] = {}; for(const $verification of verifications) { _report[$validation.key][$verification.type] = {}; if($verification.validation) { _report[$validation.key][$verification.type] = this.report($format, $verification.validation); } else { _report[$validation.key][$verification.type] = $verification; } } } return _report } if($format === "impand") { if(prevalidation.valid === false) { return false } const _report = typedObjectLiteral$1(schema.type); for(const $validation of validations) { const verifications = [].concat( $validation.advance, $validation.deadvance, $validation.unadvance ); let reportValue; iterateVerifications: for(const $verification of verifications) { if($verification.type === 'type') { if($verification.validation && $validation.valid) { reportValue = this.report($format, $verification.validation); } break iterateVerifications } } if(!reportValue) { reportValue = $validation.valid; } _report[$validation.key] = reportValue; } return _report } } class Validation extends EventTarget { constructor($settings = {}, $schema) { super(); const settings = Object.assign({ messages: Messages$1 }, $settings); let valid; const advance = []; const deadvance = []; const unadvance = []; Object.defineProperties(this, { 'schema': { value: $schema }, 'verificationType': { value: settings.verificationType }, 'required': { value: settings.required }, 'definition': { value: settings.definition }, 'key': { value: settings.key }, 'value': { value: settings.value }, 'advance': { value: advance }, 'deadvance': { value: deadvance }, 'unadvance': { value: unadvance }, 'valid': { writable: true, get valid() { return valid }, set valid($valid) { Object.defineProperty(this, 'valid', { value: $valid }); } }, 'report': { configurable: true, get() { const _report = report.bind(this); Object.defineProperty(this, 'report', { value: _report }); return _report } }, }); } } const Messages = { 'true': ($verification) => `${$verification.pass}`, 'false': ($verification) => `${$verification.pass}`, }; class Validator extends EventTarget { constructor($definition = {}, $schema) { super(); const definition = Object.freeze( Object.assign({ messages: Messages }, $definition) ); Object.defineProperties(this, { 'definition': { value: definition }, 'schema': { value: $schema }, 'type': { value: definition.type }, 'messages': { value: definition.messages }, 'validate': { configurable: true, get() { function validate($key, $value, $source, $target) { const { definition, messages, type } = this; let verification = new Verification({ type: type, key: $key, value: definition.value, messages: assign$3({}, messages, definition.messages), }); const validation = definition.validate(...arguments); if(typeof validation === 'object') { verification.validation = validation; verification.pass = validation.valid; } else { verification.pass = validation; } return verification } const boundValidate = validate.bind(this); Object.defineProperty(this, 'validate', { value: boundValidate }); return boundValidate } }, }); } } class RequiredValidator extends Validator { constructor($definition, $schema) { super(Object.assign({}, $definition, { type: 'required', validate: ($key, $value, $source, $target) => { const { requiredProperties, requiredPropertiesSize, type } = $schema; const corequiredProperties = Object.assign({}, requiredProperties); let corequiredPropertiesSize = requiredPropertiesSize; Object.assign(typedObjectLiteral$1(type), $source, $target); this.definition; let pass; if(!requiredPropertiesSize) { pass = true; } else { if(Object.hasOwn(corequiredProperties, $key)) { delete corequiredProperties[$key]; corequiredPropertiesSize--; } if(corequiredPropertiesSize) { const coschema = new Schema(corequiredProperties, { path: $schema.path, parent: $schema.parent, }); const comodel = Object.assign({}, $target, $source); const covalidation = coschema.validate(comodel); pass = covalidation.valid; } } return pass } }), $schema); } } const { ObjectKeys, TypeKeys } = variables; class TypeValidator extends Validator { constructor($definition = {}, $schema) { super(Object.assign({}, $definition, { type: 'type', validate: ($key, $value, $source, $target) => { let pass; const definition = this.definition; let typeOfDefinitionValue = typeOf$1(definition.value); if(typeOfDefinitionValue === 'function') { typeOfDefinitionValue = typeOf$1(definition.value()); } else if(definition.value instanceof Schema) { typeOfDefinitionValue = definition.value.type; } else { typeOfDefinitionValue = typeOf$1(definition.value); } if(TypeKeys.includes(typeOfDefinitionValue)) { const typeOfValue = typeOf$1($value); if(typeOfValue === 'undefined') { pass = false; } else if(typeOfDefinitionValue === 'undefined') { pass = true; } else if(definition.value instanceof Schema) { const validation = definition.value.validate($value, $source); pass = validation; } else { pass = (typeOfDefinitionValue === typeOfValue); } } else { pass = false; } return pass }, }), $schema); } } class RangeValidator extends Validator { constructor($definition = {}, $schema) { super(Object.assign($definition, { type: 'range', validate: ($key, $value) => { const definition = this.definition; let pass; if(typeof $value !== 'number') { pass = false; } else { const { min, max } = definition; let validMin, validMax; if(min !== undefined) { validMin = ($value >= min.value); } else { validMin = true; } if(max !== undefined) { validMax = ($value <= max.value); } else { validMax = true; } if(validMin && validMax) { pass = true; } else { pass = false;} } return pass } }), $schema); } } class LengthValidator extends Validator { constructor($definition = {}, $schema) { super(Object.assign($definition, { type: 'length', validate: ($key, $value) => { const definition = this.definition; let pass; if(typeof $value !== 'string') { pass = false; } else { const { min, max } = definition; let validMin, validMax; if(min !== undefined) { validMin = ($value.length >= min.value); } else { validMin = true; } if(max !== undefined) { validMax = ($value.length <= max.value); } else { validMax = true; } if(validMin && validMax) { pass = true; } else { pass = false;} } return pass }, }), $schema); } } class EnumValidator extends Validator { constructor($definition = {}, $schema) { super(Object.assign($definition, { type: 'enum', validate: ($key, $value) => { const definition = this.definition; let pass; if(![ 'string', 'number', 'boolean' ].includes(typeof $value)) { pass = false;} else { const enumeration = definition.value; pass = enumeration.includes($value); } return pass }, }), $schema); } } class MatchValidator extends Validator { constructor($settings = {}, $schema) { super(Object.assign($settings, { type: 'match', validate: ($key, $value) => { const definition = this.settings; let pass; if(![ 'string', 'number', 'boolean' ].includes(typeof $value)) { pass = false;} else { const match = definition; (match.value.exec($value) !== null); } return pass ? true : false }, }), $schema); } } var Options$1 = (...$options) => Object.assign({ required: false, verificationType: 'all', // verificationType: 'one', strict: false, properties: { type: 'type', value: 'value', }, }, ...$options); class Schema extends EventTarget { constructor($properties = {}, $options = {}) { super(); Object.defineProperties(this, { 'options': { value: Options$1($options) }, 'type': { value: typeOf$1($properties) }, 'parent': { configurable: true, get() { const { options } = this; const parent = (options.parent) ? options.parent : null; Object.defineProperty(this, 'parent', { value: parent }); return parent } }, 'root': { configurable: true, get() { let root = this; iterateParents: while(root) { if([undefined, null].includes(root.parent)) { break iterateParents } root = root.parent; } return root } }, 'key': { configurable: true, get() { const { path } = this; const key = (path) ? path.split('.').pop() : null; Object.defineProperty(this, 'key', { value: key }); return key } }, 'path': { configurable: true, get() { const { options } = this; const path = (options.path) ? String(options.path) : null; Object.defineProperty(this, 'path', { value: path }); return path } }, 'required': { configurable: true, get() { const required = this.options.required; Object.defineProperty(this, 'required', { value: required }); return required } }, 'requiredProperties': { configurable: true, get() { const requiredProperties = typedObjectLiteral$1(this.type); for(const [$propertyKey, $propertyDefinition] of Object.entries(this.target)) { if($propertyDefinition.required?.value === true) { requiredProperties[$propertyKey] = $propertyDefinition; } } Object.defineProperty(this, 'requiredProperties', { value: Object.freeze(requiredProperties) }); return requiredProperties } }, 'requiredPropertiesSize': { configurable: true, get() { const requiredPropertiesSize = Object.keys(this.requiredProperties).length; Object.defineProperty(this, 'requiredPropertiesSize', { value: requiredPropertiesSize }); return requiredPropertiesSize } }, 'verificationType': { configurable: true, get() { const verificationType = this.options.verificationType; Object.defineProperty(this, 'verificationType', { value: verificationType }); return verificationType } }, 'target': { configurable: true, get() { let properties; const type = this.type; if(type === 'array') { properties = $properties.slice(0, 1); } else if(type === 'object') { properties = $properties; } const target = parseProperties(properties, this); Object.defineProperty(this, 'target', { value: target }); return target } }, 'validate': { value: function(...$arguments) { let { $sourceName, $source, $target } = parseValidateArguments(...$arguments); $target = $target || typedObjectLiteral$1($source); const { target, path, required, type, verificationType } = this; let validation = new Validation({ required, verificationType, definition: target, key: $sourceName, value: $source, }, this); const sourceProperties = Object.entries($source); let sourcePropertyIndex = 0; while(sourcePropertyIndex < sourceProperties.length) { const [$sourceKey, $sourceValue] = sourceProperties[sourcePropertyIndex]; const propertyValidation = this.validateProperty($sourceKey, $sourceValue, $source, $target); if(propertyValidation.valid === true) { validation.advance.push(propertyValidation); } else if(propertyValidation.valid === false) { validation.deadvance.push(propertyValidation); } else if(propertyValidation.valid === undefined) { validation.unadvance.push(propertyValidation );} sourcePropertyIndex++; } if(validation.advance.length) { validation.valid = true; } else if(validation.deadvance.length) { validation.valid = false; } else if(validation.unadvance.length) { validation.valid = undefined; } else { validation.valid = true; } return validation } }, 'validateProperty': { value: function() { const { $key, $value, $source, $target } = parseValidatePropertyArguments(...arguments); const { target, path, required, schema, type, verificationType } = this; let propertyDefinition; if(type === 'array') { propertyDefinition = target[0]; } else if(type === 'object') { propertyDefinition = target[$key]; } const propertyValidation = new Validation({ required, verificationType, definition: propertyDefinition, key: $key, value: $value, }, this); if(propertyDefinition === undefined) { const verification = new Verification({ type: null, definition: null, key: $key, value: $value, }, this); verification.pass = false; propertyValidation.unadvance.push(verification); } else { iteratePropertyDefinitionValidators: for(const [$validatorIndex, $validator] of Object.entries(propertyDefinition.validators)) { const verification = $validator.validate($key, $value, $source, $target); if(verification.pass === true) { propertyValidation.advance.push(verification); } else if(verification.pass === false) { propertyValidation.deadvance.push(verification); } else if(verification.pass === undefined) { propertyValidation.unadvance.push(verification); } if(this.verificationType === 'one' && propertyValidation.deadvance.length) { break iteratePropertyDefinitionValidators } } } if(propertyValidation.deadvance.length) { propertyValidation.valid = false; } else if(propertyValidation.advance.length) { propertyValidation.valid = true; } else if(propertyValidation.unadvance.length) { propertyValidation.valid = false; } return propertyValidation } }, }); } } function parseValidateArguments(...$arguments) { let $sourceName, $source, $target; if($arguments.length === 1) { $sourceName = null; $source = $arguments.shift(); $target = null; } else if($arguments.length === 2) { if(['number', 'string'].includes(typeof $arguments[0])) { $sourceName = $arguments.shift(); $source = $arguments.shift(); $target = null; } else if($arguments[0] && typeof $arguments[0] === 'object') { $sourceName = null; $source = $arguments.shift(); $target = $arguments.shift(); } } else if($arguments.length === 3) { if(['number', 'string'].includes(typeof $arguments[0])) { $sourceName = $arguments.shift(); $source = $arguments.shift(); $target = $arguments.shift(); } } return { $sourceName, $source, $target } } function parseValidatePropertyArguments(...$arguments) { let [$key, $value, $source, $target] = $arguments; return { $key, $value, $source, $target } } function parseProperties($properties, $schema) { const properties = typedObjectLiteral$1($properties); if(_isPropertyDefinition($properties, $schema)) { return $properties } for(const [ $propertyKey, $propertyValue ] of Object.entries($properties)) { let propertyDefinition = {}; typeOf$1($propertyValue); const isPropertyDefinition = _isPropertyDefinition($propertyValue, $schema); if(variables.TypeValues.includes($propertyValue)) { Object.assign(propertyDefinition, { type: { value: $propertyValue } }); } else if(variables.TypeKeys.includes($propertyValue)) { Object.assign(propertyDefinition, { type: { value: variables.Types[$propertyValue] } }); } else if(!isPropertyDefinition) { const subpropertyPath = ($schema.path) ? [$schema.path, $propertyKey].join('.') : $propertyKey; Object.assign(propertyDefinition, { type: { type: 'type', value: new Schema($propertyValue, Object.assign({}, $schema.options, { parent: $schema, path: subpropertyPath })) } }); } else if(isPropertyDefinition) { for(const [$propertyValidatorName, $propertyValidator] of Object.entries($propertyValue)) { const isValidatorDefinition = _isValidatorDefinition($propertyValidator, $schema); if(!isValidatorDefinition) { let propertyValidator; if($propertyValidatorName === 'type') { if($propertyValidator && typeof $propertyValidator === 'object') { const subpropertyPath = ($schema.path) ? [$schema.path, $propertyKey].join('.') : $propertyKey; propertyValidator = new Schema($propertyValidator, Object.assign({}, $schema.options, { parent: $schema, path: subpropertyPath, })); } else { propertyValidator = $propertyValidator; } } else { propertyValidator = $propertyValidator; } propertyDefinition[$propertyValidatorName] = { type: $propertyValidatorName, value: propertyValidator }; } else if(isValidatorDefinition) { propertyDefinition[$propertyValidatorName] = $propertyValidator; } } } propertyDefinition.validators = []; properties[$propertyKey] = propertyDefinition; const validators = new Map(); validators.set('type', Object.assign({}, { type: 'type', validator: TypeValidator, value: propertyDefinition.type.value })); validators.set('required', Object.assign({}, { type: 'required', validator: RequiredValidator, value: propertyDefinition.required?.value || false })); if(propertyDefinition.range) { validators.set('range', Object.assign({}, propertyDefinition.range, { type: 'range', validator: RangeValidator })); } else if(propertyDefinition.min || propertyDefinition.max) { validators.set('range', Object.assign({}, { type: 'range', min: propertyDefinition.min, max: propertyDefinition.max, validator: RangeValidator })); } if(propertyDefinition.length) { validators.set('length', Object.assign({}, propertyDefinition.length, { type: 'length', validator: LengthValidator })); } else if(propertyDefinition.minLength || propertyDefinition.maxLength) { validators.set('length', Object.assign({}, { type: 'length', min: propertyDefinition.minLength, max: maxLength, validator: LengthValidator })); } if(propertyDefinition.enum) { validators.set('enum', Object.assign({}, propertyDefinition.enum, { type: 'enum', validator: EnumValidator })); } if(propertyDefinition.match) { validators.set('match', Object.assign({}, propertyDefinition.match, { type: 'match', validator: MatchValidator })); } delete propertyDefinition.min; delete propertyDefinition.max; delete propertyDefinition.minLength; delete propertyDefinition.maxLength; for(const [ $validatorName, $validatorSettings ] of validators.entries()) { const ValidatorClass = $validatorSettings.validator; propertyDefinition[$validatorName] = $validatorSettings; propertyDefinition.validators.push(new ValidatorClass($validatorSettings, $schema)); } } return properties } function _isPropertyDefinition($object, $schema) { if(!$object || $object instanceof Schema) { return false } const typeKey = $schema.options.properties.type; return Object.hasOwn($object, typeKey) } function _isValidatorDefinition($object, $schema) { if(!$object) { return false } const valueKey = $schema.options.properties.value; return Object.hasOwn($object, valueKey) } var Options = ($options) => { const Options = assign$3({ autoload: false, autosave: false, localStorage: false, path: null, parent: null, enableEvents: false, enableValidation: true, validationEvents: { 'validProperty:$key': true, 'validProperty': true, 'nonvalidProperty:$key': true, 'nonvalidProperty': true, }, pathkey: true, subpathError: false, assignObject: 'set', assignArray: 'set', methods: { map: { get: { mutatorEvents: { 'get': true, 'getProperty': true, 'getProperty:$key': true, }, }, set: { recursive: true, mutatorEvents: { 'set': true, 'setProperty': true, 'setProperty:$key': true, }, }, delete: { mutatorEvents: { 'delete': true, 'deleteProperty': true, 'deleteProperty:$key': true, }, }, }, array: { concat: { mutatorEvents: { 'concatElement:$index': true, 'concatElement': true, 'concat': true, } }, copyWithin: { mutatorEvents: { 'copyWithinElement:$index': true, 'copyWithinElement': true, 'copyWithin': true, } }, fill: { lengthen: true, mutatorEvents: { 'fillElement:$index': true, 'fillElement': true, 'fill': true, } }, pop: { mutatorEvents: { 'pop': true }, }, push: { mutatorEvents: { 'pushElement:$index': true, 'pushElement': true, 'push': true, } }, reverse: { mutatorEvents: { 'reverse': true }, }, shift: { mutatorEvents: { 'shift': true }, }, splice: { mutatorEvents: { 'spliceDeleteElement:$index': true, 'spliceDeleteElement': true, 'spliceAddElement:$index': true, 'spliceAddElement': true, 'splice': true, } }, unshift: { mutatorEvents: { 'unshiftElement:$index': true, 'unshiftElement': true, 'unshift': true, } }, }, object: { assign: { sourceTree: true, mutatorEvents: { 'assignSourceProperty:$key': true, 'assignSourceProperty': true, 'assignSource': true, 'assign': true, }, }, defineProperties: { descriptorTree: true, mutatorEvents: { 'defineProperties': true }, }, defineProperty: { descriptorTree: true, mutatorEvents: { 'defineProperty': true, 'defineProperty:$key': true, }, }, freeze: { recursive: true, mutatorEvents: { 'freezeProperty': true, 'freeze': true, }, }, seal: { recursive: true, mutatorEvents: { 'sealProperty': true, 'seal': true, }, }, }, }, }, $options); return Options }; class ModelEvent extends CustomEvent { constructor($type, $settings, $model) { super($type, $settings); Object.defineProperties(this, { 'model': { get () { return $model } }, 'key': { configurable: true, get () { const key = (this.path) ? this.path.split('.').pop() : null; Object.defineProperty(this, 'key', { value: key }); return key } }, 'change': { configurable: true, get () { const change = $settings.change; Object.defineProperty(this, 'change', { value: change }); return change } }, 'value': { configurable: true, get () { const value = $settings.value; Object.defineProperty(this, 'value', { value: value }); return value } }, 'path': { configurable: true, get () { const path = $settings.path; Object.defineProperty(this, 'path', { value: path }); return path } }, 'detail': { configurable: true, get () { const detail = $settings.detail; Object.defineProperty(this, 'detail', { value: detail }); return detail } }, }); } } class Change { #_keyter = false #_preter = false #_anter = false #_conter = false #keyter #preter #anter #conter constructor($settings = {}) { for(const [$key, $value] of Object.entries($settings)) { this[$key] = $value; } } get preter() { return this.#preter } set preter($preter) { if(this.#_preter === true) { return this.#preter } this.#preter = $preter?.valueOf(); this.#_preter = true; } get anter() { return this.#anter } set anter($anter) { if(this.#_anter === true) { return this.#anter } this.#anter = $anter?.valueOf(); this.#_anter = true; } get conter() { if( this.#_conter === true || [this.#_preter, this.#_anter].includes(false) ) { return this.#conter } const preter = JSON.stringify(this.preter); const anter = JSON.stringify(this.anter); let conter; if(anter !== preter) { conter = true; } else { conter = false; } this.#conter = conter; this.#_conter = true; return this.#conter } } let ValidatorEvent$1 = class ValidatorEvent extends CustomEvent { constructor($type, $settings, $model) { super($type); Object.defineProperties(this, { 'key': { configurable: true, get () { const key = $settings.key; Object.defineProperty(this, 'key', { value: key }); return key } }, 'path': { configurable: true, get () { const path = ($model.path) ? [$model.path, $settings.key].join('.') : $settings.key; Object.defineProperty(this, 'path', { value: path }); return path } }, 'value': { configurable: true, get () { const value = $settings.value; Object.defineProperty(this, 'value', { value: value, }); return value } }, 'valid': { configurable: true, get () { const valid = $settings.valid; Object.defineProperty(this, 'valid', { value: valid }); return valid } }, }); } }; function assign$1($model, $options, ...$sources) { const options = Object.assign({}, $options); const assignObject = 'assign'; const assignArray = options.assignArray || 'assign'; const { path, schema, source, target } = $model; const { enableValidation, mutatorEvents, required, sourceTree, validationEvents } = options; const assignedSources = []; const assignChange = new Change({ preter: $model }); for(let $source of $sources) { let assignedSource; const assignSourceChange = new Change({ preter: $model }); if(Array.isArray($source)) { assignedSource = []; } else if($source && typeof $source === 'object') { assignedSource = {}; } let validObject; if(enableValidation && schema) { validObject = schema.validate($source, $model.valueOf()); validObject.report(); } iterateSourceProperties: for(let [$sourceKey, $sourceValue] of Object.entries($source)) { const assignSourcePropertyChange = new Change({ preter: target[$sourceKey] }); const assignSourcePropertyKeyChange = new Change({ preter: target[$sourceKey] }); if(schema && enableValidation) { const validatorTarget = $model.valueOf(); const validatorSource = $source; const validSourceProperty = schema.validateProperty($sourceKey, $sourceValue, validatorSource, validatorTarget); if(validationEvents) { let type, propertyType; if(validSourceProperty.valid) { type = 'validProperty'; propertyType = ['validProperty', $sourceKey].join(':'); } else { type = 'nonvalidProperty'; propertyType = ['nonvalidProperty', $sourceKey].join(':'); } for(const $eventType of [type, propertyType]) { $model.dispatchEvent(new ValidatorEvent$1($eventType, validSourceProperty, $model)); } } if(!validSourceProperty.valid) { continue iterateSourceProperties } } let sourceValue; if($sourceValue && typeof $sourceValue === 'object') { if($sourceValue instanceof $model.constructor) { sourceValue = $sourceValue.valueOf(); } let subschema; if(schema?.type === 'array') { subschema = schema.target[0].type.value; } else if(schema?.type === 'object') { subschema = schema.target[$sourceKey].type.value; } else { subschema = null; } const modelPath = (path) ? [path, $sourceKey].join('.') : String($sourceKey); if(sourceTree === false) { const suboptions = assign$3({}, options, { path: modelPath, parent: $model, }); sourceValue = new $model.constructor($sourceValue, subschema, suboptions); const assignment = { [$sourceKey]: sourceValue }; Object.assign(target, assignment); Object.assign(assignedSource, assignment); } else { if(target[$sourceKey] instanceof $model.constructor) { sourceValue = target[$sourceKey]; } else { const subproperties = typedObjectLiteral$1($sourceValue); const suboptions = assign$3({}, options, { path: modelPath, parent: $model, }); sourceValue = new $model.constructor(subproperties, subschema, suboptions); } const assignment = { [$sourceKey]: sourceValue }; Object.assign(target, assignment); Object.assign(assignedSource, assignment); $model.retroReenableEvents(); if(sourceValue.type === 'array') { if(['push', 'unshift'].includes(assignArray)) { sourceValue[assignArray](...$sourceValue); } else { sourceValue[assignArray]($sourceValue); } } else if(sourceValue.type === 'object') { sourceValue[assignObject]($sourceValue); } } } else { sourceValue = $sourceValue; const assignment = { [$sourceKey]: sourceValue }; Object.assign(target, assignment); Object.assign(assignedSource, assignment); } if(mutatorEvents) { const modelEventPath = (path) ? [path, $sourceKey].join('.') : String($sourceKey); if(mutatorEvents['assignSourceProperty:$key']) { const type = ['assignSourceProperty', $sourceKey].join(':'); assignSourcePropertyKeyChange.anter = target[$sourceKey]; $model.dispatchEvent( new ModelEvent(type, { path: modelEventPath, value: sourceValue, change: assignSourcePropertyKeyChange, detail: { source: assignedSource, } }, $model) ); } if(mutatorEvents['assignSourceProperty']) { assignSourcePropertyChange.anter = target[$sourceKey]; $model.dispatchEvent( new ModelEvent('assignSourceProperty', { path: modelEventPath, value: sourceValue, change: assignSourcePropertyChange, detail: { source: assignedSource, } }, $model) ); } } } assignedSources.push(assignedSource); if(mutatorEvents && mutatorEvents['assignSource']) { assignSourceChange.anter = $model; $model.dispatchEvent( new ModelEvent('assignSource', { path, change: assignSourceChange, detail: { source: assignedSource, }, }, $model) ); } } if(mutatorEvents && mutatorEvents['assign']) { assignChange.anter = $model; $model.dispatchEvent( new ModelEvent('assign', { path, change: assignChange, detail: { sources: assignedSources, }, }, $model) ); } return $model } function defineProperties($model, $options, $propertyDescriptors) { const { path, schema } = $model; let { enableValidation, mutatorEvents, required, validation, validationEvents, validationReport } = $options; const propertyDescriptorEntries = Object.entries($propertyDescriptors); const definePropertiesChange = new Change({ preter: $model }); for(const [ $propertyKey, $propertyDescriptor ] of propertyDescriptorEntries) { $model.defineProperty($propertyKey, $propertyDescriptor, Object.assign({}, $options, { validation, validationReport })); } if(mutatorEvents && mutatorEvents['defineProperties']) { definePropertiesChange.anter = $model; $model.dispatchEvent( new ModelEvent( 'defineProperties', { path, value: $model.valueOf(), detail: { descriptors: $propertyDescriptors, }, }, $model ) ); } return $model } function defineProperty($model, $options, $propertyKey, $propertyDescriptor) { const options = Object.assign({}, $options); const assignObject = 'defineProperties'; const assignArray = options.assignArray || 'defineProperties'; const { descriptorTree, enableValidation, mutatorEvents, validation, validationEvents, validationReport } = options; const { target, path, schema } = $model; const propertyValue = $propertyDescriptor.value; const targetPropertyDescriptor = Object.getOwnPropertyDescriptor(target, $propertyKey) || {}; const targetPropertyValue = targetPropertyDescriptor.value; const definePropertyChange = new Change({ preter: targetPropertyValue }); const definePropertyKeyChange = new Change({ preter: targetPropertyValue }); const targetPropertyValueIsModelInstance = targetPropertyValue instanceof $model.constructor; if(schema && enableValidation) { const validProperty = schema.validateProperty( $propertyKey, impandTree(propertyValue, 'value') || propertyValue, {}, $model.valueOf() ); if(validationEvents) { let type, propertyType; if(validProperty.valid) { type = 'validProperty'; propertyType = ['validProperty', $propertyKey].join(':'); } else { type = 'nonvalidProperty'; propertyType = ['nonvalidProperty', $propertyKey].join(':'); } for(const $eventType of [type, propertyType]) { $model.dispatchEvent(new ValidatorEvent$1($eventType, validProperty, $model)); } } if(!validProperty.valid) { return $model } } if(propertyValue && typeof propertyValue === 'object') { const modelPath = (path) ? [path, $propertyKey].join('.') : String($propertyKey); if(targetPropertyValueIsModelInstance) { if(descriptorTree === true) { targetPropertyValue.defineProperties($propertyDescriptor); } else { Object.defineProperty(target, $propertyKey, $propertyDescriptor); } } else { let subschema; if(schema) { if(schema.type === 'array') { subschema = schema.target[0].type.value; } else if(schema.type === 'object') { subschema = schema.target[$propertyKey].type.value; } else { subschema = undefined; } } let subtarget = typedObjectLiteral$1(propertyValue); const suboptions = assign$3({}, options, { path: modelPath, parent: $model, }); const submodel = new $model.constructor( subtarget, subschema, suboptions ); if(descriptorTree === true) { target[$propertyKey] = submodel; $model.retroReenableEvents(); if(submodel.type === 'array') { if(['push', 'unshift'].includes(assignArray)) { submodel[assignArray](...propertyValue); } else { submodel[assignArray](propertyValue); } } else if(submodel.type === 'object') { submodel[assignObject](propertyValue); } } else if(descriptorTree === false) { Object.defineProperty(target, $propertyKey, $propertyDescriptor); } } } else { Object.defineProperty(target, $propertyKey, $propertyDescriptor); } if(mutatorEvents) { const modelEventPath = (path) ? [path, $propertyKey].join('.') : String($propertyKey); if(mutatorEvents['defineProperty:$key']) { definePropertyKeyChange.anter = target[$propertyKey]; const type = ['defineProperty', $propertyKey].join(':'); $model.dispatchEvent( new ModelEvent(type, { path: modelEventPath, value: propertyValue, change: definePropertyKeyChange, detail: { prop: $propertyKey, descriptor: $propertyDescriptor, }, }, $model )); } if(mutatorEvents['defineProperty']) { definePropertyChange.anter = target[$propertyKey]; $model.dispatchEvent( new ModelEvent('defineProperty', { path: modelEventPath, value: propertyValue, change: definePropertyChange, detail: { prop: $propertyKey, descriptor: $propertyDescriptor, }, }, $model )); } } return $model } function freeze($model, $options) { const { recursive, mutatorEvents } = $options; const { target } = $model; if(recursive === true) { for(const [ $propertyKey, $propertyValue ] of Object.entries(target)) { if($propertyValue instanceof $model.constructor) { $propertyValue.freeze(); if(mutatorEvents && mutatorEvents['freezeProperty']) { $model.dispatchEvent( new ModelEvent( 'freezeProperty', { path: $propertyValue.path }, $model ) ); } } } } Object.freeze(target); if(mutatorEvents && mutatorEvents['freeze']) { $model.dispatchEvent( new ModelEvent( 'freeze', { path: $model.path }, $model ) ); } return $model } function seal($model, $options) { const { recursive, mutatorEvents } = $options; const { target } = $model; if(recursive === true) { for(const [ $propertyKey, $propertyValue ] of Object.entries(target)) { if($propertyValue instanceof $model.constructor) { $propertyValue.seal(); if(mutatorEvents && mutatorEvents['sealProperty']) { $model.dispatchEvent( new ModelEvent( 'sealProperty', { path: $propertyValue.path }, $model ) ); } } } } Object.seal(target); if(mutatorEvents && mutatorEvents['seal']) { $model.dispatchEvent( new ModelEvent( 'seal', { path: $model.path }, $model ) ); } return $model } var ObjectProperty