#aggregate component
6 messages · Page 1 of 1 (latest)
Hi @split yacht. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
The knowledge sources don’t describe the exact internal document format of the Aggregate component, but they do explain the model clearly enough to answer your use case.
How data is stored conceptually
The Aggregate component keeps its own internal data structure in Convex, implemented as a balanced search tree (a B‑tree–style structure). This structure is a sorted key–value store with denormalized counts and sums so that operations like count, sum, min, max, at, indexOf are O(log n) instead of scanning all your documents. [How it works; What aggregates are for]
You don’t interact with those internal “aggregate tables” directly. Instead:
-
You define a
TableAggregatethat says:- which Convex table to aggregate over,
- how to compute the key (
sortKey) used for ordering, - optionally how to compute the sum value (
sumValue), - optionally a
namespaceto partition data. [Usage; What aggregates are for]
-
On every write to your source table, you also call
aggregate.insert,aggregate.replace, oraggregate.deletewith the corresponding document, so the aggregate’s internal tree stays in sync. [Usage; Operations]
Mapping this to your example
“table that should contain the amount stored by the user and i want to get the largest amount and the total amount stored this month”
Assume you have a Convex table like:
deposits:{ userId, amount, createdAt }
You might define a TableAggregate something like:
const depositsAgg = new TableAggregate<{
Key: number; // or [userId, createdAt], etc.
DataModel: DataModel;
TableName: "deposits";
}>(components.depositsAgg, {
sortKey: (doc) => doc.amount, // for max by amount
sumValue: (doc) => doc.amount, // for total amount
});
This tells the Aggregate component:
- Key:
amount(so the internal tree is ordered by amount). - Sum value: also
amount(so.sumwill give you the total of all amounts in whatever range you query). [Usage]
Then in your mutations:
// insert
const id = await ctx.db.insert("deposits", { userId, amount, createdAt });
const doc = await ctx.db.get("deposits", id);
await depositsAgg.insert(ctx, doc!);
// update
const oldDoc = await ctx.db.get("deposits", id);
await ctx.db.patch("deposits", id, { amount });
const newDoc = await ctx.db.get("deposits", id);
await depositsAgg.replace(ctx, oldDoc!, newDoc!);
// delete
const oldDoc = await ctx.db.get("deposits", id);
await ctx.db.delete("deposits", id);
await depositsAgg.delete(ctx, oldDoc!);
This keeps the internal aggregate tree up to date. [Usage]
How you query “largest amount” and “total this month”
Once the aggregate is populated, you don’t read from any “aggregate table” directly; you call methods on the TableAggregate instance:
- Largest amount overall: use
max:
const maxItem = await depositsAgg.max(ctx);
const largestAmount = maxItem?.sumValue; // or key, depending on how you set it up
This walks the internal tree to the maximum key in O(log n) time. [Other demos – max; Aggregate overview]
- Total amount stored this month: use
sumwith bounds that represent “this month”.
The docs show the pattern for summing over a subset using bounds or prefixes; for example, summing scores for a single user: [Setup – user average]
const count = await aggregateScoreByUser.count(ctx, {
bounds: { prefix: [args.name] },
});
const sum = await aggregateScoreByUser.sum(ctx, {
bounds: { prefix: [args.name] },
});
For “this month”, you’d choose a key that encodes time (e.g. _creationTime or a [month, userId] tuple) and then call:
const totalThisMonth = await depositsAgg.sum(ctx, {
bounds: {
lower: { key: monthStart, inclusive: true },
upper: { key: monthEnd, inclusive: false },
},
});
The Aggregate component uses its internal B‑tree to:
- find the range of keys between
monthStartandmonthEnd, - use its stored partial sums to compute the total in
O(log n)time, without scanning all deposits. [What aggregates are for; How it works]
How the response looks
Each method returns plain values or small objects:
count(ctx, ...)→ anumber(how many items in the range). [Aggregate overview]sum(ctx, ...)→ anumber(sum ofsumValuein the range). [Setup – stats example]max(ctx, ...)/min(ctx, ...)→ an object with at least{ key, id?, sumValue? }depending on how you configured it; in the examples they readitem.sumValueoritem.key. [Setup – userHighScore; Aggregate without a table]at(ctx, index)→{ key, id, sumValue? }for the item at that rank. [Setup – pageOfScores; Aggregate without a table]
So in your case, you’d get:
- a
numberfor “total amount this month” from.sum, - an object from
.maxfrom which you read the amount (viakeyorsumValue, depending on your configuration).