Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@jsiqle/core

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsiqle/core - npm Package Compare versions

Comparing version 1.0.5 to 1.1.0

2

dist/main.js

@@ -1,1 +0,1 @@

!function(root,factory){"object"==typeof exports&&"object"==typeof module?module.exports=factory():"function"==typeof define&&define.amd?define([],factory):"object"==typeof exports?exports["@jsiqle/core"]=factory():root["@jsiqle/core"]=factory()}(global,function(){return(()=>{"use strict";var __webpack_require__={n:module=>{var getter=module&&module.__esModule?()=>module.default:()=>module;return __webpack_require__.d(getter,{a:getter}),getter},d:(exports,definition)=>{for(var key in definition)__webpack_require__.o(definition,key)&&!__webpack_require__.o(exports,key)&&Object.defineProperty(exports,key,{enumerable:!0,get:definition[key]})},o:(obj,prop)=>Object.prototype.hasOwnProperty.call(obj,prop),r:exports=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})}},__webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{default:()=>src});var isUndefined=require("events"),external_events_default=__webpack_require__.n(isUndefined),symbols=["fields","key","keyType","methods","scopes","relationships","relationshipField","validators","recordModel","recordValue","wrappedRecordValue","recordHandler","recordTag","defaultValue","addScope","addRelationshipAsField","addRelationshipAsMethod","getField","getMethod","removeScope","copyScopes","instances","isRecord","groupTag","get","handleExperimentalAPIMessage"].reduce((acc,curr)=>(acc["$"+curr]=Symbol.for(curr),acc),{});class NameError extends Error{constructor(message){super(message),this.name="NameError"}}class ValidationError extends Error{constructor(message){super(message),this.name="ValidationError"}}class DuplicationError extends Error{constructor(message){super(message),this.name="DuplicationError"}}class DefaultValueError extends Error{constructor(message){super(message),this.name="DefaultValueError"}}class ExperimentalAPIUsageError extends Error{constructor(message){super(message),this.name="ExperimentalAPIUsageError"}}class Validator{static unique(field){return(value,data)=>data.every(item=>item[field]!==value[field])}static length(field,[min,max]){return value=>value[field].length>=min&&value[field].length<=max}static minLength(field,min){return value=>value[field].length>=min}static maxLength(field,max){return value=>value[field].length<=max}static range(field,[min,max]){return value=>value[field]>=min&&value[field]<=max}static min(field,min){return value=>value[field]>=min}static max(field,max){return value=>value[field]<=max}static integer(field){return value=>Number.isInteger(value[field])}static regex(field,regex){return value=>regex.test(value[field])}static uniqueValues(field){return value=>new Set(value[field]).size===value[field].length}static sortedAscending(field){return value=>value[field].every((item,index)=>0===index||item>=value[field][index-1])}static sortedDescending(field){return value=>value[field].every((item,index)=>0===index||item<=value[field][index-1])}static custom(field,fn){return(value,data)=>fn(value[field],data.map(item=>item[field]))}}const restrictedNames={Model:["toString","toObject","toJSON"],Field:["toString","toObject","toJSON"],Relationship:["toString","toObject","toJSON"]},validateName=(objectType,name)=>{var[isValid,message]=((name,restrictedNames=[])=>"string"!=typeof name?[!1,"must be a string"]:name?/^\d/.test(name)?[!1,"cannot start with a number"]:restrictedNames.includes(name)?[!1,"is reserved"]:[/^\w+$/.test(name),"must contain only alphanumeric characters, numbers or underscores"]:[!1,"is required"])(name,restrictedNames[objectType]);if(!isValid)throw new NameError(objectType+` name ${message}.`);return name},capitalize=([first,...rest])=>first.toUpperCase()+rest.join(""),reverseCapitalize=([first,...rest])=>first.toLowerCase()+rest.join(""),deepClone=obj=>{if(null===obj)return null;if(obj instanceof Date)return new Date(obj);let clone=Object.assign({},obj);return Object.entries(clone).forEach(([key,value])=>clone[key]="object"==typeof obj[key]?deepClone(value):value),Array.isArray(obj)?(clone.length=obj.length,Array.from(clone)):clone},validateObjectWithUniqueName=({objectType,parentType,parentName},obj,collection)=>{if(!(obj=>obj&&"object"==typeof obj)(obj))throw new TypeError(objectType+` ${obj} is not an object.`);if(((collection,item)=>collection.includes(item))(collection,obj.name))throw new DuplicationError(`${parentType} ${parentName} already has a ${objectType.toLowerCase()} named ${obj.name}.`);return!0};var isBoolean=val=>"boolean"==typeof val,isNumber=val=>"number"==typeof val&&val==val,isString=val=>"string"==typeof val,isDate=val=>val instanceof Date,and=(...types)=>val=>types.every(type=>type(val));const or=(...types)=>val=>types.some(type=>type(val));var isPositive=val=>0<=val;const isArrayOf=type=>val=>Array.isArray(val)&&val.every(type);var standardTypes=shape=>{const props=Object.keys(shape);return val=>{return null!=val&&"object"==typeof val&&(0===props.length||Object.keys(val).length===props.length&&props.every(prop=>shape[prop](val[prop])))}},isObjectOf=type=>val=>null!=val&&"object"==typeof val&&Object.keys(val).every(prop=>type(val[prop])),isNull=val=>null===val,isUndefined=val=>void 0===val;const isNil=or(isNull,isUndefined);const types={bool:isBoolean,number:isNumber,positiveNumber:and(isNumber,isPositive),string:isString,date:isDate,stringOrNumber:or(isString,isNumber),numberOrString:or(isString,isNumber),enum:(...values)=>val=>values.includes(val),boolArray:isArrayOf(isBoolean),numberArray:isArrayOf(isNumber),stringArray:isArrayOf(isString),dateArray:isArrayOf(isDate),oneOf:or,arrayOf:isArrayOf,oneOrArrayOf:type=>val=>or(isArrayOf(type),type)(val),object:standardTypes,objectOf:isObjectOf,optional:type=>val=>or(isNil,type)(val),null:isNull,undefined:isUndefined,nil:isNil};standardTypes={boolean:{type:isBoolean,defaultValue:!1},number:{type:isNumber,defaultValue:0},positiveNumber:{type:and(isNumber,isPositive),defaultValue:0},string:{type:isString,defaultValue:""},date:{type:isDate,defaultValue:new Date},stringOrNumber:{type:or(isString,isNumber),defaultValue:""},numberOrString:{type:or(isString,isNumber),defaultValue:0},booleanArray:{type:isArrayOf(isBoolean),defaultValue:[]},numberArray:{type:isArrayOf(isNumber),defaultValue:[]},stringArray:{type:isArrayOf(isString),defaultValue:[]},dateArray:{type:isArrayOf(isDate),defaultValue:[]},object:{type:standardTypes({}),defaultValue:{}},booleanObject:{type:isObjectOf(isBoolean),defaultValue:{a:!0}},numberObject:{type:isObjectOf(isNumber),defaultValue:{}},stringObject:{type:isObjectOf(isString),defaultValue:{}},dateObject:{type:isObjectOf(isDate),defaultValue:{}},objectArray:{type:isArrayOf(standardTypes({})),defaultValue:[]}};const key=and(isString,val=>0!==val.trim().length),{$defaultValue,$validators}=symbols;class Field{#name;#defaultValue;#required;#type;#validators;constructor({name,type,required=!1,defaultValue=null,validators={}}){this.#name=validateName("Field",name),this.#required=Field.#validateRequired(required),this.#type=Field.#validateType(type,required),this.#defaultValue=Field.#validateDefaultValue(defaultValue,this.#type,this.#required),this.#validators=new Map,Object.entries(validators).forEach(([validatorName,validator])=>{this.addValidator(validatorName,validator)})}addValidator(validatorName,validator){this.#validators.set(...Field.#parseFieldValidator(this.#name,validatorName,validator))}get name(){return this.#name}get required(){return this.#required}typeCheck(value){return this.#type(value)}get[$defaultValue](){return this.#defaultValue}get[$validators](){return this.#validators}static#validateType(type,required){if("function"!=typeof type)throw new TypeError("Field type must be a function.");return required?type:types.optional(type)}static#validateRequired(required){if("boolean"!=typeof required)throw new TypeError("Field required must be a boolean.");return required}static#validateDefaultValue(defaultValue,type,required){if(required&&types.nil(defaultValue))throw new ValidationError("Default value cannot be null or undefined.");if(!type(defaultValue))throw new ValidationError("Default value must be valid.");return defaultValue}static#parseFieldValidator(fieldName,validatorName,validator){if(void 0!==Validator[validatorName])return[""+fieldName+capitalize(validatorName),Validator[validatorName](fieldName,validator)];if("function"!=typeof validator)throw new TypeError(`Validator ${validatorName} is not defined.`);return[""+fieldName+capitalize(validatorName),Validator.custom(fieldName,validator)]}}Object.entries(standardTypes).forEach(([typeName,standardType])=>{const{type,defaultValue:typeDefaultValue}=standardType;Field[typeName]=options=>"string"==typeof options?new Field({name:options,type:type}):new Field({...options,type:type}),Field[typeName+"Required"]=options=>{if("string"==typeof options)return new Field({name:options,type:type,required:!0,defaultValue:typeDefaultValue});var defaultValue=options.defaultValue||typeDefaultValue;return new Field({...options,type:type,required:!0,defaultValue:defaultValue})}}),Field.enum=({name,values})=>new Field({name:name,type:types.enum(...values)}),Field.enumRequired=({name,values,defaultValue=values[0]})=>new Field({name:name,type:types.enum(...values),required:!0,defaultValue:defaultValue}),Field.auto=autoField=>{autoField="string"==typeof autoField?autoField:autoField.name;const generator=function*(){let i=0;for(;;)yield i++}();let currentValue=0;autoField=new Field({name:autoField,type:value=>value===currentValue,required:!0,defaultValue:currentValue});return Object.defineProperty(autoField,$defaultValue,{get(){var value=generator.next().value;return currentValue=value}}),autoField};const{$recordValue,$wrappedRecordValue,$recordHandler,$recordModel,$recordTag,$key}=symbols;class Record{#recordValue;#recordHandler;#proxiedRecord;constructor(value,handler){return this.#recordValue=value,this.#recordHandler=handler,this.#proxiedRecord=new Proxy(this,this.#recordHandler),this.#proxiedRecord}get[$recordHandler](){return this.#recordHandler}get[$recordValue](){return this.#recordValue}get[$wrappedRecordValue](){return this.#proxiedRecord}get[$recordModel](){return this.#recordHandler.model}get[$recordTag](){var model=this[$recordModel],key=model[$key].name;return model.name+"#"+this[$recordValue][key]}get[Symbol.toStringTag](){return this[$recordTag]}}const record=Record,partial_$recordTag=symbols["$recordTag"];class PartialRecord{#tag;constructor(value,tag){Object.keys(value).forEach(key=>{this[key]=value[key]}),this.#tag=tag}get[partial_$recordTag](){return this.#tag}get[Symbol.toStringTag](){return this[partial_$recordTag]}toObject(){return{...this}}toJSON(){return this.toObject()}}const fragment_$recordTag=symbols["$recordTag"];class RecordFragment extends Array{#tag;constructor(values,tag){super(),values.forEach(value=>{this.push(value)}),this.#tag=tag}get[fragment_$recordTag](){return this.#tag}get[Symbol.toStringTag](){return this[fragment_$recordTag]}toObject(){return[...this]}toJSON(){return this.toObject()}}const{$recordModel:set_$recordModel,$recordTag:set_$recordTag,$scopes,$addScope,$removeScope,$copyScopes,$isRecord,$key:set_$key}=symbols;class RecordSet extends Map{#frozen;#scopes;constructor({iterable=[],copyScopesFrom=null}={}){super();for(var[key,value]of iterable)this.set(key,value);this.#scopes=new Map,copyScopesFrom&&this[$copyScopes](copyScopesFrom),this.#frozen=!1}freeze(){return this.#frozen=!0,this}set(key,value){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");return super.set(key,value),this}delete(key){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");return super.delete(key)}clear(){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");super.clear()}map(callbackFn){return[...this.entries()].reduce((newMap,[key,value])=>(newMap[key]=callbackFn(value,key,this),newMap),{})}flatMap(callbackFn){return[...this.entries()].map(([key,value])=>callbackFn(value,key,this))}reduce(callbackFn,initialValue){return[...this.entries()].reduce((acc,[key,value])=>callbackFn(acc,value,key,this),initialValue)}filter(callbackFn){return[...this.entries()].reduce((newMap,[key,value])=>(callbackFn(value,key,this)&&newMap.set(key,value),newMap),new RecordSet({copyScopesFrom:this})).freeze()}flatFilter(callbackFn){return[...this.entries()].reduce((arr,[key,value])=>(callbackFn(value,key,this)&&arr.push(value),arr),[])}find(callbackFn){var match=[...this.entries()].find(([key,value])=>callbackFn(value,key,this));if(match)return match[1]}findKey(callbackFn){var match=[...this.entries()].find(([key,value])=>callbackFn(value,key,this));if(match)return match[0]}only(...keys){return new RecordSet({iterable:[...this.entries()].filter(([key])=>keys.includes(key)),copyScopesFrom:this}).freeze()}except(...keys){return new RecordSet({iterable:[...this.entries()].filter(([key])=>!keys.includes(key)),copyScopesFrom:this}).freeze()}sort(comparatorFn){var sorted=[...this.entries()].sort(([key1,value1],[key2,value2])=>comparatorFn(value1,value2,key1,key2));return new RecordSet({iterable:sorted,copyScopesFrom:this}).freeze()}every(callbackFn){return 0===this.size||[...this.entries()].every(([key,value])=>callbackFn(value,key,this))}some(callbackFn){return 0!==this.size&&[...this.entries()].some(([key,value])=>callbackFn(value,key,this))}select(...keys){return new RecordSet({iterable:[...this.entries()].map(([key,value])=>{const obj={};return keys.forEach(key=>obj[key]=value[key]),[key,new PartialRecord(obj,value[set_$recordTag])]}),copyScopesFrom:this}).freeze()}flatSelect(...keys){return[...this.values()].map(value=>keys.reduce((obj,key)=>({...obj,[key]:value[key]}),{}))}pluck(...keys){return new RecordSet({iterable:[...this.entries()].map(([key,value])=>{var values=keys.map(key=>value[key]);return[key,new RecordFragment(values,value[set_$recordTag])]}),copyScopesFrom:this}).freeze()}flatPluck(...keys){const isSingleKey=1===keys.length;return[...this.values()].map(value=>isSingleKey?value[keys[0]]:keys.map(key=>value[key]))}groupBy(key){const res=new RecordSet({copyScopesFrom:this,iterable:[]});for(var[recordKey,value]of this.entries()){let keyValue=value[key];void 0!==keyValue&&null!==keyValue&&keyValue[$isRecord]&&(keyValue=value[key][set_$key]),res.has(keyValue)||res.set(keyValue,new RecordGroup({copyScopesFrom:this,iterable:[],groupName:keyValue})),res.get(keyValue).set(recordKey,value)}for(const value of res.values())value.freeze();return res.freeze()}where(callbackFn){return this.filter(callbackFn)}whereNot(callbackFn){return this.filter((value,key,map)=>!callbackFn(value,key,map))}*batchIterator(batchSize){let batch=[];for(var[,value]of this)batch.push(value),batch.length===batchSize&&(yield batch,batch=[]);batch.length&&(yield batch)}limit(n){let records=[];for(var[key,value]of this)if(records.push([key,value]),records.length===n)break;return new RecordSet({iterable:records,copyScopesFrom:this}).freeze()}offset(n){let counter=0,records=[];for(var[key,value]of this)counter<n?counter++:records.push([key,value]);return new RecordSet({iterable:records,copyScopesFrom:this}).freeze()}get first(){for(var[,value]of this)return value}get last(){if(0!==this.size)return[...this.entries()].pop()[1]}get count(){return this.size}toArray(){return[...this.values()]}toFlatArray(){return[...this.values()].map(value=>value instanceof RecordGroup?value.toFlatArray():value.toObject())}toObject(){return[...this.entries()].reduce((obj,[key,value])=>(obj[key]=value,obj),{})}toFlatObject(){return[...this.entries()].reduce((obj,[key,value])=>(obj[key]=value instanceof RecordGroup?value.toFlatArray():value.toObject(),obj),{})}toJSON(){return this.toObject()}get[Symbol.toStringTag](){var records=[...this.values()];try{const firstModel=records[0][set_$recordModel].name;if(((arr,fn)=>{const eql=fn(arr[0]);return arr.every(val=>fn(val)===eql)})(records,value=>value[set_$recordModel].name===firstModel))return firstModel}catch(e){return""}return""}static get[Symbol.species](){return Map}[$addScope](name,scope){if(RecordSet.#validateMethod("Scope",name,scope,this.#scopes),this[name]||Object.getOwnPropertyNames(RecordSet.prototype).includes(name))throw new NameError(`Scope name ${name} is already in use.`);this.#scopes.set(name,scope),Object.defineProperty(this,name,{configurable:!0,get:()=>this.where(this.#scopes.get(name))})}[$removeScope](name){this.#scopes.delete(RecordSet.#validateContains("Scope",name,this.#scopes)),delete this[name]}[$copyScopes](otherRecordSet){otherRecordSet[$scopes].forEach((scope,name)=>{this[$addScope](name,scope)})}get[$scopes](){return this.#scopes}static#validateMethod(callbackType,callbackName,callback,callbacks){if("function"!=typeof callback)throw new TypeError(callbackType+` ${callbackName} is not a function.`);if(callbacks.has(callbackName))throw new DuplicationError(callbackType+` ${callbackName} already exists.`);return callback}static#validateContains(objectType,objectName,objects){if(!objects.has(objectName))throw new ReferenceError(objectType+` ${objectName} does not exist.`);return objectName}}const set=RecordSet,$groupTag=symbols["$groupTag"];class RecordGroup extends set{#groupName;constructor({iterable=[],copyScopesFrom=null,groupName=""}={}){super({iterable:iterable,copyScopesFrom:copyScopesFrom}),this.#groupName=groupName}get[$groupTag](){return this.#groupName}get[Symbol.toStringTag](){return this[$groupTag]}}const{$fields,$defaultValue:handler_$defaultValue,$key:handler_$key,$keyType,$methods,$relationships,$validators:handler_$validators,$recordValue:handler_$recordValue,$wrappedRecordValue:handler_$wrappedRecordValue,$recordModel:handler_$recordModel,$recordTag:handler_$recordTag,$isRecord:handler_$isRecord,$get}=symbols;class RecordHandler{#model;constructor(model){this.#model=model}get model(){return this.#model}createRecord(recordData){if(!recordData)throw new TypeError("Record data cannot be empty.");if("object"!=typeof recordData)throw new TypeError("Record data must be an object.");const modelName=this.#getModelName(),newRecordKey=RecordHandler.#validateNewRecordKey(modelName,this.#getKey(),recordData[this.#getKey().name],this.#model.records),clonedRecord=deepClone(recordData),extraProperties=Object.keys(clonedRecord).filter(property=>!this.#hasField(property)&&!this.#isModelKey(property));0<extraProperties.length&&console.warn(`${modelName} record has extra fields: ${extraProperties.join(", ")}.`);const newRecord=new record({[this.#getKey().name]:newRecordKey,...extraProperties.reduce((obj,property)=>({...obj,[property]:clonedRecord[property]}),{})},this);return this.#getFieldNames().forEach(field=>{this.set(newRecord,field,clonedRecord[field],newRecord,!0)}),this.#getValidators().forEach((validator,validatorName)=>{if(!validator(newRecord,this.#model.records))throw new RangeError(`${modelName} record with key ${newRecordKey} failed validation for ${validatorName}.`)}),[newRecordKey,newRecord]}get(record,property){return this.#hasRelationshipField(property)?this.#getRelationship(record,property):this.#isModelKey(property)||this.#hasField(property)?this.#getFieldValue(record,property):this.#hasMethod(property)?this.#getMethod(record,property):this.#isCallToSerialize(property)?RecordHandler.#recordToObject(record,this.#model,this):this.#isCallToString(property)?()=>this.#getKeyValue(record):this.#isKnownSymbol(property)?this.#getKnownSymbol(record,property):void 0}set(record,property,value,receiver,skipValidation){const recordValue=record[handler_$recordValue],recordKey=this.#getKeyValue(record),otherRecords=this.#model.records.except(recordKey);if(this.#hasMethod(property))throw new TypeError(`${this.#getModelName()} record ${recordKey} cannot set method ${property}.`);if(this.#hasField(property)){const field=this.#getField(property);RecordHandler.#setRecordField(this.#model.name,recordValue,field,value),field[handler_$validators].forEach((validator,validatorName)=>{if(![null,void 0].includes(recordValue[property])&&!validator(recordValue,otherRecords))throw new RangeError(`${this.#getModelName()} record with key ${recordKey} failed validation for ${validatorName}.`)})}else console.warn(this.#model.name+` record has extra field: ${property}.`),recordValue[property]=value;return skipValidation||this.#getValidators().forEach((validator,validatorName)=>{if(!validator(recordValue,otherRecords))throw new RangeError(`${this.#getModelName()} record with key ${recordKey} failed validation for ${validatorName}.`)}),!0}static#setRecordField(modelName,record,field,recordValue){recordValue=field.required&&types.nil(recordValue)?field[handler_$defaultValue]:recordValue;if(!field.typeCheck(recordValue))throw new TypeError(`${modelName} record has invalid value for field ${field.name}.`);record[field.name]=recordValue}static#recordToObject(record,key,handler){const recordValue=record[handler_$recordValue],fields=key[$fields],methods=key[$methods];key=key[handler_$key].name;const object={[key]:recordValue[key]};fields.forEach(field=>{void 0!==recordValue[field.name]&&(object[field.name]=recordValue[field.name])});return({include=[]}={})=>{var result=object;const included=include.map(name=>{const[field,...props]=name.split(".");return[field,props.join(".")]});return included.forEach(([includedField,props])=>{if(object[includedField])if(Array.isArray(object[includedField])){const records=handler.get(record,includedField);object[includedField]=records.map(record=>record.toObject({include:[props]}))}else object[includedField]=handler.get(record,includedField).toObject({include:[props]});else methods.has(includedField)&&(object[includedField]=handler.get(record,includedField))}),result}}static#validateNewRecordKey=(modelName,modelKey,recordKey,records)=>{let newRecordKey=recordKey;if("string"===modelKey[$keyType]&&!modelKey.typeCheck(newRecordKey))throw new TypeError(`${modelName} record has invalid value for key ${modelKey.name}.`);if("auto"===modelKey[$keyType]&&(newRecordKey=modelKey[handler_$defaultValue]),records.has(newRecordKey))throw new DuplicationError(`${modelName} record with key ${newRecordKey} already exists.`);return newRecordKey};#getModelName(){return this.#model.name}#getFieldNames(){return[...this.#model[$fields].keys()]}#getValidators(){return this.#model[handler_$validators]}#isModelKey(property){return this.#model[handler_$key].name===property}#getKey(){return this.#model[handler_$key]}#getKeyValue(record){return record[handler_$recordValue][this.#model[handler_$key].name]}#hasField(property){return this.#model[$fields].has(property)}#getField(property){return this.#model[$fields].get(property)}#getFieldValue(record,property){return record[handler_$recordValue][property]}#hasMethod(property){return this.#model[$methods].has(property)}#getMethod(record,property){return this.#model[$methods].get(property)(record[handler_$wrappedRecordValue])}#hasRelationshipField(property){return!!this.#hasField(property)&&this.#model[$relationships].has(property+"."+property)}#getRelationship(record,property){return this.#model[$relationships].get(property+"."+property)[$get](this.#getModelName(),property,record[handler_$recordValue])}#isCallToSerialize(property){return"toObject"===property||"toJSON"===property}#isCallToString(property){return"toString"===property}#isKnownSymbol(property){return[handler_$recordModel,handler_$recordTag,handler_$recordValue,handler_$isRecord,handler_$key].includes(property)}#getKnownSymbol(record,property){return property===handler_$isRecord||(property===handler_$key&&this.#getKey(),record[property])}}const handler=RecordHandler,{$fields:model_$fields,$defaultValue:model_$defaultValue,$key:model_$key,$keyType:model_$keyType,$methods:model_$methods,$scopes:model_$scopes,$relationships:model_$relationships,$validators:model_$validators,$recordHandler:model_$recordHandler,$addScope:model_$addScope,$addRelationshipAsField,$addRelationshipAsMethod,$getField,$getMethod,$removeScope:model_$removeScope,$instances,$handleExperimentalAPIMessage}=symbols,allStandardTypes=[...Object.keys(standardTypes),...Object.keys(standardTypes).map(type=>type+"Required"),"enum","enumRequired","auto"];class Model extends external_events_default(){#records;#recordHandler;#fields;#key;#methods;#relationships;#validators;#updatingField=!1;static#instances=new Map;constructor({name,fields=[],key="id",methods={},scopes={},validators={}}={}){if(super(),this.name=validateName("Model",name),Model.#instances.has(name))throw new DuplicationError(`A model named ${name} already exists.`);this.#records=new set,this.#recordHandler=new handler(this),this.#key=Model.#parseKey(this.name,key),this.#fields=new Map,this.#methods=new Map,this.#relationships=new Map,this.#validators=new Map,fields.forEach(field=>this.addField(field)),Object.entries(methods).forEach(([methodName,method])=>{this.addMethod(methodName,method)}),Object.entries(scopes).forEach(([scopeName,scope])=>{this.addScope(scopeName,scope)}),Object.entries(validators).forEach(([validatorName,validator])=>{this.addValidator(validatorName,validator)}),Model.#instances.set(this.name,this)}addField(fieldOptions,retrofill){this.#updatingField||this.emit("beforeAddField",{field:fieldOptions,model:this});var field=Model.#parseField(this.name,fieldOptions,[...this.#fields.keys(),this.#key.name,...this.#methods.keys()]);return this.#fields.set(fieldOptions.name,field),this.#updatingField||this.emit("fieldAdded",{field:field,model:this}),this.emit("beforeRetrofillField",{field:field,retrofill:retrofill,model:this}),Model.#applyFieldRetrofill(field,this.#records,retrofill),this.emit("fieldRetrofilled",{field:field,retrofill:retrofill,model:this}),this.#updatingField||this.emit("change",{type:"fieldAdded",field:field,model:this}),field}removeField(name){if(!Model.#validateContains(this.name,"Field",name,this.#fields))return!1;var field=this.#fields.get(name);return this.#updatingField||this.emit("beforeRemoveField",{field:field,model:this}),this.#fields.delete(name),this.#updatingField||(this.emit("fieldRemoved",{field:{name:name},model:this}),this.emit("change",{type:"fieldRemoved",field:field,model:this})),!0}updateField(name,field,newField){if(field.name!==name)throw new NameError(`Field name ${field.name} does not match ${name}.`);if(!Model.#validateContains(this.name,"Field",name,this.#fields))throw new ReferenceError(`Field ${name} does not exist.`);var prevField=this.#fields.get(name);this.#updatingField=!0,this.emit("beforeUpdateField",{prevField:prevField,field:field,model:this}),this.removeField(name);newField=this.addField(field,newField);this.emit("fieldUpdated",{field:newField,model:this}),this.#updatingField=!1,this.emit("change",{type:"fieldUpdated",field:newField,model:this})}addMethod(name,method){this.emit("beforeAddMethod",{method:{name:name,body:method},model:this});var methodName=validateName("Method",name);this.#methods.set(methodName,Model.#validateMethod("Method",name,method,[...this.#fields.keys(),this.#key.name,...this.#methods.keys()])),this.emit("methodAdded",{method:{name:methodName,body:method},model:this}),this.emit("change",{type:"methodAdded",method:{name:methodName,body:method},model:this})}removeMethod(name){if(!Model.#validateContains(this.name,"Method",name,this.#methods))return!1;var method=this.#methods.get(name);return this.emit("beforeRemoveMethod",{method:{name:name,body:method},model:this}),this.#methods.delete(name),this.emit("methodRemoved",{method:{name:name},model:this}),this.emit("change",{type:"methodRemoved",method:{name:name,body:method},model:this}),!0}addScope(scopeName,scope){this.emit("beforeAddScope",{scope:{name:scopeName,body:scope},model:this});scopeName=validateName("Scope",scopeName);this.#records[model_$addScope](scopeName,scope),this.emit("scopeAdded",{scope:{name:scopeName,body:scope},model:this}),this.emit("change",{type:"scopeAdded",scope:{name:scopeName,body:scope},model:this})}removeScope(name){if(!Model.#validateContains(this.name,"Scope",name,this.#records[model_$scopes]))return!1;var scope=this.#records[model_$scopes].get(name);return this.emit("beforeRemoveScope",{scope:{name:name,body:scope},model:this}),this.#records[model_$removeScope](name),this.emit("scopeRemoved",{scope:{name:name},model:this}),this.emit("change",{type:"scopeRemoved",scope:{name:name,body:scope},model:this}),!0}addValidator(name,validator){this.emit("beforeAddValidator",{validator:{name:name,body:validator},model:this}),this.#validators.set(name,Model.#validateMethod("Validator",name,validator,[...this.#validators.keys()])),this.emit("validatorAdded",{validator:{name:name,body:validator},model:this}),this.emit("change",{type:"validatorAdded",validator:{name:name,body:validator},model:this})}removeValidator(name){if(!Model.#validateContains(this.name,"Validator",name,this.#validators))return!1;var validator=this.#validators.get(name);return this.emit("beforeRemoveValidator",{validator:{name:name,body:validator},model:this}),this.#validators.delete(name),this.emit("validatorRemoved",{validator:{name:name},model:this}),this.emit("change",{type:"validatorRemoved",validator:{name:name,body:validator},model:this}),!0}createRecord(newRecord){this.emit("beforeCreateRecord",{record:newRecord,model:this});var[newRecordKey,newRecord]=this.#recordHandler.createRecord(newRecord);return this.#records.set(newRecordKey,newRecord),this.emit("recordCreated",{newRecord:newRecord,model:this}),newRecord}removeRecord(recordKey){if(!this.#records.has(recordKey))return console.warn(`Record ${recordKey} does not exist.`),!1;var record=this.#records.get(recordKey);return this.emit("beforeRemoveRecord",{record:record,model:this}),this.#records.delete(recordKey),this.emit("recordRemoved",{record:{[this.#key.name]:recordKey},model:this}),!0}updateRecord(recordKey,record){if("object"!=typeof record)throw new TypeError("Record data must be an object.");if(!this.#records.has(recordKey))throw new ReferenceError(`Record ${recordKey} does not exist.`);const oldRecord=this.#records.get(recordKey);return this.emit("beforeUpdateRecord",{record:oldRecord,newRecord:{[this.#key.name]:recordKey,...record},model:this}),Object.entries(record).forEach(([fieldName,fieldValue])=>{oldRecord[fieldName]=fieldValue}),this.emit("recordUpdated",{record:oldRecord,model:this}),oldRecord}get records(){return this.#records}static get[$instances](){return Model.#instances}get[model_$recordHandler](){return this.#recordHandler}get[model_$fields](){return this.#fields}get[model_$key](){return this.#key}get[model_$methods](){return this.#methods}get[model_$relationships](){return this.#relationships}get[model_$validators](){return this.#validators}[$addRelationshipAsField](relationship){var{name,type,fieldName,field}=relationship[$getField](),relationshipName=name+"."+fieldName;if(this.emit("beforeAddRelationship",{relationship:{name:name,type:type},model:this}),[...this.#fields.keys(),this.#key.name,...this.#methods.keys()].includes(fieldName))throw new NameError(`Relationship field ${fieldName} is already in use.`);if(this.#relationships.has(relationshipName))throw new NameError(`Relationship ${relationshipName} is already in use.`);this.#fields.set(fieldName,field),this.#relationships.set(relationshipName,relationship),this.emit("relationshipAdded",{relationship:{name:name,type:type},model:this}),this.emit("change",{type:"relationshipAdded",relationship:{relationship:{name:name,type:type},model:this},model:this})}[$addRelationshipAsMethod](relationship){var{name,type,methodName,method}=relationship[$getMethod](),relationshipName=name+"."+methodName;if(this.emit("beforeAddRelationship",{relationship:{name:name,type:type},model:this}),[...this.#fields.keys(),this.#key.name,...this.#methods.keys()].includes(methodName))throw new NameError(`Relationship method ${methodName} is already in use.`);if(this.#relationships.has(relationshipName))throw new NameError(`Relationship ${name} is already in use.`);this.#methods.set(methodName,method),this.#relationships.set(relationshipName,relationship),this.emit("relationshipAdded",{relationship:{name:name,type:type},model:this}),this.emit("change",{type:"relationshipAdded",relationship:{relationship:{name:name,type:type},model:this},model:this})}static#createKey(options){let name="id",type="string";"string"==typeof options?name=options:"object"==typeof options&&(name=options.name||name,type=options.type||type);let keyField;return"string"===type?(keyField=new Field({name:name,type:key,required:!0,defaultValue:"__emptyKey__"}),Object.defineProperty(keyField,model_$defaultValue,{get(){throw new DefaultValueError(`Key field ${name} does not have a default value.`)}})):"auto"===type&&(keyField=Field.auto(name)),Object.defineProperty(keyField,model_$keyType,{get(){return type}}),keyField}static#parseKey(modelName,key){if("string"!=typeof key&&"object"!=typeof key)throw new TypeError(modelName+` key ${key} is not a string or object.`);if("object"==typeof key&&!key.name)throw new TypeError(modelName+` key ${key} is missing a name.`);if("object"==typeof key&&!["auto","string"].includes(key.type))throw new TypeError(modelName+` key ${key} type must be either "string" or "auto".`);return Model.#createKey(key)}static#parseField(modelName,field,restrictedNames){return validateObjectWithUniqueName({objectType:"Field",parentType:"Model",parentName:modelName},field,restrictedNames),allStandardTypes.includes(field.type)?Field[field.type](field):("function"==typeof field.type&&Schema[$handleExperimentalAPIMessage](`The provided type for ${field.name} is not part of the standard types. Function types are experimental and may go away in a later release.`),new Field(field))}static#validateMethod(callbackType,callbackName,callback,restrictedNames){if("function"!=typeof callback)throw new TypeError(callbackType+` ${callbackName} is not a function.`);if(restrictedNames.includes(callbackName))throw new DuplicationError(callbackType+` ${callbackName} already exists.`);return callback}static#validateContains(modelName,objectType,objectName,objects){return!!objects.has(objectName)||(console.warn(`Model ${modelName} does not contain a ${objectType.toLowerCase()} named ${objectName}.`),!1)}static#applyFieldRetrofill(field,records,retrofill){if(field.required||void 0!==retrofill){const retrofillFunction=void 0!==retrofill?"function"==typeof retrofill?retrofill:()=>retrofill:record=>record[field.name]||field[model_$defaultValue];records.forEach(record=>{record[field.name]=retrofillFunction(record)})}}}const{$key:relationship_$key,$recordValue:relationship_$recordValue,$fields:relationship_$fields,$getField:relationship_$getField,$getMethod:relationship_$getMethod,$get:relationship_$get,$defaultValue:relationship_$defaultValue,$instances:relationship_$instances,$handleExperimentalAPIMessage:relationship_$handleExperimentalAPIMessage}=symbols,relationshipEnum={oneToOne:"oneToOne",oneToMany:"oneToMany",manyToOne:"manyToOne",manyToMany:"manyToMany"};class Relationship{#type;#from;#to;#name;#reverseName;#relationshipField;#relationshipMethod;constructor({from:fromName,to:toModel,type:toName}={}){Schema[relationship_$handleExperimentalAPIMessage]("Relationships are experimental in the current version. There is neither validation of existence in foreign tables nor guarantee that associations work. Please use with caution."),this.#type=Relationship.#validateType(toName);var[fromModel,fromName,toModel,toName]=Relationship.#parseModelsAndNames(fromName,toModel,toName);if(this.#from=fromModel,this.#to=toModel,this.#name=fromName,this.#reverseName=toName,this.#to===this.#from&&Relationship.#isSymmetric(this.#type)&&this.#name===this.#reverseName)throw new RangeError("Relationship cannot be symmetric if the from and to models are the same and no name is provided for either one.");this.#relationshipField=Relationship.#createField(this.#name,this.#type,this.#to[relationship_$key]),this.#relationshipMethod=record=>this.#getAssociatedRecordsReverse(record)}[relationship_$getField](){return{name:this.#name,type:this.#type,fieldName:this.#name,field:this.#relationshipField}}[relationship_$getMethod](){return{name:this.#name,type:this.#type,methodName:this.#reverseName,method:this.#relationshipMethod}}[relationship_$get](modelName,property,record){return modelName===this.#from.name&&property===this.#name?this.#getAssociatedRecords(record):modelName===this.#to.name&&property===this.#reverseName?(console.warn("Relationship getter called by the receiver model. This might indicate an issue with the library and should be reported."),this.#getAssociatedRecordsReverse(record)):void 0}#getAssociatedRecords(record){if(Relationship.#isToOne(this.#type)){var associationValue=record[this.#name];return this.#to.records.get(associationValue)}const associationValues=record[this.#name]||[],associatedRecordsKeyName=this.#to[relationship_$key].name;return this.#to.records.where(associatedRecord=>associationValues.includes(associatedRecord[associatedRecordsKeyName]))}#getAssociatedRecordsReverse(matcher){const associationValue=matcher[this.#to[relationship_$key].name];matcher=Relationship.#isToOne(this.#type)?associatedRecord=>associatedRecord[relationship_$recordValue][this.#name]===associationValue:associatedRecord=>{var associatedRecordValue=associatedRecord[relationship_$recordValue][this.#name];return![void 0,null].includes(associatedRecordValue)&&associatedRecord[relationship_$recordValue][this.#name].includes(associationValue)};return Relationship.#isFromOne(this.#type)?this.#from.records.find(matcher):this.#from.records.where(matcher)}static#isToOne(type){return[relationshipEnum.oneToOne,relationshipEnum.manyToOne].includes(type)}static#isToMany(type){return[relationshipEnum.oneToMany,relationshipEnum.manyToMany].includes(type)}static#isFromOne(type){return[relationshipEnum.oneToMany,relationshipEnum.oneToOne].includes(type)}static#isFromMany(type){return[relationshipEnum.manyToOne,relationshipEnum.manyToMany].includes(type)}static#isSymmetric(type){return[relationshipEnum.oneToOne,relationshipEnum.manyToMany].includes(type)}static#createField(name,type,foreignField){var isSingleSource=Relationship.#isFromOne(type),relationshipField=Relationship.#isToMany(type),type=relationshipField?types.arrayOf(value=>foreignField.typeCheck(value)):value=>foreignField.typeCheck(value);const validators={};isSingleSource&&!relationshipField&&(validators.unique=!0),relationshipField&&(validators.uniqueValues=!0);relationshipField=new Field({name:name,type:type,required:!1,defaultValue:relationshipField?[]:null,validators:validators});return Object.defineProperty(relationshipField,relationship_$defaultValue,{get(){throw new DefaultValueError("Relationship field does not have a default value.")}}),relationshipField}static#validateType(relationshipType){if(!Object.values(relationshipEnum).includes(relationshipType))throw new TypeError(`Invalid relationship type: ${relationshipType}.`);return relationshipType}static#validateModel(modelName){modelName="string"==typeof modelName?modelName:modelName.model;if(!Model[relationship_$instances].has(modelName))throw new ReferenceError(`Model ${modelName} does not exist.`);return Model[relationship_$instances].get(modelName)}static#createName(type,to){return Relationship.#isToOne(type)?reverseCapitalize(to):Relationship.#isToMany(type)?reverseCapitalize(to)+"Set":void 0}static#createReverseName=(type,from)=>Relationship.#isFromOne(type)?reverseCapitalize(from):Relationship.#isFromMany(type)?reverseCapitalize(from)+"Set":void 0;static#validateModelParams(name){const model=Relationship.#validateModel(name);name="string"==typeof name?null:validateName("Field",name.name);if(null!==name&&model[relationship_$fields].has(name))throw new DuplicationError(`Field ${name} already exists in ${model.name}.`);return[model,name]}static#parseModelsAndNames(from,to,type){let fromModel,fromName,toModel,toName;return[fromModel,fromName]=Relationship.#validateModelParams(from),[toModel,toName]=Relationship.#validateModelParams(to),null===fromName&&(fromName=Relationship.#createName(type,toModel.name)),null===toName&&(toName=Relationship.#createReverseName(type,fromModel.name)),[fromModel,fromName,toModel,toName]}}const{$addRelationshipAsField:schema_$addRelationshipAsField,$addRelationshipAsMethod:schema_$addRelationshipAsMethod,$handleExperimentalAPIMessage:schema_$handleExperimentalAPIMessage,$key:schema_$key,$keyType:schema_$keyType}=symbols;class Schema extends external_events_default(){#name;#models;static defaultConfig={experimentalAPIMessages:"warn"};static config={...Schema.defaultConfig};static#schemas=new Map;constructor({name,models=[],config={}}={}){super(),this.#name=validateName("Schema",name),this.#models=new Map,Schema.#parseConfig(config),Schema.#schemas.set(this.#name,this),models.forEach(model=>this.createModel(model))}createModel(modelData){this.emit("beforeCreateModel",{model:modelData,schema:this});const model=Schema.#parseModel(this.#name,modelData,this.#models);return this.#models.set(model.name,model),model.on("change",({type,...eventData})=>{this.emit("change",{type:"model"+capitalize(type),...eventData,schema:this})}),this.emit("modelCreated",{model:model,schema:this}),this.emit("change",{type:"modelCreated",model:model,schema:this}),model}getModel(name){return this.#models.get(name)}removeModel(name){var model=this.getModel(name);if(this.emit("beforeRemoveModel",{model:model,schema:this}),!this.#models.has(name))throw new ReferenceError(`Model ${name} does not exist in schema ${this.#name}.`);this.#models.delete(name),this.emit("modelRemoved",{model:{name:name},schema:this}),this.emit("change",{type:"modelRemoved",model:model,schema:this})}createRelationship(relationship){this.emit("beforeCreateRelationship",{relationship:relationship,schema:this});relationship=Schema.#applyRelationship(this.#name,relationship,this.#models);return this.emit("relationshipCreated",{relationship:relationship,schema:this}),this.emit("change",{type:"relationshipCreated",relationship:relationship,schema:this}),relationship}get name(){return this.#name}get models(){return this.#models}static create(schemaData){return new Schema(schemaData)}static get(name){return Schema.#schemas.get(name)}get(pathName){this.emit("beforeGet",{pathName:pathName,schema:this});const[modelName,recordKey,...rest]=pathName.split("."),model=this.getModel(modelName);if(!model)throw new ReferenceError(`Model ${modelName} does not exist in schema ${this.#name}.`);if(void 0===recordKey)return model;var result=model[schema_$key][schema_$keyType],result=model.records.get("string"===result?recordKey:Number.parseInt(recordKey));if(!rest.length)return result;if(!result)throw new ReferenceError(`Record ${recordKey} does not exist in model ${modelName}.`);result=rest.reduce((acc,key)=>acc[key],result);return this.emit("got",{pathName:pathName,result:result,schema:this}),result}static[schema_$handleExperimentalAPIMessage](message){var experimentalAPIMessages=Schema.config["experimentalAPIMessages"];if("warn"===experimentalAPIMessages)console.warn(message);else if("error"===experimentalAPIMessages)throw new ExperimentalAPIUsageError(message)}static#parseModel(schemaName,modelData,models){return validateObjectWithUniqueName({objectType:"Model",parentType:"Schema",parentName:schemaName},modelData,[...models.keys()]),new Model(modelData)}static#applyRelationship(relationship,toModelName,models){const{from,to,type}=toModelName;[from,to].forEach(model=>{if(!["string","object"].includes(typeof model))throw new TypeError(`Invalid relationship model: ${model}.`)});var fromModelName="string"==typeof from?from:from.model,toModelName="string"==typeof to?to:to.model;const fromModel=models.get(fromModelName),toModel=models.get(toModelName);if(!fromModel)throw new ReferenceError(`Model ${fromModelName} not found in schema ${relationship} when attempting to create a relationship.`);if(!toModel)throw new ReferenceError(`Model ${toModelName} not found in schema ${relationship} when attempting to create a relationship.`);relationship=new Relationship({from:from,to:to,type:type});return fromModel[schema_$addRelationshipAsField](relationship),toModel[schema_$addRelationshipAsMethod](relationship),relationship}static#parseConfig(config={}){config&&["experimentalAPIMessages"].forEach(key=>{void 0!==config[key]&&["warn","error","off"].includes(config[key])&&(Schema.config[key]=config[key])})}}const src=Schema;return __webpack_exports__})()});
!function(root,factory){"object"==typeof exports&&"object"==typeof module?module.exports=factory():"function"==typeof define&&define.amd?define([],factory):"object"==typeof exports?exports["@jsiqle/core"]=factory():root["@jsiqle/core"]=factory()}(global,function(){return(()=>{"use strict";var __webpack_require__={n:module=>{var getter=module&&module.__esModule?()=>module.default:()=>module;return __webpack_require__.d(getter,{a:getter}),getter},d:(exports,definition)=>{for(var key in definition)__webpack_require__.o(definition,key)&&!__webpack_require__.o(exports,key)&&Object.defineProperty(exports,key,{enumerable:!0,get:definition[key]})},o:(obj,prop)=>Object.prototype.hasOwnProperty.call(obj,prop),r:exports=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})}},__webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{default:()=>src});var isUndefined=require("events"),external_events_default=__webpack_require__.n(isUndefined),symbols=["fields","key","keyType","properties","methods","scopes","relationships","relationshipField","validators","recordModel","recordValue","wrappedRecordValue","recordHandler","recordTag","defaultValue","addScope","addRelationshipAsField","addRelationshipAsProperty","getField","getProperty","removeScope","copyScopes","instances","isRecord","groupTag","get","handleExperimentalAPIMessage"].reduce((acc,curr)=>(acc["$"+curr]=Symbol.for(curr),acc),{});class NameError extends Error{constructor(message){super(message),this.name="NameError"}}class ValidationError extends Error{constructor(message){super(message),this.name="ValidationError"}}class DuplicationError extends Error{constructor(message){super(message),this.name="DuplicationError"}}class DefaultValueError extends Error{constructor(message){super(message),this.name="DefaultValueError"}}class ExperimentalAPIUsageError extends Error{constructor(message){super(message),this.name="ExperimentalAPIUsageError"}}class Validator{static unique(field){return(value,data)=>data.every(item=>item[field]!==value[field])}static length(field,[min,max]){return value=>value[field].length>=min&&value[field].length<=max}static minLength(field,min){return value=>value[field].length>=min}static maxLength(field,max){return value=>value[field].length<=max}static range(field,[min,max]){return value=>value[field]>=min&&value[field]<=max}static min(field,min){return value=>value[field]>=min}static max(field,max){return value=>value[field]<=max}static integer(field){return value=>Number.isInteger(value[field])}static regex(field,regex){return value=>regex.test(value[field])}static uniqueValues(field){return value=>new Set(value[field]).size===value[field].length}static sortedAscending(field){return value=>value[field].every((item,index)=>0===index||item>=value[field][index-1])}static sortedDescending(field){return value=>value[field].every((item,index)=>0===index||item<=value[field][index-1])}static custom(field,fn){return(value,data)=>fn(value[field],data.map(item=>item[field]))}}const restrictedNames={Model:["toString","toObject","toJSON"],Field:["toString","toObject","toJSON"],Property:["toString","toObject","toJSON"],Method:["toString","toObject","toJSON"],Relationship:["toString","toObject","toJSON"]},validateName=(objectType,name)=>{var[isValid,message]=((name,restrictedNames=[])=>"string"!=typeof name?[!1,"must be a string"]:name?/^\d/.test(name)?[!1,"cannot start with a number"]:restrictedNames.includes(name)?[!1,"is reserved"]:[/^\w+$/.test(name),"must contain only alphanumeric characters, numbers or underscores"]:[!1,"is required"])(name,restrictedNames[objectType]);if(!isValid)throw new NameError(objectType+` name ${message}.`);return name},capitalize=([first,...rest])=>first.toUpperCase()+rest.join(""),reverseCapitalize=([first,...rest])=>first.toLowerCase()+rest.join(""),deepClone=obj=>{if(null===obj)return null;if(obj instanceof Date)return new Date(obj);let clone=Object.assign({},obj);return Object.entries(clone).forEach(([key,value])=>clone[key]="object"==typeof obj[key]?deepClone(value):value),Array.isArray(obj)?(clone.length=obj.length,Array.from(clone)):clone},validateObjectWithUniqueName=({objectType,parentType,parentName},obj,collection)=>{if(!(obj=>obj&&"object"==typeof obj)(obj))throw new TypeError(objectType+` ${obj} is not an object.`);if(((collection,item)=>collection.includes(item))(collection,obj.name))throw new DuplicationError(`${parentType} ${parentName} already has a ${objectType.toLowerCase()} named ${obj.name}.`);return!0};var isBoolean=val=>"boolean"==typeof val,isNumber=val=>"number"==typeof val&&val==val,isString=val=>"string"==typeof val,isDate=val=>val instanceof Date,and=(...types)=>val=>types.every(type=>type(val));const or=(...types)=>val=>types.some(type=>type(val));var isPositive=val=>0<=val;const isArrayOf=type=>val=>Array.isArray(val)&&val.every(type);var standardTypes=shape=>{const props=Object.keys(shape);return val=>{return null!=val&&"object"==typeof val&&(0===props.length||Object.keys(val).length===props.length&&props.every(prop=>shape[prop](val[prop])))}},isObjectOf=type=>val=>null!=val&&"object"==typeof val&&Object.keys(val).every(prop=>type(val[prop])),isNull=val=>null===val,isUndefined=val=>void 0===val;const isNil=or(isNull,isUndefined);const types={bool:isBoolean,number:isNumber,positiveNumber:and(isNumber,isPositive),string:isString,date:isDate,stringOrNumber:or(isString,isNumber),numberOrString:or(isString,isNumber),enum:(...values)=>val=>values.includes(val),boolArray:isArrayOf(isBoolean),numberArray:isArrayOf(isNumber),stringArray:isArrayOf(isString),dateArray:isArrayOf(isDate),oneOf:or,arrayOf:isArrayOf,oneOrArrayOf:type=>val=>or(isArrayOf(type),type)(val),object:standardTypes,objectOf:isObjectOf,optional:type=>val=>or(isNil,type)(val),null:isNull,undefined:isUndefined,nil:isNil};standardTypes={boolean:{type:isBoolean,defaultValue:!1},number:{type:isNumber,defaultValue:0},positiveNumber:{type:and(isNumber,isPositive),defaultValue:0},string:{type:isString,defaultValue:""},date:{type:isDate,defaultValue:new Date},stringOrNumber:{type:or(isString,isNumber),defaultValue:""},numberOrString:{type:or(isString,isNumber),defaultValue:0},booleanArray:{type:isArrayOf(isBoolean),defaultValue:[]},numberArray:{type:isArrayOf(isNumber),defaultValue:[]},stringArray:{type:isArrayOf(isString),defaultValue:[]},dateArray:{type:isArrayOf(isDate),defaultValue:[]},object:{type:standardTypes({}),defaultValue:{}},booleanObject:{type:isObjectOf(isBoolean),defaultValue:{a:!0}},numberObject:{type:isObjectOf(isNumber),defaultValue:{}},stringObject:{type:isObjectOf(isString),defaultValue:{}},dateObject:{type:isObjectOf(isDate),defaultValue:{}},objectArray:{type:isArrayOf(standardTypes({})),defaultValue:[]}};const key=and(isString,val=>0!==val.trim().length),{$defaultValue,$validators}=symbols;class Field{#name;#defaultValue;#required;#type;#validators;constructor({name,type,required=!1,defaultValue=null,validators={}}){this.#name=validateName("Field",name),this.#required=Field.#validateRequired(required),this.#type=Field.#validateType(type,required),this.#defaultValue=Field.#validateDefaultValue(defaultValue,this.#type,this.#required),this.#validators=new Map,Object.entries(validators).forEach(([validatorName,validator])=>{this.addValidator(validatorName,validator)})}addValidator(validatorName,validator){this.#validators.set(...Field.#parseFieldValidator(this.#name,validatorName,validator))}get name(){return this.#name}get required(){return this.#required}typeCheck(value){return this.#type(value)}get[$defaultValue](){return this.#defaultValue}get[$validators](){return this.#validators}static#validateType(type,required){if("function"!=typeof type)throw new TypeError("Field type must be a function.");return required?type:types.optional(type)}static#validateRequired(required){if("boolean"!=typeof required)throw new TypeError("Field required must be a boolean.");return required}static#validateDefaultValue(defaultValue,type,required){if(required&&types.nil(defaultValue))throw new ValidationError("Default value cannot be null or undefined.");if(!type(defaultValue))throw new ValidationError("Default value must be valid.");return defaultValue}static#parseFieldValidator(fieldName,validatorName,validator){if(void 0!==Validator[validatorName])return[""+fieldName+capitalize(validatorName),Validator[validatorName](fieldName,validator)];if("function"!=typeof validator)throw new TypeError(`Validator ${validatorName} is not defined.`);return[""+fieldName+capitalize(validatorName),Validator.custom(fieldName,validator)]}}Object.entries(standardTypes).forEach(([typeName,standardType])=>{const{type,defaultValue:typeDefaultValue}=standardType;Field[typeName]=options=>"string"==typeof options?new Field({name:options,type:type}):new Field({...options,type:type}),Field[typeName+"Required"]=options=>{if("string"==typeof options)return new Field({name:options,type:type,required:!0,defaultValue:typeDefaultValue});var defaultValue=options.defaultValue||typeDefaultValue;return new Field({...options,type:type,required:!0,defaultValue:defaultValue})}}),Field.enum=({name,values})=>new Field({name:name,type:types.enum(...values)}),Field.enumRequired=({name,values,defaultValue=values[0]})=>new Field({name:name,type:types.enum(...values),required:!0,defaultValue:defaultValue}),Field.auto=autoField=>{autoField="string"==typeof autoField?autoField:autoField.name;const generator=function*(){let i=0;for(;;)yield i++}();let currentValue=0;autoField=new Field({name:autoField,type:value=>value===currentValue,required:!0,defaultValue:currentValue});return Object.defineProperty(autoField,$defaultValue,{get(){var value=generator.next().value;return currentValue=value}}),autoField};const{$recordValue,$wrappedRecordValue,$recordHandler,$recordModel,$recordTag,$key}=symbols;class Record{#recordValue;#recordHandler;#proxiedRecord;constructor(value,handler){return this.#recordValue=value,this.#recordHandler=handler,this.#proxiedRecord=new Proxy(this,this.#recordHandler),this.#proxiedRecord}get[$recordHandler](){return this.#recordHandler}get[$recordValue](){return this.#recordValue}get[$wrappedRecordValue](){return this.#proxiedRecord}get[$recordModel](){return this.#recordHandler.model}get[$recordTag](){var model=this[$recordModel],key=model[$key].name;return model.name+"#"+this[$recordValue][key]}get[Symbol.toStringTag](){return this[$recordTag]}}const record=Record,partial_$recordTag=symbols["$recordTag"];class PartialRecord{#tag;constructor(value,tag){Object.keys(value).forEach(key=>{this[key]=value[key]}),this.#tag=tag}get[partial_$recordTag](){return this.#tag}get[Symbol.toStringTag](){return this[partial_$recordTag]}toObject(){return{...this}}toJSON(){return this.toObject()}}const fragment_$recordTag=symbols["$recordTag"];class RecordFragment extends Array{#tag;constructor(values,tag){super(),values.forEach(value=>{this.push(value)}),this.#tag=tag}get[fragment_$recordTag](){return this.#tag}get[Symbol.toStringTag](){return this[fragment_$recordTag]}toObject(){return[...this]}toJSON(){return this.toObject()}}const{$recordModel:set_$recordModel,$recordTag:set_$recordTag,$scopes,$addScope,$removeScope,$copyScopes,$isRecord,$key:set_$key}=symbols;class RecordSet extends Map{#frozen;#scopes;constructor({iterable=[],copyScopesFrom=null}={}){super();for(var[key,value]of iterable)this.set(key,value);this.#scopes=new Map,copyScopesFrom&&this[$copyScopes](copyScopesFrom),this.#frozen=!1}freeze(){return this.#frozen=!0,this}set(key,value){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");return super.set(key,value),this}delete(key){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");return super.delete(key)}clear(){if(this.#frozen)throw new TypeError("Cannot modify a frozen RecordSet.");super.clear()}map(callbackFn){return[...this.entries()].reduce((newMap,[key,value])=>(newMap[key]=callbackFn(value,key,this),newMap),{})}flatMap(callbackFn){return[...this.entries()].map(([key,value])=>callbackFn(value,key,this))}reduce(callbackFn,initialValue){return[...this.entries()].reduce((acc,[key,value])=>callbackFn(acc,value,key,this),initialValue)}filter(callbackFn){return[...this.entries()].reduce((newMap,[key,value])=>(callbackFn(value,key,this)&&newMap.set(key,value),newMap),new RecordSet({copyScopesFrom:this})).freeze()}flatFilter(callbackFn){return[...this.entries()].reduce((arr,[key,value])=>(callbackFn(value,key,this)&&arr.push(value),arr),[])}find(callbackFn){var match=[...this.entries()].find(([key,value])=>callbackFn(value,key,this));if(match)return match[1]}findKey(callbackFn){var match=[...this.entries()].find(([key,value])=>callbackFn(value,key,this));if(match)return match[0]}only(...keys){return new RecordSet({iterable:[...this.entries()].filter(([key])=>keys.includes(key)),copyScopesFrom:this}).freeze()}except(...keys){return new RecordSet({iterable:[...this.entries()].filter(([key])=>!keys.includes(key)),copyScopesFrom:this}).freeze()}sort(comparatorFn){var sorted=[...this.entries()].sort(([key1,value1],[key2,value2])=>comparatorFn(value1,value2,key1,key2));return new RecordSet({iterable:sorted,copyScopesFrom:this}).freeze()}every(callbackFn){return 0===this.size||[...this.entries()].every(([key,value])=>callbackFn(value,key,this))}some(callbackFn){return 0!==this.size&&[...this.entries()].some(([key,value])=>callbackFn(value,key,this))}select(...keys){return new RecordSet({iterable:[...this.entries()].map(([key,value])=>{const obj={};return keys.forEach(key=>obj[key]=value[key]),[key,new PartialRecord(obj,value[set_$recordTag])]}),copyScopesFrom:this}).freeze()}flatSelect(...keys){return[...this.values()].map(value=>keys.reduce((obj,key)=>({...obj,[key]:value[key]}),{}))}pluck(...keys){return new RecordSet({iterable:[...this.entries()].map(([key,value])=>{var values=keys.map(key=>value[key]);return[key,new RecordFragment(values,value[set_$recordTag])]}),copyScopesFrom:this}).freeze()}flatPluck(...keys){const isSingleKey=1===keys.length;return[...this.values()].map(value=>isSingleKey?value[keys[0]]:keys.map(key=>value[key]))}groupBy(key){const res=new RecordSet({copyScopesFrom:this,iterable:[]});for(var[recordKey,value]of this.entries()){let keyValue=value[key];void 0!==keyValue&&null!==keyValue&&keyValue[$isRecord]&&(keyValue=value[key][set_$key]),res.has(keyValue)||res.set(keyValue,new RecordGroup({copyScopesFrom:this,iterable:[],groupName:keyValue})),res.get(keyValue).set(recordKey,value)}for(const value of res.values())value.freeze();return res.freeze()}where(callbackFn){return this.filter(callbackFn)}whereNot(callbackFn){return this.filter((value,key,map)=>!callbackFn(value,key,map))}*batchIterator(batchSize){let batch=[];for(var[,value]of this)batch.push(value),batch.length===batchSize&&(yield batch,batch=[]);batch.length&&(yield batch)}limit(n){let records=[];for(var[key,value]of this)if(records.push([key,value]),records.length===n)break;return new RecordSet({iterable:records,copyScopesFrom:this}).freeze()}offset(n){let counter=0,records=[];for(var[key,value]of this)counter<n?counter++:records.push([key,value]);return new RecordSet({iterable:records,copyScopesFrom:this}).freeze()}get first(){for(var[,value]of this)return value}get last(){if(0!==this.size)return[...this.entries()].pop()[1]}get count(){return this.size}toArray(){return[...this.values()]}toFlatArray(){return[...this.values()].map(value=>value instanceof RecordGroup?value.toFlatArray():value.toObject())}toObject(){return[...this.entries()].reduce((obj,[key,value])=>(obj[key]=value,obj),{})}toFlatObject(){return[...this.entries()].reduce((obj,[key,value])=>(obj[key]=value instanceof RecordGroup?value.toFlatArray():value.toObject(),obj),{})}toJSON(){return this.toObject()}get[Symbol.toStringTag](){var records=[...this.values()];try{const firstModel=records[0][set_$recordModel].name;if(((arr,fn)=>{const eql=fn(arr[0]);return arr.every(val=>fn(val)===eql)})(records,value=>value[set_$recordModel].name===firstModel))return firstModel}catch(e){return""}return""}static get[Symbol.species](){return Map}[$addScope](name,scope){if(RecordSet.#validateProperty("Scope",name,scope,this.#scopes),this[name]||Object.getOwnPropertyNames(RecordSet.prototype).includes(name))throw new NameError(`Scope name ${name} is already in use.`);this.#scopes.set(name,scope),Object.defineProperty(this,name,{configurable:!0,get:()=>this.where(this.#scopes.get(name))})}[$removeScope](name){this.#scopes.delete(RecordSet.#validateContains("Scope",name,this.#scopes)),delete this[name]}[$copyScopes](otherRecordSet){otherRecordSet[$scopes].forEach((scope,name)=>{this[$addScope](name,scope)})}get[$scopes](){return this.#scopes}static#validateProperty(callbackType,callbackName,callback,callbacks){if("function"!=typeof callback)throw new TypeError(callbackType+` ${callbackName} is not a function.`);if(callbacks.has(callbackName))throw new DuplicationError(callbackType+` ${callbackName} already exists.`);return callback}static#validateContains(objectType,objectName,objects){if(!objects.has(objectName))throw new ReferenceError(objectType+` ${objectName} does not exist.`);return objectName}}const set=RecordSet,$groupTag=symbols["$groupTag"];class RecordGroup extends set{#groupName;constructor({iterable=[],copyScopesFrom=null,groupName=""}={}){super({iterable:iterable,copyScopesFrom:copyScopesFrom}),this.#groupName=groupName}get[$groupTag](){return this.#groupName}get[Symbol.toStringTag](){return this[$groupTag]}}const{$fields,$defaultValue:handler_$defaultValue,$key:handler_$key,$keyType,$properties,$methods,$relationships,$validators:handler_$validators,$recordValue:handler_$recordValue,$wrappedRecordValue:handler_$wrappedRecordValue,$recordModel:handler_$recordModel,$recordTag:handler_$recordTag,$isRecord:handler_$isRecord,$get}=symbols;class RecordHandler{#model;constructor(model){this.#model=model}get model(){return this.#model}createRecord(recordData){if(!recordData)throw new TypeError("Record data cannot be empty.");if("object"!=typeof recordData)throw new TypeError("Record data must be an object.");const modelName=this.#getModelName(),newRecordKey=RecordHandler.#validateNewRecordKey(modelName,this.#getKey(),recordData[this.#getKey().name],this.#model.records),clonedRecord=deepClone(recordData),extraProperties=Object.keys(clonedRecord).filter(property=>!this.#hasField(property)&&!this.#isModelKey(property));0<extraProperties.length&&console.warn(`${modelName} record has extra fields: ${extraProperties.join(", ")}.`);const newRecord=new record({[this.#getKey().name]:newRecordKey,...extraProperties.reduce((obj,property)=>({...obj,[property]:clonedRecord[property]}),{})},this);return this.#getFieldNames().forEach(field=>{this.set(newRecord,field,clonedRecord[field],newRecord,!0)}),this.#getValidators().forEach((validator,validatorName)=>{if(!validator(newRecord,this.#model.records))throw new RangeError(`${modelName} record with key ${newRecordKey} failed validation for ${validatorName}.`)}),[newRecordKey,newRecord]}get(record,property){return this.#hasRelationshipField(property)?this.#getRelationship(record,property):this.#isModelKey(property)||this.#hasField(property)?this.#getFieldValue(record,property):this.#hasProperty(property)?this.#getProperty(record,property):this.#hasMethod(property)?this.#getMethod(record,property):this.#isCallToSerialize(property)?RecordHandler.#recordToObject(record,this.#model,this):this.#isCallToString(property)?()=>this.#getKeyValue(record):this.#isKnownSymbol(property)?this.#getKnownSymbol(record,property):void 0}set(record,property,value,receiver,skipValidation){const recordValue=record[handler_$recordValue],recordKey=this.#getKeyValue(record),otherRecords=this.#model.records.except(recordKey);if(this.#hasProperty(property))throw new TypeError(`${this.#getModelName()} record ${recordKey} cannot set property ${property}.`);if(this.#hasMethod(property))throw new TypeError(`${this.#getModelName()} record ${recordKey} cannot set method ${property}.`);if(this.#hasField(property)){const field=this.#getField(property);RecordHandler.#setRecordField(this.#model.name,recordValue,field,value),field[handler_$validators].forEach((validator,validatorName)=>{if(![null,void 0].includes(recordValue[property])&&!validator(recordValue,otherRecords))throw new RangeError(`${this.#getModelName()} record with key ${recordKey} failed validation for ${validatorName}.`)})}else console.warn(this.#model.name+` record has extra field: ${property}.`),recordValue[property]=value;return skipValidation||this.#getValidators().forEach((validator,validatorName)=>{if(!validator(recordValue,otherRecords))throw new RangeError(`${this.#getModelName()} record with key ${recordKey} failed validation for ${validatorName}.`)}),!0}static#setRecordField(modelName,record,field,recordValue){recordValue=field.required&&types.nil(recordValue)?field[handler_$defaultValue]:recordValue;if(!field.typeCheck(recordValue))throw new TypeError(`${modelName} record has invalid value for field ${field.name}.`);record[field.name]=recordValue}static#recordToObject(record,key,handler){const recordValue=record[handler_$recordValue],fields=key[$fields],properties=key[$properties];key=key[handler_$key].name;const object={[key]:recordValue[key]};fields.forEach(field=>{void 0!==recordValue[field.name]&&(object[field.name]=recordValue[field.name])});return({include=[]}={})=>{var result=object;const included=include.map(name=>{const[field,...props]=name.split(".");return[field,props.join(".")]});return included.forEach(([includedField,props])=>{if(object[includedField])if(Array.isArray(object[includedField])){const records=handler.get(record,includedField);object[includedField]=records.map(record=>record.toObject({include:[props]}))}else object[includedField]=handler.get(record,includedField).toObject({include:[props]});else properties.has(includedField)&&(object[includedField]=handler.get(record,includedField))}),result}}static#validateNewRecordKey=(modelName,modelKey,recordKey,records)=>{let newRecordKey=recordKey;if("string"===modelKey[$keyType]&&!modelKey.typeCheck(newRecordKey))throw new TypeError(`${modelName} record has invalid value for key ${modelKey.name}.`);if("auto"===modelKey[$keyType]&&(newRecordKey=modelKey[handler_$defaultValue]),records.has(newRecordKey))throw new DuplicationError(`${modelName} record with key ${newRecordKey} already exists.`);return newRecordKey};#getModelName(){return this.#model.name}#getFieldNames(){return[...this.#model[$fields].keys()]}#getValidators(){return this.#model[handler_$validators]}#isModelKey(property){return this.#model[handler_$key].name===property}#getKey(){return this.#model[handler_$key]}#getKeyValue(record){return record[handler_$recordValue][this.#model[handler_$key].name]}#hasField(property){return this.#model[$fields].has(property)}#getField(property){return this.#model[$fields].get(property)}#getFieldValue(record,property){return record[handler_$recordValue][property]}#hasProperty(property){return this.#model[$properties].has(property)}#getProperty(record,property){return this.#model[$properties].get(property)(record[handler_$wrappedRecordValue])}#hasMethod(method){return this.#model[$methods].has(method)}#getMethod(record,method){const methodFn=this.#model[$methods].get(method);return(...args)=>methodFn(record[handler_$wrappedRecordValue],...args)}#hasRelationshipField(property){return!!this.#hasField(property)&&this.#model[$relationships].has(property+"."+property)}#getRelationship(record,property){return this.#model[$relationships].get(property+"."+property)[$get](this.#getModelName(),property,record[handler_$recordValue])}#isCallToSerialize(property){return"toObject"===property||"toJSON"===property}#isCallToString(property){return"toString"===property}#isKnownSymbol(property){return[handler_$recordModel,handler_$recordTag,handler_$recordValue,handler_$isRecord,handler_$key].includes(property)}#getKnownSymbol(record,property){return property===handler_$isRecord||(property===handler_$key&&this.#getKey(),record[property])}}const handler=RecordHandler,{$fields:model_$fields,$defaultValue:model_$defaultValue,$key:model_$key,$keyType:model_$keyType,$properties:model_$properties,$methods:model_$methods,$scopes:model_$scopes,$relationships:model_$relationships,$validators:model_$validators,$recordHandler:model_$recordHandler,$addScope:model_$addScope,$addRelationshipAsField,$addRelationshipAsProperty,$getField,$getProperty,$removeScope:model_$removeScope,$instances,$handleExperimentalAPIMessage}=symbols,allStandardTypes=[...Object.keys(standardTypes),...Object.keys(standardTypes).map(type=>type+"Required"),"enum","enumRequired","auto"];class Model extends external_events_default(){#records;#recordHandler;#fields;#key;#properties;#methods;#relationships;#validators;#updatingField=!1;static#instances=new Map;constructor({name,fields=[],key="id",properties={},methods={},scopes={},validators={}}={}){if(super(),this.name=validateName("Model",name),Model.#instances.has(name))throw new DuplicationError(`A model named ${name} already exists.`);this.#records=new set,this.#recordHandler=new handler(this),this.#key=Model.#parseKey(this.name,key),this.#fields=new Map,this.#properties=new Map,this.#methods=new Map,this.#relationships=new Map,this.#validators=new Map,fields.forEach(field=>this.addField(field)),Object.entries(properties).forEach(([propertyName,property])=>{this.addProperty(propertyName,property)}),Object.entries(methods).forEach(([methodName,method])=>{this.addMethod(methodName,method)}),Object.entries(scopes).forEach(([scopeName,scope])=>{this.addScope(scopeName,scope)}),Object.entries(validators).forEach(([validatorName,validator])=>{this.addValidator(validatorName,validator)}),Model.#instances.set(this.name,this)}addField(fieldOptions,retrofill){this.#updatingField||this.emit("beforeAddField",{field:fieldOptions,model:this});var field=Model.#parseField(this.name,fieldOptions,[...this.#fields.keys(),this.#key.name,...this.#properties.keys(),...this.#methods.keys()]);return this.#fields.set(fieldOptions.name,field),this.#updatingField||this.emit("fieldAdded",{field:field,model:this}),this.emit("beforeRetrofillField",{field:field,retrofill:retrofill,model:this}),Model.#applyFieldRetrofill(field,this.#records,retrofill),this.emit("fieldRetrofilled",{field:field,retrofill:retrofill,model:this}),this.#updatingField||this.emit("change",{type:"fieldAdded",field:field,model:this}),field}removeField(name){if(!Model.#validateContains(this.name,"Field",name,this.#fields))return!1;var field=this.#fields.get(name);return this.#updatingField||this.emit("beforeRemoveField",{field:field,model:this}),this.#fields.delete(name),this.#updatingField||(this.emit("fieldRemoved",{field:{name:name},model:this}),this.emit("change",{type:"fieldRemoved",field:field,model:this})),!0}updateField(name,field,newField){if(field.name!==name)throw new NameError(`Field name ${field.name} does not match ${name}.`);if(!Model.#validateContains(this.name,"Field",name,this.#fields))throw new ReferenceError(`Field ${name} does not exist.`);var prevField=this.#fields.get(name);this.#updatingField=!0,this.emit("beforeUpdateField",{prevField:prevField,field:field,model:this}),this.removeField(name);newField=this.addField(field,newField);this.emit("fieldUpdated",{field:newField,model:this}),this.#updatingField=!1,this.emit("change",{type:"fieldUpdated",field:newField,model:this})}addProperty(name,property){this.emit("beforeAddProperty",{property:{name:name,body:property},model:this});var propertyName=validateName("Property",name);this.#properties.set(propertyName,Model.#validateFunction("Property",name,property,[...this.#fields.keys(),this.#key.name,...this.#properties.keys(),...this.#methods.keys()])),this.emit("propertyAdded",{property:{name:propertyName,body:property},model:this}),this.emit("change",{type:"propertyAdded",property:{name:propertyName,body:property},model:this})}removeProperty(name){if(!Model.#validateContains(this.name,"Property",name,this.#properties))return!1;var property=this.#properties.get(name);return this.emit("beforeRemoveProperty",{property:{name:name,body:property},model:this}),this.#properties.delete(name),this.emit("propertyRemoved",{property:{name:name},model:this}),this.emit("change",{type:"propertyRemoved",property:{name:name,body:property},model:this}),!0}addMethod(name,method){this.emit("beforeAddMethod",{method:{name:name,body:method},model:this});var methodName=validateName("Method",name);this.#methods.set(methodName,Model.#validateFunction("Method",name,method,[...this.#fields.keys(),this.#key.name,...this.#properties.keys(),...this.#methods.keys()])),this.emit("methodAdded",{method:{name:methodName,body:method},model:this}),this.emit("change",{type:"methodAdded",method:{name:methodName,body:method},model:this})}removeMethod(name){if(!Model.#validateContains(this.name,"Method",name,this.#methods))return!1;var method=this.#methods.get(name);return this.emit("beforeRemoveMethod",{method:{name:name,body:method},model:this}),this.#methods.delete(name),this.emit("methodRemoved",{method:{name:name},model:this}),this.emit("change",{type:"methodRemoved",method:{name:name,body:method},model:this}),!0}addScope(scopeName,scope){this.emit("beforeAddScope",{scope:{name:scopeName,body:scope},model:this});scopeName=validateName("Scope",scopeName);this.#records[model_$addScope](scopeName,scope),this.emit("scopeAdded",{scope:{name:scopeName,body:scope},model:this}),this.emit("change",{type:"scopeAdded",scope:{name:scopeName,body:scope},model:this})}removeScope(name){if(!Model.#validateContains(this.name,"Scope",name,this.#records[model_$scopes]))return!1;var scope=this.#records[model_$scopes].get(name);return this.emit("beforeRemoveScope",{scope:{name:name,body:scope},model:this}),this.#records[model_$removeScope](name),this.emit("scopeRemoved",{scope:{name:name},model:this}),this.emit("change",{type:"scopeRemoved",scope:{name:name,body:scope},model:this}),!0}addValidator(name,validator){this.emit("beforeAddValidator",{validator:{name:name,body:validator},model:this}),this.#validators.set(name,Model.#validateFunction("Validator",name,validator,[...this.#validators.keys()])),this.emit("validatorAdded",{validator:{name:name,body:validator},model:this}),this.emit("change",{type:"validatorAdded",validator:{name:name,body:validator},model:this})}removeValidator(name){if(!Model.#validateContains(this.name,"Validator",name,this.#validators))return!1;var validator=this.#validators.get(name);return this.emit("beforeRemoveValidator",{validator:{name:name,body:validator},model:this}),this.#validators.delete(name),this.emit("validatorRemoved",{validator:{name:name},model:this}),this.emit("change",{type:"validatorRemoved",validator:{name:name,body:validator},model:this}),!0}createRecord(newRecord){this.emit("beforeCreateRecord",{record:newRecord,model:this});var[newRecordKey,newRecord]=this.#recordHandler.createRecord(newRecord);return this.#records.set(newRecordKey,newRecord),this.emit("recordCreated",{newRecord:newRecord,model:this}),newRecord}removeRecord(recordKey){if(!this.#records.has(recordKey))return console.warn(`Record ${recordKey} does not exist.`),!1;var record=this.#records.get(recordKey);return this.emit("beforeRemoveRecord",{record:record,model:this}),this.#records.delete(recordKey),this.emit("recordRemoved",{record:{[this.#key.name]:recordKey},model:this}),!0}updateRecord(recordKey,record){if("object"!=typeof record)throw new TypeError("Record data must be an object.");if(!this.#records.has(recordKey))throw new ReferenceError(`Record ${recordKey} does not exist.`);const oldRecord=this.#records.get(recordKey);return this.emit("beforeUpdateRecord",{record:oldRecord,newRecord:{[this.#key.name]:recordKey,...record},model:this}),Object.entries(record).forEach(([fieldName,fieldValue])=>{oldRecord[fieldName]=fieldValue}),this.emit("recordUpdated",{record:oldRecord,model:this}),oldRecord}get records(){return this.#records}static get[$instances](){return Model.#instances}get[model_$recordHandler](){return this.#recordHandler}get[model_$fields](){return this.#fields}get[model_$key](){return this.#key}get[model_$properties](){return this.#properties}get[model_$methods](){return this.#methods}get[model_$relationships](){return this.#relationships}get[model_$validators](){return this.#validators}[$addRelationshipAsField](relationship){var{name,type,fieldName,field}=relationship[$getField](),relationshipName=name+"."+fieldName;if(this.emit("beforeAddRelationship",{relationship:{name:name,type:type},model:this}),[...this.#fields.keys(),this.#key.name,...this.#properties.keys(),...this.#methods.keys()].includes(fieldName))throw new NameError(`Relationship field ${fieldName} is already in use.`);if(this.#relationships.has(relationshipName))throw new NameError(`Relationship ${relationshipName} is already in use.`);this.#fields.set(fieldName,field),this.#relationships.set(relationshipName,relationship),this.emit("relationshipAdded",{relationship:{name:name,type:type},model:this}),this.emit("change",{type:"relationshipAdded",relationship:{relationship:{name:name,type:type},model:this},model:this})}[$addRelationshipAsProperty](relationship){var{name,type,propertyName,property}=relationship[$getProperty](),relationshipName=name+"."+propertyName;if(this.emit("beforeAddRelationship",{relationship:{name:name,type:type},model:this}),[...this.#fields.keys(),this.#key.name,...this.#properties.keys(),...this.#methods.keys()].includes(propertyName))throw new NameError(`Relationship property ${propertyName} is already in use.`);if(this.#relationships.has(relationshipName))throw new NameError(`Relationship ${name} is already in use.`);this.#properties.set(propertyName,property),this.#relationships.set(relationshipName,relationship),this.emit("relationshipAdded",{relationship:{name:name,type:type},model:this}),this.emit("change",{type:"relationshipAdded",relationship:{relationship:{name:name,type:type},model:this},model:this})}static#createKey(options){let name="id",type="string";"string"==typeof options?name=options:"object"==typeof options&&(name=options.name||name,type=options.type||type);let keyField;return"string"===type?(keyField=new Field({name:name,type:key,required:!0,defaultValue:"__emptyKey__"}),Object.defineProperty(keyField,model_$defaultValue,{get(){throw new DefaultValueError(`Key field ${name} does not have a default value.`)}})):"auto"===type&&(keyField=Field.auto(name)),Object.defineProperty(keyField,model_$keyType,{get(){return type}}),keyField}static#parseKey(modelName,key){if("string"!=typeof key&&"object"!=typeof key)throw new TypeError(modelName+` key ${key} is not a string or object.`);if("object"==typeof key&&!key.name)throw new TypeError(modelName+` key ${key} is missing a name.`);if("object"==typeof key&&!["auto","string"].includes(key.type))throw new TypeError(modelName+` key ${key} type must be either "string" or "auto".`);return Model.#createKey(key)}static#parseField(modelName,field,restrictedNames){return validateObjectWithUniqueName({objectType:"Field",parentType:"Model",parentName:modelName},field,restrictedNames),allStandardTypes.includes(field.type)?Field[field.type](field):("function"==typeof field.type&&Schema[$handleExperimentalAPIMessage](`The provided type for ${field.name} is not part of the standard types. Function types are experimental and may go away in a later release.`),new Field(field))}static#validateFunction(callbackType,callbackName,callback,restrictedNames){if("function"!=typeof callback)throw new TypeError(callbackType+` ${callbackName} is not a function.`);if(restrictedNames.includes(callbackName))throw new DuplicationError(callbackType+` ${callbackName} already exists.`);return callback}static#validateContains(modelName,objectType,objectName,objects){return!!objects.has(objectName)||(console.warn(`Model ${modelName} does not contain a ${objectType.toLowerCase()} named ${objectName}.`),!1)}static#applyFieldRetrofill(field,records,retrofill){if(field.required||void 0!==retrofill){const retrofillFunction=void 0!==retrofill?"function"==typeof retrofill?retrofill:()=>retrofill:record=>record[field.name]||field[model_$defaultValue];records.forEach(record=>{record[field.name]=retrofillFunction(record)})}}}const{$key:relationship_$key,$recordValue:relationship_$recordValue,$fields:relationship_$fields,$getField:relationship_$getField,$getProperty:relationship_$getProperty,$get:relationship_$get,$defaultValue:relationship_$defaultValue,$instances:relationship_$instances,$handleExperimentalAPIMessage:relationship_$handleExperimentalAPIMessage}=symbols,relationshipEnum={oneToOne:"oneToOne",oneToMany:"oneToMany",manyToOne:"manyToOne",manyToMany:"manyToMany"};class Relationship{#type;#from;#to;#name;#reverseName;#relationshipField;#relationshipProperty;constructor({from:fromName,to:toModel,type:toName}={}){Schema[relationship_$handleExperimentalAPIMessage]("Relationships are experimental in the current version. There is neither validation of existence in foreign tables nor guarantee that associations work. Please use with caution."),this.#type=Relationship.#validateType(toName);var[fromModel,fromName,toModel,toName]=Relationship.#parseModelsAndNames(fromName,toModel,toName);if(this.#from=fromModel,this.#to=toModel,this.#name=fromName,this.#reverseName=toName,this.#to===this.#from&&Relationship.#isSymmetric(this.#type)&&this.#name===this.#reverseName)throw new RangeError("Relationship cannot be symmetric if the from and to models are the same and no name is provided for either one.");this.#relationshipField=Relationship.#createField(this.#name,this.#type,this.#to[relationship_$key]),this.#relationshipProperty=record=>this.#getAssociatedRecordsReverse(record)}[relationship_$getField](){return{name:this.#name,type:this.#type,fieldName:this.#name,field:this.#relationshipField}}[relationship_$getProperty](){return{name:this.#name,type:this.#type,propertyName:this.#reverseName,property:this.#relationshipProperty}}[relationship_$get](modelName,property,record){return modelName===this.#from.name&&property===this.#name?this.#getAssociatedRecords(record):modelName===this.#to.name&&property===this.#reverseName?(console.warn("Relationship getter called by the receiver model. This might indicate an issue with the library and should be reported."),this.#getAssociatedRecordsReverse(record)):void 0}#getAssociatedRecords(record){if(Relationship.#isToOne(this.#type)){var associationValue=record[this.#name];return this.#to.records.get(associationValue)}const associationValues=record[this.#name]||[],associatedRecordsKeyName=this.#to[relationship_$key].name;return this.#to.records.where(associatedRecord=>associationValues.includes(associatedRecord[associatedRecordsKeyName]))}#getAssociatedRecordsReverse(matcher){const associationValue=matcher[this.#to[relationship_$key].name];matcher=Relationship.#isToOne(this.#type)?associatedRecord=>associatedRecord[relationship_$recordValue][this.#name]===associationValue:associatedRecord=>{var associatedRecordValue=associatedRecord[relationship_$recordValue][this.#name];return![void 0,null].includes(associatedRecordValue)&&associatedRecord[relationship_$recordValue][this.#name].includes(associationValue)};return Relationship.#isFromOne(this.#type)?this.#from.records.find(matcher):this.#from.records.where(matcher)}static#isToOne(type){return[relationshipEnum.oneToOne,relationshipEnum.manyToOne].includes(type)}static#isToMany(type){return[relationshipEnum.oneToMany,relationshipEnum.manyToMany].includes(type)}static#isFromOne(type){return[relationshipEnum.oneToMany,relationshipEnum.oneToOne].includes(type)}static#isFromMany(type){return[relationshipEnum.manyToOne,relationshipEnum.manyToMany].includes(type)}static#isSymmetric(type){return[relationshipEnum.oneToOne,relationshipEnum.manyToMany].includes(type)}static#createField(name,type,foreignField){var isSingleSource=Relationship.#isFromOne(type),relationshipField=Relationship.#isToMany(type),type=relationshipField?types.arrayOf(value=>foreignField.typeCheck(value)):value=>foreignField.typeCheck(value);const validators={};isSingleSource&&!relationshipField&&(validators.unique=!0),relationshipField&&(validators.uniqueValues=!0);relationshipField=new Field({name:name,type:type,required:!1,defaultValue:relationshipField?[]:null,validators:validators});return Object.defineProperty(relationshipField,relationship_$defaultValue,{get(){throw new DefaultValueError("Relationship field does not have a default value.")}}),relationshipField}static#validateType(relationshipType){if(!Object.values(relationshipEnum).includes(relationshipType))throw new TypeError(`Invalid relationship type: ${relationshipType}.`);return relationshipType}static#validateModel(modelName){modelName="string"==typeof modelName?modelName:modelName.model;if(!Model[relationship_$instances].has(modelName))throw new ReferenceError(`Model ${modelName} does not exist.`);return Model[relationship_$instances].get(modelName)}static#createName(type,to){return Relationship.#isToOne(type)?reverseCapitalize(to):Relationship.#isToMany(type)?reverseCapitalize(to)+"Set":void 0}static#createReverseName=(type,from)=>Relationship.#isFromOne(type)?reverseCapitalize(from):Relationship.#isFromMany(type)?reverseCapitalize(from)+"Set":void 0;static#validateModelParams(name){const model=Relationship.#validateModel(name);name="string"==typeof name?null:validateName("Field",name.name);if(null!==name&&model[relationship_$fields].has(name))throw new DuplicationError(`Field ${name} already exists in ${model.name}.`);return[model,name]}static#parseModelsAndNames(from,to,type){let fromModel,fromName,toModel,toName;return[fromModel,fromName]=Relationship.#validateModelParams(from),[toModel,toName]=Relationship.#validateModelParams(to),null===fromName&&(fromName=Relationship.#createName(type,toModel.name)),null===toName&&(toName=Relationship.#createReverseName(type,fromModel.name)),[fromModel,fromName,toModel,toName]}}const{$addRelationshipAsField:schema_$addRelationshipAsField,$addRelationshipAsProperty:schema_$addRelationshipAsProperty,$handleExperimentalAPIMessage:schema_$handleExperimentalAPIMessage,$key:schema_$key,$keyType:schema_$keyType}=symbols;class Schema extends external_events_default(){#name;#models;static defaultConfig={experimentalAPIMessages:"warn"};static config={...Schema.defaultConfig};static#schemas=new Map;constructor({name,models=[],relationships=[],config={}}={}){super(),this.#name=validateName("Schema",name),this.#models=new Map,Schema.#parseConfig(config),Schema.#schemas.set(this.#name,this),models.forEach(model=>this.createModel(model)),relationships.forEach(relationship=>this.createRelationship(relationship))}createModel(modelData){this.emit("beforeCreateModel",{model:modelData,schema:this});const model=Schema.#parseModel(this.#name,modelData,this.#models);return this.#models.set(model.name,model),model.on("change",({type,...eventData})=>{this.emit("change",{type:"model"+capitalize(type),...eventData,schema:this})}),this.emit("modelCreated",{model:model,schema:this}),this.emit("change",{type:"modelCreated",model:model,schema:this}),model}getModel(name){return this.#models.get(name)}removeModel(name){var model=this.getModel(name);if(this.emit("beforeRemoveModel",{model:model,schema:this}),!this.#models.has(name))throw new ReferenceError(`Model ${name} does not exist in schema ${this.#name}.`);this.#models.delete(name),this.emit("modelRemoved",{model:{name:name},schema:this}),this.emit("change",{type:"modelRemoved",model:model,schema:this})}createRelationship(relationship){this.emit("beforeCreateRelationship",{relationship:relationship,schema:this});relationship=Schema.#applyRelationship(this.#name,relationship,this.#models);return this.emit("relationshipCreated",{relationship:relationship,schema:this}),this.emit("change",{type:"relationshipCreated",relationship:relationship,schema:this}),relationship}get name(){return this.#name}get models(){return this.#models}static create(schemaData){return new Schema(schemaData)}static get(name){return Schema.#schemas.get(name)}get(pathName){this.emit("beforeGet",{pathName:pathName,schema:this});const[modelName,recordKey,...rest]=pathName.split("."),model=this.getModel(modelName);if(!model)throw new ReferenceError(`Model ${modelName} does not exist in schema ${this.#name}.`);if(void 0===recordKey)return model;var result=model[schema_$key][schema_$keyType],result=model.records.get("string"===result?recordKey:Number.parseInt(recordKey));if(!rest.length)return result;if(!result)throw new ReferenceError(`Record ${recordKey} does not exist in model ${modelName}.`);result=rest.reduce((acc,key)=>acc[key],result);return this.emit("got",{pathName:pathName,result:result,schema:this}),result}static[schema_$handleExperimentalAPIMessage](message){var experimentalAPIMessages=Schema.config["experimentalAPIMessages"];if("warn"===experimentalAPIMessages)console.warn(message);else if("error"===experimentalAPIMessages)throw new ExperimentalAPIUsageError(message)}static#parseModel(schemaName,modelData,models){return validateObjectWithUniqueName({objectType:"Model",parentType:"Schema",parentName:schemaName},modelData,[...models.keys()]),new Model(modelData)}static#applyRelationship(relationship,toModelName,models){const{from,to,type}=toModelName;[from,to].forEach(model=>{if(!["string","object"].includes(typeof model))throw new TypeError(`Invalid relationship model: ${model}.`)});var fromModelName="string"==typeof from?from:from.model,toModelName="string"==typeof to?to:to.model;const fromModel=models.get(fromModelName),toModel=models.get(toModelName);if(!fromModel)throw new ReferenceError(`Model ${fromModelName} not found in schema ${relationship} when attempting to create a relationship.`);if(!toModel)throw new ReferenceError(`Model ${toModelName} not found in schema ${relationship} when attempting to create a relationship.`);relationship=new Relationship({from:from,to:to,type:type});return fromModel[schema_$addRelationshipAsField](relationship),toModel[schema_$addRelationshipAsProperty](relationship),relationship}static#parseConfig(config={}){config&&["experimentalAPIMessages"].forEach(key=>{void 0!==config[key]&&["warn","error","off"].includes(config[key])&&(Schema.config[key]=config[key])})}}const src=Schema;return __webpack_exports__})()});
{
"name": "@jsiqle/core",
"version": "1.0.5",
"version": "1.1.0",
"description": "JavaScript In-memory Query Language with Events.",

@@ -5,0 +5,0 @@ "main": "./dist/main.js",

@@ -45,3 +45,3 @@ # @jsiqle/core

],
methods: {
properties: {
fullName: rec => `${rec.firstName} ${rec.lastName}`

@@ -127,5 +127,5 @@ }

- `key`: (Optional) Parameter to create a key field, not part of the `fields` themselves. Can either be a string with the name of the key or an object with a `name` (string) and a `type` (either `'string'` or `'auto'`) representing a string or auto-incrementing integer key. By default, a model's key is a string field named `'id'`.
- `methods`: (Optional) An object containing key-value pairs for getter methods to be defined on the model. All methods expect a single argument representing a record of the given model. More information about method definitions can be found in one of the following sections.
- `scopes`: (Optional) An object containing key-value pairs for getter methods to be defined on the record set of the model. All scopes expect a single argument representing the record set or a subset of records from the current model. More information about scope definitions can be found in one of the following sections.
- `validators`: (Optional) An object containing key-value pairs for validation methods that return a boolean value depending on the validation's result. All validators expect two arguments, the current record and the record set of the current model. More information about validators and field validators can be found in one of the following sections.
- `properties`: (Optional) An object containing key-value pairs for getter properties to be defined on the model. All properties expect a single argument representing a record of the given model. More information about property definitions can be found in one of the following sections.
- `scopes`: (Optional) An object containing key-value pairs for getter properties to be defined on the record set of the model. All scopes expect a single argument representing the record set or a subset of records from the current model. More information about scope definitions can be found in one of the following sections.
- `validators`: (Optional) An object containing key-value pairs for validation properties that return a boolean value depending on the validation's result. All validators expect two arguments, the current record and the record set of the current model. More information about validators and field validators can be found in one of the following sections.

@@ -249,5 +249,5 @@ You can retrieve an already defined model by calling `Schema.prototype.getModel()` with the model name:

#### Method definitions
#### Property definitions
Methods can be defined as part of a model definition or added individually to a model by calling `Model.prototype.addMethod()`:
Properties can be defined as part of a model definition or added individually to a model by calling `Model.prototype.addProperty()`:

@@ -265,3 +265,3 @@ ```js

],
methods: {
properties: {
fullName: record => `${record.firstName} ${record.lastName}`

@@ -275,3 +275,3 @@ }

MyModel.addMethod(
MyModel.addProperty(
'formalName',

@@ -282,10 +282,49 @@ record => `${record.lastName} ${record.firstName}`

Methods defined as part of the model definition as specified as key-value pairs, whereas methods defined in `Model.prototype.addMethod()` are passed as two separate arguments, the name and the method body.
Properties defined as part of the model definition are specified as key-value pairs, whereas properties defined in `Model.prototype.addProperty()` are passed as two separate arguments, the name and the property body.
Methods expect one argument, the current record, and may return any type of value.
Properties expect one argument, the current record, and may return any type of value.
You can remove a property from a model using `Model.prototype.removeProperty()`:
```js
MyModel.removeProperty('formalName');
```
#### Method definitions
Methods can be defined as part of a model definition or added individually to a model by calling `Model.prototype.addMethod()`:
```js
import jsiqle from '@jsiqle/core';
const MySchema = jsiqle.create({
name: 'MySchema',
models: [
{
name: 'MyModel',
fields: [
{ name: 'firstName', type: 'string' },
{ name: 'lastName', type: 'string' },
],
methods: {
prefixedName: (record, prefix) => `${prefix} ${record.lastName}`
}
}
]
});
const MyModel = MySchema.getModel('MyModel');
MyModel.addProperty(
'suffixedName',
(record, suffix) => `${record.firstName} ${suffix}`
);
```
Methods defined as part of the model definition are specified as key-value pairs, whereas methods defined in `Model.prototype.addMethod()` are passed as two separate arguments, the name and the method body.
Methods expect any number of arguments, the current record and any arguments passed to them when called, and may return any type of value.
You can remove a method from a model using `Model.prototype.removeMethod()`:
```js
MyModel.removeMethod('formalName');
MyModel.removeMethod('prefixedName');
```

@@ -327,3 +366,3 @@

```js
MyModel.removeMethod('does');
MyModel.removeProperty('does');
```

@@ -377,3 +416,3 @@

Relationships can be defined by calling `Schema.prototype.createRelationship()`:
Relationships can be defined either as part of the schema definition or individually using `Schema.prototype.createRelationship()`:

@@ -400,2 +439,9 @@ ```js

}
],
relationships: [
{
from: { model: 'Transaction', name: 'payer' },
to: { model: 'Person', name: 'outgoingTransactions' },
type: 'manyToOne'
}
]

@@ -405,8 +451,2 @@ });

