JavaScript OData library

The JavaScript npm/bower mvcct-odata library is a JavaScript OData client. It enables the developer to build queries as trees of objects, and then to transform these trees either into OData urls or into JavaScript functions that may execute the queries against JavaScript arrays.

All JavaScript classes that work as building blocks for this tree have a constructor that accepts a JavaScript object that mimics all properties of the class. This way the whole tree may be created by a Json represntation of it passed to the root object.
Since the Json generated by a mvcct-odata query tree is the same as the Json generated by serializing a .Net query tree in the format of the MvcControlsToolkit.Core.OData Nuget package, clients and servers using these libraries may easily exchange queries.

The mvcct-odata library distributions contains 3 versions of the library: a version that follows the es6 module convention, a version that follows the UMD convention (compatible with both AMD, and Node.js), and a version that exposes all classes through the mvcct.odata global namespace.

The QueryDescription class

The QueryDescription class is the root of the query tree, and contains all methods and properties to interact with the whole query tree:

interface IQueryDescription {
    skip: number | null;
    take: number;
    page: number;
    search: IQuerySearch;
    filter: IQueryFilterBooleanOperator;
    grouping: IQueryGrouping;
    sorting: Array<IQuerySortingCondition>;
    attachedTo: IEndpoint;
}
class QueryDescription implements IQueryDescription {
    customUrlEncode(func: (x: string) => string): void;
    skip: number | null;
    take: number;
    page: number;
    search: QuerySearch;
    filter: QueryFilterBooleanOperator;
    grouping: QueryGrouping;
    sorting: Array<QuerySortingCondition>;
    attachedTo: Endpoint;
    static fromJson(x: string): QueryDescription;
    constructor(origin: IQueryDescription);
    addFilterCondition(filter: QueryFilterClause | null, useOr?: boolean): void;
    getGroupDetailQuery(o: any): QueryDescription | null;
    queryString(): string | null;
    addToUrl(url: string | null): string | null;
    toString(): string | null;
    toQuery(): (o: Array<any>) => Array<any>;
}
interface IEndpoint extends Endpoint {
}
class Endpoint implements IEndpoint {
    static Get: string;
    static Post: string;
    static Put: string;
    static Delete: string;
    static Patch: string;
    baseUrl: string;
    verb: string;
    accpetsJson: boolean;
    returnsJson: boolean;
    bearerToken: string | null;
    ajaxId: string | null;
    constructor(x: IEndpoint);
    constructor(baseUrl: string, verb: string, accpetsJson?:
        boolean, returnsJson?: boolean, bearerToken?: string | null,
        ajaxId?: string | null);
}

skip, take, page
paging components of the query. page is computed from skip and take.
search, filter, grouping, sorting
free search, filter grouping/aggregation and sorting components of the query
attachedTo
where and how to send the query to the server. Namely, baseUrl is the base url where to send the query, verb, the http verb to use, if the server endpoint requires json or url encoded data, if the server endpoint returns Json or html, and an optional bearer token to add to the request headers. If ajaxId is not null, then the request is issued with ajax also if it is not a json request, and the html returned is attached to the html node whose id is ajaxId.
static fromJson(x: string): QueryDescription;
Creates a whole query tree from its Json representation.
toString(): string | null;
returns the complete OData url.
addToUrl(url: string | null): string | null;
returns the complete OData url, but instead of using the baseUrl contained in the Endpoint object uses the url passed as argument.
queryString(): string | null;
returns just the query string part of the query url.
toQuery(): (o: Array) => Array;
compiles the query tree into a JavaScript function that may apply the query to a JavaScript array.
addFilterCondition(filter: QueryFilterClause | null, useOr?: boolean): void;
Add the filter condition passed as argument to the filter already contained in the object. If useOr is true the new condition is combined with the preexisting conditions with a logical or, otherwise with a logical and.
getGroupDetailQuery(o: any): QueryDescription | null;
If the QueryDescription contains a grouping component it gets the query that yields the grouping details of the aggregated item passed as argument(ie the query that explodes all aggregated items).

Building Filter, and Search trees

Filter Trees are made of subclasses of the QueryFilterClause class. The leafs of a filter tree are instances of the QueryFilterCondition class that represents a simple condition involving a property and a JavaScript value. QueryFilterCondition are combined into complex conditions with instances of the QueryFilterBooleanOperator class that represents boolean combinations of their children conditions. Children condtitions may be either other QueryFilterBooleanOperator instances or leaf QueryFilterCondition instances. QueryFilterCondition inherits from the QueryValue class that represents a constant value. QueryValue instances are the leaf nodes of free search queries that combine single values with free search and, or and not. Also free search booleans are represented with QueryFilterBooleanOperator instances whose operators are free search boolean operators. Below the definition of QueryValue:

interface IQueryValue {
    value: any;
    dateTimeType: number;
}
class QueryValue extends QueryFilterClause implements IQueryValue {
    static IsNotDateTime: number;
    static IsDate: number;
    static IsTime: number;
    static IsDateTime: number;
    static IsDuration: number;
    value: any;
    dateTimeType: number;
    constructor(origin?: IQueryValue);
    private formatInt(x, len);
    private normalizeTime(x, days, maxTree);
    isGuid(): boolean;
    setDate(x: Date | null): void;
    setTime(x: Date | null): void;
    setDuration(days: number, hours: number, minutes?: number, seconds?: number, milliseconds?: number): void;
    setDateTimeLocal(x: Date | null): void;
    setDateTimeInvariant(x: Date | null): void;
    setBoolean(x: boolean | null): void;
    setNumber(x: number | null): void;
    setString(x: string | null): void;
    setNotDateTime(x: any): void;
    getValue(): any;
    toString(): string | null;
    toQuery(): ((o: any) => boolean) | null;
}

