Database
Lookup
The lookup method performs a foreign key join, replacing a field in your documents with the referenced data from another table. It is available on both Stream<T> and Datum<T>.
Parameters
| Parameter | Type | Description |
|---|---|---|
other | Selection<U> | The table containing the referenced documents |
localKey | keyof T | The field in the current document holding the foreign key |
otherKey | keyof U | The field in the other table to match against |
Behavior
The lookup method replaces the local key field with the populated data:
- If the local key contains a single value, the field becomes a single document from the other table.
- If the local key contains an array of values, the field becomes an array of documents from the other table.
- TypeScript automatically tracks the type transformation.
One-to-One Relationship
Replace a single foreign key with the referenced document:
import { Schema } from "@antelopejs/interface-database";
interface Post {
_id: string;
title: string;
authorId: string;
}
interface User {
_id: string;
name: string;
email: string;
}
const schema = Schema.get<{ posts: Post; users: User }>("blog")!;
const db = schema.instance();
const postsWithAuthors = await db
.table("posts")
.lookup(db.table("users"), "authorId", "_id");
// Result type: { _id: string, title: string, authorId: User }
for (const post of postsWithAuthors) {
console.log(`${post.title} by ${post.authorId.name}`);
}
One-to-Many Relationship
Replace an array of foreign keys with an array of documents:
interface Order {
_id: string;
customerId: string;
productIds: string[];
total: number;
}
interface Product {
_id: string;
name: string;
price: number;
}
const shop = Schema.get<{ orders: Order; products: Product }>(
"shop",
)!.instance();
const ordersWithProducts = await shop
.table("orders")
.lookup(shop.table("products"), "productIds", "_id");
// Result type: { ..., productIds: Product[] }
for (const order of ordersWithProducts) {
for (const product of order.productIds) {
console.log(` - ${product.name}: $${product.price}`);
}
}
Combine with Other Operations
Lookup works seamlessly with filters, sorting, and other stream operations:
// The shop schema also declares customers and orders.createdAt.
const db = Schema.get("shop")!.instance();
// Filter before lookup
const recentWithCustomers = await db
.table("orders")
.filter((order) => order.key("createdAt").gt(new Date("2024-01-01")))
.lookup(db.table("customers"), "customerId", "_id");
// Lookup on a single document
const blog = Schema.get<{ posts: Post; users: User }>("blog")!.instance();
const post = await blog
.table("posts")
.get("post-1")
.lookup(blog.table("users"), "authorId", "_id");
Lookup vs Join
Use lookup when:
- You have a straightforward foreign key relationship.
- You want the referenced data to replace the foreign key field.
- You are working with one-to-one or one-to-many relationships.
Use join or joinInner when:
- You need custom join predicates beyond simple equality.
- You want to keep both original and joined data as separate fields.
- You need to transform the result into a custom structure.
Comparison
// Lookup: replaces the field
const withLookup = await db
.table("orders")
.lookup(db.table("customers"), "customerId", "_id");
// order.customerId is now the full customer object
// Join: custom output structure
const withJoin = await db.table("orders").joinInner(
db.table("customers"),
(order, customer) => order.key("customerId").eq(customer.key("_id")),
(order, customer) => ({
orderId: order.key("_id"),
orderTotal: order.key("total"),
customerName: customer.key("name"),
}),
);
// Result is a custom object with selected fields from both tables
Filtering and Querying
This guide covers techniques for selecting, comparing, and transforming documents using AQL's filter and ValueProxy capabilities.
Atomic single-record mutations
Table.atomicMutation(id, request) checks one immutable, instance-scoped record identity and one condition inside the database mutation. It returns Query<AtomicMutationOutcome>. It does not provide multi-record transactions, a lock service, receipts, or external-effect coordination.