algolia-search-builder
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -8,9 +8,15 @@ (function (global, factory) { | ||
class Operator { | ||
constructor(operator) { | ||
this.operator = operator; | ||
} | ||
} | ||
class LogicalOperator extends Operator { | ||
constructor(values) { | ||
super(); | ||
constructor(values, operator) { | ||
super(operator); | ||
this.values = values; | ||
} | ||
validate() { | ||
this.values.forEach((operator) => operator.validate()); | ||
} | ||
} | ||
@@ -20,4 +26,4 @@ | ||
class NumericOperator extends Operator { | ||
constructor(fieldName, value) { | ||
super(); | ||
constructor(fieldName, value, operator) { | ||
super(operator); | ||
this.fieldName = fieldName; | ||
@@ -27,3 +33,5 @@ this.value = value; | ||
validate() { | ||
return true; | ||
if (typeof this.value !== 'number') { | ||
throw new Error(`The ${this.operator} accepts numeric values only`); | ||
} | ||
} | ||
@@ -34,4 +42,4 @@ } | ||
class RangeOperator extends Operator { | ||
constructor(fieldName, value) { | ||
super(); | ||
constructor(fieldName, value, operator) { | ||
super(operator); | ||
this.fieldName = fieldName; | ||
@@ -41,3 +49,5 @@ this.value = value; | ||
validate() { | ||
return true; | ||
const allAreNumeric = this.value.every((number) => typeof number === 'number'); | ||
if (!allAreNumeric) | ||
throw new Error(`The ${this.operator} accepts only array of numeric values`); | ||
} | ||
@@ -48,2 +58,6 @@ } | ||
class AndOperator extends LogicalOperator { | ||
constructor(values) { | ||
super(values, 'and'); | ||
this.values = values; | ||
} | ||
exec() { | ||
@@ -54,5 +68,2 @@ return this.values | ||
} | ||
validate() { | ||
return true; | ||
} | ||
} | ||
@@ -62,2 +73,7 @@ | ||
class BetweenOperator extends RangeOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, 'between'); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
@@ -69,5 +85,15 @@ return `${this.fieldName}: ${this.value[0]} TO ${this.value[1]}`; | ||
/* eslint-disable class-methods-use-this */ | ||
class EqualOperator extends Operator { | ||
class NumericEqual extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(); | ||
super(fieldName, value, 'eq'); | ||
} | ||
exec() { | ||
return `${this.fieldName} = ${this.value}`; | ||
} | ||
} | ||
/* eslint-disable class-methods-use-this */ | ||
class NonNumericEqual extends Operator { | ||
constructor(fieldName, value) { | ||
super('eq'); | ||
this.fieldName = fieldName; | ||
@@ -77,5 +103,3 @@ this.value = value; | ||
exec() { | ||
// The equal operator "=" is only allowed with number else ":" should be used | ||
const [operator, newValue] = typeof this.value === 'number' ? ['=', this.value] : [':', `"${this.value}"`]; | ||
return `${this.fieldName} ${operator} ${newValue}`; | ||
return `${this.fieldName} : "${this.value}"`; | ||
} | ||
@@ -87,4 +111,15 @@ validate() { | ||
const EqualFactory = (fieldName, value) => { | ||
if (typeof value === 'number') | ||
return new NumericEqual(fieldName, value); | ||
return new NonNumericEqual(fieldName, value); | ||
}; | ||
/* eslint-disable class-methods-use-this */ | ||
class GreaterThanOrEqualOperator extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, '>='); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
@@ -97,2 +132,7 @@ return `${this.fieldName} >= ${this.value}`; | ||
class GreaterThanOperator extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, '>'); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
@@ -105,2 +145,7 @@ return `${this.fieldName} > ${this.value}`; | ||
class LessThanOrEqualOperator extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, '<='); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
@@ -113,2 +158,7 @@ return `${this.fieldName} <= ${this.value}`; | ||
class LessThanOperator extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, '<='); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
@@ -120,3 +170,37 @@ return `${this.fieldName} < ${this.value}`; | ||
/* eslint-disable constructor-super */ | ||
class InOperator extends Operator { | ||
constructor(value) { | ||
super('in'); | ||
this.value = value; | ||
} | ||
exec() { | ||
// In operator resembles and and exactly | ||
const andOperator = new AndOperator(this.value); | ||
return andOperator.exec(); | ||
} | ||
validate() { | ||
return true; | ||
} | ||
} | ||
/* eslint-disable class-methods-use-this */ | ||
class TagOperator extends Operator { | ||
constructor(value) { | ||
super('_tags'); | ||
this.value = value; | ||
} | ||
exec() { | ||
return `_tags : "${this.value}"`; | ||
} | ||
validate() { | ||
return true; | ||
} | ||
} | ||
/* eslint-disable constructor-super */ | ||
class OrOperator extends LogicalOperator { | ||
constructor(values) { | ||
super(values, 'or'); | ||
this.values = values; | ||
} | ||
exec() { | ||
@@ -130,10 +214,43 @@ const orQuery = this.values | ||
validate() { | ||
return true; | ||
// So far algolia doesn't support ands inside of ors | ||
const hasAnd = this.values.some((value) => value instanceof AndOperator); | ||
if (hasAnd) | ||
throw new Error(`The ${this.operator} can not contain an and operator`); | ||
// A list of or-ed commands cannot have mix of tags, facet search and numeric operators | ||
const numericOperationsBucket = [NumericOperator, InOperator, BetweenOperator]; | ||
const facetOperationsBucket = [NonNumericEqual]; | ||
const tagsOperationsBucket = [TagOperator]; | ||
const operationsBuckets = [numericOperationsBucket, facetOperationsBucket, tagsOperationsBucket]; | ||
const operationsBucketSet = new Set(); | ||
this.values.forEach((operator) => { | ||
operationsBuckets.forEach((bucket) => { | ||
bucket.forEach((operatorType) => { | ||
if (operator instanceof operatorType) | ||
operationsBucketSet.add(bucket); | ||
}); | ||
}); | ||
}); | ||
if (operationsBucketSet.size > 1) { | ||
throw new Error(`The ${this.operator} cannot contain a mix of numeric, facet and tags operations`); | ||
} | ||
super.validate(); | ||
} | ||
} | ||
/* eslint-disable class-methods-use-this */ | ||
class NotEqualOperator extends NumericOperator { | ||
constructor(fieldName, value) { | ||
super(fieldName, value, '!='); | ||
this.fieldName = fieldName; | ||
this.value = value; | ||
} | ||
exec() { | ||
return `${this.fieldName} != ${this.value}`; | ||
} | ||
} | ||
/* eslint-disable constructor-super */ | ||
class NotOperator extends Operator { | ||
constructor(values) { | ||
super(); | ||
super('not'); | ||
this.values = values; | ||
@@ -149,22 +266,8 @@ } | ||
validate() { | ||
return true; | ||
const hasLogicalOperator = this.values.some((operator) => operator instanceof LogicalOperator); | ||
if (hasLogicalOperator) | ||
throw new Error(`The ${this.operator} can not contain logical operators (and, or)`); | ||
} | ||
} | ||
/* eslint-disable constructor-super */ | ||
class InOperator extends Operator { | ||
constructor(value) { | ||
super(); | ||
this.value = value; | ||
} | ||
exec() { | ||
// In operator resembles and and exactly | ||
const andOperator = new AndOperator(this.value); | ||
return andOperator.exec(); | ||
} | ||
validate() { | ||
return true; | ||
} | ||
} | ||
class AlgoliaQueryParser { | ||
@@ -206,7 +309,11 @@ constructor(query) { | ||
case 'in': { | ||
const equalOperators = value.map((arrayValue) => new EqualOperator(parentFieldName, arrayValue)); | ||
const equalOperators = value.map((arrayValue) => EqualFactory(parentFieldName, arrayValue)); | ||
return new InOperator(equalOperators); | ||
} | ||
case 'eq': | ||
return new EqualOperator(parentFieldName, value); | ||
return EqualFactory(parentFieldName, value); | ||
case 'ne': | ||
return new NotEqualOperator(parentFieldName, value); | ||
case '_tags': | ||
return new TagOperator(value); | ||
// The key is an attribute name not an operator | ||
@@ -216,6 +323,9 @@ default: { | ||
if (typeof value !== 'object') { | ||
return new EqualOperator(fieldName, value); | ||
return EqualFactory(fieldName, value); | ||
} | ||
const queryKeys = Object.keys(value); | ||
const operations = queryKeys.map((operator) => this.parseQuery(operator, value[operator], fieldName)); | ||
// if The operations size is one no need to wrap it inside and and operator | ||
if (operations.length === 1) | ||
return operations[0]; | ||
return new AndOperator(operations); | ||
@@ -233,2 +343,3 @@ } | ||
const rootOperator = this.buildQueryTree(); | ||
rootOperator.validate(); | ||
return rootOperator.exec(); | ||
@@ -235,0 +346,0 @@ } |
declare abstract class Operator { | ||
protected readonly operator: string; | ||
constructor(operator: string); | ||
abstract exec(): string; | ||
@@ -3,0 +5,0 @@ abstract validate(): void; |
import Operator from './base'; | ||
declare abstract class LogicalOperator extends Operator { | ||
protected values: Operator[]; | ||
constructor(values: Operator[]); | ||
constructor(values: Operator[], operator: string); | ||
validate(): void; | ||
} | ||
export default LogicalOperator; | ||
//# sourceMappingURL=logical-operator.d.ts.map |
@@ -5,6 +5,6 @@ import Operator from './base'; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
validate(): boolean; | ||
constructor(fieldName: string, value: number, operator: string); | ||
validate(): void; | ||
} | ||
export default NumericOperator; | ||
//# sourceMappingURL=numeric-operator.d.ts.map |
@@ -5,6 +5,6 @@ import Operator from './base'; | ||
protected value: [number, number]; | ||
constructor(fieldName: string, value: [number, number]); | ||
validate(): boolean; | ||
constructor(fieldName: string, value: [number, number], operator: string); | ||
validate(): void; | ||
} | ||
export default RangeOperator; | ||
//# sourceMappingURL=range-operator.d.ts.map |
@@ -1,7 +0,8 @@ | ||
import { LogicalOperator } from './abstract'; | ||
import { LogicalOperator, Operator } from './abstract'; | ||
declare class AndOperator extends LogicalOperator { | ||
protected values: Operator[]; | ||
constructor(values: Operator[]); | ||
exec(): string; | ||
validate(): boolean; | ||
} | ||
export default AndOperator; | ||
//# sourceMappingURL=and.d.ts.map |
import { RangeOperator } from './abstract'; | ||
declare class BetweenOperator extends RangeOperator { | ||
protected fieldName: string; | ||
protected value: [number, number]; | ||
constructor(fieldName: string, value: [number, number]); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
import NumericOperator from './abstract/numeric-operator'; | ||
declare class GreaterThanOrEqualOperator extends NumericOperator { | ||
protected fieldName: string; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
import NumericOperator from './abstract/numeric-operator'; | ||
declare class GreaterThanOperator extends NumericOperator { | ||
protected fieldName: string; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
export { default as AndOperator } from './and'; | ||
export { default as BetweenOperator } from './between'; | ||
export { default as EqualOperator } from './equal'; | ||
export { default as NumericEqual } from './numeric-equal'; | ||
export { default as NonNumericEqual } from './no-numeric-equal'; | ||
export { default as EqualFactory } from './equal-factory'; | ||
export { default as GreaterThanOrEqualOperator } from './greater-than-or-equal'; | ||
@@ -5,0 +7,0 @@ export { default as GreaterThanOperator } from './greater-than'; |
import NumericOperator from './abstract/numeric-operator'; | ||
declare class LessThanOrEqualOperator extends NumericOperator { | ||
protected fieldName: string; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
import NumericOperator from './abstract/numeric-operator'; | ||
declare class LessThanOperator extends NumericOperator { | ||
protected fieldName: string; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
import NumericOperator from './abstract/numeric-operator'; | ||
declare class NotEqualOperator extends NumericOperator { | ||
protected fieldName: string; | ||
protected value: number; | ||
constructor(fieldName: string, value: number); | ||
exec(): string; | ||
@@ -4,0 +7,0 @@ } |
@@ -6,5 +6,5 @@ import { Operator } from './abstract'; | ||
exec(): string; | ||
validate(): boolean; | ||
validate(): void; | ||
} | ||
export default NotOperator; | ||
//# sourceMappingURL=not.d.ts.map |
@@ -1,7 +0,9 @@ | ||
import { LogicalOperator } from './abstract'; | ||
import { LogicalOperator, Operator } from './abstract'; | ||
declare class OrOperator extends LogicalOperator { | ||
protected values: Operator[]; | ||
constructor(values: Operator[]); | ||
exec(): string; | ||
validate(): boolean; | ||
validate(): void; | ||
} | ||
export default OrOperator; | ||
//# sourceMappingURL=or.d.ts.map |
export declare type Equal = { | ||
/** | ||
* Used to match **numeric** values with given field | ||
* | ||
* ```json | ||
* { field: { eq: 3 } } | ||
* ``` | ||
* OR | ||
* ```json | ||
* { field: 3 } | ||
* ``` | ||
*/ | ||
'eq': Number; | ||
}; | ||
export declare type NotEqual = { | ||
/** | ||
* Used to find documents withe the provided field **not equal** to the provided **numeric** value | ||
* | ||
* ```json | ||
* { field: { ne: 3 } } | ||
* ``` | ||
*/ | ||
'ne': Number; | ||
}; | ||
export declare type GreaterThan = { | ||
/** | ||
* Used to find documents with the provided field **greater than** the provided **numeric** value | ||
* | ||
* ```json | ||
* { field: { gt: 3 } } | ||
* ``` | ||
*/ | ||
'gt': Number; | ||
}; | ||
export declare type GreaterThanOrEqual = { | ||
/** | ||
* Used to find documents with the provided field **greater than or equal** the provided **numeric** value | ||
* | ||
* ```json | ||
* { field: { gte: 3 } } | ||
* ``` | ||
*/ | ||
'gte': Number; | ||
}; | ||
export declare type SmallerThan = { | ||
/** | ||
* Used to find documents with the provided field **smaller than** the provided **numeric** value | ||
* | ||
* ```json | ||
* { field: { lt: 3 } } | ||
* ``` | ||
*/ | ||
'lt': Number; | ||
}; | ||
export declare type SmallerThanOrEqual = { | ||
/** | ||
* Used to find documents with the provided field **smaller than or equal** the provided **numeric** value | ||
* | ||
* ```json | ||
* { field: { lte: 3 } } | ||
* ``` | ||
*/ | ||
'lte': Number; | ||
}; | ||
export declare type NumericOperation = Equal | NotEqual | GreaterThan | GreaterThanOrEqual | SmallerThan | SmallerThanOrEqual; | ||
export declare type NonNumericEqual = { | ||
/** | ||
* Used to find documents with the provided field **equal** the provided **string** value | ||
* | ||
* ```json | ||
* { field: { eq: "string" } } | ||
* ``` | ||
*/ | ||
'eq': string; | ||
}; | ||
export declare type Between = { | ||
/** | ||
* Used to find documents with the provided field **between** the provided **numeric** pair of values | ||
* | ||
* ```json | ||
* { field: { between: [1,10] } } | ||
* ``` | ||
*/ | ||
'between': [Number, Number]; | ||
}; | ||
export declare type In = { | ||
/** | ||
* Used to find documents with the provided field **in** the provided **numeric** values | ||
* | ||
* ```json | ||
* { field: { in: [1,2,3] } } | ||
* ``` | ||
*/ | ||
'in': (number | string)[]; | ||
}; | ||
export declare type FieldOperations = NumericOperation | Between | In; | ||
export declare type FieldOperations = NumericOperation | Between | In | NonNumericEqual; | ||
export declare type Not = { | ||
/** | ||
* Used to find documents that **don't match** the provided query | ||
* | ||
* ```json | ||
* { field: { not: { eq: 3 } } } | ||
* ``` | ||
* | ||
* ### Notes | ||
* - Cannot negate a disjunction or conjunction of a group of queries: | ||
* ```json | ||
* { field: { not: { or: [{ eq: 1 }, { eq: 2 } ] } } } | ||
* ``` | ||
* | ||
* Check this [link](https://www.algolia.com/doc/api-reference/api-parameters/filters/) | ||
* for more info about algolia filter constraints | ||
*/ | ||
'not': FieldOperations; | ||
@@ -34,7 +120,36 @@ }; | ||
export declare type Or = { | ||
/** | ||
* Used to find documents that **match at least one** of the provided queries | ||
* | ||
* ```json | ||
* { or: [ { field1: 3 }, {field2: 4} ] } | ||
* ``` | ||
* | ||
* ### Notes | ||
* - Cannot mix between facet, numeric and tags filters | ||
* ```json | ||
* { | ||
* or : | ||
* [ | ||
* { field1: 3 }, // numeric query | ||
* { field2: "string"}, // facet query | ||
* { _tags:"tag" }, // tag query | ||
* ] | ||
* } | ||
* ``` | ||
* Check this [link](https://www.algolia.com/doc/api-reference/api-parameters/filters/) | ||
* for more info about algolia filter constraints | ||
*/ | ||
'or': FieldsObjectQuery[]; | ||
}; | ||
export declare type And = { | ||
/** | ||
* Used to find documents that **match all** of the provided queries | ||
* | ||
* ```json | ||
* { and: [ { field1: 3 }, {field2: 4} ] } | ||
* ``` | ||
*/ | ||
'and': (FieldsObjectQuery | Or)[]; | ||
}; | ||
//# sourceMappingURL=operators.d.ts.map |
@@ -0,1 +1,16 @@ | ||
### 1.1.0 (2021-03-28) | ||
##### Documentation Changes | ||
* **readme:** update the README file to reflect the new ne operator and the query validation ([5a012410](https://github.com/khaledosama999/algolia-filter-query-builder/commit/5a0124106c281ef654b53fd9845b3b7c2cd96f32)) | ||
* **types:** update the Types JSDocs so they are easy to use ([0f35a9a7](https://github.com/khaledosama999/algolia-filter-query-builder/commit/0f35a9a777502b67aef25967821ad1322a745654)) | ||
##### New Features | ||
* **operators:** make each operator a standlone class to facilitate query parsing and validation ([64df1237](https://github.com/khaledosama999/algolia-filter-query-builder/commit/64df12373369688791a36e168cd2928c4ea5fdaa)) | ||
##### Tests | ||
* **operator and query parser:** add tests for the new operator classes and the query parser ([550c8d89](https://github.com/khaledosama999/algolia-filter-query-builder/commit/550c8d89114d1bac879499be151559599ed26896)) | ||
## 1.0.0 (2021-03-26) | ||
@@ -2,0 +17,0 @@ |
{ | ||
"name": "algolia-search-builder", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "A query parser for algolia filters", | ||
@@ -5,0 +5,0 @@ "repository": { |
@@ -15,2 +15,3 @@ # Algolia Filters Query Builder | ||
* [eq](#eq) | ||
* [ne](#ne) | ||
* [gt](#gt) | ||
@@ -55,8 +56,14 @@ * [gte](#gte) | ||
```json | ||
{ x : { eq : 3 } } | ||
{ x: { eq: 3 } } | ||
``` | ||
- #### ne | ||
Checks if a field is not equal to the given value | ||
```json | ||
{ x: { ne: 3 } } | ||
``` | ||
- #### gt | ||
Checks if a field is greater than the given value | ||
```json | ||
{ x : { gt : 3 } } | ||
{ x: { gt: 3 } } | ||
``` | ||
@@ -66,3 +73,3 @@ - #### gte | ||
```json | ||
{ x : { gte: 3 } } | ||
{ x: { gt: 3 } } | ||
``` | ||
@@ -72,3 +79,3 @@ - #### lt | ||
```json | ||
{ x : { lt : 3 } } | ||
{ x: { lt: 3 } } | ||
``` | ||
@@ -78,3 +85,3 @@ - #### lte | ||
```json | ||
{ x : { lte: 3 } } | ||
{ x: { lt: 3 } } | ||
``` | ||
@@ -84,8 +91,8 @@ - #### between | ||
```json | ||
{ x : { between : [1,2] } } | ||
{ x: { between: [1,2] } } | ||
``` | ||
- #### in | ||
**** - #### in | ||
Checks if a field is in the array of given values (can contain strings or number) | ||
```json | ||
{ x : { in : [1,2,3] } } | ||
{ x: { in: [1,2,3] } } | ||
``` | ||
@@ -96,3 +103,3 @@ - ### Logical | ||
```json | ||
{ x : { not : { between :[ 1 , 2] } } } | ||
{ x: { not: { between:[ 1 , 2] } } } | ||
``` | ||
@@ -102,6 +109,6 @@ - #### or | ||
```json | ||
{ or : | ||
{ or: | ||
[ | ||
{ x : { eq :1 } }, | ||
{ y : { eq :2 } }, | ||
{ x: { eq:1 } }, | ||
{ y: { eq:2 } }, | ||
] | ||
@@ -113,6 +120,6 @@ } | ||
```json | ||
{ and : | ||
{ and: | ||
[ | ||
{ x : { eq :1 } }, | ||
{ y : { eq :2 } }, | ||
{ x: { eq:1 } }, | ||
{ y: { eq:2 } }, | ||
] | ||
@@ -146,3 +153,3 @@ } | ||
- The validity of the filter query is **your** response the builder only (for now) transforms it to a string. To find out algolia filters constraint check this [link](http://algolia.com/doc/api-reference/api-parameters/filters). | ||
- The query builder validates the query after parsing and before returning the query string the limitations and constraints of the query builder are mostly related to the limitations given by [algolia](https://www.algolia.com/doc/api-reference/api-parameters/filters/#boolean-operators) | ||
@@ -149,0 +156,0 @@ - To use `_tags` for filtering just add the a custom field with name `_tags` and pass it the value |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
59702
53
642
149
0