Skip to content

Commit

Permalink
feat(filtering-condition): allow any types for conditional methods
Browse files Browse the repository at this point in the history
methods like isGreaterThan, isEqualTo and others now can handle any type. In case user provides
dataset generic type, Typescript will strict type of those methods parameters.
  • Loading branch information
KutsenkoA committed Jun 29, 2018
1 parent 2066ff8 commit ee1e01a
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 144 deletions.
20 changes: 16 additions & 4 deletions src/api/dataops/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ import { UpdateQuery } from "./updateQuery";
*/
export type DefaultDatasetFields = "id" | "created_at" | "updated_at";

/**
* Default dataset interface type
*/
export type DefaultDatasetInterface = {
[P in DefaultDatasetFields]: string;
};

/**
* Extend user provided interface (T) with default dataset fields
*/
export type DatasetInterface<T> = T & DefaultDatasetInterface;

/**
* Dataset object used to fetch and modify data at your datasets.
* For TypeScript users it implements a generic type T that represents your dataset, default to any.
Expand Down Expand Up @@ -61,7 +73,7 @@ export class Dataset<T = any> implements IResource {
* @returns Query object specialized for select statements.
* With no filters set, returns all records in the selected dataset.
*/
public select(): SelectQuery<T> {
public select(): SelectQuery<DatasetInterface<T>> {
return new SelectQuery(this.requestExecuter, this.datasetName);
}

Expand All @@ -71,13 +83,13 @@ export class Dataset<T = any> implements IResource {
* @returns Query object specialized for update statements.
* Don't forget to apply a filter to specify the fields that will be modified.
*/
public update(data: T): UpdateQuery<T> {
public update(data: DatasetInterface<T>): UpdateQuery<DatasetInterface<T>> {
return new UpdateQuery(this.requestExecuter, data, this.datasetName);
}

/**
* Creates an Insert query.
* @param data An array of dictionaries that contains the key:value pairs for
* @param records An array of dictionaries that contains the key:value pairs for
* the fields that you want to store at this dataset
* @returns Query object specialized for insert statements
* If saving into a strict schema dataset, you need to provide values for the
Expand All @@ -93,7 +105,7 @@ export class Dataset<T = any> implements IResource {
* You need to specify a filter to narrow down the records that you want deleted
* from the backend.
*/
public delete(): DeleteQuery<T> {
public delete(): DeleteQuery<DatasetInterface<T>> {
return new DeleteQuery(this.requestExecuter, this.datasetName);
}
}
31 changes: 15 additions & 16 deletions src/api/dataops/deleteQuery.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MESSAGE } from "../../config/message";
import { RequestExecuter } from "../../internal/executer";
import { DataRequest } from "./dataRequest";
import { DefaultDatasetFields } from "./dataset";
import { FieldFilter, IFilteringCriterion, IFilteringCriterionCallback } from "./filteringApi";
import { IExecutable, IFields, IFilterable, ILimit, IOffset, ISortable } from "./query.interfaces";

Expand Down Expand Up @@ -38,25 +37,25 @@ export class DeleteQuery<T = any> implements IFields<T>, ILimit, IOffset, IFilte
* Select the fields to be returned at the response that represent the affected data
* @param fields fields names
*/
public fields<K extends keyof T>(fields: Array<K | DefaultDatasetFields>): this;
public fields<K extends keyof T>(...fields: Array<K | DefaultDatasetFields>): this;
public fields<K extends keyof T>(field: K | DefaultDatasetFields, ...fields: Array<K | DefaultDatasetFields>): this {
public fields<K extends keyof T>(fields: K[]): this;
public fields<K extends keyof T>(...fields: K[]): this;
public fields<K extends keyof T>(field: K, ...fields: K[]): this {
this.request.Query.Fields = Array.isArray(field) ? field : [field, ...fields];
return this;
}

/**
* Limit the operation for the first n records
* @param fields fields names
* @param limit number limit records amount
*/
public limit(limit: number): this {
this.request.Query.Limit = limit;
return this;
}

/**
* Limit the operation for the last n records
* @param fields fields names
* Offset selection with the offset records
* @param offset number offset amount
*/
public offset(offset: number): this {
this.request.Query.Offset = offset;
Expand All @@ -67,11 +66,11 @@ export class DeleteQuery<T = any> implements IFields<T>, ILimit, IOffset, IFilte
* Filter the dataset rows with some conditions where the operation will be applied
* @param filter Filtering criteria or a callback that returns a filtering criteria with the conditions
*/
public where<K extends keyof T>(
filter: IFilteringCriterion<T> | IFilteringCriterionCallback<T, K>): this {
public where(
filter: IFilteringCriterion<T> | IFilteringCriterionCallback<T>): this {
this.request.Query.setFilterCriteria(
typeof filter === "function" ?
filter((field) => new FieldFilter<T, K>(field)) :
filter((field) => new FieldFilter(field)) :
filter,
);
return this;
Expand All @@ -81,9 +80,9 @@ export class DeleteQuery<T = any> implements IFields<T>, ILimit, IOffset, IFilte
* Sort ascendent the response that will represent the affected data
* @param fields fields names to sort with
*/
public sortAsc<K extends keyof T>(fields: Array<K | DefaultDatasetFields>): this;
public sortAsc<K extends keyof T>(...fields: Array<K | DefaultDatasetFields>): this;
public sortAsc<K extends keyof T>(field: K | DefaultDatasetFields, ...fields: Array<K | DefaultDatasetFields>): this {
public sortAsc<K extends keyof T>(fields: K[]): this;
public sortAsc<K extends keyof T>(...fields: K[]): this;
public sortAsc<K extends keyof T>(field: K, ...fields: K[]): this {
if (!field || field.length === 0) {
throw new Error(MESSAGE.QUERY.MUST_PROVIDE_SORTING_FIELD);
}
Expand All @@ -95,10 +94,10 @@ export class DeleteQuery<T = any> implements IFields<T>, ILimit, IOffset, IFilte
* Sort decedent the response that will represent the affected data
* @param fields fields names to sort with
*/
public sortDesc<K extends keyof T>(fields: Array<K | DefaultDatasetFields>): this;
public sortDesc<K extends keyof T>(...fields: Array<K | DefaultDatasetFields>): this;
public sortDesc<K extends keyof T>(fields: K[]): this;
public sortDesc<K extends keyof T>(...fields: K[]): this;
public sortDesc<K extends keyof T>(
field: K | DefaultDatasetFields, ...fields: Array<K | DefaultDatasetFields>): this {
field: K, ...fields: K[]): this {
if (!field || field.length === 0) {
throw new Error(MESSAGE.QUERY.MUST_PROVIDE_SORTING_FIELD);
}
Expand Down
93 changes: 93 additions & 0 deletions src/api/dataops/filteringApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,97 @@ describe("FieldFilter class", () => {
});

});

describe("when building a filter with number type value", () => {
describe("greater than operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isGreaterThan(100);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: ">", values: [ 100 ], type: "and" });
});
});
describe("less than operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isLessThan(100);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "<", values: [ 100 ], type: "and" });
});
});
describe("is equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isEqualTo(100);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "=", values: [ 100 ], type: "and" });
});
});
});

