### Install Dependencies and Start Development Server Source: https://github.com/get-convex/aggregate/blob/main/CONTRIBUTING.md Install project dependencies and start the local development server. ```sh npm i npm run dev ``` -------------------------------- ### Example SideBounds Configurations Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Illustrates different ways to configure SideBounds, showing examples with only a lower bound, only an upper bound, or both. ```typescript { lower: { key: 50, inclusive: true } } ``` ```typescript { upper: { key: 150, inclusive: false } } ``` ```typescript { lower: { key: 50, inclusive: true }, upper: { key: 150, inclusive: true } } ``` -------------------------------- ### Install Aggregate Package Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Install the @convex-dev/aggregate package using npm. ```bash npm install @convex-dev/aggregate ``` -------------------------------- ### Usage Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt A collection of 6 complete usage examples demonstrating various patterns, including leaderboards, photo albums, random shuffles, percentiles, time-based analytics, and batch operations. ```APIDOC ## Usage Examples ### Description This section provides 6 complete, production-ready examples showcasing diverse patterns and use cases for the API. ### Patterns Covered - **Leaderboard**: Global and per-user leaderboards. - **Photo albums**: Implementation with pagination. - **Random shuffle**: Generating random shuffles. - **Percentiles**: Calculating percentiles. - **Time-based analytics**: Examples for time-series data. - **Batch operations**: Demonstrating batch processing. ``` -------------------------------- ### Install Aggregate Component Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/configuration.md Install the Aggregate component using `app.use()`. You can use the default name 'aggregate' or specify a custom name for different instances. ```typescript import aggregate from "@convex-dev/aggregate/convex.config.js"; app.use(aggregate); app.use(aggregate, { name: "gameLeaderboard" }); app.use(aggregate, { name: "photoIndex" }); ``` -------------------------------- ### Build a One-Off Package Source: https://github.com/get-convex/aggregate/blob/main/CONTRIBUTING.md Clean the project, install exact dependencies, and pack the project for distribution. ```sh npm run clean npm ci npm pack ``` -------------------------------- ### Bound Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Illustrates how to define boundary points for range queries using the Bound type. Shows examples for inclusive and exclusive boundaries, with and without an ID for tie-breaking. ```typescript { key: 100, inclusive: true } // >= 100 { key: 100, inclusive: false } // > 100 { key: 100, id: "xyz", inclusive: true } // >= [100, "xyz"] ``` -------------------------------- ### Leaderboard Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Demonstrates a complete pattern for implementing a global and per-user leaderboard using aggregate classes. ```typescript // Example usage for leaderboard pattern // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Photo Albums with Pagination Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Illustrates a pattern for managing photo albums with pagination capabilities. ```typescript // Example usage for photo albums with pagination // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Percentiles Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Demonstrates a pattern for calculating and displaying percentiles. ```typescript // Example usage for percentiles // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Batch Operations Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Shows a pattern for performing batch operations efficiently. ```typescript // Example usage for batch operations // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Configuration Options Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Details on component installation, named instances, and aggregate clear options like maxNodeSize and rootLazy are provided. ```typescript const aggregate = new Aggregate({ maxNodeSize: 1000, rootLazy: true }); ``` -------------------------------- ### Time-Based Analytics Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Illustrates a pattern for implementing time-based analytics. ```typescript // Example usage for time-based analytics // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Setup Convex Project Source: https://github.com/get-convex/aggregate/blob/main/example/README.md Run this command from the root of the repository to set up your Convex project. Follow the on-screen instructions for project configuration. ```bash npm run setup ``` -------------------------------- ### Pagination with Offset and `paginate` Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Get the Nth page of results using `aggregate.paginate`. This example demonstrates calculating the offset for a specific page number and size. ```typescript const pageNumber = 2; const pageSize = 50; const offset = (pageNumber - 1) * pageSize; const { page } = await aggregate.paginate(ctx, { pageSize, order: "asc" }); ``` -------------------------------- ### Install Aggregate Component Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/api-index.md Install the aggregate component into your Convex app. You can use the default name or provide a custom name. ```typescript import aggregate from "@convex-dev/aggregate/convex.config.js"; const app = defineApp(); app.use(aggregate); // Default name app.use(aggregate, { name: "customName" }); // Custom name ``` -------------------------------- ### Random Shuffle Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Provides a pattern for implementing a random shuffle functionality. ```typescript // Example usage for random shuffle // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### Install Aggregate Component Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/configuration.md Add the Aggregate component to your `convex/convex.config.ts` file to integrate it into your Convex app. ```typescript import { defineApp } from "convex/server"; import aggregate from "@convex-dev/aggregate/convex.config.js"; const app = defineApp(); app.use(aggregate); export default app; ``` -------------------------------- ### Example Equality Query Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Illustrates an equality query to select items where the key exactly matches a specified value. ```typescript { eq: 100 } // Only items with key exactly equal to 100 ``` -------------------------------- ### TuplePrefix Usage Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Illustrates valid and invalid prefix values for a tuple key type `[string, number, boolean]`. ```typescript { prefix: [] } // Valid { prefix: ["game"] } // Valid { prefix: ["game", 100] } // Valid { prefix: ["game", 100, true] } // Valid { prefix: ["game", 100, true, "extra"] } // Invalid (too long) ``` -------------------------------- ### DirectAggregate Custom Data Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Demonstrates how to use DirectAggregate for custom data operations. ```typescript // Example usage for DirectAggregate custom data // (Specific code not provided in source, conceptual representation) ``` -------------------------------- ### TableAggregate Initialization Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Shows how to initialize a TableAggregate for a Convex table, specifying functions to extract sort keys and sum values. ```typescript const aggregate = new TableAggregate({ sortKey: (doc) => doc.score, sumValue: (doc) => doc.points }); ``` -------------------------------- ### Configuration Options Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Details on configuration options for the system, including component installation, named instances, aggregate clear options, and table aggregate options. ```APIDOC ## Configuration Options ### Description This section outlines the various configuration options available for setting up and tuning the system. ### Options - **Component installation**: Guidance on how to install components. - **Named component instances**: Information on creating and managing named instances. - **Aggregate clear() options**: Details on `maxNodeSize` and `rootLazy` for clearing aggregates. - **TableAggregate options**: Configuration for `sortKey`, `sumValue`, and `namespace`. - **Performance tuning guidance**: Recommendations for optimizing performance. - **Test registration**: Information on how to register tests. ``` -------------------------------- ### TableAggregate Initialization with Namespace Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Demonstrates initializing a TableAggregate with a namespace function to partition data by a specific field, like userId. ```typescript const byUser = new TableAggregate({ namespace: (doc) => doc.userId, sortKey: (doc) => doc.score, sumValue: (doc) => doc.points }); ``` -------------------------------- ### Aggregate Bounds Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Illustrates different types of bounds for filtering aggregate items: range, prefix, equality, and no bounds. ```typescript { bounds: { lower: { key: 95 }, upper: { key: 100 } } } ``` ```typescript { bounds: { prefix: ["game1"] } } ``` ```typescript { bounds: { eq: 100 } } ``` ```typescript {} ``` -------------------------------- ### Insert Score Mutation Example Source: https://github.com/get-convex/aggregate/blob/main/README.md This example demonstrates a mutation that inserts a score into the 'scores' table and then updates an aggregate. It's part of a recommended pattern for encapsulating table update logic. ```typescript export const playAGame = mutation(async (ctx) => { ... await insertScore(ctx, gameId, user1, user1Score); await insertScore(ctx, gameId, user2, user2Score); }); // All inserts to the "scores" table go through this function. async function insertScore(ctx, gameId, username, score) { const id = await ctx.db.insert("scores", { gameId, username, score }); await doc = await ctx.db.get(id); await aggregateByGame.insert(ctx, doc!); } ``` -------------------------------- ### TableAggregateType Example Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md An example demonstrating how to define a specific table aggregate type for scores, including its key, data model, table name, and namespace. ```typescript type ScoresAggregate = TableAggregateType<{ Key: number; DataModel: DataModel; TableName: "scores"; Namespace: string; // Optional }>; ``` -------------------------------- ### Example Range Query with Side Bounds Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Demonstrates a range query using both lower and upper bounds, specifying inclusive boundaries for the selected items. ```typescript { lower: { key: 95, inclusive: true }, upper: { key: 100, inclusive: true } } ``` -------------------------------- ### Aggregate Class Methods Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the Aggregate base class, including over 40 public methods with full signatures, parameter tables, return value documentation, and usage examples. ```APIDOC ## Aggregate Class Documentation ### Description This section details the public methods available in the Aggregate base class. It includes comprehensive information for over 40 methods, ensuring users have full visibility into their signatures, parameters, return values, and practical usage. ### Methods - **Full TypeScript signatures** are provided for each method. - **Parameters tables** detail each parameter's name, type, requirement status, default value, and description. - **Return value documentation** explains what each method returns. - **Error/exception conditions** are listed where applicable. - **At least one usage example** is provided per method. - **Source file path and line references** are included for easy code lookup. ### Types Documented - **Item** - **Key, Bound, Bounds, SideBounds, TuplePrefix** - **Position (internal)** - **DirectAggregateType, TableAggregateType** - **NamespacedArgs, NamespacedOpts, NamespacedOptsBatch** - **Trigger, Change** - **QueryCtx, MutationCtx, ActionCtx** Each type includes field definitions, usage examples, and source file references. ``` -------------------------------- ### Aggregate Class Documentation Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the Aggregate base class includes full TypeScript signatures, parameter tables, return value descriptions, and usage examples. ```typescript class Aggregate { // ... 40+ public methods clear(opts?: { maxNodeSize?: number, rootLazy?: boolean }): Promise; } ``` -------------------------------- ### DirectAggregate Class Documentation Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the DirectAggregate class covers its 6 write methods, including signatures, parameter details, and usage examples. ```typescript class DirectAggregate { // ... 6 write methods } ``` -------------------------------- ### DirectAggregate Class Methods Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the DirectAggregate class, covering 6 write methods with detailed signatures, parameter information, return values, and usage examples. ```APIDOC ## DirectAggregate Class Documentation ### Description This section covers the DirectAggregate class, which provides 6 write methods. Each method is documented with its full TypeScript signature, parameter details, return type, and usage examples. ### Methods - **Full TypeScript signatures** are provided. - **Parameters tables** detail name, type, requirement, default value, and description. - **Return type description** is included. - **Error/exception conditions** are listed if applicable. - **At least one usage example** is provided per method. - **Source file path and line reference** is included. ``` -------------------------------- ### Type Definitions Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation includes definitions for over 15 types used within the aggregate classes, such as Item, Key, Bounds, and QueryCtx, with field definitions and usage examples. ```typescript type Item = { key: K, id: ID, value: any }; type Key = string | number | any[]; type Bounds = { min?: Key, max?: Key, minInclusive?: boolean, maxInclusive?: boolean }; type QueryCtx = { // ... fields }; type Trigger = { // ... fields }; ``` -------------------------------- ### Basic Trigger Setup for TableAggregate Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/triggers-guide.md Set up Triggers to automatically handle insert, update, and delete operations for a TableAggregate. This involves initializing Triggers, registering the aggregate's trigger, and wrapping mutations to ensure automatic aggregate updates. ```typescript import { Triggers } from "convex-helpers/server/triggers"; import { customCtx, customMutation } from "convex-helpers/server/customFunctions"; import { mutation as rawMutation } from "./_generated/server"; import { components } from "./_generated/api"; import type { DataModel } from "./_generated/dataModel"; import { TableAggregate } from "@convex-dev/aggregate"; const aggregate = new TableAggregate<{ Key: number; DataModel: DataModel; TableName: "scores"; }>(components.aggregate, { sortKey: (doc) => doc.score }); // Create triggers and register the aggregate trigger const triggers = new Triggers(); triggers.register("scores", aggregate.trigger()); // Wrap mutations with trigger support const mutation = customMutation( rawMutation, customCtx(triggers.wrapDB) ); // Now all mutations to "scores" automatically update the aggregate export const addScore = mutation({ args: { score: v.number() }, handler: async (ctx, args) => { // The trigger automatically calls aggregate.insert() return await ctx.db.insert("scores", { score: args.score }); } }); export const updateScore = mutation({ args: { id: v.id("scores"), score: v.number() }, handler: async (ctx, args) => { // The trigger automatically calls aggregate.replace() await ctx.db.patch(args.id, { score: args.score }); } }); export const deleteScore = mutation({ args: { id: v.id("scores") }, handler: async (ctx, args) => { // The trigger automatically calls aggregate.delete() await ctx.db.delete(args.id); } }); ``` -------------------------------- ### NamespacedArgs Usage Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/types.md Demonstrates how NamespacedArgs behaves when the Namespace type is undefined versus a specific type like string. ```typescript // Namespace is undefined NamespacedArgs<{ key: number }, undefined> = { key: number } | { key: number; namespace: undefined } // Namespace is string NamespacedArgs<{ key: number }, string> = { key: number; namespace: string } ``` -------------------------------- ### Basic Trigger Function Implementation Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/triggers-guide.md An example of a trigger function that handles insert, update, and delete operations by calling corresponding aggregate functions. ```typescript const trigger: Trigger = async ( ctx, change ) => { if (change.operation === "insert") { await aggregate.insert(ctx, change.newDoc); } else if (change.operation === "update") { await aggregate.replace(ctx, change.oldDoc, change.newDoc); } else if (change.operation === "delete") { await aggregate.delete(ctx, change.oldDoc); } }; ``` -------------------------------- ### TableAggregate namespace Examples Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/configuration.md The `namespace` function partitions data by document field. It can return strings, numbers, or tuples for multi-level partitioning. This option is optional and only required if you need to aggregate per partition. ```typescript // String namespace namespace: (doc) => doc.userId // Numeric namespace namespace: (doc) => doc.gameId // Tuple namespace for multi-level partitioning namespace: (doc) => [doc.gameId, doc.teamId] ``` -------------------------------- ### Photo Album with Namespaced Pagination Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/examples.md This snippet defines functions for managing photos organized into albums using namespace-based pagination. It includes adding photos, fetching paginated results, and counting photos per album. Ensure you have the necessary Convex setup and imports. ```typescript import { TableAggregate } from "@convex-dev/aggregate"; import { mutation, query } from "./_generated/server"; import { components } from "./_generated/api"; import type { DataModel } from "./_generated/dataModel"; import { v } from "convex/values"; const photoAlbums = new TableAggregate<{ Namespace: string; // album name Key: number; // creation time DataModel: DataModel; TableName: "photos"; }>(components.photos, { namespace: (doc) => doc.album, sortKey: (doc) => doc._creationTime }); export const addPhoto = mutation({ args: { album: v.string(), url: v.string() }, handler: async (ctx, args) => { const id = await ctx.db.insert("photos", { album: args.album, url: args.url }); const doc = await ctx.db.get(id); await photoAlbums.insert(ctx, doc!); return id; } }); export const getPhotosPage = query({ args: { album: v.string(), pageNumber: v.number() }, handler: async (ctx, args) => { const pageSize = 50; const offset = (args.pageNumber - 1) * pageSize; const totalCount = await photoAlbums.count(ctx, { namespace: args.album }); const results = []; let current = 0; for await (const item of photoAlbums.iter(ctx, { namespace: args.album, pageSize })) { if (current >= offset && current < offset + pageSize) { results.push(item); } current++; } return { photos: results, total: totalCount, pageCount: Math.ceil(totalCount / pageSize) }; } }); export const countPhotosByAlbum = query({ args: { album: v.string() }, handler: async (ctx, args) => { return await photoAlbums.count(ctx, { namespace: args.album }); } }); ``` -------------------------------- ### Calculate user-specific scores with Aggregate component Source: https://github.com/get-convex/aggregate/blob/main/README.md This example demonstrates how to calculate maximum and average scores for a specific user by querying an aggregate that is grouped by username. It uses 'bounds' to filter for the user's data. ```typescript // aggregateScoreByUser is the leaderboard scores grouped by username. const bounds = { prefix: [username] }; const highScoreForUser = await aggregateScoreByUser.max(ctx, { bounds }); const avgScoreForUser = (await aggregateScoreByUser.sum(ctx, { bounds })) / (await aggregateScoreByUser.count(ctx, { bounds })); // It still enables adding or averaging all scores across all usernames. const globalAverageScore = (await aggregateScoreByUser.sum(ctx)) / (await aggregateScoreByUser.count(ctx)); ``` -------------------------------- ### Count total scores using Aggregate component Source: https://github.com/get-convex/aggregate/blob/main/README.md Use this snippet to get the total count of all entries in the aggregate. No specific setup is required beyond having the aggregate initialized. ```typescript aggregate.count(ctx) ``` -------------------------------- ### TableAggregate Class Methods Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the TableAggregate class, detailing 6 write methods and trigger functionality, including signatures, parameters, return values, and examples. ```APIDOC ## TableAggregate Class Documentation ### Description This section details the TableAggregate class, which includes 6 write methods and trigger capabilities. Comprehensive documentation covers method signatures, parameters, return values, and usage examples. ### Methods - **Full TypeScript signatures** are provided. - **Parameters tables** detail name, type, requirement, default value, and description. - **Return type description** is included. - **Error/exception conditions** are listed if applicable. - **At least one usage example** is provided per method. - **Source file path and line reference** is included. ### Triggers - Documentation on **automatic synchronization patterns** using triggers is available. ``` -------------------------------- ### Fast Range Bounds Query Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Use range bounds to efficiently query a specific subset of data. This example counts items within the key range [95, 100]. ```typescript await aggregate.count(ctx, { bounds: { lower: { key: 95, inclusive: true }, upper: { key: 100, inclusive: true } } }); ``` -------------------------------- ### Deploy a New Version Source: https://github.com/get-convex/aggregate/blob/main/CONTRIBUTING.md Run the release script to deploy a new version of the project. ```sh npm run release ``` -------------------------------- ### Deploy an Alpha Release Source: https://github.com/get-convex/aggregate/blob/main/CONTRIBUTING.md Run the alpha script to deploy an alpha version of the project. ```sh npm run alpha ``` -------------------------------- ### Run Project Tests and Checks Source: https://github.com/get-convex/aggregate/blob/main/CONTRIBUTING.md Execute cleaning, building, type checking, linting, and testing commands. ```sh npm run clean npm run build npm run typecheck npm run lint npm run test ``` -------------------------------- ### Count Total Items Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Get the total number of items. Use for simple counts. ```typescript const total = await aggregate.count(ctx); ``` -------------------------------- ### Get Random Value Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Retrieve a random value from the aggregate. Use for sampling or random selection. ```typescript const random = await aggregate.random(ctx); ``` -------------------------------- ### Get Maximum Value Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Retrieve the maximum value from the aggregate. Use for finding the largest element. ```typescript const max = await aggregate.max(ctx); ``` -------------------------------- ### Get Minimum Value Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Retrieve the minimum value from the aggregate. Use for finding the smallest element. ```typescript const min = await aggregate.min(ctx); ``` -------------------------------- ### Find Index of a Document Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Get the index of a specific document within the aggregate. Requires a reference to the document itself. ```typescript const docRank = await tableAggregate.indexOfDoc(ctx, doc); ``` -------------------------------- ### Robust Backfill with @convex-dev/migrations Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/migration-guide.md Utilize the `@convex-dev/migrations` package for a resumable backfill process. This provides a more robust solution for migrating existing data to the aggregate. ```typescript import { Migrations } from "@convex-dev/migrations"; import { mutation, internalMutation } from "./_generated/server"; import { components } from "./_generated/api"; import type { DataModel } from "./_generated/dataModel"; const migrations = new Migrations(); migrations.register("backfill-scores-aggregate", async (ctx) => { const docs = await ctx.db.query("scores").collect(); for (const doc of docs) { await aggregate.insertIfDoesNotExist(ctx, doc); } }); // To run the migration: export const runMigration = internalMutation({ handler: async (ctx) => { return await migrations.run(ctx); } }); ``` -------------------------------- ### Find Index of a Key Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Get the zero-based index of a specific key. Use when the position of a known value is needed. ```typescript const rank = await aggregate.indexOf(ctx, 100); ``` -------------------------------- ### Get Single Item Boundary Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md When an equality bound matches exactly one item, `aggregate.at()` returns that item. ```typescript const first = await aggregate.at(ctx, 0, { bounds: { eq: 100 } }); // Returns: the first item with key === 100 ``` -------------------------------- ### Instantiate DirectAggregate Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/direct-aggregate-class.md Create a new DirectAggregate instance by passing the component from `components.aggregate`. This is the initial setup for using the class. ```typescript const aggregate = new DirectAggregate({ Key: number; Id: string; }, components.aggregate); ``` -------------------------------- ### Leaderboard Implementation Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Create a leaderboard using `TableAggregate` to display top items or player ranks. Use `iter` for paginated results and `indexOf` for rank. ```typescript const leaderboard = new TableAggregate(components.aggregate, { sortKey: (doc) => -doc.score // Descending for top scores first }); // Top 10 for await (const item of leaderboard.iter(ctx, { pageSize: 10 })) { // ... } // Player rank const rank = await leaderboard.indexOf(ctx, -playerScore); // High scores (>1000) const count = await leaderboard.count(ctx, { bounds: { lower: { key: -1000, inclusive: true } } }); ``` -------------------------------- ### eventsInTimeRange Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/examples.md Counts and sums events within a specified start and end timestamp. Calculates the average based on the sum and count. ```APIDOC ## eventsInTimeRange ### Description Counts and sums events within a specified start and end timestamp. Calculates the average based on the sum and count. ### Method query ### Endpoint /get-convex/aggregate/eventsInTimeRange ### Parameters #### Arguments - **startMs** (number) - Required - The starting timestamp in milliseconds. - **endMs** (number) - Required - The ending timestamp in milliseconds. ### Response #### Success Response (200) - **count** (number) - The total number of events in the specified range. - **sum** (number) - The sum of values for events in the specified range. - **average** (number) - The average value of events in the specified range. ``` -------------------------------- ### Performance Tuning for Balanced Workloads Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/configuration.md Use default settings for balanced workloads, which include a `maxNodeSize` of 16 and `rootLazy` set to true. ```typescript await aggregate.clear(ctx); // maxNodeSize: 16, rootLazy: true ``` -------------------------------- ### Configuration Methods Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/TABLE-OF-CONTENTS.md Methods for configuring aggregate behavior, such as clearing and setting root node properties. ```APIDOC ## Configuration Methods ### Description Methods for configuring aggregate behavior, such as clearing and setting root node properties. ### Methods - `aggregate.clear(ctx, options)`: Clears the aggregate with specified configuration options. - `aggregate.makeRootLazy(ctx, namespace)`: Sets the root node to be lazy for a given namespace. ### Parameters #### `aggregate.clear(ctx, options)` - `ctx` (object): The Convex context. - `options` (object): Configuration options. - `maxNodeSize` (number): Maximum size of a node. - `rootLazy` (boolean): Whether the root node should be lazy. #### `aggregate.makeRootLazy(ctx, namespace)` - `ctx` (object): The Convex context. - `namespace` (string): The namespace for which to make the root lazy. ### Request Example ```typescript // Example for clear await aggregate.clear(ctx, {maxNodeSize: 16, rootLazy: true}); // Example for makeRootLazy await aggregate.makeRootLazy(ctx, namespace); ``` ``` -------------------------------- ### Get Item with Non-Existent Key Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Attempting to retrieve an item using `aggregate.at()` with bounds that select no items will throw an error. ```typescript const item = await aggregate.at(ctx, 0, { bounds: { eq: 999999 } }); // Returns: throws (no items) ``` -------------------------------- ### Access First Item Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Retrieve the first item (minimum value) based on the default ordering. Use for accessing the start of a dataset. ```typescript const first = await aggregate.at(ctx, 0); ``` -------------------------------- ### clear() Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/aggregate-class.md (Re-)initializes the data structure, removing all items if it exists. Allows tuning of btree node size and root computation. ```APIDOC ## clear() ### Description (Re-)initializes the data structure, removing all items if it exists. Allows tuning of btree node size and root computation. ### Method Async Function ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **ctx** (MutationCtx | ActionCtx) - Required - Convex context - **maxNodeSize** (number) - Optional - Width/depth tuning for the btree, defaults to 16 - **rootLazy** (boolean) - Optional - Compute aggregates on root eagerly (false) or lazily (true), defaults to true - **namespace** (Namespace) - Optional - Namespace to clear ### Configuration - Larger `maxNodeSize` reduces write contention but increases read latency - `rootLazy: false` improves aggregation latency at expense of write contention ### Request Example ```typescript await aggregate.clear(ctx); await aggregate.clear(ctx, { maxNodeSize: 32, rootLazy: false }); ``` ``` -------------------------------- ### Batch Query Optimization Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/examples.md Demonstrates how to use `countBatch` and `sumBatch` for more efficient multiple queries on the 'scores' table. ```APIDOC ## Batch Query Optimization Use batch methods for multiple queries: ### Description This example shows how to perform multiple count and sum aggregations on the `scores` table efficiently using `countBatch` and `sumBatch`. It demonstrates querying all items, items with a score greater than or equal to 1000, and items with a score less than or equal to 500. ### Method `query` ### Endpoint `/multipleRangeStats` ### Parameters #### Query Parameters None #### Request Body None ### Request Example ```json {} ``` ### Response #### Success Response (200) - **all** (object) - Contains count and sum for all items. - **count** (number) - Total count of items. - **sum** (number) - Total sum of values. - **high** (object) - Contains count and sum for high-scoring items (score >= 1000). - **count** (number) - Count of high-scoring items. - **sum** (number) - Sum of values for high-scoring items. - **low** (object) - Contains count and sum for low-scoring items (score <= 500). - **count** (number) - Count of low-scoring items. - **sum** (number) - Sum of values for low-scoring items. #### Response Example ```json { "all": { "count": 1000, "sum": 500000 }, "high": { "count": 200, "sum": 300000 }, "low": { "count": 300, "sum": 50000 } } ``` ``` -------------------------------- ### Renaming Aggregate Component Instance Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/migration-guide.md This example shows how to rename an aggregate component in `convex/convex.config.ts` to reset it, creating a new, clean instance. ```typescript // convex/convex.config.ts const app = defineApp(); app.use(aggregate, { name: "aggregateScores_v1" }); // Old, broken instance app.use(aggregate, { name: "aggregateScores_v2" }); // New, clean instance export default app; ``` -------------------------------- ### Get max score for a user in a game Source: https://github.com/get-convex/aggregate/blob/main/README.md Retrieves the maximum score for a specific user in a given game using a prefix query. ```typescript aggregateByGame.max(ctx, { prefix: [game, username] }) ``` -------------------------------- ### Configuration Methods Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/TABLE-OF-CONTENTS.md Methods for configuring the aggregate's behavior, such as clearing data with specific options or making the root node lazy. ```typescript await aggregate.clear(ctx, {maxNodeSize: 16, rootLazy: true}); await aggregate.makeRootLazy(ctx, namespace); ``` -------------------------------- ### Querying Aggregate Data with `iter` Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/migration-guide.md Begin using aggregate queries with the `iter` method after the backfill is complete. This allows efficient iteration over aggregate results, suitable for fetching top items. ```typescript export const getTopTen = query({ args: {}, handler: async (ctx) => { const results = []; for await (const item of aggregate.iter(ctx, { pageSize: 10 })) { results.push(item); } return results; } }); ``` -------------------------------- ### TableAggregate Class Documentation Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/COMPLETION-REPORT.txt Documentation for the TableAggregate class details its 6 write methods and trigger capabilities, with full signatures and usage examples. ```typescript class TableAggregate { // ... 6 write methods + triggers constructor(opts?: { sortKey?: string, sumValue?: string, namespace?: string }); } ``` -------------------------------- ### Phase 3: Switch to Standard Insert After Migration Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Once the migration is complete and data is consistent, switch to using standard insert methods for ongoing operations. ```typescript await aggregate.insert(ctx, doc); ``` -------------------------------- ### Get Maximum Value in Range Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Find the maximum value within a specified upper bound. Useful for finding the highest value below a threshold. ```typescript const maxInRange = await aggregate.max(ctx, { bounds: { upper: { key: 100 } } }); ``` -------------------------------- ### makeAllRootsLazy() Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/aggregate-class.md Makes all namespace roots lazy to reduce contention on frequent writes. ```APIDOC ## makeAllRootsLazy() ### Description Makes all namespace roots lazy to reduce contention on frequent writes. ### Method Async Function ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **ctx** (MutationCtx | ActionCtx) - Required - Convex context ### Request Example ```typescript await aggregate.makeAllRootsLazy(ctx); ``` ``` -------------------------------- ### Get Document Rank with indexOfDoc Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Retrieves the rank or offset of a document within an aggregate based on its sort key. Use this when you have the document object itself. ```typescript async indexOfDoc( ctx: QueryCtx | MutationCtx | ActionCtx, doc: Document, opts?: { id?: GenericId; bounds?: Bounds>; order?: "asc" | "desc"; } ): Promise ``` ```typescript const doc = await ctx.db.get(id); const rank = await aggregate.indexOfDoc(ctx, doc!); ``` -------------------------------- ### Leaderboard with Global and Per-User Aggregates Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/examples.md This snippet shows how to set up and use TableAggregate for both global leaderboards and per-user score tracking. It includes mutations for adding scores and queries for retrieving top scores, user high scores, average scores, and player ranks. ```typescript import { TableAggregate } from "@convex-dev/aggregate"; import { mutation, query } from "./_generated/server"; import { components } from "./_generated/api"; import type { DataModel } from "./_generated/dataModel"; import { v } from "convex/values"; // Global leaderboard sorted by score (descending) const globalLeaderboard = new TableAggregate<{ Key: number; DataModel: DataModel; TableName: "leaderboard"; }>(components.aggregateByScore, { sortKey: (doc) => -doc.score // Negative for descending }); // Per-user aggregate: [username, score] const scoresByUser = new TableAggregate<{ Key: [string, number]; DataModel: DataModel; TableName: "leaderboard"; }>(components.aggregateScoreByUser, { sortKey: (doc) => [doc.username, doc.score], sumValue: (doc) => doc.score }); export const addScore = mutation({ args: { username: v.string(), score: v.number() }, handler: async (ctx, args) => { const id = await ctx.db.insert("leaderboard", { username: args.username, score: args.score }); const doc = await ctx.db.get(id); await globalLeaderboard.insert(ctx, doc!); await scoresByUser.insert(ctx, doc!); return id; } }); export const getTopTen = query({ args: {}, handler: async (ctx) => { const results = []; for await (const item of globalLeaderboard.iter(ctx, { pageSize: 10 })) { results.push(item); } return results; } }); export const getUserHighScore = query({ args: { username: v.string() }, handler: async (ctx, args) => { return await scoresByUser.max(ctx, { bounds: { prefix: [args.username] } }); } }); export const getAverageScore = query({ args: { username: v.string() }, handler: async (ctx, args) => { const count = await scoresByUser.count(ctx, { bounds: { prefix: [args.username] } }); if (count === 0) return null; const sum = await scoresByUser.sum(ctx, { bounds: { prefix: [args.username] } }); return sum / count; } }); export const getPlayerRank = query({ args: { score: v.number() }, handler: async (ctx, args) => { return await globalLeaderboard.indexOf(ctx, -args.score); } }); ``` -------------------------------- ### Automatic Synchronization with Triggers Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Use triggers to automatically keep the aggregate in sync with table changes. This example demonstrates setting up triggers for the 'scores' table. ```typescript import { Triggers } from "convex-helpers/server/triggers"; import { customCtx, customMutation } from "convex-helpers/server/customFunctions"; const aggregate = new TableAggregate<{ Key: number; DataModel: DataModel; TableName: "scores"; }>(components.aggregate, { sortKey: (doc) => doc.score }); const triggers = new Triggers(); triggers.register("scores", aggregate.trigger()); const mutation = customMutation( mutation as any, customCtx(triggers.wrapDB) ); export const addScore = mutation({ args: { score: v.number() }, handler: async (ctx, args) => { // Trigger automatically syncs to aggregate return await ctx.db.insert("scores", args); } }); ``` -------------------------------- ### Configure Aggregate with Namespace for High-Cardinality Keys Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/performance-guide.md When dealing with high-cardinality sequential keys (like timestamps), use a `namespace` or randomize the keys to distribute inserts across different tree regions and avoid hot spots. ```typescript const aggregate = new TableAggregate(components.aggregate, { namespace: (doc) => doc.userId, sortKey: (doc) => doc._creationTime }); ``` -------------------------------- ### Find Position from End Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md Get the index of a key counting from the end of the dataset (descending order). Use for finding ranks relative to the maximum value. ```typescript const fromEnd = await aggregate.indexOf(ctx, 100, { order: "desc" }); ``` -------------------------------- ### Phase 1: Use Idempotent Insert for Migration Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md During the first phase of migration, update your code to use idempotent methods like insertIfDoesNotExist to safely handle potential duplicate insertions. ```typescript await aggregate.insertIfDoesNotExist(ctx, doc); ``` -------------------------------- ### at() Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/aggregate-class.md Retrieves the item at a specific offset (index) within the ordered keys, supporting both positive and negative offsets for forward and reverse indexing. ```APIDOC ## at() ### Description Returns the item at the given offset/index/rank in the order of keys within the bounds. Zero-indexed, so at(0) is the smallest key. Negative offsets count from the end (at(-1) is the largest key). ### Method async at ### Parameters #### Path Parameters None #### Query Parameters * **ctx** (QueryCtx | MutationCtx | ActionCtx) - Required - Convex context * **offset** (number) - Required - The position to retrieve. Can be negative for reverse indexing * **opts** (NamespacedOpts) - Optional - Bounds and optional namespace ### Request Example ```typescript const smallest = await aggregate.at(ctx, 0); const largest = await aggregate.at(ctx, -1); const nthItem = await aggregate.at(ctx, 42); ``` ### Response #### Success Response (200) * **Item** - Object with { key, id, sumValue } ``` -------------------------------- ### Random Sampling and Indexing Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Get a random item using `aggregate.random`, find its rank using `aggregate.indexOf`, and retrieve neighboring items using `aggregate.paginate`. ```typescript const random = await aggregate.random(ctx); if (random) { const rank = await aggregate.indexOf(ctx, random.key); const neighbors = await aggregate.paginate(ctx, { cursor: undefined, pageSize: 10 }); } ``` -------------------------------- ### Monitor Backfill Progress with Progress Logging Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/migration-guide.md Use this mutation to backfill data from a table while logging progress at specified intervals. It's useful for large tables to track completion and performance. ```typescript export const backfillAggregateWithProgress = internalMutation({ args: { pageSize: v.optional(v.number()), logEvery: v.optional(v.number()) }, handler: async (ctx, args) => { const pageSize = args.pageSize ?? 100; const logEvery = args.logEvery ?? 1000; let cursor: string | undefined; let count = 0; const startTime = Date.now(); const startMs = startTime; while (true) { const { page, isDone, cursor: nextCursor } = await ctx.db .query("scores") .paginate({ limit: pageSize, cursor }); for (const doc of page) { await aggregate.insertIfDoesNotExist(ctx, doc); count++; if (count % logEvery === 0) { const elapsed = Date.now() - startMs; const rate = (count / elapsed) * 1000; console.log(`Backfilled ${count} in ${elapsed}ms (${rate.toFixed(1)}/s)`); } } if (isDone) break; cursor = nextCursor; } const totalMs = Date.now() - startTime; console.log(`Backfill complete: ${count} items in ${totalMs}ms`); return { backfilledCount: count, totalMs }; } }); ``` -------------------------------- ### Query by Range (Lower and Upper Bounds) Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Combine lower and upper bounds to query items within a specific key range. Ensure `inclusive` flags are set correctly for desired boundary behavior. ```typescript // Items with 95 <= key <= 100 await aggregate.count(ctx, { bounds: { lower: { key: 95, inclusive: true }, upper: { key: 100, inclusive: true } } }); ``` -------------------------------- ### TableAggregate Constructor Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Illustrates how to create a TableAggregate instance, defining how to extract sort keys, sum values, and namespaces from documents. ```typescript constructor( component: ComponentApi, options: { sortKey: (doc: Document) => K; sumValue?: (doc: Document) => number; namespace?: (doc: Document) => Namespace; } & ( undefined extends Namespace ? { namespace?: ... } : { namespace: ... } ) ) ``` -------------------------------- ### Randomize Table Data with TableAggregate Source: https://github.com/get-convex/aggregate/blob/main/README.md Use TableAggregate without sorting to get random documents from a table. Set namespace to undefined and sortKey to null for this behavior. ```typescript const randomize = new TableAggregate({ sortKey: (doc) => null, }); ``` -------------------------------- ### Phase 2: Backfill Existing Data with Idempotent Insert Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/README.md In the second phase, run a backfill process on existing data using idempotent methods to ensure all historical records are processed correctly. ```typescript const docs = await ctx.db.query("table").collect(); for (const doc of docs) { await aggregate.insertIfDoesNotExist(ctx, doc); } ``` -------------------------------- ### Getting an Item at a Specific Offset Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/bounds-guide.md Retrieve an item at a specific position (offset) in the dataset using `aggregate.at`. This is useful for direct access when pagination is not the primary goal. ```typescript const startItem = await aggregate.at(ctx, offset); ``` -------------------------------- ### Paginate Photos by Album Offset Source: https://github.com/get-convex/aggregate/blob/main/README.md Retrieve a page of photos from a specific album based on offset and number of items. Uses the 'photos' TableAggregate to find the starting key. ```typescript export const pageOfPhotos({ args: { offset: v.number(), numItems: v.number(), album: v.string() }, handler: async (ctx, { offset, numItems, album }) => { const { key } = await photos.at(ctx, offset, { namespace: album }); return await ctx.db.query("photos") .withIndex("by_album_creation_time", q=>q.eq("album", album).gte("_creationTime", key)) .take(numItems); }, }); ``` -------------------------------- ### min() Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/aggregate-class.md Retrieves the item with the smallest key within the specified bounds. Returns null if no items are found within the bounds. ```APIDOC ## min() ### Description Gets the minimum item (smallest key) within the given bounds. ### Method async ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **ctx** (QueryCtx | MutationCtx | ActionCtx) - Required - Convex context - **opts** (NamespacedOpts) - Optional - Bounds and optional namespace ``` -------------------------------- ### Import DirectAggregate Class Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/api-index.md Import the DirectAggregate class for managing data manually without a backing Convex table. ```typescript import { DirectAggregate } from "@convex-dev/aggregate" ``` -------------------------------- ### indexOfDoc() Source: https://github.com/get-convex/aggregate/blob/main/_autodocs/table-aggregate-class.md Gets the rank or offset of a document within an aggregate based on its sort key. This method takes the document itself as input, unlike `indexOf()` which takes a raw key. ```APIDOC ## indexOfDoc() API ### Description Gets the rank/offset of a document based on its sort key. Differs from `indexOf()` in that it takes the document rather than a raw key. ### Method Signature ```typescript async indexOfDoc( ctx: QueryCtx | MutationCtx | ActionCtx, doc: Document, opts?: { id?: GenericId; bounds?: Bounds>; order?: "asc" | "desc"; } ): Promise ``` ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None #### Function Parameters - **ctx** (QueryCtx | MutationCtx | ActionCtx) - Required - Convex context - **doc** (Document) - Required - The document whose position to find - **opts.id** (GenericId) - Optional - - **opts.bounds** (Bounds) - Optional - Range constraints - **opts.order** ("asc" | "desc") - Optional - Default: "asc" - Sort direction ### Request Example ```typescript const doc = await ctx.db.get(id); const rank = await aggregate.indexOfDoc(ctx, doc!); ``` ### Response #### Success Response - **Promise** - The rank/index of the document #### Response Example ```typescript // Example return value: 3 ``` ```