Loading...
Loading...
Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.
npx skill4agent add get-convex/agent-skills convex-helpers-guideconvex-helpersnpm install convex-helpersimport { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";
export const getTaskWithUser = query({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
if (!task) return null;
// Get related user
const user = await getOneFrom(
ctx.db,
"users",
"by_id",
task.userId,
"_id"
);
// Get related comments
const comments = await getManyFrom(
ctx.db,
"comments",
"by_task",
task._id,
"taskId"
);
return { ...task, user, comments };
},
});getOneFromgetManyFromgetManyVia// convex/lib/customFunctions.ts
import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";
export const authenticatedQuery = customQuery(
query,
{
args: {}, // No additional args required
input: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) throw new Error("User not found");
// Add user to context
return { ctx: { ...ctx, user }, args };
},
}
);
// Usage in your functions
export const getMyTasks = authenticatedQuery({
handler: async (ctx) => {
// ctx.user is automatically available!
return await ctx.db
.query("tasks")
.withIndex("by_user", q => q.eq("userId", ctx.user._id))
.collect();
},
});import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";
// Organization-scoped query - automatic access control
export const orgQuery = customQuery(query, {
args: { orgId: v.id("organizations") },
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
// Verify user is a member of this organization
const member = await ctx.db
.query("organizationMembers")
.withIndex("by_org_and_user", q =>
q.eq("orgId", args.orgId).eq("userId", user._id)
)
.unique();
if (!member) {
throw new Error("Not authorized for this organization");
}
// Inject org context
return {
ctx: {
...ctx,
user,
orgId: args.orgId,
role: member.role
},
args
};
},
});
// Usage - data automatically scoped to organization
export const getOrgProjects = orgQuery({
args: { orgId: v.id("organizations") },
handler: async (ctx) => {
// ctx.user and ctx.orgId automatically available and verified!
return await ctx.db
.query("projects")
.withIndex("by_org", q => q.eq("orgId", ctx.orgId))
.collect();
},
});import { customMutation } from "convex-helpers/server/customFunctions";
import { mutation } from "../_generated/server";
export const adminMutation = customMutation(mutation, {
args: {},
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
if (user.role !== "admin") {
throw new Error("Admin access required");
}
return { ctx: { ...ctx, user }, args };
},
});
// Usage - only admins can call this
export const deleteUser = adminMutation({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
// Only admins reach this code
await ctx.db.delete(args.userId);
},
});import { filter } from "convex-helpers/server/filter";
export const getActiveTasks = query({
handler: async (ctx) => {
const now = Date.now();
const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1000;
return await filter(
ctx.db.query("tasks"),
(task) =>
!task.completed &&
task.createdAt > threeDaysAgo &&
task.priority === "high"
).collect();
},
});// convex/sessions.ts
import { SessionIdArg } from "convex-helpers/server/sessions";
import { query } from "./_generated/server";
export const trackView = query({
args: {
...SessionIdArg, // Adds sessionId: v.string()
pageUrl: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.insert("pageViews", {
sessionId: args.sessionId,
pageUrl: args.pageUrl,
timestamp: Date.now(),
});
},
});import { useSessionId } from "convex-helpers/react/sessions";
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
function MyComponent() {
const sessionId = useSessionId();
// Automatically includes sessionId in all requests
useQuery(api.sessions.trackView, {
sessionId,
pageUrl: window.location.href,
});
}import { zCustomQuery } from "convex-helpers/server/zod";
import { z } from "zod";
import { query } from "./_generated/server";
const argsSchema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120),
});
export const createUser = zCustomQuery(query, {
args: argsSchema,
handler: async (ctx, args) => {
// args is typed from Zod schema
return await ctx.db.insert("users", args);
},
});import { makeMigration } from "convex-helpers/server/migrations";
export const addDefaultPriority = makeMigration({
table: "tasks",
migrateOne: async (ctx, doc) => {
if (doc.priority === undefined) {
await ctx.db.patch(doc._id, { priority: "medium" });
}
},
});
// Run: npx convex run migrations:addDefaultPriorityimport { Triggers } from "convex-helpers/server/triggers";
const triggers = new Triggers();
triggers.register("tasks", "insert", async (ctx, task) => {
// Send notification when task is created
await ctx.db.insert("notifications", {
userId: task.userId,
type: "task_created",
taskId: task._id,
});
});import { customQuery } from "convex-helpers/server/customFunctions";
export const authedQuery = customQuery(query, {
args: {},
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
return { ctx: { ...ctx, user }, args };
},
});
// Now all queries automatically have user in context
export const getMyData = authedQuery({
handler: async (ctx) => {
// ctx.user is typed and available!
return await ctx.db
.query("data")
.withIndex("by_user", q => q.eq("userId", ctx.user._id))
.collect();
},
});import { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";
export const getPostWithDetails = query({
args: { postId: v.id("posts") },
handler: async (ctx, args) => {
const post = await ctx.db.get(args.postId);
if (!post) return null;
const author = await getOneFrom(ctx.db, "users", "by_id", post.authorId, "_id");
const comments = await getManyFrom(ctx.db, "comments", "by_post", post._id, "postId");
const tagLinks = await getManyFrom(ctx.db, "postTags", "by_post", post._id, "postId");
const tags = await Promise.all(
tagLinks.map(link =>
getOneFrom(ctx.db, "tags", "by_id", link.tagId, "_id")
)
);
return { ...post, author, comments, tags };
},
});import { asyncMap } from "convex-helpers";
export const batchUpdateTasks = mutation({
args: {
taskIds: v.array(v.id("tasks")),
status: v.string(),
},
handler: async (ctx, args) => {
const results = await asyncMap(args.taskIds, async (taskId) => {
try {
const task = await ctx.db.get(taskId);
if (task) {
await ctx.db.patch(taskId, { status: args.status });
return { success: true, taskId };
}
return { success: false, taskId, error: "Not found" };
} catch (error) {
return { success: false, taskId, error: error.message };
}
});
return results;
},
});| Need | Use | Import From |
|---|---|---|
| Load related data | | |
| Auth in all functions | | |
| Complex filters | | |
| Anonymous users | | |
| Zod validation | | |
| Data migrations | | |
| Triggers | | |
npm install convex-helpers