describe("when building a filter with boolean type value", () => {
describe("is equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isEqualTo(true);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "=", values: [ true ], type: "and" });
});
});
describe("is not equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isDifferentFrom(false);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "<>", values: [ false ], type: "and" });
});
});
});

describe("when building a filter with Date type value", () => {
let date = new Date();
describe("greater than operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isGreaterThan(date);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: ">", values: [ date ], type: "and" });
});
});
describe("less than operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isLessThan(date);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "<", values: [ date ], type: "and" });
});
});
describe("is equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isEqualTo(date);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "=", values: [ date ], type: "and" });
});
});
describe("is between operator", () => {
it("should compile to the proper JSON", () => {
let date2 = new Date();
let filter = field("name").isBetween(date, date2);
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any).toEqual({ field: "name", operator: "BETWEEN", values: [ date, date2 ], type: "and" });
});
});
});

describe("when building a filter with object type value", () => {
describe("is equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isEqualTo({ name: "Peter Jackson" });
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any)
.toEqual({ field: "name", operator: "=", values: [ { name: "Peter Jackson" } ], type: "and" });
});
});
describe("is not equal to operator", () => {
it("should compile to the proper JSON", () => {
let filter = field("name").isDifferentFrom({ name: "Peter Jackson" });
let jsonResult: object = (filter as any).lowLevelCondition.compile();
expect(jsonResult as any)
.toEqual({ field: "name", operator: "<>", values: [ { name: "Peter Jackson" } ], type: "and" });
});
});
});
});
100 changes: 18 additions & 82 deletions src/api/dataops/filteringApi.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
// tslint:disable:max-classes-per-file
import { DefaultDatasetFields } from "jexia-sdk-js/api/dataops/dataset";
import { DatasetInterface } from "jexia-sdk-js/api/dataops/dataset";
import { CompositeFilteringCondition, FilteringCondition, ICondition } from "./filteringCondition";

/**
* @internal
*/
export class FieldFilter<T, K extends keyof T> implements IFieldFilter {
constructor(private fieldName: K | DefaultDatasetFields) {}
export class FieldFilter<U> {
constructor(private fieldName: string) {}

public isGreaterThan(value: string): IFilteringCriterion {
public isGreaterThan(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, ">", [value]));
}

public isLessThan(value: string): IFilteringCriterion {
public isLessThan(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "<", [value]));
}