Ledger.createRelationship({
from: { model: 'Transaction', name: 'payer' },
to: { model: 'Person', name: 'outgoingTransactions' },
type: 'manyToOne'
});
Ledger.createRelationship({
from: { model: 'Transaction', name: 'payee' },

@@ -424,5 +464,5 @@ to: { model: 'Person', name: 'incomingTransactions' },

When a relationship is defined between to models, the model specified as `from` will receive a new field named accordingly. Similarly, the `to` model will receive a new method instead that performs the reverse operation. Only the field on the `from` model is writeable.
When a relationship is defined between to models, the model specified as `from` will receive a new field named accordingly. Similarly, the `to` model will receive a new property instead that performs the reverse operation. Only the field on the `from` model is writeable.
The names for the field and method are automatically generated if not specified, must be valid names and not already exist in the model. For singular relationships, the name of the other model is used, whereas for plural the name of the model followed by `Set`. For example, a `manyToOne` relationship between two models, `Person` and `Transaction`, would be named `transactionSet` on `Person` and `person` on `Transaction`.
The names for the field and property are automatically generated if not specified, must be valid names and not already exist in the model. For singular relationships, the name of the other model is used, whereas for plural the name of the model followed by `Set`. For example, a `manyToOne` relationship between two models, `Person` and `Transaction`, would be named `transactionSet` on `Person` and `person` on `Transaction`.

@@ -452,3 +492,3 @@ Relationships between records of the same model are allowed. The only caveat is that symemtric (i.e. `oneToOne` and `manyToMany`) relationships in the same model need to be named on both sides.

],
methods: {
properties: {
fullName: record => `${record.firstName} ${record.lastName}`

@@ -505,3 +545,3 @@ }

],
methods: {
properties: {
fullName: record => `${record.firstName} ${record.lastName}`

@@ -540,3 +580,3 @@ }

Specific attributes can be selected from records via the following methods:
Specific attributes can be selected from records via the following properties:

@@ -550,3 +590,3 @@ - `RecordSet.prototype.select()`: Expects any number of field names in a record. Returns a record set with partial records containing only those fields.

Record sets can be grouped or sorted via the following methods:
Record sets can be grouped or sorted via the following properties:

@@ -564,3 +604,3 @@ - `RecordSet.prototype.groupBy()`: Expects a field name and groups the records based on its value. Returns a record set containing record groups, which in turn behave like nested record sets themselves.

Additionally, you can get the first `n` elements of a record set using `RecordSet.prototype.limit()` with an appropriate numeric argument or skip over them and get all other records using `RecordSet.prototype.offset()` with an appropriate numeric argument. These methods can be combined to get specific records in a record set based on the order of insertion.
Additionally, you can get the first `n` elements of a record set using `RecordSet.prototype.limit()` with an appropriate numeric argument or skip over them and get all other records using `RecordSet.prototype.offset()` with an appropriate numeric argument. These properties can be combined to get specific records in a record set based on the order of insertion.

@@ -599,3 +639,3 @@

Relationships can be queried from either side of the relationship using the field/method name added to the model. For more information refer to the section about relationship definitions and how they are represented in models.
Relationships can be queried from either side of the relationship using the field/property name added to the model. For more information refer to the section about relationship definitions and how they are represented in models.

@@ -632,5 +672,5 @@ #### Querying from the schema

Records and record sets can be serialized to regular objects, arrays or JSON. Calling `JSON.stringify()` will suffice in most cases, as all records and record sets have appropriate methods to handle serialization. Apart from that, `Record.prototype.toObject()` can be called for individual records to convert them into regular objects.
Records and record sets can be serialized to regular objects, arrays or JSON. Calling `JSON.stringify()` will suffice in most cases, as all records and record sets have appropriate properties to handle serialization. Apart from that, `Record.prototype.toObject()` can be called for individual records to convert them into regular objects.
Additionally, record sets implement the following serialization methods:
Additionally, record sets implement the following serialization properties:

@@ -662,3 +702,3 @@ - `RecordSet.prototype.toArray()`: Returns an array of records contained in the record set.

All emitted events contain an object argument with the related data in appropriate keys, as well as a `schema` key with the schema itself. Events prefixed with `before` contain the raw data passed to the related method call, whereas events emitted after a method finishes contain the result of the method. `change` events are emitted for all non-`before` events except `got` and have the same arguments, as well as a `type` argument that specifies the event type. `change` events are also emitted as wrappers of model `change` events with the model event `type` prefixed (e.g. `modelMethodAdded` instead of `methodAdded`).
All emitted events contain an object argument with the related data in appropriate keys, as well as a `schema` key with the schema itself. Events prefixed with `before` contain the raw data passed to the related method call, whereas events emitted after a method finishes contain the result of the method. `change` events are emitted for all non-`before` events except `got` and have the same arguments, as well as a `type` argument that specifies the event type. `change` events are also emitted as wrappers of model `change` events with the model event `type` prefixed (e.g. `modelPropertyAdded` instead of `propertyAdded`).

@@ -673,4 +713,4 @@ #### Model events

beforeUpdateField fieldUpdated
beforeAddMethod methodAdded
beforeRemoveMethod methodRemoved
beforeAddProperty propertyAdded
beforeRemoveProperty propertyRemoved
beforeAddScope scopeAdded

@@ -702,3 +742,3 @@ beforeRemoveScope scopeRemoved

- A schema is a set of definitions that contain models, fields, relationships etc. The data contained within a schema is called a dataset.
- A model is a set of field, method, validator and scope definitions. The data contained within a model is called a record set and each individual item within it is called a record.
- A model is a set of field, property, validator and scope definitions. The data contained within a model is called a record set and each individual item within it is called a record.
- A record is a set of values corresponding to different keys. Each of these values is called an attribute.

@@ -705,0 +745,0 @@

@@ -43,2 +43,9 @@ const jsiqle = require('../dist/main').default;

],
relationships: [
{
from: { model: 'snippet', name: 'children' },
to: { model: 'snippet', name: 'parent' },
type: 'oneToMany',
},
],
config: {

@@ -112,3 +119,3 @@ experimentalAPIMessages: 'off',

snippet.addMethod('isCool', record => {
snippet.addProperty('isCool', record => {
return record.tags.includes('cool');

@@ -127,8 +134,2 @@ });

schema.createRelationship({
from: { model: 'snippet', name: 'children' },
to: { model: 'snippet', name: 'parent' },
type: 'oneToMany',
});
const snippetC = snippet.createRecord({

@@ -135,0 +136,0 @@ name: 'snippetC',

@@ -15,2 +15,3 @@ import EventEmitter from 'events';

$keyType,
$properties,
$methods,

@@ -23,5 +24,5 @@ $scopes,

$addRelationshipAsField,
$addRelationshipAsMethod,
$addRelationshipAsProperty,
$getField,
$getMethod,
$getProperty,
$removeScope,

@@ -45,2 +46,3 @@ $instances,

#key;
#properties;
#methods;

@@ -57,2 +59,3 @@ #relationships;

key = 'id',
properties = {},
methods = {},

@@ -81,2 +84,3 @@ scopes = {},

this.#fields = new Map();
this.#properties = new Map();
this.#methods = new Map();

@@ -89,2 +93,7 @@ this.#relationships = new Map();

// Add properties, checking for duplicates and invalids
Object.entries(properties).forEach(([propertyName, property]) => {
this.addProperty(propertyName, property);
});
// Add methods, checking for duplicates and invalids

@@ -115,2 +124,3 @@ Object.entries(methods).forEach(([methodName, method]) => {

this.#key.name,
...this.#properties.keys(),
...this.#methods.keys(),

@@ -162,2 +172,49 @@ ]);

addProperty(name, property) {
this.emit('beforeAddProperty', {
property: { name, body: property },
model: this,
});
const propertyName = validateName('Property', name);
this.#properties.set(
propertyName,
Model.#validateFunction('Property', name, property, [
...this.#fields.keys(),
this.#key.name,
...this.#properties.keys(),
...this.#methods.keys(),
])
);
this.emit('propertyAdded', {
property: { name: propertyName, body: property },
model: this,
});
this.emit('change', {
type: 'propertyAdded',
property: { name: propertyName, body: property },
model: this,
});
}
removeProperty(name) {
if (!Model.#validateContains(this.name, 'Property', name, this.#properties))
return false;
const property = this.#properties.get(name);
this.emit('beforeRemoveProperty', {
property: { name, body: property },
model: this,
});
this.#properties.delete(name);
this.emit('propertyRemoved', {
property: { name },
model: this,
});
this.emit('change', {
type: 'propertyRemoved',
property: { name, body: property },
model: this,
});
return true;
}
addMethod(name, method) {

@@ -171,5 +228,6 @@ this.emit('beforeAddMethod', {

methodName,
Model.#validateMethod('Method', name, method, [
Model.#validateFunction('Method', name, method, [
...this.#fields.keys(),
this.#key.name,
...this.#properties.keys(),
...this.#methods.keys(),

@@ -259,3 +317,3 @@ ])

name,
Model.#validateMethod('Validator', name, validator, [
Model.#validateFunction('Validator', name, validator, [
...this.#validators.keys(),

@@ -370,2 +428,6 @@ ])

get [$properties]() {
return this.#properties;
}
get [$methods]() {

@@ -394,2 +456,3 @@ return this.#methods;

this.#key.name,
...this.#properties.keys(),
...this.#methods.keys(),

@@ -421,5 +484,5 @@ ].includes(fieldName)

[$addRelationshipAsMethod](relationship) {
const { name, type, methodName, method } = relationship[$getMethod]();
const relationshipName = `${name}.${methodName}`;
[$addRelationshipAsProperty](relationship) {
const { name, type, propertyName, property } = relationship[$getProperty]();
const relationshipName = `${name}.${propertyName}`;
this.emit('beforeAddRelationship', {

@@ -433,7 +496,8 @@ relationship: { name, type },

this.#key.name,
...this.#properties.keys(),
...this.#methods.keys(),
].includes(methodName)
].includes(propertyName)
)
throw new NameError(
`Relationship method ${methodName} is already in use.`
`Relationship property ${propertyName} is already in use.`
);

@@ -443,3 +507,3 @@ if (this.#relationships.has(relationshipName))

this.#methods.set(methodName, method);
this.#properties.set(propertyName, property);
this.#relationships.set(relationshipName, relationship);

@@ -541,3 +605,3 @@

static #validateMethod(
static #validateFunction(
callbackType,

@@ -544,0 +608,0 @@ callbackName,

@@ -12,2 +12,3 @@ import Record from './record';

$keyType,
$properties,
$methods,

@@ -92,3 +93,5 @@ $relationships,

return this.#getFieldValue(record, property);
// Method, get and call, this also matches relationship reverses (methods)
// Property, get and call, this also matches relationship reverses (properties)
if (this.#hasProperty(property)) return this.#getProperty(record, property);
// Method, get and call
if (this.#hasMethod(property)) return this.#getMethod(record, property);

@@ -112,4 +115,9 @@ // Serialize method, call and return

const otherRecords = this.#model.records.except(recordKey);
// Throw an error when trying to set a method, also catches
// Throw an error when trying to set a property, also catches
// relationship reverses, safeguarding against issues there.
if (this.#hasProperty(property))
throw new TypeError(
`${this.#getModelName()} record ${recordKey} cannot set property ${property}.`
);
// Throw an error when trying to set a method.
if (this.#hasMethod(property))

@@ -174,3 +182,3 @@ throw new TypeError(

const fields = model[$fields];
const methods = model[$methods];
const properties = model[$properties];
const key = model[$key].name;

@@ -212,3 +220,3 @@ const object = {

}
} else if (methods.has(includedField)) {
} else if (properties.has(includedField)) {
object[includedField] = handler.get(record, includedField);

@@ -277,10 +285,19 @@ }

#hasMethod(property) {
return this.#model[$methods].has(property);
#hasProperty(property) {
return this.#model[$properties].has(property);
}
#getMethod(record, property) {
return this.#model[$methods].get(property)(record[$wrappedRecordValue]);
#getProperty(record, property) {
return this.#model[$properties].get(property)(record[$wrappedRecordValue]);
}
#hasMethod(method) {
return this.#model[$methods].has(method);
}
#getMethod(record, method) {
const methodFn = this.#model[$methods].get(method);
return (...args) => methodFn(record[$wrappedRecordValue], ...args);
}
#hasRelationshipField(property) {

@@ -290,3 +307,3 @@ // A relationship field exists if a field with the same name exists and

// relationships being stored as a `.`-delimited tuple of the relationship
// name and the field/method name. In the case of the field name, it's the
// name and the field/property name. In the case of the field name, it's the
// same as the actual relationship name.

@@ -293,0 +310,0 @@ if (!this.#hasField(property)) return false;

@@ -34,4 +34,4 @@ import symbols from 'src/symbols';

// This is used to get the record wrapped in the handler proxy. It's useful
// for method calls in records, so that they can access relationships and
// other methods via the handler proxy.
// for property calls in records, so that they can access relationships and
// other properties via the handler proxy.
/* istanbul ignore next */

@@ -38,0 +38,0 @@ get [$wrappedRecordValue]() {

@@ -568,3 +568,3 @@ import { allEqualBy } from 'src/utils';

[$addScope](name, scope) {
RecordSet.#validateMethod('Scope', name, scope, this.#scopes);
RecordSet.#validateProperty('Scope', name, scope, this.#scopes);
if (

@@ -604,3 +604,3 @@ this[name] ||

static #validateMethod(callbackType, callbackName, callback, callbacks) {
static #validateProperty(callbackType, callbackName, callback, callbacks) {
if (typeof callback !== 'function')

@@ -607,0 +607,0 @@ throw new TypeError(`${callbackType} ${callbackName} is not a function.`);

@@ -14,3 +14,3 @@ import { Field } from 'src/field';

$getField,
$getMethod,
$getProperty,
$get,

@@ -36,3 +36,3 @@ $defaultValue,

#relationshipField; // relationship field in the from model
#relationshipMethod; // relationship method in the to model
#relationshipProperty; // relationship property in the to model

@@ -69,3 +69,3 @@ // TODO: V2 enhancements

this.#relationshipMethod = record => {
this.#relationshipProperty = record => {
return this.#getAssociatedRecordsReverse(record);

@@ -86,8 +86,8 @@ };

[$getMethod]() {
[$getProperty]() {
return {
name: this.#name,
type: this.#type,
methodName: this.#reverseName,
method: this.#relationshipMethod,
propertyName: this.#reverseName,
property: this.#relationshipProperty,
};

@@ -94,0 +94,0 @@ }

@@ -14,3 +14,3 @@ import EventEmitter from 'events';

$addRelationshipAsField,
$addRelationshipAsMethod,
$addRelationshipAsProperty,
$handleExperimentalAPIMessage,

@@ -43,3 +43,3 @@ $key,

constructor({ name, models = [], config = {} } = {}) {
constructor({ name, models = [], relationships = [], config = {} } = {}) {
super();

@@ -52,2 +52,5 @@ this.#name = validateName('Schema', name);

models.forEach(model => this.createModel(model));
relationships.forEach(relationship =>
this.createRelationship(relationship)
);
}

@@ -250,3 +253,3 @@

fromModel[$addRelationshipAsField](relationship);
toModel[$addRelationshipAsMethod](relationship);
toModel[$addRelationshipAsProperty](relationship);

@@ -253,0 +256,0 @@ return relationship;

@@ -16,2 +16,3 @@ /**

'keyType',
'properties',
'methods',

@@ -30,5 +31,5 @@ 'scopes',

'addRelationshipAsField',
'addRelationshipAsMethod',
'addRelationshipAsProperty',
'getField',
'getMethod',
'getProperty',
'removeScope',

@@ -35,0 +36,0 @@ 'copyScopes',

@@ -8,2 +8,4 @@ import { DuplicationError, NameError } from 'src/errors';

Field: ['toString', 'toObject', 'toJSON'],
Property: ['toString', 'toObject', 'toJSON'],
Method: ['toString', 'toObject', 'toJSON'],
Relationship: ['toString', 'toObject', 'toJSON'],

@@ -10,0 +12,0 @@ };

@@ -5,4 +5,12 @@ import { Model } from 'src/model';

const { $instances, $key, $keyType, $fields, $methods, $scopes, $validators } =
symbols;
const {
$instances,
$key,
$keyType,
$fields,
$properties,
$methods,
$scopes,
$validators,
} = symbols;

@@ -73,15 +81,15 @@ describe('Model', () => {

it('throws if "methods" contain invalid values', () => {
it('throws if "properties" contain invalid values', () => {
const modelParams = { name: 'aModel', key: 'id' };
expect(() => new Model({ ...modelParams, methods: null })).toThrow();
expect(() => new Model({ ...modelParams, methods: [2] })).toThrow();
expect(() => new Model({ ...modelParams, properties: null })).toThrow();
expect(() => new Model({ ...modelParams, properties: [2] })).toThrow();
expect(
() => new Model({ ...modelParams, methods: { aMethod: 'hi' } })
() => new Model({ ...modelParams, properties: { aProperty: 'hi' } })
).toThrow();
expect(
() => new Model({ ...modelParams, methods: { id: () => null } })
() => new Model({ ...modelParams, properties: { id: () => null } })
).toThrow();
expect(
() => new Model({ ...modelParams, methods: { '2d': () => null } })
() => new Model({ ...modelParams, properties: { '2d': () => null } })
).toThrow();

@@ -155,11 +163,11 @@ });

it('has the correct methods', () => {
const methods = {
aMethod: () => null,
bMethod: () => null,
it('has the correct properties', () => {
const properties = {
aProperty: () => null,
bProperty: () => null,
};
const model = new Model({ name: 'aModel', methods });
expect(model[$methods].has('aMethod')).toEqual(true);
expect(model[$methods].has('bMethod')).toEqual(true);
const model = new Model({ name: 'aModel', properties });
expect(model[$properties].has('aProperty')).toEqual(true);
expect(model[$properties].has('bProperty')).toEqual(true);
});

@@ -299,2 +307,48 @@

describe('addProperty', () => {
let model;
beforeEach(() => {
model = new Model({ name: 'aModel', key: 'id' });
});
it('throws if "name" is invalid', () => {
expect(() => model.addProperty(null)).toThrow();
expect(() => model.addProperty(2)).toThrow();
expect(() => model.addProperty('2f')).toThrow();
expect(() => model.addProperty('id')).toThrow();
});
it('throws if "property" is not a function', () => {
expect(() => model.addProperty('aProperty', null)).toThrow();
expect(() => model.addProperty('aProperty', 2)).toThrow();
});
it('creates the appropriate property', () => {
model.addProperty('aProperty', () => null);
expect(model[$properties].has('aProperty')).toEqual(true);
});
});
describe('removeProperty', () => {
let model;
beforeEach(() => {
model = new Model({
name: 'aModel',
key: 'id',
properties: { aProperty: () => null },
});
});
it('returns false if "propertyName" does not exist', () => {
expect(model.removeProperty('bProperty')).toEqual(false);
});
it('removes the appropriate field and returns true', () => {
expect(model.removeProperty('aProperty')).toEqual(true);
expect(model[$properties].has('aProperty')).toEqual(false);
});
});
describe('addMethod', () => {

@@ -315,7 +369,7 @@ let model;

it('throws if "method" is not a function', () => {
expect(() => model.addMethod('aMethod', null)).toThrow();
expect(() => model.addMethod('aMethod', 2)).toThrow();
expect(() => model.addMethod('aProperty', null)).toThrow();
expect(() => model.addMethod('aProperty', 2)).toThrow();
});
it('creates the appropriate method', () => {
it('creates the appropriate property', () => {
model.addMethod('aMethod', () => null);

@@ -337,3 +391,3 @@ expect(model[$methods].has('aMethod')).toEqual(true);

it('returns false if "methodName" does not exist', () => {
it('returns false if "propertyName" does not exist', () => {
expect(model.removeMethod('bMethod')).toEqual(false);

@@ -458,5 +512,8 @@ });

},
methods: {
properties: {
nameAndId: record => `${record.name}_${record.id}`,
},
methods: {
nameWithSuffix: (record, suffix) => `${record.name}_${suffix}`,
},
validators: {

@@ -533,6 +590,10 @@ nameNotEqualToId: record => record.id !== record.name,

it('the record has the correct methods', () => {
it('the record has the correct properties', () => {
expect(record.nameAndId).toEqual('aName_a');
});
it('the record has the correct methods', () => {
expect(record.nameWithSuffix('suffix')).toEqual('aName_suffix');
});
it('matches the record to the correct scopes', () => {

@@ -683,18 +744,18 @@ expect(model.records.adult.has('a')).toEqual(true);

it('when a method is added', () => {
it('when a property is added', () => {
const spy = jest.fn();
model.on('change', spy);
model.addMethod('nameAndId', () => 'aName_a');
model.addProperty('nameAndId', () => 'aName_a');
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({ type: 'methodAdded' })
expect.objectContaining({ type: 'propertyAdded' })
);
});
it('when a method is removed', () => {
it('when a property is removed', () => {
const spy = jest.fn();
model.addMethod('nameAndId', () => 'aName_a');
model.addProperty('nameAndId', () => 'aName_a');
model.on('change', spy);
model.removeMethod('nameAndId');
model.removeProperty('nameAndId');
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({ type: 'methodRemoved' })
expect.objectContaining({ type: 'propertyRemoved' })
);

@@ -701,0 +762,0 @@ });

@@ -35,5 +35,8 @@ import { Model } from 'src/model';

],
methods: {
properties: {
firstName: rec => rec.name.split(' ')[0],
},
methods: {
prefixedName: (rec, prefix) => `${prefix} ${rec.name}`,
},
validators: {

@@ -68,2 +71,3 @@ nameNotSameAsId: rec => rec.name !== rec.id,

expect(() => (record.name = 'jd')).toThrow();
expect(() => (record.prefixedName = 'jd')).toThrow();
});

@@ -70,0 +74,0 @@

@@ -33,3 +33,3 @@ import { Model } from 'src/model';

],
methods: {
properties: {
firstName: rec => rec.name.split(' ')[0],

@@ -36,0 +36,0 @@ lastName: rec => rec.name.split(' ')[1],

@@ -9,5 +9,5 @@ import { Schema } from 'src/schema';

$getField,
$getMethod,
$getProperty,
$addRelationshipAsField,
$addRelationshipAsMethod,
$addRelationshipAsProperty,
} = symbols;

@@ -154,3 +154,3 @@

models.modelAlpha[$addRelationshipAsField](relationships.oneToOne);
models.modelBeta[$addRelationshipAsMethod](relationships.oneToOne);
models.modelBeta[$addRelationshipAsProperty](relationships.oneToOne);

@@ -163,3 +163,3 @@ relationships.oneToMany = new Relationship({

models.modelBeta[$addRelationshipAsField](relationships.oneToMany);
models.modelGamma[$addRelationshipAsMethod](relationships.oneToMany);
models.modelGamma[$addRelationshipAsProperty](relationships.oneToMany);

@@ -172,3 +172,3 @@ relationships.manyToOne = new Relationship({

models.modelGamma[$addRelationshipAsField](relationships.manyToOne);
models.modelDelta[$addRelationshipAsMethod](relationships.manyToOne);
models.modelDelta[$addRelationshipAsProperty](relationships.manyToOne);

@@ -181,3 +181,3 @@ relationships.manyToMany = new Relationship({

models.modelDelta[$addRelationshipAsField](relationships.manyToMany);
models.modelAlpha[$addRelationshipAsMethod](relationships.manyToMany);
models.modelAlpha[$addRelationshipAsProperty](relationships.manyToMany);

@@ -190,3 +190,3 @@ relationships.oneToOneNamed = new Relationship({

models.modelAlpha[$addRelationshipAsField](relationships.oneToOneNamed);
models.modelBeta[$addRelationshipAsMethod](relationships.oneToOneNamed);
models.modelBeta[$addRelationshipAsProperty](relationships.oneToOneNamed);

@@ -199,3 +199,5 @@ relationships.oneToManyNamed = new Relationship({

models.modelBeta[$addRelationshipAsField](relationships.oneToManyNamed);
models.modelGamma[$addRelationshipAsMethod](relationships.oneToManyNamed);
models.modelGamma[$addRelationshipAsProperty](
relationships.oneToManyNamed
);

@@ -208,3 +210,5 @@ relationships.manyToOneNamed = new Relationship({

models.modelGamma[$addRelationshipAsField](relationships.manyToOneNamed);
models.modelDelta[$addRelationshipAsMethod](relationships.manyToOneNamed);
models.modelDelta[$addRelationshipAsProperty](
relationships.manyToOneNamed
);

@@ -217,3 +221,3 @@ relationships.manyToManyNamed = new Relationship({

models.modelDelta[$addRelationshipAsField](relationships.manyToManyNamed);
models.modelAlpha[$addRelationshipAsMethod](
models.modelAlpha[$addRelationshipAsProperty](
relationships.manyToManyNamed

@@ -230,3 +234,3 @@ );

);
models.modelAlpha[$addRelationshipAsMethod](
models.modelAlpha[$addRelationshipAsProperty](
relationships.sameModelOneToOne

@@ -243,3 +247,3 @@ );

);
models.modelAlpha[$addRelationshipAsMethod](
models.modelAlpha[$addRelationshipAsProperty](
relationships.sameModelManyToMany

@@ -249,3 +253,3 @@ );

// Indirectly checking correct arguments via methods
// Indirectly checking correct arguments via properties
describe('$getField', () => {

@@ -326,24 +330,26 @@ it('returns the correct relationship name', () => {

describe('$getMethod', () => {
describe('$getProperty', () => {
it('returns the correct relationship name', () => {
expect(relationships.oneToOne[$getMethod]().name).toBe('modelBeta');
expect(relationships.oneToMany[$getMethod]().name).toBe(
expect(relationships.oneToOne[$getProperty]().name).toBe('modelBeta');
expect(relationships.oneToMany[$getProperty]().name).toBe(
'modelGammaSet'
);
expect(relationships.manyToOne[$getMethod]().name).toBe('modelDelta');
expect(relationships.manyToMany[$getMethod]().name).toBe(
expect(relationships.manyToOne[$getProperty]().name).toBe('modelDelta');
expect(relationships.manyToMany[$getProperty]().name).toBe(
'modelAlphaSet'
);
expect(relationships.oneToOneNamed[$getMethod]().name).toBe('parent');
expect(relationships.oneToManyNamed[$getMethod]().name).toBe(
expect(relationships.oneToOneNamed[$getProperty]().name).toBe('parent');
expect(relationships.oneToManyNamed[$getProperty]().name).toBe(
'children'
);
expect(relationships.manyToOneNamed[$getMethod]().name).toBe('parent2');
expect(relationships.manyToManyNamed[$getMethod]().name).toBe(
expect(relationships.manyToOneNamed[$getProperty]().name).toBe(
'parent2'
);
expect(relationships.manyToManyNamed[$getProperty]().name).toBe(
'friends'
);
expect(relationships.sameModelOneToOne[$getMethod]().name).toBe(
expect(relationships.sameModelOneToOne[$getProperty]().name).toBe(
'friend'
);
expect(relationships.sameModelManyToMany[$getMethod]().name).toBe(
expect(relationships.sameModelManyToMany[$getProperty]().name).toBe(
'friends2'

@@ -354,20 +360,24 @@ );

it('returns the correct relationship type', () => {
expect(relationships.oneToOne[$getMethod]().type).toBe('oneToOne');
expect(relationships.oneToMany[$getMethod]().type).toBe('oneToMany');
expect(relationships.manyToOne[$getMethod]().type).toBe('manyToOne');
expect(relationships.manyToMany[$getMethod]().type).toBe('manyToMany');
expect(relationships.oneToOneNamed[$getMethod]().type).toBe('oneToOne');
expect(relationships.oneToManyNamed[$getMethod]().type).toBe(
expect(relationships.oneToOne[$getProperty]().type).toBe('oneToOne');
expect(relationships.oneToMany[$getProperty]().type).toBe('oneToMany');
expect(relationships.manyToOne[$getProperty]().type).toBe('manyToOne');
expect(relationships.manyToMany[$getProperty]().type).toBe(
'manyToMany'
);
expect(relationships.oneToOneNamed[$getProperty]().type).toBe(
'oneToOne'
);
expect(relationships.oneToManyNamed[$getProperty]().type).toBe(
'oneToMany'
);
expect(relationships.manyToOneNamed[$getMethod]().type).toBe(
expect(relationships.manyToOneNamed[$getProperty]().type).toBe(
'manyToOne'
);
expect(relationships.manyToManyNamed[$getMethod]().type).toBe(
expect(relationships.manyToManyNamed[$getProperty]().type).toBe(
'manyToMany'
);
expect(relationships.sameModelOneToOne[$getMethod]().type).toBe(
expect(relationships.sameModelOneToOne[$getProperty]().type).toBe(
'oneToOne'
);
expect(relationships.sameModelManyToMany[$getMethod]().type).toBe(
expect(relationships.sameModelManyToMany[$getProperty]().type).toBe(
'manyToMany'

@@ -377,37 +387,37 @@ );

it('returns the correct relationship method name', () => {
expect(relationships.oneToOne[$getMethod]().methodName).toBe(
it('returns the correct relationship property name', () => {
expect(relationships.oneToOne[$getProperty]().propertyName).toBe(
'modelAlpha'
);
expect(relationships.oneToMany[$getMethod]().methodName).toBe(
expect(relationships.oneToMany[$getProperty]().propertyName).toBe(
'modelBeta'
);
expect(relationships.manyToOne[$getMethod]().methodName).toBe(
expect(relationships.manyToOne[$getProperty]().propertyName).toBe(
'modelGammaSet'
);
expect(relationships.manyToMany[$getMethod]().methodName).toBe(
expect(relationships.manyToMany[$getProperty]().propertyName).toBe(
'modelDeltaSet'
);
expect(relationships.oneToOneNamed[$getMethod]().methodName).toBe(
expect(relationships.oneToOneNamed[$getProperty]().propertyName).toBe(
'child'
);
expect(relationships.oneToManyNamed[$getMethod]().methodName).toBe(
expect(relationships.oneToManyNamed[$getProperty]().propertyName).toBe(
'parent'
);
expect(relationships.manyToOneNamed[$getMethod]().methodName).toBe(
expect(relationships.manyToOneNamed[$getProperty]().propertyName).toBe(
'children2'
);
expect(relationships.manyToManyNamed[$getMethod]().methodName).toBe(
expect(relationships.manyToManyNamed[$getProperty]().propertyName).toBe(
'friends'
);
expect(relationships.sameModelOneToOne[$getMethod]().methodName).toBe(
'colleague'
);
expect(relationships.sameModelManyToMany[$getMethod]().methodName).toBe(
'colleagues'
);
expect(
relationships.sameModelOneToOne[$getProperty]().propertyName
).toBe('colleague');
expect(
relationships.sameModelManyToMany[$getProperty]().propertyName
).toBe('colleagues');
});
});
// We also check the reverse via methods in this test suite.
// We also check the reverse via properties in this test suite.
describe('$get', () => {

@@ -453,3 +463,3 @@ let records = {};

expect(records.a1.friend.id).toBe(records.a2.id);
// Filled method
// Filled property
expect(records.b1.modelAlpha.id).toBe(records.a1.id);

@@ -462,3 +472,3 @@ expect(records.b2.child.id).toBe(records.a1.id);

expect(records.a2.friend).toBe(undefined);
// Empty method
// Empty property
expect(records.b2.modelAlpha).toBe(undefined);

@@ -479,3 +489,3 @@ expect(records.b1.child).toBe(undefined);

]);
// Filled method
// Filled property
expect(records.g1.modelBeta.id).toBe(records.b1.id);

@@ -488,3 +498,3 @@ expect(records.g2.modelBeta.id).toBe(records.b1.id);

expect(records.b2.children.size).toBe(0);
// Empty method
// Empty property
expect(records.g3.modelBeta).toBe(undefined);

@@ -500,3 +510,3 @@ expect(records.g1.parent).toBe(undefined);

expect(records.g3.parent2.id).toBe(records.d2.id);
// Filled method
// Filled property
expect(records.d1.modelGammaSet.flatPluck('id').flat()).toEqual([

@@ -513,3 +523,3 @@ records.g1.id,

expect(records.g2.parent2).toBe(undefined);
// Empty method
// Empty property
expect(records.d2.modelGammaSet.size).toBe(0);

@@ -533,3 +543,3 @@ expect(records.d1.children2.size).toBe(0);

]);
// Filled method
// Filled property
expect(records.a1.modelDeltaSet.flatPluck('id').flat()).toEqual([

@@ -548,3 +558,3 @@ records.d1.id,

expect(records.a2.friends2.size).toBe(0);
// Empty method
// Empty property
expect(records.a3.modelDeltaSet.size).toBe(0);

@@ -551,0 +561,0 @@ expect(records.a1.friends.size).toBe(0);

@@ -5,3 +5,3 @@ import { Schema } from 'src/schema';

const { $instances, $fields, $methods } = symbols;
const { $instances, $fields, $properties } = symbols;

@@ -175,7 +175,7 @@ describe('Schema', () => {

it('creates the correct fields and methods on the given models', () => {
it('creates the correct fields and properties on the given models', () => {
expect(schema.getModel('aModel')[$fields].has('bModel')).toBe(true);
expect(schema.getModel('aModel')[$methods].has('children')).toBe(true);
expect(schema.getModel('aModel')[$properties].has('children')).toBe(true);
expect(schema.getModel('bModel')[$fields].has('parent')).toBe(true);
expect(schema.getModel('bModel')[$methods].has('aModel')).toBe(true);
expect(schema.getModel('bModel')[$properties].has('aModel')).toBe(true);
});

@@ -182,0 +182,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc