Use Composable QueryStreams for complex queries
mainA QueryStream is an async iterable of documents ordered by indexed fields. They allow you to perform operations equivalent to SQL's UNION ALL, WHERE, JOIN, and ORDER BY directly on Convex data streams before returning the final result to the client. This is useful for merging multiple queries, filtering results based on complex predicates, or joining data from different tables.
Core Stream Operations:
stream(ctx.db, schema): Constructs a new stream usingDatabaseReadersyntax.mergedStream(streams, fields): Combines multiple streams into one, maintaining order based on the providedfields..flatMap(async (doc) => ...): Expands each document into its own stream and chains them together (useful for joins)..map(async (doc) => ...): Modifies each item in the stream while preserving order..filterWith(async (doc) => ...): Filters documents based on a TypeScript predicate.
Finalizing a Stream:
Once configured, you can treat a stream like a standard Convex query by calling methods like .first(), .collect(), .take(n), or .paginate(paginationOpts).
import { stream, mergedStream } from "convex-helpers/server/stream";
import schema from "./schema";
export const listForAuthors = query({
args: {
authors: v.array(v.id("users")),
paginationOpts: paginationOptsValidator,
},
handler: async (ctx, { authors, paginationOpts }) => {
const authorStreams = authors.map((author) =>
stream(ctx.db, schema)
.query("messages")
.withIndex("by_author", (q) => q.eq("author", author)),
);
const allAuthorsStream = mergedStream(authorStreams, [
"author",
"_creationTime",
]);
return await allAuthorsStream.paginate(paginationOpts);
},
});