UNPKG

spec-pattern-ts

Version:
8 lines (7 loc) 8.36 kB
{ "version": 3, "sources": ["../src/specification-v3.ts"], "sourcesContent": ["/**\n * spec-pattern-ts v3.0.0\n * \uD1B5\uD569\uB41C \uC2A4\uD399 \uC778\uD130\uD398\uC774\uC2A4\uB85C \uC644\uBCBD\uD55C \uC870\uD569 \uAC00\uB2A5\n */\n\n/**\n * \uD1B5\uD569 \uC2A4\uD399 \uC778\uD130\uD398\uC774\uC2A4\n * \uBAA8\uB4E0 \uC2A4\uD399\uC740 \uCEE8\uD14D\uC2A4\uD2B8 \uAC1D\uCCB4\uB97C \uB2E4\uB8F9\uB2C8\uB2E4\n * @template TContext - \uC2A4\uD399\uC774 \uAC80\uC99D\uD558\uB294 \uCEE8\uD14D\uC2A4\uD2B8 \uD0C0\uC785\n */\nexport interface ISpecification<TContext extends Record<string, any>> {\n isSatisfiedBy(candidate: TContext): boolean\n /**\n * isSatisfiedBy\uC758 \uAC04\uD3B8\uD55C alias\n * @alias isSatisfiedBy\n * @param candidate - \uAC80\uC99D\uD560 \uCEE8\uD14D\uC2A4\uD2B8 \uAC1D\uCCB4\n * @returns \uC2A4\uD399 \uB9CC\uC871 \uC5EC\uBD80\n * @example\n * ```typescript\n * const isValid = spec.is(data); // spec.isSatisfiedBy(data)\uC640 \uB3D9\uC77C\n * ```\n */\n is(candidate: TContext): boolean\n and<TOther extends Record<string, any>>(\n other: ISpecification<TOther>\n ): ISpecification<TContext & TOther>\n or<TOther extends Record<string, any>>(\n other: ISpecification<TOther>\n ): ISpecification<TContext | TOther>\n not(): ISpecification<TContext>\n}\n\n/**\n * \uAE30\uBCF8 \uC2A4\uD399 \uAD6C\uD604\n */\nabstract class BaseSpecification<TContext extends Record<string, any>> \n implements ISpecification<TContext> {\n \n abstract isSatisfiedBy(candidate: TContext): boolean\n\n /**\n * isSatisfiedBy\uC758 \uAC04\uD3B8\uD55C alias\n * @alias isSatisfiedBy\n */\n is(candidate: TContext): boolean {\n return this.isSatisfiedBy(candidate)\n }\n\n and<TOther extends Record<string, any>>(\n other: ISpecification<TOther>\n ): ISpecification<TContext & TOther> {\n return new AndSpecification(this, other)\n }\n\n or<TOther extends Record<string, any>>(\n other: ISpecification<TOther>\n ): ISpecification<TContext | TOther> {\n return new OrSpecification(this, other)\n }\n\n not(): ISpecification<TContext> {\n return new NotSpecification(this)\n }\n}\n\n/**\n * \uB2E8\uC77C \uD0A4 \uC2A4\uD399 - \uD558\uB098\uC758 \uD504\uB85C\uD37C\uD2F0\uB9CC \uAC80\uC99D\n */\nclass SingleKeySpecification<K extends string, T> \n extends BaseSpecification<{ [P in K]: T }> {\n \n constructor(\n private key: K,\n private predicate: (value: T) => boolean\n ) {\n super()\n }\n\n isSatisfiedBy(candidate: { [P in K]: T }): boolean {\n return this.key in candidate && this.predicate(candidate[this.key])\n }\n}\n\n/**\n * AND \uBCF5\uD569 \uC2A4\uD399\n */\nclass AndSpecification<TLeft extends Record<string, any>, TRight extends Record<string, any>> \n extends BaseSpecification<TLeft & TRight> {\n \n constructor(\n private left: ISpecification<TLeft>,\n private right: ISpecification<TRight>\n ) {\n super()\n }\n\n isSatisfiedBy(candidate: TLeft & TRight): boolean {\n // \uD0A4 \uC911\uBCF5 \uCC98\uB9AC: \uAC19\uC740 \uD0A4\uAC00 \uC788\uC73C\uBA74 \uB458 \uB2E4 \uB9CC\uC871\uD574\uC57C \uD568\n const leftResult = this.left.isSatisfiedBy(candidate as TLeft)\n const rightResult = this.right.isSatisfiedBy(candidate as TRight)\n return leftResult && rightResult\n }\n}\n\n/**\n * OR \uBCF5\uD569 \uC2A4\uD399\n */\nclass OrSpecification<TLeft extends Record<string, any>, TRight extends Record<string, any>> \n extends BaseSpecification<TLeft | TRight> {\n \n constructor(\n private left: ISpecification<TLeft>,\n private right: ISpecification<TRight>\n ) {\n super()\n }\n\n isSatisfiedBy(candidate: TLeft | TRight): boolean {\n try {\n if (this.left.isSatisfiedBy(candidate as TLeft)) return true\n } catch {}\n \n try {\n if (this.right.isSatisfiedBy(candidate as TRight)) return true\n } catch {}\n \n return false\n }\n}\n\n/**\n * NOT \uC2A4\uD399\n */\nclass NotSpecification<TContext extends Record<string, any>> \n extends BaseSpecification<TContext> {\n \n constructor(\n private spec: ISpecification<TContext>\n ) {\n super()\n }\n\n isSatisfiedBy(candidate: TContext): boolean {\n return !this.spec.isSatisfiedBy(candidate)\n }\n}\n\n/**\n * \uC2A4\uD399 \uC0DD\uC131 \uD568\uC218\n */\nexport function Spec<T, K extends string>(\n key: K,\n predicate: (value: T) => boolean\n): ISpecification<{ [P in K]: T }> {\n return new SingleKeySpecification(key, predicate)\n}\n\n/**\n * \uC5EC\uB7EC \uC870\uAC74\uC744 \uD55C\uBC88\uC5D0 \uCCB4\uD06C\uD558\uB294 \uBCF5\uD569 \uC2A4\uD399\n */\nexport function CompositeSpec<TContext extends Record<string, any>>(\n predicate: (context: TContext) => boolean\n): ISpecification<TContext> {\n return new PredicateSpecification(predicate)\n}\n\n/**\n * \uC77C\uBC18 \uD568\uC218 \uAE30\uBC18 \uC2A4\uD399\n */\nclass PredicateSpecification<TContext extends Record<string, any>> \n extends BaseSpecification<TContext> {\n \n constructor(\n private predicate: (context: TContext) => boolean\n ) {\n super()\n }\n\n isSatisfiedBy(candidate: TContext): boolean {\n return this.predicate(candidate)\n }\n}\n\n/**\n * \uC2A4\uD399 \uBE4C\uB354\n */\nexport class SpecBuilder<T> {\n constructor(private predicate: (value: T) => boolean) {}\n\n as<K extends string>(key: K): ISpecification<{ [P in K]: T }> {\n return Spec(key, this.predicate)\n }\n}\n\n/**\n * \uC2A4\uD399 \uC815\uC758 \uD5EC\uD37C\n */\nexport function define<T>(predicate: (value: T) => boolean): SpecBuilder<T> {\n return new SpecBuilder(predicate)\n}\n\n/**\n * \uD3B8\uC758 \uD568\uC218\uB4E4\n */\n\n// \uBAA8\uB4E0 \uC2A4\uD399\uC774 \uB9CC\uC871\uB418\uC5B4\uC57C \uD568\nexport function allOf<TContext extends Record<string, any>>(\n ...specs: ISpecification<any>[]\n): ISpecification<TContext> {\n return specs.reduce((acc, spec) => acc.and(spec)) as ISpecification<TContext>\n}\n\n// \uD558\uB098 \uC774\uC0C1\uC758 \uC2A4\uD399\uC774 \uB9CC\uC871\uB418\uC5B4\uC57C \uD568\nexport function anyOf<TContext extends Record<string, any>>(\n ...specs: ISpecification<any>[]\n): ISpecification<TContext> {\n return specs.reduce((acc, spec) => acc.or(spec)) as ISpecification<TContext>\n}\n\n// \uC2A4\uD399\uC774 \uB9CC\uC871\uB418\uC9C0 \uC54A\uC544\uC57C \uD568\nexport function not<TContext extends Record<string, any>>(\n spec: ISpecification<TContext>\n): ISpecification<TContext> {\n return spec.not()\n}"], "mappings": ";;;;AAmCA,IAAe,oBAAf,MACsC;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpC,GAAG,WAA8B;AAC/B,WAAO,KAAK,cAAc,SAAS;AAAA,EACrC;AAAA,EAEA,IACE,OACmC;AACnC,WAAO,IAAI,iBAAiB,MAAM,KAAK;AAAA,EACzC;AAAA,EAEA,GACE,OACmC;AACnC,WAAO,IAAI,gBAAgB,MAAM,KAAK;AAAA,EACxC;AAAA,EAEA,MAAgC;AAC9B,WAAO,IAAI,iBAAiB,IAAI;AAAA,EAClC;AACF;AA5Be;AAiCf,IAAM,yBAAN,cACU,kBAAmC;AAAA,EAE3C,YACU,KACA,WACR;AACA,UAAM;AAHE;AACA;AAAA,EAGV;AAAA,EAEA,cAAc,WAAqC;AACjD,WAAO,KAAK,OAAO,aAAa,KAAK,UAAU,UAAU,KAAK,GAAG,CAAC;AAAA,EACpE;AACF;AAbM;AAkBN,IAAM,mBAAN,cACU,kBAAkC;AAAA,EAE1C,YACU,MACA,OACR;AACA,UAAM;AAHE;AACA;AAAA,EAGV;AAAA,EAEA,cAAc,WAAoC;AAEhD,UAAM,aAAa,KAAK,KAAK,cAAc,SAAkB;AAC7D,UAAM,cAAc,KAAK,MAAM,cAAc,SAAmB;AAChE,WAAO,cAAc;AAAA,EACvB;AACF;AAhBM;AAqBN,IAAM,kBAAN,cACU,kBAAkC;AAAA,EAE1C,YACU,MACA,OACR;AACA,UAAM;AAHE;AACA;AAAA,EAGV;AAAA,EAEA,cAAc,WAAoC;AAChD,QAAI;AACF,UAAI,KAAK,KAAK,cAAc,SAAkB;AAAG,eAAO;AAAA,IAC1D,QAAE;AAAA,IAAO;AAET,QAAI;AACF,UAAI,KAAK,MAAM,cAAc,SAAmB;AAAG,eAAO;AAAA,IAC5D,QAAE;AAAA,IAAO;AAET,WAAO;AAAA,EACT;AACF;AArBM;AA0BN,IAAM,mBAAN,cACU,kBAA4B;AAAA,EAEpC,YACU,MACR;AACA,UAAM;AAFE;AAAA,EAGV;AAAA,EAEA,cAAc,WAA8B;AAC1C,WAAO,CAAC,KAAK,KAAK,cAAc,SAAS;AAAA,EAC3C;AACF;AAZM;AAiBC,SAAS,KACd,KACA,WACiC;AACjC,SAAO,IAAI,uBAAuB,KAAK,SAAS;AAClD;AALgB;AAUT,SAAS,cACd,WAC0B;AAC1B,SAAO,IAAI,uBAAuB,SAAS;AAC7C;AAJgB;AAShB,IAAM,yBAAN,cACU,kBAA4B;AAAA,EAEpC,YACU,WACR;AACA,UAAM;AAFE;AAAA,EAGV;AAAA,EAEA,cAAc,WAA8B;AAC1C,WAAO,KAAK,UAAU,SAAS;AAAA,EACjC;AACF;AAZM;AAiBC,IAAM,cAAN,MAAqB;AAAA,EAC1B,YAAoB,WAAkC;AAAlC;AAAA,EAAmC;AAAA,EAEvD,GAAqB,KAAyC;AAC5D,WAAO,KAAK,KAAK,KAAK,SAAS;AAAA,EACjC;AACF;AANa;AAWN,SAAS,OAAU,WAAkD;AAC1E,SAAO,IAAI,YAAY,SAAS;AAClC;AAFgB;AAST,SAAS,SACX,OACuB;AAC1B,SAAO,MAAM,OAAO,CAAC,KAAK,SAAS,IAAI,IAAI,IAAI,CAAC;AAClD;AAJgB;AAOT,SAAS,SACX,OACuB;AAC1B,SAAO,MAAM,OAAO,CAAC,KAAK,SAAS,IAAI,GAAG,IAAI,CAAC;AACjD;AAJgB;AAOT,SAAS,IACd,MAC0B;AAC1B,SAAO,KAAK,IAAI;AAClB;AAJgB;", "names": [] }