Batch operations
Batch operations let you bundle several read operations into a single HTTP request. Instead of issuing one request per operation, you send them all at once and receive a tuple of results back, fully typed and in the same order as the operations you provided.
This is useful for reducing round-trips when a view or screen needs data from multiple collections or multiple queries at the same time.
"json"format. Calling batch() while the client is in "routed" format throws an error, since routed requests rely on URL path segments that cannot describe multiple operations.Supported operations
Only read operations can be batched:
| Operation | Description |
|---|---|
findOne | Find a single document matching a filter |
find | Find multiple documents matching a filter |
aggregate | Run an aggregation pipeline |
Usage
Call batch() on a Db instance with an array of operations. Each operation is created with the BatchOperation helper, which provides typed factory methods for every supported operation.
import { MongalayerClient, BatchOperation } from "@mongalayer/client";
const client = new MongalayerClient("http://localhost");
const db = client.db("app");
const [project, projectAssets, assetStats] = await db.batch([
BatchOperation.findOne<Project>("projects", {
filter: { _id: "project-123" },
}),
BatchOperation.find<Asset>("assets", {
filter: { projectID: "project-123" },
options: { sort: { supply: -1 } }
}),
BatchOperation.aggregate<Asset>("assets", {
pipeline: [
{ $match: { projectID: "project-123" } },
{ $group: { _id: "$status", count: { $sum: 1 } } }
]
})
]);
// project: Project | null
// projectAssets: Asset[]
// assetStats: { _id: string; count: number }[]
The result is a tuple whose types match each operation in order:
findOnereturnsTSchema | nullfindreturnsTSchema[]aggregatereturns the projected result type[]
Defining operations
There are three ways to define an operation in a batch. They are interchangeable and can be mixed within the same batch — pick whichever reads best for your code.
To get the most accurate result types, the collection's schema type must reach BatchOperation. The recommended way to do this is to type the collection name as a CollectionName<TSchema>, either through a variable, a helper function, or an inline assertion.
import { MongalayerClient, BatchOperation } from "@mongalayer/client";
import type { CollectionName } from "@mongalayer/client";
const db = new MongalayerClient("http://localhost").db("app");
// A typed collection name can be reused across operations
const users: CollectionName<User> = "users";
Static methods
The factory methods (findOne, find, aggregate) are the most convenient option. When you cannot supply a typed CollectionName, pass the schema as a generic to keep result inference intact.
await db.batch([
// Typed collection name — operation and schema are inferred
BatchOperation.find(users, {
filter: { roles: { $in: ["admin"] } }
}),
// Schema passed as a generic — preferred when using a plain string name
BatchOperation.findOne<Project>("projects", {
filter: { _id: "project-123" }
})
]);
Constructor
Use new BatchOperation(collection, operation, payload) when you prefer constructing instances directly. Prefer passing a typed CollectionName so the operation argument narrows the payload and result types.
await db.batch([
new BatchOperation(users, "find", {
filter: { roles: { $in: ["admin"] } }
})
]);
new BatchOperation<User>(...)). Doing so breaks inference for the operation argument, so the result widens to a union of all supported operation return types. Use a typed CollectionName or the static methods instead.Object literals
You can also pass plain objects matching the BatchOperation shape, which is handy when operations are built dynamically. Type the collection field as a CollectionName<TSchema> to retain result types.
await db.batch([
{
collection: "users" as CollectionName<User>,
operation: "find",
payload: {
filter: { roles: { $in: ["admin"] } }
}
}
]);
Mixing across collections
A single batch can combine all three styles over different collections. The result tuple stays fully typed in order:
const [activeUsers, projectA, nearbyStats] = await db.batch([
new BatchOperation(users, "find", {
filter: { roles: { $in: ["admin"] } }
}),
BatchOperation.findOne("projects" as CollectionName<Project>, {
filter: { _id: "project-123" }
}),
{
collection: "assets" as CollectionName<Asset>,
operation: "aggregate",
payload: {
pipeline: [
{ $match: { projectID: "project-123" } },
{ $group: { _id: "$status", count: { $sum: 1 } } }
]
}
}
]);
// admins: User[]
// projectA: Project | null
// nearbyStats: { _id: string, count: number }[]
string, operations fall back to the generic Document schema, so filters and results are not type-checked against a specific shape.BatchOperation helpers
Each factory method takes the target collection name and the operation payload.
BatchOperation.findOne(collection, payload)
BatchOperation.findOne<Project>("projects", {
filter: { _id: "project-123" },
options: { projection: { name: 1 } }
});
| Payload field | Type | Description |
|---|---|---|
filter | Filter<TSchema> | The filter to match a document |
options | FindOneOptions | Optional projection and sort |
BatchOperation.find(collection, payload)
BatchOperation.find<Asset>("assets", {
filter: { projectID: "project-123" },
options: { limit: 10, sort: { supply: -1 } }
});
| Payload field | Type | Description |
|---|---|---|
filter | Filter<TSchema> | The filter to match documents |
options | FindOptions | Optional projection, limit, skip and sort |
BatchOperation.aggregate(collection, payload)
BatchOperation.aggregate<Asset>("assets", {
pipeline: [
{ $match: { projectID: "project-123" } },
{ $group: { _id: "$status", count: { $sum: 1 } } }
]
});
| Payload field | Type | Description |
|---|---|---|
pipeline | Document[] | The aggregation pipeline stages |
options | AggregateOptions | Optional aggregation options |
Automatic batching
Instead of constructing batches by hand, you can let the client batch operations for you. When autoBatch is enabled, supported read operations (find, findOne, aggregate) called through the normal collection API are queued and combined into a single batch request automatically.
const client = new MongalayerClient("http://localhost", { autoBatch: true });
const db = client.db("app");
// These three calls are automatically batched together in one request
const user = await db.collection<User>("users").findOne({ _id: "user-1" });
const projects = await db.collection<Project>("projects").find({ owner: "user-1" });
const stats = await db.collection<Asset>("assets").aggregate([{ $group: { _id: "$status", count: { $sum: 1 } } }]);
Operations are collected for a short window — controlled by autoBatchDelay (default 10 ms) — and then flushed together. Only the batchable read operations participate; all other operations are sent as individual requests.
Per-collection control
You can enable or disable auto-batching for a specific collection, regardless of the client-wide setting, by passing the autoBatch option when creating the collection:
// Force batching on for this collection even if the client default is off
const users = db.collection<User>("users", { autoBatch: true });
// Opt this collection out even if the client default is on
const audit = db.collection<Log>("audit", { autoBatch: false });
Opting a single call out
Use the withoutBatch getter to run one call outside of auto-batching while keeping the collection's batching behavior for everything else:
const users = db.collection<User>("users");
// Sent as its own request, bypassing the auto-batch queue
const user = await users.withoutBatch.findOne({ _id: "user-1" });
Context
Like collection methods, batch() accepts an optional context parameter as its last argument. The context applies to the whole batch request:
await db.batch(
[
BatchOperation.find<Project>("projects", { filter: { owner: "user-1" } })
],
{ requestId: "abc-123" }
);
See context for details on how the value is encoded and used.