Nutritious Pumpkin Masses

    @paar-it-gmbh/odata-query
    TypeScript icon, indicating that this package has built-in type declarations

    6.7.2 • Public • Published

    odata-query

    OData v4 query builder that uses a simple object-based syntax similar to MongoDB and js-data

    Install

    yarn add odata-query
    

    or

    npm install --save odata-query
    

    and then use the library

    import buildQuery from 'odata-query'
    
    const query = buildQuery({...})
    fetch(`http://localhost${query}`)

    where the query object syntax for {...} is defined below. There is also react-odata which utilizies this library for a declarative React component.

    Usage

    See tests for examples as well

    Filtering

    buildQuery({ filter: {...} })
    => '?$filter=...'

    Simple equality filter

    const filter = { PropName: 1 };
    buildQuery({ filter })
    => '?$filter=PropName eq 1'

    Comparison operators

    const filter = { PropName: { gt: 5 } };
    buildQuery({ filter })
    => '?$filter=PropName gt 5'

    Supported operators: eq, ne, gt, ge, lt, le, in

    Using the in operator is also similar to the previous example.

    const filter = { PropName: { in: [1, 2, 3] } };
    buildQuery({ filter })
    => '?$filter=PropName in (1,2,3)'

    Logical operators

    Implied and with an array of objects
    const filter = [{ SomeProp: 1 }, { AnotherProp: 2 }, 'startswith(Name, "foo")'];
    buildQuery({ filter })
    => '?$filter=SomeProp eq 1 and AnotherProp eq 2 and startswith(Name, "foo")'
    Implied and with multiple comparison operators for a single property

    Useful to perform a between query on a Date property

    const startDate = new Date(Date.UTC(2017, 0, 1))
    const endDate = new Date(Date.UTC(2017, 2, 1))
    const filter = { DateProp: { ge: startDate, le: endDate } }
    buildQuery({ filter })
    => "?$filter=DateProp ge 2017-01-01T00:00:00Z and DateProp le 2017-03-01T00:00:00Z"
    Explicit operator
    const filter = {
      and: [
        { SomeProp: 1 },
        { AnotherProp: 2 },
        'startswith(Name, "foo")'
      ]
    };
    
    buildQuery({ filter })
    => '?$filter=SomeProp eq 1 and AnotherProp eq 2 and startswith(Name, "foo")'
    const filter = {
      not: {
        and:[
          {SomeProp: 1}, 
          {AnotherProp: 2}
        ]
      }
    };
    
    buildQuery({ filter })
    => '?$filter=(not (SomeProp eq 1) and (AnotherProp eq 2))'

    Supported operators: and, or, and not.

    Collection operators

    Empty any

    Using an empty object

    const filter = {
      ItemsProp: {
        any: {}
      }
    };
    
    buildQuery({ filter })
    => '?$filter=ItemsProp/any()'

    or also as an empty array

    const filter = {
      ItemsProp: {
        any: []
      }
    };
    
    buildQuery({ filter })
    => '?$filter=ItemsProp/any()'
    Implied and

    Using an object

    const filter = {
      ItemsProp: {
        any: {
          SomeProp: 1,
          AnotherProp: 2
        }
      }
    };
    
    buildQuery({ filter })
    => '?$filter=ItemsProp/any(i:i/SomeProp eq 1 and i/AnotherProp eq 2)'

    or also as an array of object

    const filter = {
      ItemsProp: {
        any: [
          { SomeProp: 1 },
          { AnotherProp: 2},
        ]
      }
    };
    
    buildQuery({ filter })
    => '?$filter=ItemsProp/any(i:i/SomeProp eq 1 and i/AnotherProp eq 2)'
    Explicit operator (and, or, and not)
    const filter = {
      ItemsProp: {
        any: {
          or: [
            { SomeProp: 1 },
            { AnotherProp: 2},
          ]
        }
      }
    };
    
    buildQuery({ filter })
    => '?$filter=ItemsProp/any(i:(i/SomeProp eq 1 or i/AnotherProp eq 2)'
    const filter = {
      not: {
        ItemsProp: {
          any: {
            or: [
              { SomeProp: 1 },
              { AnotherProp: 2},
            ]
          }
        }
      }
    };
    
    buildQuery({ filter })
    => '?$filter=not ItemsProp/any(i:((i/SomeProp eq 1) or (i/AnotherProp eq 2)))'
    Implied all operators on collection item itself

    ITEM_ROOT is special constant to mark collection with primitive type

    'in' operator

    const filter = {
        tags: {
          any: {
            [ITEM_ROOT]: { in: ['tag1', 'tag2']},
          },
        },
    };
    
    buildQuery({ filter })
    => "?$filter=tags/any(tags:tags in ('tag1','tag2'))"

    'or' operator on collection item itself

    const filter = {
        tags: {
          any: {
            or: [
              { [ITEM_ROOT]: 'tag1'},
              { [ITEM_ROOT]: 'tag2'},
            ]
          }
        }
    };
    
    buildQuery({ filter })
    => "?$filter=tags/any(tags:((tags eq 'tag1') or (tags eq 'tag2')))";

    'and' operator on collection item itself and nested item

     const filter = {
        tags: {
          any: [
              { [ITEM_ROOT]: 'tag1'},
              { [ITEM_ROOT]: 'tag2'},
              { prop: 'tag3'},
            ]
        }
    };
    
    buildQuery({ filter });
    => "?$filter=tags/any(tags:tags eq 'tag1' and tags eq 'tag2' and tags/prop eq 'tag3')";

    Function on collection item itself

    const filter = {
        tags: {
          any: {
            [`tolower(${ITEM_ROOT})`]: 'tag1'
          }
        }
    };
    
    buildQuery({ filter });
    => "?$filter=tags/any(tags:tolower(tags) eq 'tag1')";

    Supported operators: any, all

    Functions

    String functions returning boolean
    const filter = { PropName: { contains: 'foo' } };
    buildQuery({ filter })
    => "$filter=contains(PropName,'foo')"

    Supported operators: startswith, endswith, contains

    Functions returning non-boolean values (string, int)
    const filter = { 'length(PropName)': { gt: 10 } };
    buildQuery({ filter })
    => "$filter=length(PropName) gt 10"

    Supported operators: length, tolower, toupper, trim, day, month, year, hour, minute, second, round, floor, ceiling

    Functions returning non-boolean values (string, int) with parameters
    const filter = { "indexof(PropName, 'foo')": { eq: 3 } };
    buildQuery({ filter })
    => "$filter=indexof(PropName, 'foo') eq 3"

    Supported operators: indexof, substring

    Strings

    A string can also be passed as the value of the filter and it will be taken as is. This can be useful when using something like odata-filter-builder or if you want to just write the OData filter sytnax yourself but use the other benefits of the library, such as groupBy, expand, etc.

    import f from 'odata-filter-builder';
    
    const filter = f().eq('TypeId', '1')
                      .contains(x => x.toLower('Name'), 'a')
                      .toString();
    buildQuery({ filter })

    Data types

    GUID:

    const filter = { "someProp": { eq: { type: 'guid', value: 'cd5977c2-4a64-42de-b2fc-7fe4707c65cd' } } };
    buildQuery({ filter })
    => "?$filter=someProp eq cd5977c2-4a64-42de-b2fc-7fe4707c65cd"

    Duration:

    const filter = { "someProp": { eq: { type: 'duration', value: 'PT1H' } } };
    buildQuery({ filter })
    => "?$filter=someProp eq duration'PT1H'"

    Binary:

    const filter = { "someProp": { eq: { type: 'binary', value: 'YmluYXJ5RGF0YQ==' } } };
    buildQuery({ filter })
    => "?$filter=someProp eq binary'YmluYXJ5RGF0YQ=='"

    Decimal:

    const filter = { "someProp": { eq: { type: 'decimal', value: '12.3456789' } } };
    buildQuery({ filter })
    => "?$filter=someProp eq 12.3456789M"

    Raw:

    const filter = { "someProp": { eq: { type: 'raw', value: `datetime'${date.toISOString()}'` } } };
    buildQuery({ filter })
    => "?$filter=someProp eq datetime'2021-07-08T12:27:08.122Z'"
    • Provides full control over the serialization of the value. Useful to pass a data type.

    Note that as per OData specification, binary data is transmitted as a base64 encoded string. Refer to Primitive Types in JSON Format, and binary representation.

    Search

    const search = 'blue OR green';
    buildQuery({ search });
    => '?$search=blue OR green';

    Selecting

    const select = ['Foo', 'Bar'];
    buildQuery({ select })
    => '?$select=Foo,Bar'

    Ordering

    const orderBy = ['Foo desc', 'Bar'];
    buildQuery({ orderBy })
    => '?$orderby=Foo desc,Bar'

    Expanding

    Nested expand using slash seperator

    const expand = 'Friends/Photos'
    buildQuery({ expand })
    => '?$expand=Friends($expand=Photos)';

    Nested expand with an object

    const expand = { Friends: { expand: 'Photos' } }
    buildQuery({ expand })
    => '?$expand=Friends($expand=Photos)';

    Multiple expands as an array

    Supports both string (with slash seperators) and objects

    const expand = ['Foo', 'Baz'];
    buildQuery({ expand })
    => '?$expand=Foo,Bar';

    Filter expanded items

    const expand = { Trips: { filter: { Name: 'Trip in US' } } };
    buildQuery({ expand })
    => "?$expand=Trips($filter=Name eq 'Trip in US')";

    Select only specific properties of expanded items

    const expand = { Friends: { select: ['Name', 'Age'] } };
    buildQuery({ expand })
    => '?$expand=Friends($select=Name,Age)';

    Return only a subset of expanded items

    const expand = { Friends: { top: 10 } };
    buildQuery({ expand })
    => '?$expand=Friends($top=10)';

    Order expanded items

    const expand = { Products: { orderBy: 'ReleaseDate asc' } };
    buildQuery({ expand })
    => "?$expand=Products($orderby=ReleaseDate asc)";

    filter, select, top, and orderBy can be used together

    Select only the first and last name of the top 10 friends who's first name starts with "R" and order by their last name

    const expand = {
      Friends: {
        select: ['FirstName', 'LastName'],
        top: 10,
        filter: {
          FirstName: { startswith: 'R' }
        },
        orderBy: 'LastName asc'
      }
    };
    buildQuery({ expand })
    => '?$expand=Friends($select=Name,Age;$top=10;$filter=startswith eq 'R'))';

    Pagination (skip and top)

    Get page 3 (25 records per page)

    const page = 3;
    const perPage = 25;
    const top = perPage;
    const skip = perPage * (page - 1);
    buildQuery({ top, skip })
    => '?$top=25&$skip=50'

    Single-item (key)

    Simple value

    const key = 1;
    buildQuery({ key })
    => '(1)'

    As object (explicit key property

    const key = { Id: 1 };
    buildQuery({ key })
    => '(Id=1)'

    Counting

    Include count inline with result

    const count = true;
    const filter = { PropName: 1}
    buildQuery({ count, filter })
    => '?$count=true&$filter=PropName eq 1'

    Or you can return only the count by passing a filter object to count (or empty object to count all)

    const count = { PropName: 1 }
    const query = buildQuery({ count })
    => '/$count?$filter=PropName eq 1'

    Actions

    Action on an entity

    const key = 1;
    const action = 'Test';
    buildQuery({ key, action })
    => '(1)/Test'

    Action on a collection

    const action = 'Test';
    buildQuery({ action })
    => '/Test'

    Action parameters are passed in the body of the request.

    Functions

    Function on an entity

    const key = 1;
    const func = 'Test';
    buildQuery({ key, func })
    => '(1)/Test'

    Function on an entity with parameters

    const key = 1;
    const func = { Test: { One: 1, Two: 2 } };
    buildQuery({ key, func })
    => '(1)/Test(One=1,Two=2)'

    Function on a collection

    const func = 'Test';
    buildQuery({ func })
    => '/Test'

    Function on a collection with parameters

    const func = { Test: { One: 1, Two: 2 } };
    buildQuery({ func })
    => '/Test(One=1,Two=2)'

    Transforms

    Transforms can be passed as an object or an array (useful when applying the same transform more than once, such as filter)

    Aggregations

    const transform = {
      aggregate: {
        Amount: {
          with: 'sum',
          as: 'Total'
        }
      }
    };
    buildQuery({ transform });
    => '?$apply=aggregate(Amount with sum as Total)';

    Supported aggregations: sum, min, max, average, countdistinct

    Group by (simple)

    const transform = [{
      groupBy: {
        properties: ['SomeProp'],
      }
    }]
    buildQuery({ transform });
    => '?$apply=groupby((SomeProp))';

    Group by with aggregation

    const transform = {
      groupBy: {
        properties: ['SomeProp'],
        transform: {
          aggregate: {
            Id: {
              with: 'countdistinct',
              as: 'Total'
            }
          }
        }
      }
    }
    buildQuery({ transform });
    => '?$apply=groupby((SomeProp),aggregate(Id with countdistinct as Total))';

    Group by with filtering before and after

    const transform = [{
      filter: {
        PropName: 1
      }
    },{
      groupBy: {
        properties: ['SomeProp'],
        transform: [{
          aggregate: {
            Id: {
              with: 'countdistinct',
              as: 'Total'
            }
          }
        }]
      }
    },{
      filter: {
        Total: { ge: 5 }
      }
    }]
    buildQuery({ transform });
    => '?$apply=filter(PropName eq 1)/groupby((SomeProp),aggregate(Id with countdistinct as Total))/filter(Total ge 5)';

    Supported transforms: aggregate, groupby, filter. Additional transforms may be added later

    OData specs

    Keywords

    none

    Install

    npm i @paar-it-gmbh/odata-query

    DownloadsWeekly Downloads

    3

    Version

    6.7.2

    License

    MIT

    Unpacked Size

    41.8 kB

    Total Files

    5

    Last publish

    Collaborators

    • mlink
    • jmussmann