Last active
June 11, 2019 10:22
-
-
Save CreeJee/4aa302ef7e63ba0b0389ca1b1e141aff to your computer and use it in GitHub Desktop.
FixedType.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const FixedType = (()=>{ | |
"use-strict"; | |
/******************** | |
* basic variables * | |
*********************/ | |
const parentSymbol = Symbol("@@Parent"); | |
const TypeListSymbol = Symbol("@@TypeList"); | |
const TypeTable = class TypeTable extends Map{ | |
constructor(...args){ | |
super(...args); | |
} | |
}; | |
const JUST_VALUE_CONFIG = Object.freeze({ | |
enumerable: false, | |
configurable: false, | |
writable: false | |
}); | |
/** | |
* | |
* @param {*} obj | |
* @description detect NaN,undefined,null | |
*/ | |
const isErrorValue = (obj) => obj === null || obj === undefined || (typeof obj === "number" && isNaN(obj)); | |
/** | |
*obj convert as Function or value | |
* @param {Any} obj | |
* @returns {(Function)} | |
*/ | |
const justConstructor = (obj)=>isErrorValue(obj) ? (()=>{}) : (typeof obj === "function" ? obj : obj.constructor); | |
/** | |
* | |
* @param {*} obj | |
* @description gain Type name | |
*/ | |
const gainTypeName = (obj)=>{ | |
let result = justConstructor(obj); | |
return isErrorValue(obj) ? obj : result.name; | |
}; | |
/** | |
*definePropery suger | |
* | |
* @param {Any} target | |
* @param {Any} prop | |
* @param {Any} value | |
* @returns | |
*/ | |
const justValueProp = (target,prop,value) => { | |
Object.defineProperty(target,prop,Object.assign( | |
{}, | |
JUST_VALUE_CONFIG, | |
{ | |
value : value | |
} | |
)); | |
return target; | |
}; | |
const justValueProps = (target,obj)=>{ | |
Object.keys(obj).concat(Object.getOwnPropertySymbols(obj)).forEach( (k)=>{ | |
obj[k] = Object.assign({},JUST_VALUE_CONFIG,{value : obj[k]}); | |
}); | |
Object.defineProperties(target,obj); | |
return target; | |
}; | |
const expectBinder = (parent,obj)=>{ | |
let child = null; | |
if(parent.has(obj)){ | |
child = parent.get(obj); | |
} | |
else{ | |
parent.set(obj,child = new TypeTable()); | |
justValueProp(child,parentSymbol,parent); | |
} | |
return child; | |
}; | |
const expectHandler = { | |
apply : (target,thisArg,args)=>{ | |
FixedType.Instance.__search(target,...args); | |
return Reflect.apply(target,thisArg,args); | |
}, | |
construct : (target,args,newTarget)=>{ | |
FixedType.Instance.__search(newTarget,...args); | |
return Reflect.construct(target,args,newTarget); | |
} | |
}; | |
const PropertyHandler = { | |
set : (obj,prop,value)=>{ | |
let o = FixedType.Instance.__init(obj,prop); | |
FixedType.Instance.__search(o,value); | |
return Reflect.set(obj,prop,value); | |
} | |
}; | |
/** | |
* fixed type method then expect | |
* @param {Object} caller proxy chain Function caller | |
* @param {ProxyHandler} ProxyHandler proxyHandler custom action | |
* @param {Function} func expext type for each arguments | |
* @param {...Type} args type as Function | |
* @return {Proxy<Function>} return this | |
* | |
* please do not break type instead of create new Type | |
*/ | |
const __expect = (chainedName,proxyHandler,value,...args) =>{ | |
let proxyValue = null; | |
(typeof value[TypeListSymbol] === "object" ? value[TypeListSymbol] : {}) instanceof TypeTable ? proxyValue = value : (justValueProps((proxyValue = new Proxy(value,proxyHandler)),{ | |
[TypeListSymbol] : new TypeTable(), | |
[chainedName] : __expect.bind(proxyValue,chainedName,proxyHandler,proxyValue) | |
})); | |
args.reduce(expectBinder , proxyValue[TypeListSymbol]); | |
return proxyValue; | |
}; | |
/******************** | |
* addons attached * | |
*********************/ | |
const FixedBaseType = class FixedBaseType{ | |
/** | |
* @type {Function} | |
* @readonly | |
*/ | |
action(parent,objType){ | |
return objType === FixedBaseType; | |
} | |
same(type){ | |
return this.TypeClass === type; | |
} | |
constructor(TypeClass){ | |
this.TypeClass = TypeClass; | |
} | |
}; | |
const FixedTypeSpread = class Spread extends FixedBaseType{ | |
action(parent,objType){ | |
return Array.from(parent.keys()).find((typeInstance)=>typeInstance.same(objType)) ? parent : null; | |
} | |
/** | |
*Creates an instance of FixedType.Spread. | |
* @param {Function|T} TypeClass | |
*/ | |
constructor(TypeClass){ | |
super(TypeClass); | |
} | |
}; | |
const FixedTypeOr = class FixedTypeOr extends FixedBaseType{ | |
action(parent,objType){ | |
return this.TypeClass.includes(objType) ? this : null; | |
} | |
constructor(...TypeClass){ | |
super(TypeClass); | |
} | |
}; | |
const FixedTypeAny = class FixedTypeAny extends FixedBaseType{ | |
action(parent,objType){ | |
return true; | |
} | |
constructor(){ | |
super(); | |
} | |
}; | |
/*********************** | |
* FixedType Instance * | |
************************/ | |
let FixedTypeInstance = null; | |
/** | |
*Type Fixed Class | |
* | |
* @class FixedType | |
*/ | |
class __FixedType{ | |
/** | |
*Creates an instance of FixedType. | |
* @memberof FixedType | |
*/ | |
constructor(){ | |
justValueProps(this,{ | |
"__useProps__" : [ | |
FixedTypeSpread, | |
FixedTypeOr | |
], | |
}); | |
return this; | |
} | |
/** | |
* | |
* @description Singletone | |
* @readonly | |
* @static | |
* @memberof FixedType | |
*/ | |
static get Instance(){ | |
return (FixedTypeInstance === null) ? FixedTypeInstance = new FixedType() : FixedTypeInstance; | |
} | |
/** | |
* use MiddleFilter | |
* @param {FixedType.BaseType} {Type,action} | |
* @returns {FixedType} return fixed type | |
* @memberof FixedType | |
*/ | |
use(...baseTypes){ | |
baseTypes.filter((type)=>type instanceof FixedType.BaseType).forEach((baseTypes)=>this.__useProps__.push(baseTypes)); | |
return this; | |
} | |
/** | |
* fixed type method then expect | |
* please do not break type instead of create new Type | |
* @static | |
* @param {Function} func expext type for each arguments | |
* @param {...Type} args type as Function | |
* @return {FixedType} return this | |
* @memberof FixedType | |
* | |
* @example | |
* const foo = FixedType.expect(function(str){return typeof str},String); | |
* foo("") //"string" | |
* foo(1) //throw error | |
* | |
*/ | |
static expect(func,...args){ | |
return __expect("expect",expectHandler,func,...args); | |
} | |
/** | |
*fixed type property setter | |
* | |
* @static | |
* @param {Object} referencedObj | |
* @param {...{Type}} Type | |
* @memberof FixedType | |
* | |
* @example | |
* const foo = FixedType.property({a : "1"}).expect("a",String) | |
* b.a = "3"; //"3" | |
* b.a = 3; //throw error | |
*/ | |
static property(referencedObj,...args){ | |
return __expect("expect",PropertyHandler,referencedObj,...args); | |
} | |
static get BaseType(){ | |
return FixedBaseType; | |
} | |
/** | |
* @param {Type} Type | |
* @example | |
* const foo = FixedType.expect((...v)=>v.map((o)=>typeof o).join(","),FixedType.Spread(String)); | |
* foo("a","b") // "string,string" | |
*/ | |
static Spread(Type){ | |
return new FixedTypeSpread(Type); | |
} | |
/** | |
* @param {...{Type}} Type | |
* @example | |
* const foo = FixedType.expect((o)=>typeof o,FixedType.Spread(String,Number)); | |
* foo("a") // "string" | |
* foo(1) // "number" | |
*/ | |
static Or(...Type){ | |
return new FixedTypeOr(...Type); | |
} | |
/** | |
* @private | |
* @param {TypeTable} parent Derived from TypeListSymbol | |
* @param {Any} obj the constructor for each Arguments | |
* @return {Boolean} is middleWare success | |
*/ | |
__callMiddleWare(parent,obj){ | |
const types = Array.from(parent.keys()); | |
let res = types.filter((metaObj)=>this.__useProps__.includes(justConstructor(metaObj))); | |
return res.length > 0 ? res.reduce((parent,v)=>v.action(parent,obj),parent) : false; | |
} | |
/** | |
* @private | |
* @param {Object|TypeTable} referenced | |
* @param {...Any} args argument object | |
* @return {Boolean} is expect for function call | |
* @throws {TypeError} | |
*/ | |
__search(referenced,...args){ | |
let middlewareResult = null; | |
let errorObj = new TypeError(`not Matching Type [in : ${args.map(gainTypeName).join(",")}]`); | |
args.reduce( | |
(function(parent,obj){ | |
let TypeClass = justConstructor(obj); | |
if( | |
parent.has(TypeClass) || | |
!!this.__searchExtendTree(parent,TypeClass) || | |
(typeof TypeClass[Symbol.hasInstance] === "function" ? !!Array.from(parent.keys()).find(TypeClass[Symbol.hasInstance]) : false) || | |
(middlewareResult = this.__callMiddleWare(parent,TypeClass)) === true | |
) | |
{ | |
return middlewareResult ? middlewareResult : parent.get(TypeClass); | |
} | |
throw errorObj; | |
}).bind(this), | |
(referenced instanceof TypeTable ? referenced : referenced[TypeListSymbol]) | |
); | |
} | |
/** | |
* | |
* @param {Object} referenced | |
* @param {String} prop | |
* @returns {TypeTable<any,any>} props | |
* @throws {Error} when `referenced` is not contains TypeList TypeTable | |
* | |
* @todo AnyType으로 변수타입을 강제추가해서 side effect를 일으키는게 옳은것인지 흠... | |
*/ | |
__init(referenced,prop,orInitValue = new FixedTypeAny()){ | |
let refObject = referenced[TypeListSymbol]; | |
if(refObject instanceof TypeTable){ | |
return refObject.has(prop) ? refObject.get(prop) : (refObject.set(orInitValue) && orInitValue); | |
} | |
else{ | |
throw new Error("first argumetnt is not contained [@@TypeListSymbol]"); | |
} | |
} | |
/** | |
* get closest vaild parent | |
* @param {TypeTable} parent [from [TypeListSymbol]] | |
* @param {Function} classLike [function Class,es6 class,etc...] | |
* @return {Function} [that extended class] | |
*/ | |
__searchExtendTree(parent,classLike){ | |
if(parent instanceof TypeTable && classLike instanceof Function){ | |
return ( | |
( | |
(classLike = Object.getPrototypeOf(classLike)) && | |
classLike !== Object && | |
classLike.name | |
) ? | |
(parent.has(classLike) ? | |
classLike | |
: | |
this.__searchExtendTree(parent,classLike)) | |
: | |
false | |
); | |
} | |
throw new Error(`need arguments [TypeTable,ClassLike]`); | |
} | |
/** | |
*use clear already binding middleware Elements | |
* | |
* @memberof FixedType | |
*/ | |
clear(){ | |
this.__useProps__.splice(0); | |
} | |
} | |
return __FixedType; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment