Skip to content

themost-framework/client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

npm GitHub top language License GitHub last commit GitHub Release Date npm Snyk Vulnerabilities for npm package

@themost/client

MOST Web Framework Logo

@themost-framework core module for javascript clients.

@themost/client provides a set of methods for creating OData v4 queries by using javascript closures in both client and server.

e.g. get name and price of products with category equals to 'Laptops'

// GET /Products?$select=name,price&$filter=category eq 'Laptops'

const items = await context.model('Products').select(({name, price}) => {
    return {
        name,
        price
    }
}).where(({category}) => {
    return category === 'Laptops'
}).getItems();

Javascript closure prototypes introduced by @themost/query uses native language and produces equivalent query expressions for both client and server environments:

import { round } from '@themost/query';

context.model('Products').select((x) => {
    return {
        name: x.name,
        releaseYear: x.releaseDate.getFullYear(),
        price: round(x.price, 2)
    }
}).where((x) => {
    return x.category === 'Laptops';
})
...

which produces the following OData expression /Products?$select=name,year(releaseDate) as releaseYear,round(price,2) as price&$filter=category eq 'Laptops'

or an equivalent SQL statement for server-side enviroments SELECT Products.name AS name, YEAR(Products.releaseDate) AS releaseYear, ROUND(Products.price,2) AS price FROM Products WHERE Products.category = 'Laptops'

node.js client

@themost/node

@themost/node is a client module for node.js applications which are going to use @themost-framework as backend api server.

angular client

@themost/angular

@themost/angular is a client module for angular 2.x+ applications which are going to use @themost-framework as backend api server.

react client

@themost/react

@themost/react is a client module for react applications which are going to use @themost-framework as backend api server.

jQuery client

@themost/jquery

@themost/jquery is a client module for JQuery scripts and applications which are going to use @themost-framework as backend api server.

Usage

use ClientDataContext which is being provided by your environment and initialize an instance of ClientDataQueryable class.

System Query Options

Read OData v4 specification for more information about system query options:

http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360955

select(expr: QueryFunc, ...params: any[])

Define $select system query option by using a javascript closure:

const items = await context.model('Orders')
    .asQueryable()
    .select((x) => {
        return {
            id: x.id,
            customer: x.customer.description,
            orderDate: x.orderDate,
            product: x.orderedItem.name
        }
    })
    .where((x) => {
        return x.paymentMethod.alternateName === 'DirectDebit';
    }).orderByDescending((x) => x.orderDate)
    .take(10)
    .getItems();

/Orders?$select=id,customer/description as customer,orderDate,orderedItem/name as product&$filter=paymentMethod/alternateName eq 'DirectDebit'&$orderby=orderDate desc&$top=10

where(expr: QueryFunc, ...params: any[])

Define $filter system query option by using a javascript closure:

const items = await context.model('Orders')
    .asQueryable()
    .where((x, orderStatus) => {
        return x.orderStatus.alternateName === orderStatus;
    }, 'OrderPickup').take(10)
    .getItems();

/Orders?$filter=orderStatus/alternateName eq 'OrderPickup'&$top=10

Using parameters

A query expression can accept parameters as additional arguments. The following example demonstrates how to use parameters in a query expression e.g.

const items = await context.model('Orders')
    .asQueryable()
    .where((x, orderStatus) => {
        return x.orderStatus.alternateName === orderStatus;
    }, 'OrderPickup').take(10)
    .getItems();

where the first parameter is a query closure and the second parameter is a string value which is going to be passed to closure as orderStatus argument.

const items = await context.model('Orders')
    .asQueryable()
    .where((x, orderStatus, productCategory) => {
        return x.orderStatus.alternateName === orderStatus &&
            x.orderedItem.category === productCategory;
    }, 'OrderPickup', 'Desktops').take(10)
    .getItems();

Logical Operators

Use logical operators while querying data:

const items = await context.model('Products')
    .asQueryable()
    .where(({category}) => {
        return category === 'Laptops' ||
            category === 'Desktops';
    }).getItems();

/People?$filter=(category eq 'Laptops' or category eq 'Desktops')

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where(({category, price}) => {
        return category === 'Laptops' && price <=900;
    }).getItems();

/People?$filter=(category eq 'Laptops' and price le 900)

Comparison operators

@themost/client supports the usage of OData comparison operators like eq, ne, lt, le etc

equals
const items = await context.model('Orders')
    .asQueryable()
    .where(({id}) => {
        return id === 100;
    }).getItem();

