Hello, Typescript SDK
- Date
- Michael Stone
Hot on the heels of our Kotlin SDK, we’re excited to release our Typescript SDK that lets you fetch data from APIs, databases and kafka topics from within your browser or Nodejs apps, without writing any integration code.
The Typescript ecosystem is close to our hearts and we’re looking forward to bringing more features and examples in different web and nodejs frameworks over the next few months.
In this post
- Submitting TaxiQL queries
- Building a type-safe query
- Providing parameters
- Type-safe querying
- Why Orbital?
- What next?
Getting Started
npm install @orbitalhq/orbital-client
Usage
There’s two methods of using the SDK depending on your preferred style.
- Submit a TaxiQL query string
- Use the typesafe query building
For both cases, the first step is to generate a taxonomy of terms based on the schemas registered with Orbital. That’s done using a script provided in the Orbital client npm package that connects to the Orbital schema server and downloads the typescript version of the taxonomy. Simply define an orbital.config.json
file with the schema server URL and away you go.
{
"schemaServerUrl": "http://localhost:9305/"
}
The content of the taxonomy depends on the data providers that have uploaded their schemas to Orbital. There’s more info available in our docs on how schemas can be published.
To run the taxonomy generation script from your project:
npx orbital-generate
Submitting TaxiQL
This method is closer to what you’d use if you’ve experimented with the Query Editor in the Orbital UI. If you’ve already written a query in the UI you’re happy with, you can generate this code directly from the Query Editor by clicking the the copy
button .
const orbitalClient = new HttpQueryClient(`http://localhost:9022`)
let films = orbitalClient.query(```
import film.types.FilmId
import film.types.Title
import com.petflix.listings.StreamingProviderName
import com.petflix.listings.StreamingProviderPrice
find { Film[] } as {
id: FilmId
title : Title
// where can I watch this?
provider: StreamingProviderName
cost: StreamingProviderPrice
}[]
```);
Here, we simply send the query string over to Orbital which describes:
- the data we want to find (Films),
- which pieces of data we want (id and title)
- the structure we want it in (flat).
In our projection of the pieces of data we want, we’re asking for two different types of information. Some details about the film itself (the FilmId and the Title), and information about where it’s availabled to watch (StreamingProviderName and StreamingProviderPrice).
If we check out the query history in the Orbital UI after running this query, we can see that the data has come from two different systems. We fetched the id
and title
from our Films
database, and then we used the FilmId
to call our StreamingProvidersService
to fetch the provider
and cost
properties.
It’s worth noting that when defining the structure of data we want to receive, we can use whatever property names we want. It’s the semantic tags that we’ve imported from our generated taxonomy that are significant. In this example, FilmId
and Title
are the tags we’ve used, shown as the types of the id
and title
properties of our response projection.
Check out our other content for why we’re advocating for using semantic metadata to queries and schemas.
You can also extract the query to a separate *.taxi
where you can take advantage of our VSCode Taxi language plugin for editing your query.
Type safe query builder
The other option is to use the query builder in our SDK which allows us to build our query as a series of chained functions. Just like in our query string, we’ll define the data we want, the properties we’re interested in, and the structure.
import { com, film } from '@/app/taxonomy';
const filmTaxonomy = new film.Taxonomy();
const filmTypesTaxonomy = new film.types.Taxonomy();
const listingsTaxonomy = new com.petflix.listings.Taxonomy();
let films = orbitalClient
// What data do I want to find
.find(asArray(filmTaxonomy.Film))
// Which properties do I want returned, and in what structure
.as({
// The property names can be whatever you want
// The types on the right hand side are our semantic tags that describe
// to Orbital what data to find
id: filmTypesTaxonomy.FilmId,
title: filmTypesTaxonomy.Title,
provider: listingsTaxonomy.StreamingProviderName,
cost: listingsTaxonomy.StreamingProviderPrice
})
.execute()
.subscribe()
Providing parameters
Sometimes we want to be more specific about the data we’re fetching. Instead of ALL the films, maybe we want more detailed information about one film.
Orbital works with your existing API endpoints and data sources - so provided you’ve got and endpoint that returns a single Film from it’s Id, Orbital will automatically query it.
We’ll add an extra function call given()
, using the semantic tag of the item we’re specifying, the film ID.
import { com, film } from '@/app/taxonomy';
const filmTaxonomy = new film.Taxonomy();
const filmTypesTaxonomy = new film.types.Taxonomy();
const listingsTaxonomy = new com.petflix.listings.Taxonomy();
let film = orbitalClient
// Return the data for the film with a FilmId of "5"
.given(filmTypesTaxonomy.FilmId, "5")
.find(asArray(filmTaxonomy.Film))
.as({
id: filmTypesTaxonomy.FilmId,
title: filmTypesTaxonomy.Title,
provider: listingsTaxonomy.StreamingProviderName,
cost: listingsTaxonomy.StreamingProviderPrice
})
.execute()
.subscribe()
Type-safe querying
A big advantage from using the DSL is type safe querying of our data. For whatever property names and structure we specify in our as()
block, the responses from Orbital will be correctly typed.
That’s significant because instead of making a request to a service and providing a type parameter based on what the response should be, we’re asking Orbital to fetch specific data and return it in a specific structure. There’s a direct link between the types we’re asking for from Orbital and how the data will be returned. Additionally, the response type of our query is set of our specific object, not typed as any
.
For example, if I add in explicit types to the variable we’re assigning our response to, we’ll see that the Typescript compiler won’t raise any concerns.
let films: Observable<{
'id': number,
'title': string,
'provider': string,
'cost': number
}> = orbitalClient
.find(asArray(taxonomy.film.Film))
.as({
id: taxonomy.film.types.FilmId,
title: taxonomy.film.types.Title,
provider: taxonomy.com.petflix.listings.StreamingProviderName,
cost: taxonomy.com.petflix.listings.StreamingProviderPrice
})
.execute()
Why Orbital?
We think there’s some great ergonomics here for Typescript developers when fetching data. There’s also a bunch of other benefits you get from using Orbital to power your Typescript apps:
- Adapt to schema changes - your code will automatically adapt to changes in schemas
- Eliminate integration code - you’ll eliminate loads of integration code and code-gen of other teams schemas
- Consumer defined schemas - regardless of the tech used by other teams you’ll gain the power to pick and choose which data is delivered to you.
- Observability - see exactly how an integration was performed in the Orbital console, including response payloads and cell level lineage
Check out our docs for more information.
What next?
In terms of where we’re heading, there’s a number of exciting features we’ll be adding in the near future. They’re already available in the core platform, and we’ll be adding support for them in the Typescript SDK.
- Streaming query results - for larger result sets, start receiving responses as a stream as soon as the first results become available
- Subscriptions - connect to kafka topics and other streaming sources of data
- Publishing schemas from Nodejs apps - similar to the capability in our Kotlin SDK, this let’s publishers add a couple of decorators to their code to push their schemas up to Orbital
- Type safety for projections to nested data structures - query projections for the DSL are currently limited to flat objects. While the Orbital query runner is capable of returning results in any shape requested in the query string, the DSL option in the SDK isn’t able to provide type safety for nested structures.