# AsterQL

AsterQL is a small, synchronous, lazy JSON query and projection language. It is
inspired by GROQ's `*[filter] {projection}` shape, JSONMatch's tiny cursor-based
implementation style, and jq's compositional filters.

## Values and Scope

Every JSON literal is a valid AsterQL expression. The core scope operators are:

- `*`: the root input value.
- `$`: the current value.
- `^`: the parent current value.
- `$name`: a parameter supplied by the host.

Identifiers read attributes from the current value, so `title` is equivalent to
`$.title`. `@` remains accepted as a legacy current-value spelling. Missing
fields evaluate to `null`.

Programs may start with `const identifier = expression;` declarations. They are
evaluated before the body, bind lexical names, and cannot be reassigned.

`-> expression` creates a first-class function value. Functions capture visible
constants and selector built-ins call them with each candidate item as `$`.

## Traversal

```asterql
*.posts[status == "published"] {id, title}
*.posts[0]
*.posts[-1]
*.posts[:10]
*.posts[].tags[]
*.posts..slug
*.settings["theme-color"]
```

`[]` is the explicit fan-out operator. Filters and projections also produce lazy
sequences when applied to arrays or sequences.

## Projection

Projection is JSON-like:

```asterql
*.posts {
  id,
  headline: title,
  cost: price * quantity,
  ...meta
}
```

Projection maps arrays and lazy sequences element-by-element. On a single object
it returns one object.

## Joins

`left -> right` evaluates `right` with `$` bound to the result of `left` and `^`
bound to the previous current value.

```asterql
*.posts {
  title,
  author: authorId -> *.users[id == $][0] {id, name}
}
```

## Pipes

Pipes are deliberate whole-value composition points. They feed the value on the
left into the expression on the right as `$`.

```asterql
*.posts[status == "published"] | sort(-date)[:10] {title, date}
*.posts | [status == "published"] | sort(-date) | slice($, 0, 10) {title, date}
```

Initial collection stages:

- `| [condition]`: filter stage that keeps current items whose condition is
  truthy. `filter(expr?)` and `select(expr?)` are equivalent function forms.
- `map(expr?)`: map each current item.
- `sort(expr, ...)`: stable sort.
- `sort_by(expr, ...)`: jq-friendly alias for `sort(expr, ...)`.
- `group(expr)`: group into `{key, items}` objects.
- `slice(value, start?, end?)`: slice arrays, sequences, or strings.
- `length(value?)`: jq-friendly alias for `len(value?)`.
- `reverse(value?)`: reverse arrays, sequences, or strings.
- `add(value?)`: add numbers or concatenate strings/arrays in a collection.
- `fold {...}`: aggregate the current collection into one projected object.

## Built-ins

The v1 built-in surface is intentionally small:

- `len(value?)`
- `length(value?)`
- `sum(expr?)`
- `avg(expr?)`
- `min(expr?)`
- `max(expr?)`
- `any(expr?)`
- `all(expr?)`
- `unique(expr?)`
- `filter(expr?)`
- `select(expr?)`
- `map(expr?)`
- `sort(expr, ...)`
- `sort_by(expr, ...)`
- `group(expr?)`
- `reverse(value?)`
- `add(value?)`
- `keys(value?)`
- `type(value?)`
- `when(condition, then, else?)`

Reducer built-ins with an expression argument evaluate that expression for each
item in the current array or sequence. `sum(total)` is the terse "sum by total"
form; no separate `sumBy` syntax exists.

`when(condition, then, else?)` is the expression-level conditional. It evaluates
and returns `then` when the condition is truthy (the same truthiness as filters)
and `else` otherwise, returning `null` when the untaken branch is omitted. Only
the taken branch is evaluated.

Equality may be written as `=`, `==`, or `===`. All three spellings use the
same deep JSON equality semantics, with no JavaScript coercion distinction.

## Public API

```ts
parse(query: string): ProgramNode
scan(query: string): Token[]
query(data, queryOrAst, options?): Iterable<QueryEntry>
evaluate(data, queryOrAst, options?): JsonValue
compile(query: string): CompiledQuery
```

Hosts can extend scalar calls with `EvaluationOptions.functions` and collection
reducers with `EvaluationOptions.reducers`.

The evaluator is synchronous. Traversal, filters, projections, joins, and
positive slices are lazy. Sorting, grouping, negative slices, and aggregation
buffer because their semantics require seeing the collection.
