Javascript OData Client for OData (v2/v4) Service.
npm i -S @odata/client
Alternative, in native browser environment, just add unpkg or jsdeliver umd link to your page, and the OData
object will be available in window
.
<script src="https://cdn.jsdelivr.net/npm/@odata/client/lib/odata-client-umd.js"></script>
- Installation
- Table of Contents
- OData Client
- OData Param
- OData Filter
- Entity Set
- Batch Requests
- Server Side Polyfill
- Others
- CHANGELOG
- LICENSE
- Thanks JetBrains
How to use @odata/client
Start with a simple query, following code start a GET
http request, and asks the server to respond to all customers which phone number equals 030-0074321
import { OData } from "@odata/client"
// odata.org sample odata service
const TestServiceURL = "https://services.odata.org/V2/Northwind/Northwind.svc/$metadata"
const client = OData.New({
metadataUri: TestServiceURL,
// variant: "c4c"
})
const runner = async () => {
// Query by filter
//
// GET /Customers?$format=json&$filter=Phone eq '030-0074321'
const filter = client.newFilter().field("Phone").eqString("030-0074321");
const result = await client.newRequest({ // ODataRequest object
collection: "Customers", // collection name
params: client.newParam().filter(filter) // odata param
})
}
// OData V4 client
const Service = "https://odata-v4-demo-001.herokuapp.com/odata/$metadata"
const client = OData.New4({
metadataUri: Service,
variant: "cap"
})
interface ODataRequest<T> {
collection: string, /** collection name */
id?: any, /** object key in READ/UPDATE/DELETE, user could set this field with number/string/object type */
params?: ODataQueryParam, /** params in QUERY */
/**
* GET for QUERY/READ; for QUERY, you can use params to control response data
* PATCH for UPDATE (partial)
* PUT for UPDATE (overwrite)
* POST for CREATE
* DELETE for DELETE
*/
method?: HTTPMethod,
entity?: T /** data object in CREATE/UPDATE */
}
interface PlainODataResponse {
d?: {
__count?: string; /** $inlinecount values */
results: any | Array<any>; /** result list/object */
[key: string]: any;
};
error?: { /** if error occurred, node error will have value */
code: string;
message: {
lang: string,
value: string /** server error message */
}
}
}
use ODataParam
to control data size, involved fields and order
How to use ODataParam
skip
first 30 records and top
10 records
// equal to $format=json&$skip=30&$top=10
OData.newParam().skip(30).top(10)
filter data by fields value
// $format=json&$filter=A eq 'test'
OData.newParam().filter(OData.newFilter().field("A").eqString("test"))
response with all records count, usefully.
also could set with filter
, and response with filtered records count.
// equal to $format=json&$inlinecount=allpages
OData.newParam().inlinecount(true).top(1).select("ObjectID")
sort response data
// result is $format=json&$orderby=CreationDateTime desc
OData.newParam().orderby("CreationDateTime")
// result is $format=json&$orderby=A desc,B asc
OData.newParam().orderby([{ field: "A" }, { field: "B", order: "asc" }])
expand association data
// $expand=Customers
OData.newParam().expand("Customers")
// $expand=Customers,Employees
OData.newParam().expand(["Customers", "Employees"])
remove unused fields from response
// $format=json&$select=ObjectID,Name
OData.newParam().select("ObjectID").select("Name");
search all supported fields with text
SAP systems feature
LOW PERFORMANCE
// fuzzy
// $format=json&$search=%any word%
OData.newParam().search("any word");
// not fuzzy
// $format=json&$search=any word
OData.newParam().search("any word", false);
use the ODataFilter
to filter data
How to use ODataFilter
Most SAP
systems only support AND
operator between different fields, and OR
operator in a same field. (it depends on SAP Netweaver implementation)
So you don't need to specify AND/OR
operator between fields, @odata/client
will auto process it.
Though C4C only support AND operator in different fields, but for gt/lt/ge/le
, you can use AND for filtering period data.
just ref following examples
// Name eq 'test string'
OData.newFilter().field("Name").eqString("test string")
// ID lt '1024'
OData.newFilter().field("ID").lt("'1024'")
// also support eq/ne/le/lt/gt/ge ...
// Name eq 'test string1' and Name2 eq 'test string2'
OData
.newFilter()
.field("Name").eq("'test string1'")
.field("Name2").eqString("test string2")
// Name eq 'test string1' and (Name2 eq 'test string1' or Name2 eq 'test string2')
OData.newFilter()
.field("Name").eq("'test string1'")
.field("Name2").in(["test string3", "test string2"])
Depends on field type, use betweenDateTime
or betweenDateTimeOffset
to filter date。
Please provide Date
object in this api.
// Name eq 'test string1' and (CreationDateTime gt datetime'2018-01-24T12:43:31.839Z' and CreationDateTime lt datetime'2018-05-24T12:43:31.839Z')
OData
.newFilter()
.field("Name").eq("'test string1'")
.field("CreationDateTime").betweenDateTime(
new Date("2018-01-24T12:43:31.839Z"),
new Date("2018-05-24T12:43:31.839Z"),
false
)
.build()
// > include boundary value
// (CreationDateTime ge datetime'2018-01-24T12:43:31.839Z' and CreationDateTime le datetime'2018-05-24T12:43:31.839Z')
OData
.newFilter()
.field("CreationDateTime").betweenDateTime(
new Date("2018-01-24T12:43:31.839Z"),
new Date("2018-05-24T12:43:31.839Z")
)
.build()
// CompagnyName contains 'testName' case sensitive
OData
.newFilter()
.filter("indexof(CompanyName, 'testName')").gt(-1)
// CompagnyName has legth 8
OData
.newFilter()
.filter("length(CompanyName)").eq(8)
// CompagnyName has substring 'test'
OData
.newFilter()
.filter("substringof('test', CompanyName)").eq(true)
see more possiblility at 4.5. Filter System Query Option ($filter) function odata v2 documentation
use EntitySet
to CRUD
entity
How to use EntitySet
const runner = async () => {
// odata client
const client = createClient()
const es = client.getEntitySet<CapDemoPeople>("Peoples")
// CREATE instnace
const res0 = await es.create({
UserName: name
})
expect(res0.UserName).toEqual(name)
expect(res0.ID).not.toBeUndefined()
// QUERY by name, quick find
const res = await es.find({ UserName: name })
expect(res).not.toBeUndefined()
expect(res.length > 0).toBeTruthy()
const id = res[0].ID
expect(id).not.toBeUndefined()
// RETRIEVE by id
const res2 = await es.retrieve(id)
expect(res2.UserName).toEqual(name)
// UPDATE
const firstName = Random.name();
await es.update(id, { Name_FirstName: firstName })
// DELETE
await es.delete(id)
}
use odata $batch
api for operating multi entities in single HTTP request, it will save a lot of time between client & server (In the case of processing a large number of requests).
How to send batch request
const runner = async () => {
const odata = OData.New({
metadataUri: `https://services.odata.org/V2/(S(${v4()}))/OData/OData.svc/$metadata`,
})
const testDesc1 = v4(); // a generated uuid
const testDesc2 = v4();
// execute reqeusts and return mocked responses
const result = await odata.execBatchRequests([
odata.newBatchRequest({
collection: "Products",
entity: {
ID: 100009,
Description: testDesc1,
},
method: "POST",
// withContentLength: true, for SAP OData, please set this flag as true
}),
odata.newBatchRequest({
collection: "Products",
entity: {
ID: 100012,
Description: testDesc2,
},
method: "POST",
// withContentLength: true, for SAP OData, please set this flag as true
})
])
result.map(r => expect(r.status).toEqual(201)) // Created
}
Use polyfill for your server-side application.
// import "@odata/client/lib/polyfill"
require("@odata/client/lib/polyfill")
// use OData
Use the go tool 'markdown-toc' to generate table of contents, with following commands:
markdown-toc --replace --inline --depth 2 --header "## Table of Contents" --skip-headers=1 README.md