Each instance contains a field for the JavaScript value and the dateTimeType field that further specifies the type when the value is a JavaScript date. The possible values of dateTimeType are defined as static fields of the QueryValue class. One may create an empty instance and then set its value with the various setType methods; this way the dateTimeType field is automatically set properly.

Once a free search tree has been built it must be assigned to an instance of the QuerySearch class:

interface IQuerySearch {
    value: IQueryFilterBooleanOperator;
}
class QuerySearch extends QueryNode implements IQuerySearch {
    value: QueryFilterBooleanOperator;
    constructor(origin: IQuerySearch | IQueryFilterBooleanOperator | IQueryFilterCondition);
    toString(): string | null;
    toQuery(): ((o: any) => boolean) | null;
}

filter trees, instead, have the QueryFilterCondition subclass of QueryValue as leaf nodes:

interface IQueryFilterCondition extends IQueryValue {
    operator: string | null;
    property: string | null;
    inv: boolean;
}
class QueryFilterCondition
    extends QueryValue implements IQueryFilterCondition {
    static readonly eq: string;
    static readonly ne: string;
    static readonly gt: string;
    static readonly lt: string;
    static readonly ge: string;
    static readonly le: string;
    static readonly startswith: string;
    static readonly endswith: string;
    static readonly contains: string;
    static fromModelAndName(dateTimeType: number, property: string, o: any, op?: string, inv?: boolean): QueryFilterCondition | null;
    operator: string | null;
    property: string | null;
    inv: boolean;
    constructor(origin?: IQueryFilterCondition);
    toQuery(): ((o: any) => boolean) | null;
    toString(): string | null;
}

The QueryFilterCondition subclass adds the operator and property fields. All possible values for the operator field are listed as static members of the class. The property field contains the name of the property involved in the condition; nested properties are separated by dots.
One may create an instance of QueryFilterCondition with a fake or undefined value and then set the value by calling one of the setType methods inherited by QueryValue.

Below the definition of QueryFilterBooleanOperator:

interface IQueryFilterBooleanOperator {
    operator?: number;
    argument1?: IQueryValue;
    argument2?: IQueryValue;
    child1?: IQueryFilterBooleanOperator;
    child2?: IQueryFilterBooleanOperator;
}
class QueryFilterBooleanOperator
    extends QueryFilterClause
    implements IQueryFilterBooleanOperator {
    static readonly and: number;
    static readonly or: number;
    static readonly not: number;
    static readonly AND: number;
    static readonly OR: number;
    static readonly NOT: number;
    operator: number;
    argument1: QueryValue;
    argument2: QueryValue;
    child1: QueryFilterBooleanOperator;
    child2: QueryFilterBooleanOperator;
    constructor(origin: IQueryFilterBooleanOperator);
    constructor(operator: number, a1: QueryFilterClause, a2?: QueryFilterClause);
    toString(): string | null;
    toQuery(): ((o: any) => boolean) | null;
}

The class has two different properties to store its children depending if they are QueryFilterBooleanOperator or leaf conditions. All possible values for the operator field are listed as static fields of the class; The upper case operators are the free search boolean operators.
An instance may be created either by passing an instance of the IQueryFilterBooleanOperator interface or by passing the operator with the children nodes. In case the operator is unary (not for instance) the unique child node may be passed either as first or as second child, the other child being null.

Buildin sorting clauses

Sorting is an array of QuerySortingCondition instances. Below the definition of the QuerySortingCondition class:

interface IQuerySortingCondition {
    property: string;
    down: boolean;
}
class QuerySortingCondition
    extends QueryNode
    implements IQuerySortingCondition {
    property: string;
    down: boolean;
    constructor(x: IQuerySortingCondition);
    constructor(property: string, down?: boolean);
    toString(): string | null;
    toCompare(): ((o1: any, o2: any) => number) | null;
}

The class contains just the name of the property involved in the sorting (nested properties are separated by dots) and a boolean that specifies if the sorting is inverse.

Building groupings and aggregations

Groupings+aggregations are encoded by instances of the QueryGrouping class:

interface IQueryGrouping {
    keys: Array<string>;
    aggregations: Array<IQueryAggregation>;
    dateTimeTypes: Array<number>;
}
class QueryGrouping extends QueryNode implements IQueryGrouping {
    keys: Array<string>;
    aggregations: Array<QueryAggregation>;
    dateTimeTypes: Array<number>;
    constructor(origin?: IQueryGrouping);
    toString(): string | null;
    toQuery(): (input: any[]) => any[];
}

The class contains an array with the names of all grouped properties (nested properties are separated by dots), a parallel array containing all dateTimeTypes of the grouped properties, and an array of QueryAggregation instances, each specifying a property aggregation.

class QueryAggregation extends QueryNode implements IQueryAggregation {
    static readonly count: string;
    static readonly sum: string;
    static readonly average: string;
    static readonly min: string;
    static readonly max: string;
    operator: string;
    property: string;
    isCount: boolean;
    alias: string;
    constructor(x: IQueryAggregation);
    constructor(operator: string, property: string, alias: string);
    toString(): string | null;
    toQuery(): IAggregation;
}

Each instance contains the name of the property to aggregate (nested properties are separated by dots), the aggregation operator and the name of the aggregated property (alias). The name of the aggregated property can't contain nested properties. All possible values for the aggregation operator are listed as static properties of the class.


Fork me on GitHub