@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'
@themost/node is a client module for node.js applications which are going to use @themost-framework as backend api server.
@themost/angular is a client module for angular 2.x+ applications which are going to use @themost-framework as backend api server.
@themost/react is a client module for react applications which are going to use @themost-framework as backend api server.
@themost/jquery is a client module for JQuery scripts and applications which are going to use @themost-framework as backend api server.
use ClientDataContext
which is being provided by your environment and initialize an instance of ClientDataQueryable
class.
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
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
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
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();
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)
@themost/client
supports the usage of OData comparison operators like eq
, ne
, lt
, le
etc
const items = await context.model('Orders')
.asQueryable()
.where(({id}) => {
return id === 100;
}).getItem();
/Orders?$filter=id eq 100
const item = await context.model('Orders')
.asQueryable()
.where(({category}) => {
return category !== 'Desktops';
}).getItems();
/Orders?$filter=category ne 'Desktops'
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)
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)
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)
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)
@themost/client
supports the usage of aggregate functions like count
, min
, max
for getting
aggregated results
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
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
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
@themost/client
supports the usage of string functions while querying data
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return x.name.indexOf('Intel') >= 0;
})
.getItems();
/Products?$filter=indexof(name,'Intel') ge 0
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return x.name.startsWith('Intel') === true;
})
.getItems();
/Products?$filter=startswith(name,'Intel') eq true
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return x.name.endsWith('Edition') === true;
})
.getItems();
/Products?$filter=endswith(name,'Edition') eq true
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return x.category.toLowerCase() === 'laptops';
})
.getItems();
/Products?$filter=tolower(category) eq 'laptops'
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return x.category.toUpperCase() === 'LAPTOPS';
})
.getItems();
/Products?$filter=toupper(category) eq 'LAPTOPS'
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'
@themost/client
supports also the usage of date functions while querying data
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getDate() === 0;
})
.getItems();
/Orders?$filter=day(orderDate) eq 19
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getMonth() === 0;
})
.getItems();
/Orders?$filter=(month(orderDate) sub 1) eq 0
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getFullYear() === 2019;
})
.getItems();
/Orders?$filter=(month(orderDate) sub 1) eq 0
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getHours() === 14;
})
.getItems();
/Orders?$filter=hour(orderDate) eq 14
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getMinutes() === 30;
})
.getItems();
/Orders?$filter=minute(orderDate) eq 30
const items = await context.model('Orders')
.asQueryable()
.where((x) => {
return x.orderDate.getSeconds() === 30;
})
.getItems();
/Orders?$filter=second(orderDate) eq 30
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return Math.floor(x.price) <= 177;
})
.getItems();
/Products?$filter=floor(price) le 177
const items = await context.model('Products')
.asQueryable()
.where((x) => {
return Math.ceil(x.price) >= 177;
})
.getItems();
/Products?$filter=floor(price) ge 177
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
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
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
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
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
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'
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
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
Define $orderby
system query option for sorting records
const items = await context.model('People')
.asQueryable()
.orderBy(({familyName}) => familyName)
.getItems();
/People?$orderby=familyName
const items = await context.model('People')
.asQueryable()
.orderBy(({familyName}) => familyName)
.thenBy(({givenName}) => givenName)
.getItems();
/People?$orderby=familyName,givenName
const items = await context.model('People')
.asQueryable()
.orderByDescending(({familyName}) => familyName)
.getItems();
/People?$orderby=familyName desc
const items = await context.model('People')
.asQueryable()
.orderByDescending(({familyName}) => familyName)
.thenByDescending(({givenName}) => givenName)
.getItems();
/People?$orderby=familyName desc,givenName desc
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
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)
@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