/Orders?$filter=id eq 100

not equals
const item = await context.model('Orders')
    .asQueryable()
    .where(({category}) => {
        return category !== 'Desktops';
    }).getItems();

/Orders?$filter=category ne 'Desktops'

greater than
const items = await context.model('Orders')
    .asQueryable()
    .where(({category, price}) => {
        return category === 'Desktops' && price > 1000;
    }).getItems();

/Orders?$filter=(category eq 'Desktops' and price gt 1000)

greater than or equal
const item = await context.model('Orders')
    .asQueryable()
    .where(({category, price}) => {
        return category === 'Desktops' && price >= 1000;
    }).getItems();

/Orders?$filter=(category eq 'Desktops' and price ge 1000)

lower than
const items = await context.model('Orders')
    .asQueryable()
    .where(({category, price}) => {
        return category === 'Desktops' && price < 1200;
    }).getItems();

/Orders?$filter=(category eq 'Desktops' and price lt 1200)

lower than or equal
const items = await context.model('Orders')
    .asQueryable()
    .where(({category, price}) => {
        return category === 'Desktops' && price <= 1200;
    }).getItems();

/Orders?$filter=(category eq 'Desktops' and price le 1200)

Aggregate functions

@themost/client supports the usage of aggregate functions like count, min, max for getting aggregated results

count
import { count } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .select((x) => {
        return {
            category: x.category,
            total: count(x.id)
        };
    }).groupBy((x) => x.category)
    .getItems();

/Products?$select=category,count(id) as total&$groupby=category

min
import { min } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .select((x) => {
        return {
            category: x.category,
            minimumPrice: min(x.price)
        };
    }).groupBy((x) => x.category)
    .getItems();

/Products?$select=category,min(price) as minimumPrice&$groupby=category

min
import { max } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .select((x) => {
        return {
            category: x.category,
            maxPrice: max(x.price)
        };
    }).groupBy((x) => x.category)
    .getItems();

/Products?$select=category,max(price) as maxPrice&$groupby=category

String functions

@themost/client supports the usage of string functions while querying data

indexof
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.name.indexOf('Intel') >= 0;
    })
    .getItems();

/Products?$filter=indexof(name,'Intel') ge 0

startsWith
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.name.startsWith('Intel') === true;
    })
    .getItems();

/Products?$filter=startswith(name,'Intel') eq true

endsWith
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.name.endsWith('Edition') === true;
    })
    .getItems();

/Products?$filter=endswith(name,'Edition') eq true

toLowerCase
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.category.toLowerCase() === 'laptops';
    })
    .getItems();

/Products?$filter=tolower(category) eq 'laptops'

toUpperCase
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.category.toUpperCase() === 'LAPTOPS';
    })
    .getItems();

/Products?$filter=toupper(category) eq 'LAPTOPS'

substring
const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return x.category.substring(0,3) === 'Lapt';
    })
    .getItems();

/Products?$filter=substring(category,0,3) eq 'Lapt'

Date functions

@themost/client supports also the usage of date functions while querying data

getDate
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getDate() === 0;
    })
    .getItems();

/Orders?$filter=day(orderDate) eq 19

getMonth
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getMonth() === 0;
    })
    .getItems();

/Orders?$filter=(month(orderDate) sub 1) eq 0

getFullYear
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getFullYear() === 2019;
    })
    .getItems();

/Orders?$filter=(month(orderDate) sub 1) eq 0

getHours
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getHours() === 14;
    })
    .getItems();

/Orders?$filter=hour(orderDate) eq 14

getMinutes
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getMinutes() === 30;
    })
    .getItems();

/Orders?$filter=minute(orderDate) eq 30

getSeconds
const items = await context.model('Orders')
    .asQueryable()
    .where((x) => {
        return x.orderDate.getSeconds() === 30;
    })
    .getItems();

/Orders?$filter=second(orderDate) eq 30

Math Functions

floor

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return Math.floor(x.price) <= 177;
    })
    .getItems();

/Products?$filter=floor(price) le 177

ceil

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return Math.ceil(x.price) >= 177;
    })
    .getItems();

/Products?$filter=floor(price) ge 177

round

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return round(x.price, 2) >= 177;
    })
    .getItems();

/Products?$filter=round(price, 2) ge 177

Arithmetic operators

add

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return round(x.price, 2) + 100 >= 277;
    })
    .getItems();

/Products?$filter=(round(price,2) add 100) ge 277

