diff --git a/README.md b/README.md
index 0b34b39..bfb92d2 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,28 @@
-# @antelopejs/interface-database
+# `@antelopejs/interface-database`
-AQL (Antelope Query Language), the database-agnostic query builder of AntelopeJS: write a chainable, typed query once and run it on any database module that implements the interface.
+AQL (Antelope Query Language) is the database-agnostic query builder of AntelopeJS. You write a chainable, typed query once, and any database module that implements the interface runs it.
## What it offers
-- `Schema` declares tables, fields and indexes; each schema can hold several isolated instances.
+- `Schema` declares tables, fields and indexes. Each schema can hold several isolated instances.
- `Table`, `Selection` and `SingleSelection` insert, read, update and delete documents.
-- `Stream` filters, maps, joins, groups and aggregates; `ValueProxy` builds the expressions inside them.
+- `Stream` filters, maps, joins, groups and aggregates. `ValueProxy` builds the expressions inside them.
- `changes()` streams inserts, updates and deletions as they happen.
-- Queries are lazy: nothing runs until you `await` them, call `.run()` or iterate them.
+- Queries are lazy. Nothing runs until you `await` a query, call `.run()` or iterate it.
## Implementations
-| Module | Notes |
-| ------------------------------------------------------------------ | ------------------------------------------------------------ |
-| [`@antelopejs/mongodb`](https://github.com/AntelopeJS/mongodb) | MongoDB; also implements `@antelopejs/interface-mongodb` |
-| [`@antelopejs/rethinkdb`](https://github.com/AntelopeJS/rethinkdb) | RethinkDB; also implements `@antelopejs/interface-rethinkdb` |
+| Module | Notes |
+| ------------------------------------------------------------------ | ------------------------------------------------------------- |
+| [`@antelopejs/mongodb`](https://github.com/AntelopeJS/mongodb) | MongoDB. Also implements `@antelopejs/interface-mongodb`. |
+| [`@antelopejs/rethinkdb`](https://github.com/AntelopeJS/rethinkdb) | RethinkDB. Also implements `@antelopejs/interface-rethinkdb`. |
## Install
@@ -69,7 +69,7 @@ export async function listLongNovels() {
}
```
-Callbacks such as the `filter` above receive a `ValueProxy`, not a document: only its methods travel to the database.
+Callbacks such as the `filter` above receive a `ValueProxy`, not a document. The callback runs once, when the query is built, and the query records only the `ValueProxy` calls it makes.
## Documentation
diff --git a/docs/1.query_types/1.index.md b/docs/1.query_types/1.index.md
index c19cbad..4faa45c 100644
--- a/docs/1.query_types/1.index.md
+++ b/docs/1.query_types/1.index.md
@@ -1,6 +1,10 @@
-# Query Types
+---
+title: Query types
+description: The AQL query classes, what each one represents, how they inherit from each other, and the execution methods they share.
+mode: reference
+---
-AQL provides several query classes that form a hierarchy of capabilities. Each class encapsulates a different aspect of database interactions, from full table access down to individual value manipulation.
+AQL has several query classes that form a hierarchy of capabilities. Each class covers one level of a database interaction, from a whole table down to one value inside a query.
## Choose by the result you need
@@ -13,13 +17,13 @@ AQL provides several query classes that form a hierarchy of capabilities. Each c
| One query result | `Datum` | Read a field or transform the value. |
| A value inside a query callback | `ValueProxy` | Build an expression with `eq`, `add`, or other query operators. |
-`ValueProxy` describes database expressions; it is not the fetched JavaScript value. Use its operators inside query callbacks, then execute the outer query to obtain data.
+`ValueProxy` describes a database expression. It is not the fetched JavaScript value. Use its operators inside query callbacks, then execute the outer query to get data.
-{width="480"}
+{width="480"}
-## Query Type Hierarchy
+## Query type hierarchy
-All query types inherit from `StagedObject`, which records operations as a pipeline of stages. The hierarchy is:
+Every query type inherits from `StagedObject`, which records operations as a pipeline of stages. The hierarchy is:
```
Query
@@ -32,14 +36,14 @@ Query
ValueProxy (used within query callbacks)
```
-## Common Features
+## Common features
-All query types that extend `Query` share these capabilities:
+Every query type that extends `Query` has these methods:
-- **`run()`** - Execute the query and return a `Promise` with the result.
-- **`then()`** - `PromiseLike` support, so queries can be awaited directly.
-- **`cursor()`** - Return an `AsyncGenerator` for streaming results one at a time.
-- **`[Symbol.asyncIterator]()`** - Enable `for await...of` loops over query results.
+- `run()` executes the query and returns a `Promise` with the result.
+- `then()` makes the query `PromiseLike`, so you can await it directly.
+- `cursor()` returns an `AsyncGenerator` that yields results one at a time.
+- `[Symbol.asyncIterator]()` enables `for await...of` loops over the query results.
```typescript fragment
import { Schema } from "@antelopejs/interface-database";
@@ -56,7 +60,7 @@ for await (const user of schema.instance().table("users")) {
}
```
-## Recognize a Query Type
+## Recognize a query type
`IsQuery`, `IsDatum`, `IsStream` and `IsValueProxy` tell what kind of staged object a value is:
@@ -68,15 +72,15 @@ function describe(query: unknown) {
}
```
-Each provider binding of the interface has its own copy of these classes, so a query built by a module bound to another database is not an `instanceof` this module's `Stream`. The predicates recognize it whichever copy built it. Keep `instanceof` for checking that a query was built for this module's own binding.
+Each provider binding of the interface has its own copy of these classes. A query that a module bound to another database built is therefore not an `instanceof` this module's `Stream`. The predicates recognize a query whichever copy built it. Use `instanceof` only to check that a query was built for this module's own binding.
-## Available Query Types
+## Available query types
-- [**Table**](./2.table.md) - Represents a database table with insert, get, getAll, and between operations
-- [**Selection**](./3.selection.md) - A set of documents supporting update, replace, and delete
-- [**SingleSelection**](./4.single_selection.md) - A single document with update, replace, delete, and change feed support
-- [**Stream**](./5.stream.md) - A sequence of results with map, filter, join, group, aggregate, and sort operations
-- [**Change Feeds**](./6.feed.md) - Real-time change notifications via the `changes()` method
-- [**Query**](./7.query.md) - The base class providing execution and async iteration
-- [**Datum**](./8.datum.md) - A single value with field access, transformation, and lookup capabilities
-- [**ValueProxy**](./9.valueproxy.md) - Proxy for in-query value operations (arithmetic, string, date, array, object)
+- [Table](./2.table.md) is a database table with `insert`, `get`, `getAll`, `between`, and `atomicMutation`.
+- [Selection](./3.selection.md) is a set of documents that supports `update`, `replace`, and `delete`.
+- [SingleSelection](./4.single_selection.md) is one document with `update`, `replace`, `delete`, and change feed support.
+- [Stream](./5.stream.md) is a sequence of results with `map`, `filter`, `join`, `group`, aggregation, and sort operations.
+- [Change feeds](./6.feed.md) are real-time change notifications from the `changes()` method.
+- [Query](./7.query.md) is the base class that provides execution and async iteration.
+- [Datum](./8.datum.md) is a single value with field access, transformation, and lookup.
+- [ValueProxy](./9.valueproxy.md) is a proxy for in-query operations on numbers, strings, dates, arrays, and objects.
diff --git a/docs/1.query_types/2.table.md b/docs/1.query_types/2.table.md
index 0a1bc60..7299b80 100644
--- a/docs/1.query_types/2.table.md
+++ b/docs/1.query_types/2.table.md
@@ -1,25 +1,26 @@
-# Table
+---
+title: Table
+description: The Table query type and its methods for inserting documents, reading them by primary key, secondary index, or range, and applying atomic mutations.
+mode: reference
+---
-The `Table` class represents a database table and provides methods for inserting documents and retrieving them by primary key, secondary index, or range. It extends `Selection`, inheriting all multi-document operations and stream capabilities.
+The `Table` class represents a database table. It inserts documents and retrieves them by primary key, secondary index, or range. It extends `Selection`, so it also has every multi-document operation and stream method.
-## Class Definition
+## Class definition
```typescript fragment
class Table extends Selection {
atomicMutation(
- id: string,
+ key: string,
request: AtomicMutation,
): Query;
insert(
- obj: DeepPartial | DeepPartial[],
+ obj: (DeepPartial & object) | (DeepPartial & object)[],
options?: InsertOptions,
): Query;
- get(key: string): SingleSelection;
- getAll(
- keys: string | number | boolean | (string | number | boolean)[],
- index?: string,
- ): Selection;
- between(
+ get(key: ValueProxyOrValue): SingleSelection;
+ getAll(keys: IndexKeys, index?: string): Selection;
+ between(
index: TK,
low: T[TK] & IndexBound,
high: T[TK] & IndexBound,
@@ -27,9 +28,9 @@ class Table extends Selection {
}
```
-## Access a Table
+## Access a table
-Tables are accessed through a `SchemaInstance`. Define a schema, create an instance, then call `table()`:
+You reach a table through a `SchemaInstance`. Define a schema, create an instance, then call `table()`:
```typescript
import { Schema } from "@antelopejs/interface-database";
@@ -45,7 +46,7 @@ await schema.createInstance();
const users = schema.instance().table("users");
```
-## Insert Documents
+## Insert documents
The `insert` method adds one or more documents to the table and returns the generated IDs.
@@ -78,13 +79,13 @@ const upsertIds = await users.insert(
| ---------- | ----------------------- | ------------------------------------------------- |
| `conflict` | `"update" \| "replace"` | Behavior when a document with the same ID exists. |
-## Retrieve Documents
+## Retrieve documents
### `get(key)`
-Retrieve a single document by its primary key. Returns a `SingleSelection`.
+Retrieves a single document by its primary key, given as a string or a `ValueProxy` of a string. Returns a `SingleSelection`.
-Awaiting this selection is declared as `T`, not `T | null`. The declaration does not prove a row exists; providers can return `null` for missing keys, so check before reading fields.
+Awaiting this selection is declared as `T`, not `T | null`. The declaration does not prove that a row exists. A provider can return `null` or `undefined` for a missing key, so check the result before you read its fields.
```typescript fragment
const user = await users.get("user-123");
@@ -96,7 +97,7 @@ if (user) {
### `getAll(keys, index?)`
-Retrieve multiple documents by key values. When `index` is provided, the lookup uses the specified secondary index instead of the primary key.
+Retrieves multiple documents by key values. `keys` is an `IndexKeys`: one key, a list of keys, or a list of compound keys that each hold a compound index's values in its field order. A key is a string, a number, or a boolean. When you pass `index`, the lookup uses that secondary index instead of the primary key.
```typescript fragment
// By primary key
@@ -108,7 +109,7 @@ const admins = await users.getAll("admin", "role");
### `between(index, low, high)`
-Retrieve documents where the specified index value falls within a range. The lower bound is inclusive, and the upper bound is exclusive. A bound is an `IndexBound`: a string, number, boolean, `Date` or `null`, or a list of them for a compound index.
+Retrieves the documents whose value for the index falls within a range. The lower bound is inclusive, and the upper bound is exclusive. A bound is an `IndexBound`: a string, number, boolean, `Date` or `null`, or a list of them for a compound index.
```typescript fragment
const recent = await users.between(
@@ -118,11 +119,11 @@ const recent = await users.between(
);
```
-## Inherited Methods
+## Inherited methods
-For conditional single-record writes, see [Atomic Mutations](../2.operations/8.atomic_mutations.md). `atomicMutation()` is a table method, not a selection method; it returns an explicit outcome rather than a modified count.
+For conditional single-record writes, see [Atomic mutations](../2.operations/8.atomic_mutations.md). `atomicMutation()` is a table method, not a selection method. It returns an explicit outcome, `"applied"`, `"not-applied"`, or `"unknown"`, rather than a modified count.
-Since `Table` extends `Selection`, which extends `Stream`, it inherits:
+`Table` extends `Selection`, which extends `Stream`, so it inherits:
-- **Selection methods**: `update()`, `replace()`, `delete()` -- see [Selection](./3.selection.md)
-- **Stream methods**: `map()`, `filter()`, `orderBy()`, `count()`, `join()`, and more -- see [Stream](./5.stream.md)
+- The selection methods `update()`, `replace()`, and `delete()`. See [Selection](./3.selection.md).
+- The stream methods `map()`, `filter()`, `orderBy()`, `count()`, `join()`, and the others listed on [Stream](./5.stream.md).
diff --git a/docs/1.query_types/3.selection.md b/docs/1.query_types/3.selection.md
index f6e98fb..0eca855 100644
--- a/docs/1.query_types/3.selection.md
+++ b/docs/1.query_types/3.selection.md
@@ -1,26 +1,40 @@
-# Selection
+---
+title: Selection
+description: The Selection query type and its bulk update, replace, and delete methods, including how the update overloads resolve their return type.
+mode: reference
+---
-The `Selection` class represents a set of documents and provides methods for bulk mutation operations. It extends `Stream`, inheriting all transformation, filtering, and aggregation capabilities.
+The `Selection` class represents a set of documents and provides bulk mutation methods. It extends `Stream`, so it also has every transformation, filtering, and aggregation method.
-## Class Definition
+## Class definition
```typescript fragment
class Selection extends Stream {
- update(document: DeepPartial): Query;
- update(document: (val: ValueProxy) => U): Query;
- replace(document: DeepPartial): Query;
+ update(
+ document: (val: ValueProxy) => U,
+ ): ExtractType extends DeepPartial ? Query : never;
+ update(document: DeepPartial & object): Query;
+ replace(document: DeepPartial & object): Query;
delete(): Query;
}
```
-## Create a Selection
+The updater overload comes first and the document overload last:
-Selections are produced by table methods such as `getAll()` and `between()`, or by chaining stream operations like `filter()`:
+- A function argument resolves to the updater overload. It returns `Query` when the type the updater builds fits `DeepPartial`, and `never` otherwise.
+- An object argument resolves to the document overload, which returns `Query`.
+- An argument typed `any` resolves to the updater overload, and the conditional gives `never`. The result has no `.run()` and cannot be awaited for a count. Give the argument a real type.
+- A type-level read such as `ReturnType` takes the last overload and reads `Query`.
+
+## Create a selection
+
+Table methods such as `getAll()` and `between()` produce selections. So does a chained stream operation such as `filter()`:
```typescript
import { Schema } from "@antelopejs/interface-database";
-const schema = Schema.get("myapp")!;
+const schema = Schema.get("myapp");
+if (!schema) throw new Error("Define the myapp schema first");
const users = schema.instance().table("users");
// Selection from getAll
@@ -34,9 +48,9 @@ const recentUsers = users.between(
);
```
-## Update Documents
+## Update documents
-The `update` method modifies matching documents with the provided partial data. Fields not included in the update object remain unchanged. It returns the number of modified documents.
+The `update` method changes the matching documents with the given partial data. Fields that the update object leaves out stay unchanged. It returns the number of modified documents.
```typescript fragment
// Update with a partial object
@@ -52,9 +66,9 @@ await users.getAll("active", "status").update((user) => ({
}));
```
-## Replace Documents
+## Replace documents
-The `replace` method substitutes entire documents with the provided data. Unlike `update`, all fields not present in the replacement are removed.
+The `replace` method replaces each whole document with the given data. Unlike `update`, it removes every field that the replacement leaves out.
```typescript fragment
const count = await users.getAll("user-123").replace({
@@ -67,9 +81,9 @@ const count = await users.getAll("user-123").replace({
console.log(`Replaced ${count} documents`);
```
-## Delete Documents
+## Delete documents
-The `delete` method removes all documents in the selection and returns the number of deleted documents.
+The `delete` method removes every document in the selection and returns the number of deleted documents.
```typescript fragment
const count = await users.getAll("inactive", "status").delete();
@@ -77,9 +91,9 @@ const count = await users.getAll("inactive", "status").delete();
console.log(`Deleted ${count} inactive users`);
```
-## Inherited Methods
+## Inherited methods
-Since `Selection` extends `Stream`, it inherits all stream operations:
+`Selection` extends `Stream`, so it inherits every stream operation. These are grouped by purpose:
- Transformation: `map()`, `pluck()`, `without()`
- Filtering: `filter()`
@@ -88,5 +102,8 @@ Since `Selection` extends `Stream`, it inherits all stream operations:
- Ordering: `orderBy()`, `slice()`, `nth()`
- Grouping: `group()`
- Deduplication: `distinct()`
+- Fields and fallbacks: `key()`, `default()`
+- Combining and typing: `union()`, `cast()`
+- Change feeds: `changes()`
-See [Stream](./5.stream.md) for complete details on these methods.
+See [Stream](./5.stream.md) for the details of these methods.
diff --git a/docs/1.query_types/4.single_selection.md b/docs/1.query_types/4.single_selection.md
index 3a3a3c5..85791b1 100644
--- a/docs/1.query_types/4.single_selection.md
+++ b/docs/1.query_types/4.single_selection.md
@@ -1,38 +1,47 @@
-# SingleSelection
+---
+title: SingleSelection
+description: The SingleSelection query type for one document, with its read, update, replace, delete, and change feed methods.
+mode: reference
+---
-The `SingleSelection` class represents a reference to a single document in a table. It extends `Datum` and provides methods for updating, replacing, deleting, and monitoring changes to an individual document.
+The `SingleSelection` class represents a reference to one document in a table. It extends `Datum` and adds methods that update, replace, delete, and watch that document.
-## Class Definition
+## Class definition
```typescript fragment
class SingleSelection extends Datum {
- update(document: DeepPartial): Query;
- update(document: (val: ValueProxy) => U): Query;
- replace(document: DeepPartial): Query;
+ update(
+ document: (val: ValueProxy) => U,
+ ): ExtractType extends DeepPartial ? Query : never;
+ update(document: DeepPartial & object): Query;
+ replace(document: DeepPartial & object): Query;
delete(): Query;
changes(): Query[]>;
}
```
-## Create a SingleSelection
+The `update` overloads resolve as they do on [Selection](./3.selection.md). An argument typed `any` resolves to `never`, with no `.run()`. A type-level read such as `ReturnType["update"]>` reads `Query`.
-SingleSelection objects are produced by calling `get()` on a table:
+## Create a `SingleSelection`
+
+Calling `get()` on a table produces a `SingleSelection`:
```typescript
import { Schema } from "@antelopejs/interface-database";
-const schema = Schema.get("myapp")!;
+const schema = Schema.get("myapp");
+if (!schema) throw new Error("Define the myapp schema first");
const users = schema.instance().table("users");
// Get a document by primary key
const user = users.get("user-123");
```
-## Read a Document
+## Read a document
-Since `SingleSelection` extends `Datum`, you can execute it directly to retrieve the document value:
+`SingleSelection` extends `Datum`, so you can execute it directly to retrieve the document.
-The declared result is `T`, even though a provider can return `null` for a missing key. Check for absence before accessing the document; the generic type is not an existence guarantee.
+The declared result is `T`, even though a provider can return `null` or `undefined` for a missing key. Check for absence before you access the document. The generic type does not guarantee that the document exists.
```typescript fragment
const user = await users.get("user-123");
@@ -52,9 +61,9 @@ const display = await users.get("user-123").do((u) => ({
}));
```
-## Update a Document
+## Update a document
-The `update` method modifies fields in the document. Unmentioned fields remain unchanged. Returns the number of modified documents.
+The `update` method changes fields in the document. Fields it does not mention stay unchanged. It returns the number of modified documents.
```typescript fragment
// Update with a partial object
@@ -70,9 +79,9 @@ await users.get("user-123").update((user) => ({
}));
```
-## Replace a Document
+## Replace a document
-The `replace` method substitutes the entire document with new data.
+The `replace` method replaces the whole document with new data.
```typescript fragment
const count = await users.get("user-123").replace({
@@ -83,9 +92,9 @@ const count = await users.get("user-123").replace({
});
```
-## Delete a Document
+## Delete a document
-The `delete` method removes the document from the table.
+The `delete` method removes the document from the table and returns the number of deleted documents.
```typescript fragment
const count = await users.get("user-123").delete();
@@ -95,9 +104,9 @@ if (count === 1) {
}
```
-## Monitor Changes
+## Monitor changes
-The `changes` method returns change events for the document. Each event contains a `changeType` and the `oldValue`/`newValue` of the document.
+The `changes` method returns change events for the document. Each event holds a `changeType`, plus the `oldValue` and `newValue` of the document.
```typescript fragment
for await (const change of users.get("user-123").changes()) {
@@ -107,15 +116,15 @@ for await (const change of users.get("user-123").changes()) {
}
```
-## Inherited Methods
+## Inherited methods
-Since `SingleSelection` extends `Datum`, it inherits:
+`SingleSelection` extends `Datum`, so it inherits:
-- **`key()`** - Access a field by name
-- **`default()`** - Provide a fallback value for null
-- **`do()`** - Apply a transformation function
-- **`lookup()`** - Perform a foreign key lookup
-- **`pluck()`** - Select specific fields
-- **`cast()`** - Change the TypeScript type
+- `key()` reads a field by name.
+- `default()` gives a fallback value for `null`.
+- `do()` applies a transformation function.
+- `lookup()` performs a foreign key lookup.
+- `pluck()` selects specific fields.
+- `cast()` changes the TypeScript type.
-See [Datum](./8.datum.md) for complete details.
+See [Datum](./8.datum.md) for the details.
diff --git a/docs/1.query_types/5.stream.md b/docs/1.query_types/5.stream.md
index 3919d5b..9090112 100644
--- a/docs/1.query_types/5.stream.md
+++ b/docs/1.query_types/5.stream.md
@@ -1,8 +1,12 @@
-# Stream
+---
+title: Stream
+description: The Stream query type and its methods for transforming, filtering, joining, grouping, ordering, aggregating, and watching a sequence of documents.
+mode: reference
+---
-The `Stream` class represents a sequence of documents that can be transformed, filtered, joined, grouped, and aggregated. It extends `Query` and serves as the foundation for collection-based operations in AQL.
+The `Stream` class represents a sequence of documents that you can transform, filter, join, group, and aggregate. It extends `Query` and is the base of every collection operation in AQL.
-## Class Definition
+## Class definition
```typescript fragment
class Stream extends Query {
@@ -10,27 +14,54 @@ class Stream extends Query {
cast(): Stream;
// Field access
- key(key: K, def?: U): Stream<
- U extends undefined ? T[K] : Exclude | U
- >;
- default(val: Value): Stream | U>;
+ key(
+ key: ValueProxyOrValue,
+ def?: U & QueryValue,
+ ): Stream | U>;
+ default(
+ val: Value,
+ ): Stream | U>;
// Transformation
map(mapper: (val: ValueProxy) => U): Stream>;
filter(predicate: (val: ValueProxy) => ValueProxyOrValue): this;
- pluck(...fields: string[]): Stream>;
- without(...fields: string[]): Stream>;
+ pluck(...fields: K[]): Stream>;
+ without(...fields: K[]): Stream>;
// Combining
union(other: Stream): Stream;
// Joining
- join(right: Stream, predicate, mapper): Stream>;
- joinInner(right: Stream, predicate, mapper): Stream>;
- lookup(right: Selection, localKey: TK, otherKey: keyof U): Stream<...>;
+ join(
+ right: Stream,
+ predicate: (
+ left: ValueProxy,
+ right: ValueProxy,
+ ) => ValueProxyOrValue,
+ mapper: (left: ValueProxy, right: ValueProxy) => V,
+ ): Stream>;
+ joinInner(
+ right: Stream,
+ predicate: (
+ left: ValueProxy,
+ right: ValueProxy,
+ ) => ValueProxyOrValue,
+ mapper: (left: ValueProxy, right: ValueProxy) => V,
+ ): Stream>;
+ lookup(
+ right: Stream,
+ localKey: TK,
+ otherKey: keyof U & string,
+ ): Stream & Record>;
// Grouping
- group(index: K, mapper: (stream: Stream, group: ValueProxy) => U): Stream>;
+ group(
+ index: K,
+ mapper: (
+ stream: Stream,
+ group: ValueProxy,
+ ) => U,
+ ): Stream>;
// Ordering and pagination
orderBy(index: string, direction?: "asc" | "desc"): this;
@@ -38,24 +69,25 @@ class Stream extends Query {
nth(n: Value): Datum;
// Aggregation
- count(field?: keyof T): Datum;
- sum(field?: keyof T): Datum;
- avg(field?: keyof T): Datum;
- min(field?: keyof T): Datum;
- max(field?: keyof T): Datum;
+ count(field?: keyof T & string): Datum;
+ sum(field?: keyof T & string): Datum;
+ avg(field?: keyof T & string): Datum;
+ min(field?: keyof T & string): Datum;
+ max(field?: keyof T & string): Datum;
distinct(): Datum;
- distinct(field: TK): Stream;
+ distinct(field: undefined): Datum;
+ distinct(field: TK): Stream;
// Change feeds
changes(): Query[]>;
}
```
-## Field Access
+## Field access
-Access a specific field from all documents in the stream with `key()`, or provide a default value for null fields with `default()`.
+`key()` reads one field from every document in the stream. `default()` gives a fallback value for `null` values.
-`key(field, fallback)` takes a field name, or a `ValueProxy` yielding one. It also accepts a fallback. When it is not `undefined`, the result type excludes `null` and `undefined` from the field type and includes the fallback's type; without it, the declared field type is unchanged.
+`key(field, fallback)` takes a field name, or a `ValueProxy` that yields one, and an optional fallback. When the fallback is not `undefined`, the result type drops `null` and `undefined` from the field type and adds the fallback's type. Without a fallback, the declared field type is unchanged.
```typescript fragment
const users = schema.instance().table("users");
@@ -67,11 +99,11 @@ const emails = await users.key("email");
const phones = await users.key("phone").default("N/A");
```
-## Transform Data
+## Transform data
### `map()`
-Transform each document using a mapping function. The callback receives a `ValueProxy` for constructing expressions.
+Transforms each document with a mapping function. The callback receives a `ValueProxy` to build expressions with.
```typescript fragment
const profiles = await users.map((user) => ({
@@ -83,7 +115,7 @@ const profiles = await users.map((user) => ({
### `filter()`
-Select documents matching a predicate. The callback returns a boolean expression.
+Keeps the documents that match a predicate. The callback returns a boolean expression.
```typescript fragment
const active = await users.filter((user) =>
@@ -93,7 +125,7 @@ const active = await users.filter((user) =>
### `pluck()` and `without()`
-Select or exclude specific fields from all documents.
+`pluck()` keeps only the named fields of every document, and `without()` removes them. Both accept only field names of `T`.
```typescript fragment
// Keep only specific fields
@@ -103,11 +135,11 @@ const slim = await users.pluck("_id", "name", "email");
const safe = await users.without("password", "ssn");
```
-## Combine Streams
+## Combine streams
### `union()`
-Concatenate two streams without deduplication.
+Concatenates two streams without deduplication.
```typescript fragment
const allPremium = await users
@@ -115,11 +147,11 @@ const allPremium = await users
.union(users.filter((u) => u.key("plan").eq("enterprise")));
```
-## Join Data
+## Join data
-### `join()` - Left Join
+### `join()` for a left join
-Perform a left join between two streams. Every document from the left stream appears in the result, with `null` for the right side when no match exists.
+Performs a left join between two streams. Every document from the left stream appears in the result. When no right document matches, the right side is `null`.
```typescript fragment
const usersWithOrders = await users.join(
@@ -132,9 +164,9 @@ const usersWithOrders = await users.join(
);
```
-### `joinInner()` - Inner Join
+### `joinInner()` for an inner join
-Perform an inner join. Only documents with matches on both sides appear in the result.
+Performs an inner join. Only documents with a match on both sides appear in the result.
```typescript fragment
const matched = await users.joinInner(
@@ -149,7 +181,7 @@ const matched = await users.joinInner(
### `lookup()`
-Replace a foreign key field with the referenced document from another table. See [Lookup](../2.operations/7.lookup.md) for detailed usage.
+Replaces a foreign key field, or a list of foreign keys, with the referenced documents from another stream. See [Lookup](../2.operations/7.lookup.md) for the details.
```typescript fragment
const postsWithAuthors = await schema
@@ -158,9 +190,9 @@ const postsWithAuthors = await schema
.lookup(schema.instance().table("users"), "authorId", "_id");
```
-## Group Data
+## Group data
-The `group` method partitions the stream by an index value, then applies a mapping function to each group. The mapper receives the group's sub-stream and the group key.
+The `group` method splits the stream by an index value, then applies a mapping function to each group. The mapper receives the stream of the group's documents and the group key. Each mapper result is one element of the new stream.
```typescript fragment
const statsByRole = await users.group("role", (stream, role) => ({
@@ -170,11 +202,11 @@ const statsByRole = await users.group("role", (stream, role) => ({
}));
```
-## Order and Paginate
+## Order and paginate
### `orderBy()`
-Sort the stream by an index field.
+Sorts the stream by an index field, in ascending or descending order.
```typescript fragment
const sorted = await users.orderBy("name", "asc");
@@ -183,7 +215,7 @@ const newest = await users.orderBy("createdAt", "desc");
### `slice()`
-Extract a subsection of the stream for pagination.
+Takes a subsection of the stream, from an offset and for a number of documents, for pagination.
```typescript fragment
const page1 = await users.orderBy("createdAt").slice(0, 10);
@@ -192,15 +224,15 @@ const page2 = await users.orderBy("createdAt").slice(10, 10);
### `nth()`
-Retrieve a single document by its position in the stream. Returns a `Datum`.
+Retrieves one document by its position in the stream. Returns a `Datum`.
```typescript fragment
const first = await users.orderBy("createdAt").nth(0);
```
-## Aggregate Data
+## Aggregate data
-Stream provides several aggregation methods that return a `Datum`:
+These aggregation methods return a `Datum`. `count(field)` counts the distinct values of the field, and `count()` counts the documents.
```typescript fragment
const totalUsers = await users.count();
@@ -213,7 +245,7 @@ const minOrder = await schema.instance().table("orders").min("amount");
### `distinct()`
-Retrieve deduplicated documents or distinct values of a specific field.
+Without a field, `distinct()` returns a `Datum` of the deduplicated documents. With a field, it returns a `Stream` of that field's distinct values.
```typescript fragment
// All distinct documents
@@ -223,9 +255,9 @@ const unique = await users.distinct();
const roles = await users.distinct("role");
```
-## Change Feeds
+## Change feeds
-The `changes` method subscribes to real-time change notifications on the stream.
+The `changes` method turns the stream into a feed of real-time change notifications.
```typescript fragment
for await (const change of users.changes()) {
@@ -233,6 +265,6 @@ for await (const change of users.changes()) {
}
```
-An open-ended feed may never produce a completed array. Consume events incrementally, and stop the loop when the consumer no longer needs them.
+An open-ended feed may never produce a completed array. Consume the events one at a time, and stop the loop when the consumer no longer needs them.
-See [Change Feeds](./6.feed.md) for more details.
+See [Change feeds](./6.feed.md) for the details.
diff --git a/docs/1.query_types/6.feed.md b/docs/1.query_types/6.feed.md
index 84da48d..bc79dca 100644
--- a/docs/1.query_types/6.feed.md
+++ b/docs/1.query_types/6.feed.md
@@ -1,8 +1,12 @@
-# Change Feeds
+---
+title: Change feeds
+description: How changes() turns a stream or a single selection into a feed of change events, the Changes event shape, and how to consume a feed.
+mode: reference
+---
-Change feeds provide real-time notifications when documents are inserted, updated, or deleted. Both `Stream` and `SingleSelection` expose a `changes()` method that returns change events.
+A change feed sends a notification when a document is inserted, updated, or deleted. Both `Stream` and `SingleSelection` expose a `changes()` method that returns change events.
-## The Changes Interface
+## The `Changes` interface
```typescript
interface Changes {
@@ -12,20 +16,21 @@ interface Changes {
}
```
-| Property | Description |
-| ------------ | ------------------------------------------------------------------ |
-| `changeType` | The type of change: `"added"`, `"removed"`, or `"modified"`. |
-| `oldValue` | The document value before the change. Undefined for new documents. |
-| `newValue` | The document value after the change. Undefined for deletions. |
+| Property | Description |
+| ------------ | -------------------------------------------------------------------- |
+| `changeType` | The type of change: `"added"`, `"removed"`, or `"modified"`. |
+| `oldValue` | The document value before the change. `undefined` for new documents. |
+| `newValue` | The document value after the change. `undefined` for deletions. |
-## Create a Change Feed
+## Create a change feed
-Call `changes()` on a stream or single selection to build a feed query. Consume it with async iteration to receive events as they arrive:
+Calling `changes()` on a stream or a single selection builds a feed query. Async iteration over the feed receives the events as they arrive:
```typescript
import { Schema } from "@antelopejs/interface-database";
-const schema = Schema.get("myapp")!;
+const schema = Schema.get("myapp");
+if (!schema) throw new Error("Define the myapp schema first");
const users = schema.instance().table("users");
// Monitor all changes to a table
@@ -35,9 +40,9 @@ const allChanges = users.changes();
const userChanges = users.get("user-123").changes();
```
-## Process Change Events
+## Process change events
-Each change event includes a `changeType` field that indicates the nature of the change:
+Each change event has a `changeType` field that tells what kind of change happened:
```typescript fragment
for await (const change of users.changes()) {
@@ -52,9 +57,9 @@ for await (const change of users.changes()) {
}
```
-## Use with Async Iteration
+## Use with async iteration
-`changes()` returns `Query[]>`, but `Query`'s cursor and async iterator yield each array element: one `Changes` event, not a batch. A direct `await` uses `run()` and asks the provider for a completed result; an open-ended feed may never complete, so use iteration for continuous monitoring.
+`changes()` returns `Query[]>`, but the cursor and the async iterator of `Query` yield each array element. Each iteration gives one `Changes` event, not a batch. A direct `await` calls `run()` and asks the provider for a completed result. An open-ended feed may never complete, so use iteration to watch a feed continuously.
```ts docs-check
import { Schema } from "@antelopejs/interface-database";
@@ -77,13 +82,13 @@ export async function watchUsers(onChange: (change: Changes) => boolean) {
}
```
-Breaking out of the loop closes its cursor. When using `cursor()` manually, call `return()` when finished.
+Breaking out of the loop closes its cursor. If you call `cursor()` yourself, call `return()` on the cursor when you are done.
-## Use Cases
+## Use cases
-Change feeds are well suited for:
+Change feeds fit these uses:
-- **Real-time UI updates** - Push changes to connected clients as they happen.
-- **Event-driven architectures** - React to data changes with downstream processing.
-- **Change observation** - Observe writes while a feed is active. Durable auditing requires provider-specific delivery and recovery guarantees; this interface alone does not promise a complete audit log.
-- **Cache invalidation** - Clear or update caches when the underlying data changes.
+- Real-time UI updates, which push changes to connected clients as they happen.
+- Event-driven architectures, which react to data changes with downstream processing.
+- Change observation, which sees writes while a feed is active. Durable auditing needs delivery and recovery guarantees from the provider. This interface alone does not promise a complete audit log.
+- Cache invalidation, which clears or updates caches when the underlying data changes.
diff --git a/docs/1.query_types/7.query.md b/docs/1.query_types/7.query.md
index 7a73d00..a753e6f 100644
--- a/docs/1.query_types/7.query.md
+++ b/docs/1.query_types/7.query.md
@@ -1,51 +1,60 @@
-# Query
+---
+title: Query
+description: The Query base class and the ways to execute a query, with run(), await, a cursor, or for await...of, plus lazy composition.
+mode: reference
+---
-The `Query` class is the base class for all query types in the AQL system. It implements `PromiseLike`, so queries can be awaited directly, and provides cursor-based async iteration for streaming results.
+The `Query` class is the base class of every AQL query type. It implements `PromiseLike`, so you can await a query directly. It also provides cursor-based async iteration that yields results one at a time.
-## Class Definition
+## Class definition
```typescript fragment
class Query extends StagedObject implements PromiseLike {
run(): Promise;
- then(
- onfulfilled?: (value: T) => TResult1 | PromiseLike,
- onrejected?: (reason: any) => TResult2 | PromiseLike,
+ then(
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | null,
): PromiseLike;
- cursor(): AsyncGenerator ? T1 : T, void, unknown>;
- [Symbol.asyncIterator](): AsyncGenerator<...>;
+ cursor(): AsyncGenerator ? U : T, void, unknown>;
+ [Symbol.asyncIterator](): AsyncGenerator<
+ T extends Array ? U : T,
+ void,
+ unknown
+ >;
}
```
-## Execute a Query
+## Execute a query
### `run()`
-Execute the query pipeline and return the result as a promise.
+Executes the query pipeline and returns the result as a promise.
```typescript
import { Schema } from "@antelopejs/interface-database";
-const schema = Schema.get("myapp")!;
+const schema = Schema.get("myapp");
+if (!schema) throw new Error("Define the myapp schema first");
const users = await schema.instance().table("users").run();
```
### Direct `await`
-Since `Query` implements `PromiseLike`, you can await it directly without calling `run()`:
+`Query` implements `PromiseLike`, so you can await it directly without calling `run()`:
```typescript fragment
const users = await schema.instance().table("users");
```
-Both approaches are equivalent. Use whichever reads better in context.
+Both forms run the same stages: `then()` calls `run()`.
-## Async Iteration
+## Async iteration
-Array-valued queries yield one element at a time, including one `Changes` event per iteration of a change feed. [Atomic mutations](../2.operations/8.atomic_mutations.md) are an exception: they reject cursor execution and must be awaited directly or executed with `run()`.
+A query whose result is an array yields one element at a time. A change feed yields one `Changes` event per iteration. [Atomic mutations](../2.operations/8.atomic_mutations.md) are the exception. Their `cursor()` throws a `TypeError`, so await them directly or execute them with `run()`.
### `cursor()`
-Return an `AsyncGenerator` that yields results one at a time. This is useful for processing large result sets without loading everything into memory.
+Returns an `AsyncGenerator` that yields results one at a time. Each `next()` call reads one result from the provider, so you can process a large result set without holding all of it at once.
```typescript fragment
const cursor = schema.instance().table("users").cursor();
@@ -59,7 +68,7 @@ while (!result.done) {
### `for await...of`
-The `[Symbol.asyncIterator]()` method enables the `for await...of` syntax:
+The `[Symbol.asyncIterator]()` method returns the query's cursor, which enables the `for await...of` syntax:
```typescript fragment
for await (const user of schema.instance().table("users")) {
@@ -67,9 +76,9 @@ for await (const user of schema.instance().table("users")) {
}
```
-## Query Composition
+## Query composition
-Queries use lazy evaluation. Operations are recorded as pipeline stages and only execute when `run()`, `then()`, or iteration begins. This means you can build queries incrementally:
+Queries are lazy. Each operation records a pipeline stage, and the stages execute only when `run()`, `then()`, or iteration starts. You can therefore build queries step by step:
```typescript fragment
const usersTable = schema.instance().table("users");
diff --git a/docs/1.query_types/8.datum.md b/docs/1.query_types/8.datum.md
index d3c29c5..5640183 100644
--- a/docs/1.query_types/8.datum.md
+++ b/docs/1.query_types/8.datum.md
@@ -1,30 +1,46 @@
-# Datum
+---
+title: Datum
+description: The Datum query type for a single value, with its field access, default, transformation, lookup, pluck, and cast methods.
+mode: reference
+---
-The `Datum` class represents a single value in the query system. It extends `Query` and provides methods for accessing fields, applying transformations, and performing foreign key lookups on individual values.
+The `Datum` class represents a single value in a query. It extends `Query` and adds methods that read fields, apply transformations, and perform foreign key lookups on that value.
-## Class Definition
+## Class definition
```typescript fragment
class Datum extends Query {
cast(): Datum;
- key, U = undefined>(key: K, def?: U): Datum<
+ key & string, U = undefined>(
+ key: K,
+ def?: U & QueryValue,
+ ): Datum<
U extends undefined ? NonNullable[K] : NonNullable[K]> | U
>;
- default(val: Value): Datum | U>;
+ default(
+ val: Value,
+ ): Datum | U>;
do(mapper: (val: ValueProxy) => U): Datum>;
- lookup(other: Selection, localKey: keyof T, otherKey: keyof U): Datum<...>;
- pluck(...fields: string[]): Datum>;
+ lookup(
+ other: Stream,
+ localKey: TK,
+ otherKey: keyof U & string,
+ ): Datum & Record>;
+ pluck & string>(
+ ...fields: K[]
+ ): Datum, K> | Extract>;
}
```
-## Field Access
+## Field access
-The `key` method retrieves a field from a document datum. You can chain `key` calls for nested field access. A non-undefined fallback removes `null` and `undefined` from the declared field result and adds the fallback's type. Omitting the fallback preserves the declared field type.
+The `key` method reads a field from a document datum. Chain `key` calls to read a nested field. A fallback that is not `undefined` removes `null` and `undefined` from the declared field type and adds the fallback's type. Without a fallback, the declared field type is unchanged.
```typescript
import { Schema } from "@antelopejs/interface-database";
-const schema = Schema.get("myapp")!;
+const schema = Schema.get("myapp");
+if (!schema) throw new Error("Define the myapp schema first");
const users = schema.instance().table("users");
// Access a field
@@ -37,9 +53,9 @@ const city = await users.get("user-123").key("address").key("city");
const phone = await users.get("user-123").key("phone", "N/A");
```
-## Default Values
+## Default values
-The `default` method provides a fallback value when the datum is null or undefined.
+The `default` method gives a fallback value for a datum that is `null`.
```typescript fragment
const user = await users.get("maybe-missing").default({
@@ -51,7 +67,7 @@ const user = await users.get("maybe-missing").default({
## Transform with `do()`
-The `do` method applies a transformation function to the datum. The callback receives a `ValueProxy` that provides operators for building expressions.
+The `do` method applies a transformation function to the datum. The callback receives a `ValueProxy`, whose operators build expressions.
```typescript fragment
const formatted = await users.get("user-123").do((user) => ({
@@ -61,9 +77,9 @@ const formatted = await users.get("user-123").do((user) => ({
}));
```
-## Foreign Key Lookup
+## Foreign key lookup
-The `lookup` method replaces a foreign key field with the referenced document from another table.
+The `lookup` method replaces a foreign key field with the referenced document from another stream, such as a table. A field that holds a list of keys becomes a list of documents.
```typescript
import { Schema } from "@antelopejs/interface-database";
@@ -79,7 +95,8 @@ interface User {
name: string;
}
-const blog = Schema.get<{ posts: Post; users: User }>("blog")!;
+const blog = Schema.get<{ posts: Post; users: User }>("blog");
+if (!blog) throw new Error("Define the blog schema first");
const post = await blog
.instance()
.table("posts")
@@ -90,17 +107,17 @@ const post = await blog
console.log(post.authorId.name);
```
-## Field Selection
+## Field selection
-The `pluck` method selects specific fields from a document datum, discarding the rest.
+The `pluck` method keeps the named fields of a document datum and discards the rest.
```typescript fragment
const profile = await users.get("user-123").pluck("name", "email", "avatar");
```
-## Type Casting
+## Type casting
-The `cast` method changes the TypeScript type of the datum without performing any runtime conversion. Use this when you know the actual type differs from what the compiler infers.
+The `cast` method changes the TypeScript type of the datum and performs no runtime conversion. Use it when the actual type differs from what the compiler infers. Nothing checks that the new type matches what the query returns.
```typescript fragment
const raw = users.get("user-123").cast<{ name: string; role: string }>();
diff --git a/docs/1.query_types/9.valueproxy.md b/docs/1.query_types/9.valueproxy.md
index 43e1f15..86be34f 100644
--- a/docs/1.query_types/9.valueproxy.md
+++ b/docs/1.query_types/9.valueproxy.md
@@ -1,14 +1,18 @@
-# ValueProxy
+---
+title: ValueProxy
+description: The ValueProxy class that query callbacks receive, and its operators on any value, numbers, dates, strings, arrays, and objects.
+mode: reference
+---
-The `ValueProxy` class acts as a proxy to a database value. Operations on a ValueProxy are not executed immediately but are recorded as query stages. ValueProxy objects appear inside callback functions for `map`, `filter`, `do`, `update`, `join`, and other query methods.
+The `ValueProxy` class is a proxy to a value in the database. An operation on a `ValueProxy` does not run right away. The proxy records it as a query stage. Callbacks of `map`, `filter`, `do`, `update`, `join`, and other query methods receive `ValueProxy` objects.
-## Class Definition
+## Class definition
-ValueProxy provides type-specific methods based on the underlying value type. The available operations are organized by category.
+Which `ValueProxy` methods compile depends on the type of the underlying value. This page groups the operations by that type.
-## Create a ValueProxy
+## Create a `ValueProxy`
-ValueProxy instances are created automatically when you use callback-based query methods. You can also create one from a constant value:
+Query methods that take a callback create the `ValueProxy` instances they pass to it. `ValueProxy.constant()` creates one from a constant value:
```typescript
import { ValueProxy } from "@antelopejs/interface-database";
@@ -21,19 +25,19 @@ A constant, and any operand a stage records (`eq`, `ne`, `default`, `gt`, `inclu
`eq`, `ne`, `gt`, `ge`, `lt`, `le` and `includes` check the operand against the type of the proxy, so a record type declared with `interface` is accepted there. `ValueProxy.constant`, `default`, `sub`, `merge` and the default of `key` infer their type from the argument and need a `QueryValue`: an `interface` has no implicit index signature and does not compile, so declare the record with `type`.
-## General Operations
+## General operations
-These methods are available on all ValueProxy types.
+These methods are available on every `ValueProxy`.
### `default(value)`
-Return the given value if the proxy is null.
+Returns the given value if the proxy is `null`.
```typescript fragment
const phone = user.key("phone").default("N/A");
```
-### `eq(value)` / `ne(value)`
+### `eq(value)` and `ne(value)`
Equality and inequality comparison.
@@ -42,9 +46,9 @@ const isActive = user.key("status").eq("active");
const isNotAdmin = user.key("role").ne("admin");
```
-### `and(value)` / `or(value)` / `not()`
+### `and(value)`, `or(value)`, and `not()`
-Logical operators for combining boolean expressions. The operand of `and` and `or` compiles when it is a `boolean`, `null`, `undefined`, `any`, or a `ValueProxy` of `boolean`, `boolean | null` or `boolean | undefined`. A `string`, a `number`, a `ValueProxy`, a `ValueProxy`, a generic `ValueProxy` and a union such as `ValueProxy | undefined` are compile-time errors. The database evaluates operands by truthiness, so the type is narrower than what the runtime accepts.
+Logical operators that combine boolean expressions. The operand of `and` and `or` compiles when it is a `boolean`, `null`, `undefined`, `any`, or a `ValueProxy` of `boolean`, `boolean | null` or `boolean | undefined`. A `string`, a `number`, a `ValueProxy