Skip to content

Instantly share code, notes, and snippets.

@CreeJee
Last active June 11, 2019 10:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CreeJee/4aa302ef7e63ba0b0389ca1b1e141aff to your computer and use it in GitHub Desktop.
Save CreeJee/4aa302ef7e63ba0b0389ca1b1e141aff to your computer and use it in GitHub Desktop.
FixedType.js
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