Query Scripting

Learn about querying for models with chainable filters.

Stacklane uses method chaining to define the criteria of returned query results. In general with method chaining order matters — for example, field filters must be defined before calling limit.

The results of a query are generally "used" outside of the script itself. For example, a Mustache endpoint may iterate over a query given by a supplier to display results as HTML.

Import

import {Article, Product} from '📦';

All Results

To query all models of a type, without any filters, use the all() method. For example, Article.all()

Results will be returned in the model's natural ordering.

Field Filters

Queries besides all() start with a field filter. Multiple field filters may be chained together. Keep in mind that field filters are effectively and conditions.

eq

Article.title('theTitle')

gt

Article.created_gt(new Date(2000))

gte

Product.price_gte(30)

lt

Article.created_gt(new Date(2000)).created_lt(new Date(2010))

lte

Product.price_gte(30).price_lte(40)

Ordering and Limits

Field filters are typically used to return a small number of specific results. By default they return unordered results. Unordered results are limited to 100 results by default, which may be explicitly raised to 500 results.

Methods

The following methods influence the query results.

asc()/desc()

All model types have a natural ordering. It is recommended to minimize the use of asc() and desc() and to typically rely on the default ordering of various query types.

filter(function)

The callback function to filter returns a boolean that indicates whether the item should be included in the results (return false to exclude an entry). This should only be used if another field filter (eq, gt, gte, lt, lte) is not sufficient.

limit(number)

Limits the results of a query.

Modifiers

Modifiers transform the original results.

map(function)

The callback function to map transforms the current stream element into a map or other value. This is often used to transform a model to a JSON object literal.

Article.all().map(article=>({title:article.title}));

flatMap(function)

The callback function to flatMap transforms the current stream element into another stream.

Return a single value, array, or other stream from the callback. The results will be flattened into the resulting stream. For example, given an options[] field, get only distinct options:

Product.all().flatMap(product=>product.options).distinct();

insert(function)

Using the results from either map or flatMap, use insert to grow or inflate those results. The insert method transforms the modified original results by optionally inserting new elements of the same format. It may also take otherwise empty results and fill them. To ensure predictable results, keep in mind the default ordering of various query types.

insert must only be used after map, and the results inserted must be the same type of value as were originally returned from map. at the beginning, between two elements (previous and next), or at the very end.

This method is particularly useful for inserting points along sparsely populated time series data, in conjunction with Dates to help generate the missing points.

The callback function uses a single parameter as an "iterator". The iterator exposes the optional/nullable fields next and previous, which will be null in specific cases. Those 3 cases are exposed as booleans fields empty (next/previous both null), first (previous is null), last (next is null).

Returned values from the callback function are inserted into the results. Returnable values are null, a single value, arrays, or other streams.

Keep in mind that returned results must be the same type of value as were originally returned from map (on the source). In other words, if the original map operation returned results like {date: value.created} then values being returned from the insert callback but also be in the format {date: /* value */}.

distinct()

Using the results from map, flatMap, or insert, creates new results which contain only unique values.

let distinctTitleCount = Article.all()
    .map(article=>({title:article.title}))
    .distinct()
    .count();

Execution

Once a query is built up with various methods, the query results are executed and used by including the query in JSON output, or Mustache HTML output. The following methods also execute and use the results from a query:

count()

Return the total number of results, after considering all other methods, such as limit.

get()

Returns a single result (effectively limit(1)). If there is no result, then a $ModelNotFound exception is thrown, similar to loading a model by its ID.

optional()

Behaves similarly to get(), but returns null instead of throwing $ModelNotFound.

exists()

Returns true if there is at least 1 result for the query.

sum(field)

Must be called before a modifier. Returns the sum of a field (integer or double).

Product.all().sum(Product.price);

The sum will be zero if there are no results (results are empty).

avg(field)

Must be called before a modifier.

Product.all().avg(Product.rating);

avg may return null if there are no results to average (results are empty).

modify(function)

The callback function to modify receives a Model instance as its parameter, and does not expect any return value. This should only be used to update fields, or remove() models in bulk. It is only available during POST, PUT, DELETE, and is limited to the quota on batch size for the request.

It is not required that every Model be modified, for example if it doesn't satisfy some condition. However consider using filter(...) in the case where there are well defined conditions that must be met before updating a model. This will also keep the modify function simpler.

Contained Models

Querying a model contained by a parent model is performed in much the same way as any other model query. The main difference is that these queries must first specify a parent selector.

Specific Parent

To query for models within a specific container, use the method by its lowercase name. Given a container named List and its contained model named Note, then a query for every Note in a specific List would be:

Note.list(theListVar)

Any Parent

To query contained models across all of its containers, use the any prefix, followed by the name of the parent type:

Note.anyList()

Additional Filters

After a parent container selector, any other field filters and query methods may be specified as usual.

Grouping

When querying across any parent, it's also possible to group by each distinct parent.

List
  .grouping(Note.anyList().status('active'))
  .map(group=>({
    listName: group.list.name,
    noteNames: group.note.map(note=>note.name)
  }))

For each parent group, the children returned will be limited. By default each parent is limited to 100 child results. This may be decreased using the limit(n) on the child's query.

Embedded Models

Queries for embedded models are similar to equality queries.

The following assumes Article has a field named metadata. The metadata field is an embedded model name ArticleMetadata with the field title.

Article.metadata({title: 'The Title'}).get();

This will return an Article where Article.metadata.title == 'The Title', regardless of whether ArticleMetadata has other defined fields.

Queries for values in an embedded model list are identical.

Unique Value Queries

Query unique fields and UID fields as you would any other type of equality query. Assuming a model named Article and a UID field named "slug":

let slugId = '....';
let found = Article.slug(slugId).get();
// Because of error handling, 'found' is always defined at this point

Mustache Use

The "all" query on a model type is available directly to Mustache.

/index.html
<!--TEMPLATE mustache-->
{{% import {List} from '📦' }}

{{#List.all as list}}
  {{list.title}}
{{/List.all}}

Simple contained model queries are also available directly to Mustache:

/index.html
<!--TEMPLATE mustache-->
{{% import {Note} from '📦' }}
{{% import {list} from '🔗' }}

{{#Note.list list as note}}
  {{note.title}}
{{/Note.list}}

Any other queries must be defined and built within a supplier before importing them into Mustache.