Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# @antelopejs/interface-database
# `@antelopejs/interface-database`

<div align="center">
<a href="https://www.npmjs.com/package/@antelopejs/interface-database"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/interface-database?style=for-the-badge&labelColor=000000&color=000000"></a>
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/interface-database?style=for-the-badge&labelColor=000000&color=000000"></a>
<a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000"></a>
<a href="https://interfaces.antelopejs.com/docs/database"><img alt="Docs" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000"></a>
<a href="https://www.npmjs.com/package/@antelopejs/interface-database"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/interface-database?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a>
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/interface-database?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a>
<a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000" data-illustration></a>
<a href="https://interfaces.antelopejs.com/docs/database"><img alt="Docs" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000" data-illustration></a>
</div>

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

Expand Down Expand Up @@ -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

Expand Down
50 changes: 27 additions & 23 deletions docs/1.query_types/1.index.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -13,13 +17,13 @@ AQL provides several query classes that form a hierarchy of capabilities. Each c
| One query result | `Datum<T>` | Read a field or transform the value. |
| A value inside a query callback | `ValueProxy<T>` | 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.

![Compose query stages without database I/O, then execute through the runtime provider. The staged-query entry point only builds stages.](../assets/query-execution.svg){width="480"}
![Compose query stages without database I/O, then execute through the runtime provider. The staged-query entry point only builds stages.](../assets/query-execution.svg "illustration"){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<T>
Expand All @@ -32,14 +36,14 @@ Query<T>
ValueProxy<T> (used within query callbacks)
```

## Common Features
## Common features

All query types that extend `Query<T>` share these capabilities:
Every query type that extends `Query<T>` has these methods:

- **`run()`** - Execute the query and return a `Promise<T>` 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<T>` 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";
Expand All @@ -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:

Expand All @@ -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.
49 changes: 25 additions & 24 deletions docs/1.query_types/2.table.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
# 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<T>` class represents a database table and provides methods for inserting documents and retrieving them by primary key, secondary index, or range. It extends `Selection<T>`, inheriting all multi-document operations and stream capabilities.
The `Table<T>` class represents a database table. It inserts documents and retrieves them by primary key, secondary index, or range. It extends `Selection<T>`, so it also has every multi-document operation and stream method.

## Class Definition
## Class definition

```typescript fragment
class Table<T> extends Selection<T> {
atomicMutation(
id: string,
key: string,
request: AtomicMutation<T>,
): Query<AtomicMutationOutcome>;
insert(
obj: DeepPartial<T> | DeepPartial<T>[],
obj: (DeepPartial<T> & object) | (DeepPartial<T> & object)[],
options?: InsertOptions,
): Query<string[]>;
get(key: string): SingleSelection<T>;
getAll(
keys: string | number | boolean | (string | number | boolean)[],
index?: string,
): Selection<T>;
between<TK extends keyof T>(
get(key: ValueProxyOrValue<string>): SingleSelection<T>;
getAll(keys: IndexKeys, index?: string): Selection<T>;
between<TK extends keyof T & string>(
index: TK,
low: T[TK] & IndexBound,
high: T[TK] & IndexBound,
): Selection<T>;
}
```

## 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";
Expand All @@ -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.

Expand Down Expand Up @@ -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<T>`.
Retrieves a single document by its primary key, given as a string or a `ValueProxy` of a string. Returns a `SingleSelection<T>`.

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");
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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).
53 changes: 35 additions & 18 deletions docs/1.query_types/3.selection.md
Original file line number Diff line number Diff line change
@@ -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<T>` class represents a set of documents and provides methods for bulk mutation operations. It extends `Stream<T>`, inheriting all transformation, filtering, and aggregation capabilities.
The `Selection<T>` class represents a set of documents and provides bulk mutation methods. It extends `Stream<T>`, so it also has every transformation, filtering, and aggregation method.

## Class Definition
## Class definition

```typescript fragment
class Selection<T> extends Stream<T> {
update(document: DeepPartial<T>): Query<number>;
update<U>(document: (val: ValueProxy<T>) => U): Query<number>;
replace(document: DeepPartial<T>): Query<number>;
update<U>(
document: (val: ValueProxy<T>) => U,
): ExtractType<U> extends DeepPartial<T> ? Query<number> : never;
update(document: DeepPartial<T> & object): Query<number>;
replace(document: DeepPartial<T> & object): Query<number>;
delete(): Query<number>;
}
```

## 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<number>` when the type the updater builds fits `DeepPartial<T>`, and `never` otherwise.
- An object argument resolves to the document overload, which returns `Query<number>`.
- 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<typeof table.update>` takes the last overload and reads `Query<number>`.

## 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
Expand All @@ -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
Expand All @@ -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({
Expand All @@ -67,19 +81,19 @@ 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();

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()`
Expand All @@ -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.
Loading
Loading