- Notifications
You must be signed in to change notification settings - Fork4
Specification design pattern for JavaScript and TypeScript with bonus classes
License
thiagodp/spec-pattern
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Implementation of theSpecification Pattern for JavaScript and TypeScript.
Build complex filters and rules easily.
- No external dependencies;
- Fullytested;
- Semantic versioning;
- Forks are welcome! Seehow to contribute.
$ npm i spec-pattern
import{Between}from'spec-pattern';constrating=newBetween(1,5);console.log(rating.isSatisfiedBy(3));// trueconsole.log(rating.isSatisfiedBy(0));// false
import{Between}from'spec-pattern';constdesiredAgesToAnswerSurvey=newBetween(16,21).or(newBetween(65,120));console.log(desiredAgesToAnswerSurvey.isSatisfiedBy(18));// trueconsole.log(desiredAgesToAnswerSurvey.isSatisfiedBy(70));// trueconsole.log(desiredAgesToAnswerSurvey.isSatisfiedBy(5));// false
import{Between,In,GreaterThan}from'spec-pattern';constsomeCrazyRule=newBetween(1,3).or(newBetween(6,9)).or(newIn([11,25,31])).or(newGreaterThan(50));console.log(someCrazyRule.isSatisfiedBy(2));// trueconsole.log(someCrazyRule.isSatisfiedBy(7));// trueconsole.log(someCrazyRule.isSatisfiedBy(5));// falseconsole.log(someCrazyRule.isSatisfiedBy(11));// trueconsole.log(someCrazyRule.isSatisfiedBy(50));// falseconsole.log(someCrazyRule.isSatisfiedBy(51));// true// Printable !console.log(someCrazyRule.toString());// (((between (1, 3) or between (6, 9)) or in [11, 25, 31]) or greater than 50)
import{StartsWith,Contains}from'spec-pattern';consthelloWithoutWorld=newStartsWith('Hello').andNot(newContains('world'));console.log(helloWithoutWorld.isSatisfiedBy('Hello Bob'));// trueconsole.log(helloWithoutWorld.isSatisfiedBy('Hello world'));// false
import{LengthBetween,EqualTo}from'spec-pattern';constcrazyText=newLengthBetween(2,5).andNot(newEqualTo('Hello'));console.log(crazyText.isSatisfiedBy(''));// falseconsole.log(crazyText.isSatisfiedBy('Hi'));// trueconsole.log(crazyText.isSatisfiedBy('Hello'));// falseconsole.log(crazyText.isSatisfiedBy('Howdy'));// trueconsole.log(crazyText.isSatisfiedBy('Hello world'));// false
import{between}from'spec-pattern';constrating=between(1,5);console.log(rating.isSatisfiedBy(3));// trueconsole.log(rating.isSatisfiedBy(0));// false
import{between}from'spec-pattern';constdesiredAgesToAnswerSurvey=between(16,21).or(between(65,120));console.log(desiredAgesToAnswerSurvey.isSatisfiedBy(18));// trueconsole.log(desiredAgesToAnswerSurvey.isSatisfiedBy(70));// trueconsole.log(desiredAgesToAnswerSurvey.isSatisfiedBy(5));// false
import{between,isIn,greaterThan}from'spec-pattern';constsomeCrazyRule=between(1,3).or(between(6,9)).or(isIn([11,25,31])).or(greaterThan(50));console.log(someCrazyRule.isSatisfiedBy(2));// trueconsole.log(someCrazyRule.isSatisfiedBy(7));// trueconsole.log(someCrazyRule.isSatisfiedBy(5));// falseconsole.log(someCrazyRule.isSatisfiedBy(11));// trueconsole.log(someCrazyRule.isSatisfiedBy(50));// falseconsole.log(someCrazyRule.isSatisfiedBy(51));// true// Printable !console.log(someCrazyRule.toString());// (((between (1, 3) or between (6, 9)) or in [11, 25, 31]) or greater than 50)
import{startsWith,contains}from'spec-pattern';consthelloWithoutWorld=startsWith('Hello').andNot(contains('world'));console.log(helloWithoutWorld.isSatisfiedBy('Hello Bob'));// trueconsole.log(helloWithoutWorld.isSatisfiedBy('Hello world'));// false
import{lengthBetween,equalTo}from'spec-pattern';constcrazyText=lengthBetween(2,5).andNot(equalTo('Hello'));console.log(crazyText.isSatisfiedBy(''));// falseconsole.log(crazyText.isSatisfiedBy('Hi'));// trueconsole.log(crazyText.isSatisfiedBy('Hello'));// falseconsole.log(crazyText.isSatisfiedBy('Howdy'));// trueconsole.log(crazyText.isSatisfiedBy('Hello world'));// false
There is a corresponding sugar function for every available class. Sugar functions are always named incamelCase.For instance,sameValueAs() for the classSameValueAs.The only exception is the classIn. Sincein is a reserved word in JavaScript and thus cannot be a function name, the corresponding sugar isisIn.
SameValueAs( value: any ): equality of values, not of types, not of instancesStrictSameValueAs( value: any ): equality of values and types, not of instancesEqualTo( value: any ): equality of values or instances, with==StrictEqualTo( value: any ): equality of values and types or of instances, with===SameTypeAs( value: any ): equality of typesGreaterThan( value: any )GreaterThanOrEqualTo( value: any )LessThan( value: any )LessThanOrEqualTo( value: any )Between( min: any, max: any )In( values: array ): inside an arrayStartsWith( value: string, ignoreCase: boolean = false ): string starts withEndsWith( value: string, ignoreCase: boolean = false ): string ends withContains( value: string, ignoreCase: boolean = false ): string containsLengthBetween( min: any, max: any ): string length between two valuesEmpty(): string is empty or array is emptyMatches( regex: RegExp ): matches a regular expressionAny( ...specs: Spec ): composite that takes in multipleSpecs and performs an orAll( ...specs: Spec ): composite that takes in multipleSpecs and performs an and
All these classes extend the abstract classComposite, which in turn implements the interfaceSpec:
exportinterfaceSpec<C,TextendsC|unknown>{isSatisfiedBy(candidate:C|T):boolean;and(other:Spec<C,T>):Spec<C,T>;andNot(other:Spec<C,T>):Spec<C,T>;or(other:Spec<C,T>):Spec<C,T>;orNot(other:Spec<C,T>):Spec<C,T>;xor(other:Spec<C,T>):Spec<C,T>;xorNot(other:Spec<C,T>):Spec<C,T>;not():Spec<C,T>;}
Create your own class by extending theabstract classComposite, like in the following example. Of course, you can also extend one of the aforementioned classes or implement the interfaceSpec(but why reinventing the wheel, right?).
Let's create a classDifferentFrom ...
...in TypeScript:
import{Composite}from'spec-pattern';exportclassDifferentFrom<C,TextendsC|unknown>extendsComposite<C,T>{constructor(private_value:T){super();}isSatisfiedBy(candidate:C|T):boolean{returnthis._value!=candidate;}toString():string{return'different from '+this._value;}}
...or in JavaScript 6+:
import{Composite}from'spec-pattern';classDifferentFromextendsComposite{constructor(value){this.value=value;}isSatisfiedBy(candidate){returnthis.value!=candidate;}toString(){return'different from '+this.value;}}
...or in JavaScript 5+:
varComposite=require('spec-pattern').Composite;functionDifferentFrom(value){Composite.call(this);// super()this.value=value;this.isSatisfiedBy=function(candidate){returnthis.value!=candidate;};this.toString=function(){return'different from '+this.value;};}DifferentFrom.prototype=Object.create(Composite.prototype);DifferentFrom.prototype.constructor=DifferentFrom;
That's it! Just three methods:constructor,isSatisfiedBy, andtoString().
About
Specification design pattern for JavaScript and TypeScript with bonus classes
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.