npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typescript-odata-client

v1.7.0

Published

**Clientside queries with extensive filtering and typesafe joins**

Downloads

348

Readme

ts-odata-client

Clientside queries with extensive filtering and typesafe joins

See also the examples to see the library in action

Looking for all the filteroperators? They are listed here

Development

Dependencies are managed by using yarn. To install all the dependencies run:

yarn

To build the project run:

yarn build

The output files will be placed in the build directory. This project contains unittest using jest and ts-jest. They are placed in the __test__ directory. To run all the test run:

yarn test

After this you can open coverage/lcov-report/index.html in your browser to see all the details about you tests. To publish the package you can run:

npm publish

Generating the QueryContext

The querycontext can be generated with the ts-odata-scaffolder. It has its own repo and readme.

Using the QueryContext

import * as QueryContext from "./query_context"

The querycontext contains all the information about your Odata endpoint. This includes:

  • The models of the backend
  • The relationships between those types
  • The available entitysets

A entityset is an entrypoint of the odata endpoint, those are the things that you use to start quering. The QueryContext exports them as [Modelname]Query. Those queries expose an api to build complex queries using the wellknow method like Filter, Expand, Paginate, etc.

You should always call Select first on a query (it is also the only option typescript will let you pick). After this you get a SelectedQuery wich exposes all the other querybuilding methods. The RelationQuery and SelectedRelationQuery work in the same way. Those different queryclasses are a represatation of the different states a query goes through.

import * as QueryContext from "./query_context"
QueryContext.UserQuery().Select('Id', 'UserName').ToList()

This query will give you a list with all the users.

Filtering with Filter

Every query exposes a method called Filter. This method accepts a function as parameter that builds an expersion. For example:

import * as QueryContext from "./query_context"
QueryContext.UserQuery()
  .Filter(u => u.Id.Equals(1))
  .First()

Note that the parameter u is not of type User, but of the type FilterBuiderComplex<User>. The FilterBuiderComplex type is a very special and important type. It exposes for every property of the type T a Filterbuilder of that actual property. The FilterBuilders of the primitive types do expose the methods that return an instance of FilterExpersion.

export type FilterBuiderComplex<T extends object> = {
  [P in keyof T]: FilterBuider<T[P]>
}

the FilterBuiderComplex type from the sourcecode

The FilterExpersion class exposes an API to alter and combine the existing expresion. Those are Not(), And() and Or(). For example:

.Filter(u => u.Username.Contains('dave').Not()) //where the username doest not contain dave

.Filter(u => u.EmailActivaed.Equals(true).And(u.Username.Contains('dave')))

Calling Filter multiple times on a query will merge the experions in a bigger expersion via the and operator. In this example you will get the users where the id is not equal to 1 AND the username start with 'harry'.

import * as QueryContext from "./query_context"
QueryContext.UserQuery()
  .Filter(u => u.Id.NotEquals(1))
  .Filter(u => u.Username.StartsWith('Harry'))
  .ToList()

See FILTER_BUILDER_API.md for a complete list of all filteroperators

More examples:

.Filter(u => Not(u.Id.Equals(1)))        // where the Id is not 1

.Filter(u => 
    u.Id.Equals(1)
  .And(
    u.Username.StartsWith('Harry')
)))                                     // where the Id is 1 AND the username starts with 'harry'

.Filter(u =>
    u.Id.Equals(1)
  .And(
    u.Username.StartsWith('Harry')
)))                                     // where the Id is 1 OR the username starts with 'harry'

.Filter(u => u.Email.Startswith(u.Name)) // You can also use properties of the same type instead of just values

Expanding with Expand

Expand is used to load the relationships of the model within the current query. Expand is called with the name of the relationship you want to include and a lambdaQuery<UnBoxed<TheRelation>> => Query<UnBoxed<TheRelation>>. This query can be used to filter, expand and select on the relation you are including. Just like the regular Query class, you have to first call Select to get a SelectedQuery before you have acces to all the other methods.

import * as QueryContext from "./query_context"
QueryContext.UserQuery()
  .Select('Id')
  .Expand('Blogs', q => q
    .Select('Id', 'Title')
    .Filter(b => b.Public.Equals(true))
  )
  // This is now a Query<User & { Blogs: Pick<Blog, 'Id', 'Title'>[] }>

all the query methods are available inside an Expand call

QueryContext.UserQuery()
  .Select('Id')
  .Expand('Blogs', q => q
    .Select('Id', 'Title')
    .Filter(b => b.Public.Equals(true))
    .OrderBy({props: 'Id'})
    .Paginate({ page: 0, pagesize: 10})
  )

it is posible to nest Expand calls inside each other

import * as QueryContext from "./query_context"
QueryContext.UserQuery()
  .Select('Id')
  .Expand('Blogs', q => q
    .Select('Id', 'Title')
    .Expand('Reactions' q => q
      .Select('Id', 'Title')
  ))

There is also an ExpandStrict method to expand a relationship in the strict modus (with $expand=rel!(...)).

Selecting properties with Select

Select is used to select a set of properties of your model:

import * as QueryContext from "./query_context"
QueryContext.UserQuery()
  .Select('Id', 'Username')
  // This is now a Query<Pick<User, 'Id' | 'Username'>>

Ordering with OrderBy

Orderby is used to order the result of your query. This method accepts a lamda to that return the property on witch you want to order.

QueryContext.LectureQuery()
  .Select('Title')
  .OrderBy(l => l.Id)

It is posible to order on relations:

QueryContext.LectureQuery()
  .Select('Title')
  .OrderBy(l => l.TeachingActivities().Position)

You can set the order mode by calling Desc or Asc.

QueryContext.LectureQuery()
  .Select('Title')
  .OrderBy(l => l.Id.Desc())

Executing queries

There are multiple ways to execute a query. You can use ToList to get a promise of an Immutable.List with all the results. There is also the First method witch will give you a promise with the first item. First will reject the promise is their was no item found. An other option is the Paginate method witch accepts an object with the page and pagesize.

type PaginationParams = { page: number, pagesize: number }

Paginate return a Promise of Page<Model> witch contains all the info about the pagination:

type Page<M> = {
  pagesize: number
  page: number
  items: List<M>
  totalCount: number
  totalPages: number
}

First, ToList and Paginate all accept a parameter with options to run the query. It's type is the following:

type QueryContext = {
  QueryParams?: Map<string, string>
  Fetch?: (url: string) => Promise<Response>
  Debug?: boolean
}

QueryParams can be used to add custom search params to the OData query (note that those shouldn't start with a '$'). Fetch allows you to alter the way the http call get executed. For example, set the fetch options:

QueryContext.CourseQuery()
  .Select('Id', 'Name')
  .First({Fetch: async url => fetch(url, {credentials: 'include'})})

Finally, setting Debug to true will log the query before execution.