public isEqualTo(value: string): IFilteringCriterion {
public isEqualTo(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "=", [value]));
}

public isDifferentFrom(value: string): IFilteringCriterion {
public isDifferentFrom(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "<>", [value]));
}

public isEqualOrGreaterThan(value: string): IFilteringCriterion {
public isEqualOrGreaterThan(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, ">=", [value]));
}

public isEqualOrLessThan(value: string): IFilteringCriterion {
public isEqualOrLessThan(value: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "<=", [value]));
}

Expand All @@ -40,11 +40,11 @@ export class FieldFilter<T, K extends keyof T> implements IFieldFilter {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "IS_NOT_NULL", []));
}

public isInArray(values: string[]): IFilteringCriterion {
public isInArray(values: U[]): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "IN", values));
}

public isNotInArray(values: string[]): IFilteringCriterion {
public isNotInArray(values: U[]): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "NOT_IN", values));
}

Expand All @@ -56,7 +56,7 @@ export class FieldFilter<T, K extends keyof T> implements IFieldFilter {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "REGEXP", [regexp]));
}

public isBetween(start: string, end: string): IFilteringCriterion {
public isBetween(start: U, end: U): IFilteringCriterion {
return new FilteringCriterion(new FilteringCondition(this.fieldName, "BETWEEN", [start, end]));
}
}
Expand Down Expand Up @@ -103,79 +103,15 @@ export interface IFilteringCriterion<T = any> {
or(conditionToAdd: IFilteringCriterion<T>): IFilteringCriterion<T>;
}

/**
* Base interface for filtering by a dataset field
*
* @example
* ```typescript
* import { field } from "jexia-sdk-js/node";
* const criterion = field("username").isEqualTo("Tom").or(field("username").isEqualTo("Dick"));
* ```
*
* @template T Generic type of your dataset, default to any
*/
export interface IFieldFilter<T = any> {
/**
* This field should be greater then the given value
*/
isGreaterThan(value: string): IFilteringCriterion<T>;
/**
* This field should be lesser then the given value
*/
isLessThan(value: string): IFilteringCriterion<T>;
/**
* This field should be equal to the given value
*/
isEqualTo(value: string): IFilteringCriterion<T>;
/**
* This field should be different the given value
*/
isDifferentFrom(value: string): IFilteringCriterion<T>;
/**
* This field should be equal or greater the given value
*/
isEqualOrGreaterThan(value: string): IFilteringCriterion<T>;
/**
* This field should be equal of lesser then the given value
*/
isEqualOrLessThan(value: string): IFilteringCriterion<T>;
/**
* This field should be null
*/
isNull(): IFilteringCriterion<T>;
/**
* This field should be not null
*/
isNotNull(): IFilteringCriterion<T>;
/**
* This field should have one of the given values
*/
isInArray(values: string[]): IFilteringCriterion<T>;
/**
* This field should not have any of the given values
*/
isNotInArray(values: string[]): IFilteringCriterion<T>;
/**
* This field should be contain the given value
*/
isLike(value: string): IFilteringCriterion<T>;
/**
* This field should be satisfies the given regexp
*/
satisfiesRegexp(regexp: string): IFilteringCriterion<T>;
/**
* This field should be in between these given values
*/
isBetween(start: string, end: string): IFilteringCriterion<T>;
}

/**
* Starts a filtering criteria for a given field
* @param name Field name to be filtered
* @template T Generic type of your dataset, default to any
* @template K Generic name of the dataset field you want to filter by
*/
export function field<T = any>(name: string): IFieldFilter {
return new FieldFilter<T, any>(name);
export function field<T = DatasetInterface<any>, K extends keyof DatasetInterface<T> = any>(name: K):
FieldFilter<DatasetInterface<T>[K]> {
return new FieldFilter<DatasetInterface<T>[K]>(name);
}

/**
Expand All @@ -185,8 +121,8 @@ export function field<T = any>(name: string): IFieldFilter {
* @template T Generic type of your dataset, default to any
* @template K Keys of the of your dataset, default to any
*/
export type IFilteringCriterionCallback<T, K extends keyof T> =
(filter: (field: K | DefaultDatasetFields) => IFieldFilter<T>) => IFilteringCriterion<T>;
export type IFilteringCriterionCallback<T> =
(filter: <K extends keyof T>(field: K) => FieldFilter<T[K]>) => IFilteringCriterion<T>;

/**
* Starts a filtering criteria combination with a filtering criteria
Expand Down
Loading

0 comments on commit ee1e01a

Please sign in to comment.