docs: function compileModel( modelName, modelSchema = {}, baseClass = null, adapter = null ) {
  	if ( typeof modelName !== "string" || !ptnKeyword.test( modelName ) ) {
  		throw new TypeError( "invalid model name" );
  	}
  
  	// manage base class for deriving class to be defined from
  	const _baseClass = baseClass == null ? Model : baseClass;
  
  	if ( _baseClass !== Model && !( _baseClass.prototype instanceof Model ) ) {
  		throw new TypeError( "provided base class must be inheriting from AbstractModel" );
  	}
  
  	if ( adapter != null && !( adapter instanceof Adapter ) ) {
  		throw new TypeError( "invalid adapter" );
  	}
  
  
  	const splitSchema = splitSchemaElements( modelName, modelSchema );
  	const { attributes, computeds } = splitSchema;
  
  	validateAttributes( modelName, attributes );
  
  
  	let DefinedModel;
  
  	// eslint-disable-next-line no-eval
  	eval( `DefinedModel = class ${modelName} extends _baseClass { 
  		constructor( ...args ) { 
  			super( ...args ); 
  		}
  	}` );
  
  
  	// implement getter/setter for every defined attribute
  	Object.defineProperties( DefinedModel.prototype, compileGettersAndSetters( attributes, computeds ) );
  
  
  	// customize prototype of defined model
  	Object.defineProperties( DefinedModel.prototype, {
  		validate: {
  			value: function() {
  				const myClass = this.constructor;
  				const { schema, _validateProperties } = myClass;
  
  				const { hooks } = schema;
  				const { onBeforeValidate = [], onAfterValidate = [] } = hooks;
  
  
  				if ( !onBeforeValidate.length && !onAfterValidate.length ) {
  					return new Promise( resolve => resolve( _validateProperties.call( this ) ) );
  				}
  
  
  				let promise;
  
  				if ( onBeforeValidate.length ) {
  					promise = PromiseUtil.each( onBeforeValidate, hook => hook.call( this ) )
  						.then( () => _validateProperties.call( this ) );
  				} else {
  					promise = new Promise( resolve => resolve( _validateProperties.call( this ) ) );
  				}
  
  				if ( onAfterValidate.length ) {
  					promise = promise
  						.then( errors => PromiseUtil.each( onAfterValidate, hook => hook.call( this, errors ) ) );
  				}
  
  
  				return promise;
  			},
  		}
  	} );
  
  
  	// customize static methods and properties
  	Object.defineProperties( DefinedModel, {
  		/**
  		 * @name Model.adapter
  		 * @property {Adapter}
  		 * @readonly
  		 */
  		adapter: { value: adapter },
  
  		/**
  		 * @name Model.name
  		 * @property {string}
  		 * @readonly
  		 */
  		name: { value: modelName },
  
  		/**
  		 * @name Model.schema
  		 * @property {object}
  		 * @readonly
  		 */
  		schema: { value: deepSeal( splitSchema ) },
  
  		/**
  		 * @name Model._coerceProperties
  		 * @property {function}
  		 * @readonly
  		 */
  		_coerceProperties: { value: compileCoercion( attributes ) },
  
  		/**
  		 * @name Model._coercionHandlers
  		 * @property {object<string,function(*,string):*>}
  		 * @readonly
  		 */
  		_coercionHandlers: { value: compileCoercionMap( attributes ) },
  
  		/**
  		 * @name Model._validateProperties
  		 * @property {function():Error[]}
  		 * @readonly
  		 */
  		_validateProperties: { value: compileValidator( attributes ) },
  
  		/**
  		 * @name Model._serializeProperties
  		 * @property {function(object):object}
  		 * @readonly
  		 */
  		_serializeProperties: { value: compileSerializer( attributes ) },
  
  		/**
  		 * @name Model._deserializeProperties
  		 * @property {function(object, object):object}
  		 * @readonly
  		 */
  		_deserializeProperties: { value: compileDeserializer( attributes ) },
  	} );
  
  
  	// inject all static methods of Collection into compiled model
  	Collection.injectInto( DefinedModel );
  
  
  	return DefinedModel;
  }