subtract

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return round(x.price, 2) - 100 <= 277;
    })
    .getItems();

/Products?$filter=(round(price,2) sub 100) le 277

multiply

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return round(x.price, 2) * 0.75 < 800;
    })
    .getItems();

/Products?$filter=(round(price,2) mul 0.75) lt 800

divide

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .where((x) => {
        return round(x.price, 2) / 1.25 < 800;
    })
    .getItems();

/Products?$filter=(round(price,2) div 1.25) lt 800

case

import { round } from '@themost/query';

const items = await context.model('Products')
    .asQueryable()
    .select(({name, price}) => {
        return {
            name: name,
            value: price < 800 ? 'Normal' : 'Expensive'
        }
    })
    .where(({category}) => {
        return category === 'Laptops';
    })
    .getItems();

/Products?$select=name,case(price lt 800:'Normal',true:'Expensive') as value&$filter=category eq 'Laptops'

take(n: number)

Set $top system query option for defining the number of records to be taken

const items = await context.model('Orders')
    .asQueryable()
    .where((x, orderStatus) => {
        return x.orderStatus.alternateName === orderStatus;
    }, 'OrderPickup').take(10)
    .getItems();

/Orders?$filter=orderStatus/alternateName eq 'OrderPickup'&$top=10

skip(n: number)

Set $skip system query option for defining the number of records to be skipped

const items = await context.model('Orders')
    .asQueryable()
    .where((x, orderStatus) => {
        return x.orderStatus.alternateName === orderStatus;
    }, 'OrderPickup').take(25)
    .skip(25)
    .getItems();

/Orders?$filter=orderStatus/alternateName eq 'OrderPickup'&$top=25&$skip=25

orderBy(expr: QueryFunc, ...params: any[])

Define $orderby system query option for sorting records

const items = await context.model('People')
    .asQueryable()
    .orderBy(({familyName}) => familyName)
    .getItems();

/People?$orderby=familyName

thenBy(expr: QueryFunc, ...params: any[])

const items = await context.model('People')
    .asQueryable()
    .orderBy(({familyName}) => familyName)
    .thenBy(({givenName}) => givenName)
    .getItems();

/People?$orderby=familyName,givenName

orderByDescending(expr: QueryFunc, ...params: any[])

const items = await context.model('People')
    .asQueryable()
    .orderByDescending(({familyName}) => familyName)
    .getItems();

/People?$orderby=familyName desc

thenByDescending(expr: QueryFunc, ...params: any[])

const items = await context.model('People')
    .asQueryable()
    .orderByDescending(({familyName}) => familyName)
    .thenByDescending(({givenName}) => givenName)
    .getItems();

/People?$orderby=familyName desc,givenName desc

groupBy(...arg: [QueryFunc], params?: any)

Define $groupby system query option to group records by using javascript closures:

const results = await context.model('Orders')
    .asQueryable()
    .select(({id, orderStatus}) => {
        return {
            total: count(id),
            orderStatus
        }
    }).groupBy(({orderStatus}) => orderStatus)
    .getItems();

/Orders?$select=count(id) as total,orderStatus&$groupby=orderStatus

expand(...args: (OpenDataQuery | QueryFunc)[])

Define $expand system query option for getting nested objects

Read more about $expand at http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361039

const items= await context.model('Orders')
    .asQueryable()
    .select(({id, orderStatus, orderDate}) => {
        return {
            id,
            orderStatus,
            orderDate
        }
    }).expand(
        (x) => x.customer,
        (x) => x.orderedItem
    ).getItems();

/Orders?$select=id,orderStatus,orderDate&$expand=customer,orderedItem

or use query expressions for applying nested query options:

import { any } from '@themost/query';

const items= await context.model('People')
    .asQueryable()
    .expand(
        any((x) => x.address)
        .select(({id, streetAddress, addressLocalilty}) => ({
            id, streetAddress, addressLocalilty
        }))
    ).getItems();

/People?$expand=address($select=id,streetAddress,addressLocalilty;$expand=addressCountry)

Using CLI

@themost/client provides a command line interface for generating client-side type declarations from an OData metadata service.

Connect to an OData service and generate client-side type declarations:

$ npx @themost/client http://localhost:3000/api/

or extract metadata from an OData metadata document:

$ npx @themost/client ./metadata.xml

Use --out-file option for specifying the output file:

$ npx @themost/client http://localhost:3000/api/ --out-file ./client.d.ts