SimplyStore uses javascript as its query engine. This means that you can write any traditional javascript to search through the dataspace. However, this can get quite complex quickly. So SimplyStore adds a more convenient javascript library that mimics how GraphQL works. Just in javascript.
- Select
- Aliases
- Fragments
- Functions
- Filtering with Where()
- Request parameters
- Paginating results
- Sorting with orderBy()
- Grouping with groupBy()
- Default values
- Array functions
- Conditional execution
- Indexes
The SimplyStore example dataspace contains some information from the Star Wars movies, e.g.
from(data.people)
.select({
name: _
})
Returns:
[
{
"name":<text>"Luke Skywalker"
},
{
"name":<text>"C-3PO"
},
{
"name":<text>"R2-D2"
},
...
]
The equivalent GraphQL query would be:
query People {
people {
name
}
}
Or to see in which movies each character appears, the query would be:
from(data.people)
.select({
name: _,
films: {
title: _
}
})
With the following result:
[
{
"name":<text>"Luke Skywalker",
"films":[
{
"title":<text>"Revenge of the Sith"
},
{
"title":<text>"Return of the Jedi"
},
{
"title":<text>"The Empire Strikes Back"
},
{
"title":<text>"A New Hope"
},
{
"title":<text>"The Force Awakens"
}
]
},
...
]
The equivalent GraphQL query would be:
query PeopleAndFilms {
people {
name
films {
title
}
}
}
You can also use aliases:
from(data.people)
.select({
fullName: _.name
})
The equivalent GraphQL query:
query PeopleAndFilms {
people {
fullName: name
}
}
And you can re-use fragments:
const names = {
name: _,
title: _
}
from(data.people)
.select({
...names,
films: {
...names
}
})
With the equivalent GraphQL query:
fragment names on People {
name
}
fragment names on Films {
title
}
query PeopleAndFilms {
people {
...names
films {
..names
}
}
}
The queries are still just javascript, so instead of the _
value, we can add a function:
from(data.people)
.select({
info: o => o.name+' ('+o.gender+')'
})
Resulting in:
[
{
"info":"Luke Skywalker (male)"
},
{
"info":"C-3PO (n/a)"
},
{
"info":"R2-D2 (n/a)"
},
{
"info":"Darth Vader (male)"
},
...
]
GraphQL doesn't allow you to write your own functions in the query, so there is no equivalent GraphQL query.
You can filter results like this:
from(data.people)
.where({
films: {
title: "A New Hope"
}
})
.select({
name: _
})
And the result is:
[
{
"name":<text>"Luke Skywalker"
},
{
"name":<text>"C-3PO"
},
{
"name":<text>"R2-D2"
},
{
"name":<text>"Darth Vader"
},
...
]
In GraphQL you would have to have a prepared query or reducer that allows you to filter on a film title. In that case the query would probably look like this:
query PeopleByFilm($film: Film) {
people(film: $film) {
name
}
}
But you cannot filter on any property, just the ones for which support has been added to the GraphQL server.
The GraphQL query above adds an important feature: you can define the query once and then call it with different variables. You can do the same in SimplyStore, like this:
from(data.people)
.where({
films: {
title: request.query.film
}
})
.select({
name: _
})
And then call the SimplyStore query endpoint with this query and the url query string ?film=A+New+Hope
.
When you have a lot of results, it is often better to return a subset of all results. There are many strategies on how to do this. SimplyStore does not force you to use a specific one, you can build any pagination strategy you want.
Here is an example with fixed page sizes:
const PageSize = parseInt(request.query.pageSize) || 10
const Page = parseInt(request.query.page) || 0
const Paging = {
start: Page*PageSize,
end: (Page+1)*PageSize-1
}
function Paginate(results) {
let meta = {
count: results.length,
data: results.slice(Paging.start,Paging.end)
}
if (Paging.end<results.length) {
meta.next = new JSONTag.Link('?page='+(Page+1)+'&pageSize='+PageSize)
}
if (Paging.start>0) {
meta.prev = new JSONTag.Link('?page='+(Page-1)+'&pageSize='+PageSize)
}
return meta
}
const results = from(data.people)
.select({
name: _
})
Paginate(results)
This will result in:
{
"count":87,
"data":[
{
"name":<text>"Anakin Skywalker"
},
{
"name":<text>"Wilhuff Tarkin"
},
... contents cut
{
"name":<text>"Jek Tono Porkins"
},
{
"name":<text>"Yoda"
}
],
"next":<link>"?page=2&pageSize=10",
"prev":<link>"?page=0&pageSize=10"
}
The query above is getting rather complex. This is not a problem when you are writing a javascript client. You can just prepend common used fragments to the query, like this:
const fragments = `
const PageSize = parseInt(request.query.pageSize) || 10
const Page = parseInt(request.query.page) || 0
const Paging = {
start: Page*PageSize,
end: (Page+1)*PageSize-1
}
function Paginate(results) {
// contents cut, see function above
}`
const query = 'Paginate(from(data.people).select({name:_}))'
simplyStoreApi.query(fragments+';'+query)
But if you are using the SimplyStore /query/ user interface, you can get the same effect by loading the 'Fixtures' query. The contents of this query will be prepended automatically to every query you send using SimplyStores query user interface.
You can order results by one or more properties, like this:
from(data.people)
.orderBy({
homeworld: {
gravity: asc
},
name: asc
})
.select({
name: _,
homeworld: {
gravity: _
}
})
Which gives this result:
[
{
"name":<text>"Bossk",
"homeworld":{
"gravity":"0.62 standard"
}
},
{
"name":<text>"Wicket Systri Warrick",
"homeworld":{
"gravity":"0.85 standard"
}
},
...
]
orderBy() sorts on the properties in the order given. Only if an earlier sort property is equal, will the next property be compared.
orderBy() can be added before or after the select() clause. If you add it before, you can sort on fields that aren't part of the result set.
The corresponding GraphQL query depends on which GraphQL engine you are using. But usually it would be:
query People {
people(order_by:{home_world:{gravity:asc},name:asc}) {
name
home_world {
gravity
}
}
}
Note: This function is still very experimental and likely to change.
You can group results by distinct field values, like this:
from(data.people)
.groupBy({
films: {
title: _.name
}
})
Which results in:
{
"Revenge of the Sith":[
"Luke Skywalker",
"C-3PO",
"R2-D2",
...
],
"Return of the Jedi":[
"Luke Skywalker",
"C-3PO",
"R2-D2",
...
],
...
}
In addition, you can also use the SQL-like functions count()
,sum()
,avg()
,min()
and max()
. Of these count()
will work on any value. The others need number values.
from(data.people)
.groupBy({
films: {
title: count()
}
})
Results in:
{
"Revenge of the Sith":34,
"Return of the Jedi":20,
"The Empire Strikes Back":16,
"A New Hope":18,
"The Force Awakens":11,
"Attack of the Clones":40,
"The Phantom Menace":34
}
Default values are supported as well, like this:
from(data.people)
.where({
films: {
title: request.query.film || "A New Hope"
}
})
.select({
name: _
})
The result of the from() and from().where() functions behave like arrays--unless you use from() on an object. This means that you can also use all the array functions from javascript, like filter(), map(), reduce(), sort(), etc:
from(data.people)
.filter(p => p.name=="Luke Skywalker")
.select({
films: {
title: _
}
})
from(data.people)
.select({
name: _
})
.sort((a,b) => a.name<b.name ? -1 : 1)
GraphQL supports something called Directives. E.g.
query People($film: Film, $withFilms: Boolean) {
People(film: $film) {
name
films @include(if: $withFilms) {
title
}
}
}
SimplyStore queries are just javascript. So you can achieve the same effect like this:
let fields = {
name: _
}
if (request.query.withFilms) {
fields.films = { name: _ }
}
from(data.people)
.select(fields)
GraphQL systems usually add indexes, based on the parameters built into the reducers. SimplyStore has no default indexes, except for the id
index. Each object with a JSONTag id
attribute, is added to this index. You can access the index like this:
function Index(id) {
return meta.index.id.get(id)?.deref()
}
And then use it in your queries like this:
from(Index(request.query.id))
.select({
name: _,
title: _
})
Since people have names and films have titles, I've just added both in the select fields. SimplyStore will automatically omit and fields that aren't in the dataset. You won't get errors are warnings here, so be careful about typo's.