Convex Helpers Setup Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Set up convex-helpers with Zod validation, relationship helpers, and auth wrappers to eliminate api as any type casting in the HOL codebase.
Architecture: Create three library files in packages/backend/convex/lib/ that wrap convex-helpers with HOL-specific patterns: (1) zod.ts for type-safe function builders, (2) relationships.ts for FK traversal helpers, (3) auth.ts for consistent auth middleware. New functions use Zod builders; existing functions continue with api as any until migrated.
Tech Stack: convex-helpers@^0.1.115, zod@^3.25.0, Convex 1.37.0
Task 1: Install Dependencies
Files:
-
Modify:
packages/backend/convex/package.json -
Modify:
pnpm-lock.yaml -
Step 1: Add convex-helpers and zod to dependencies
Run in packages/backend/convex/:
cd packages/backend/convex && pnpm add convex-helpers zod && pnpm add -D @types/nodeExpected output:
packages/backend/convex ∴ pnpm add convex-helpers zod
... (installs packages)
packages/backend/convex ∴ pnpm add -D @types/node
... (installs types)- Step 2: Verify package.json updated
cat packages/backend/convex/package.json | grep -A5 '"dependencies"'Expected: convex-helpers and zod in dependencies list.
Task 2: Create lib/zod.ts
Files:
-
Create:
packages/backend/convex/lib/zod.ts -
Step 1: Write zod.ts with Zod-aware function builders
// packages/backend/convex/lib/zod.ts
import { query, mutation, action } from "../_generated/server";
import {
zCustomQuery,
zCustomMutation,
zCustomAction,
} from "convex-helpers/server/zod3";
import { NoOp } from "convex-helpers/server/customFunctions";
import { z } from "zod";
export const zQuery = zCustomQuery(query, NoOp);
export const zMutation = zCustomMutation(mutation, NoOp);
export const zAction = zCustomAction(action, NoOp);
export { z };
export { zid } from "convex-helpers/server/zod3";
export { withSystemFields } from "convex-helpers/server/zod3";- Step 2: Verify file compiles
Run TypeScript check (file will be validated when Convex codegen runs):
cd packages/backend/convex && npx tsc --noEmit lib/zod.ts 2>&1 | head -20Expected: No errors (or only expected missing module errors that Convex handles).
Task 3: Create lib/relationships.ts
Files:
-
Create:
packages/backend/convex/lib/relationships.ts -
Step 1: Write relationships.ts with typed helpers
// packages/backend/convex/lib/relationships.ts
import {
getOrThrow,
getOneFrom,
getManyFrom,
getAll,
} from "convex-helpers/server/relationships";
import { QueryCtx } from "../_generated/server";
import { Id } from "../_generated/dataModel";
export { getOrThrow, getOneFrom, getManyFrom, getAll };
// HOL-specific: get occurrence with its show template
export async function getOccurrenceWithShow(
ctx: QueryCtx,
occurrenceId: Id<"showOccurrences">,
) {
const occurrence = await getOrThrow(ctx, "showOccurrences", occurrenceId);
const show = await getOrThrow(ctx, "showTemplates", occurrence.templateId);
return { occurrence, show };
}
// HOL-specific: get reservation with occurrence + show
export async function getReservationWithDetails(
ctx: QueryCtx,
reservationId: Id<"reservations">,
) {
const reservation = await getOrThrow(ctx, "reservations", reservationId);
const { occurrence, show } = await getOccurrenceWithShow(
ctx,
reservation.occurrenceId,
);
return { reservation, occurrence, show };
}
// HOL-specific: get all reservations for an occurrence
export async function getReservationsForOccurrence(
ctx: QueryCtx,
occurrenceId: Id<"showOccurrences">,
) {
return await getManyFrom(ctx, "reservations", {
table: "reservations",
index: "by_occurrence",
id: occurrenceId,
});
}- Step 2: Verify file compiles
cd packages/backend/convex && npx tsc --noEmit lib/relationships.ts 2>&1 | head -20Expected: No errors related to our code.
Task 4: Create lib/auth.ts
Files:
-
Create:
packages/backend/convex/lib/auth.ts -
Step 1: Write auth.ts with customQuery/mutation wrappers
// packages/backend/convex/lib/auth.ts
import {
customQuery,
customMutation,
} from "convex-helpers/server/customFunctions";
import { query, mutation } from "../_generated/server";
import { QueryCtx, MutationCtx } from "../_generated/server";
export const authedQuery = customQuery(query, {
args: {},
input: async (ctx: QueryCtx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthorized");
}
return { ctx: { ...ctx, identity }, args: {} };
},
});
export const authedMutation = customMutation(mutation, {
args: {},
input: async (ctx: MutationCtx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthorized");
}
return { ctx: { ...ctx, identity }, args: {} };
},
});- Step 2: Verify file compiles
cd packages/backend/convex && npx tsc --noEmit lib/auth.ts 2>&1 | head -20Expected: No errors.
Task 5: Create lib/index.ts (barrel export)
Files:
-
Create:
packages/backend/convex/lib/index.ts -
Step 1: Write barrel export for lib
// packages/backend/convex/lib/index.ts
export * from "./zod";
export * from "./relationships";
export * from "./auth";
export * from "./errors";- Step 2: Verify TypeScript compilation
cd packages/backend/convex && npx tsc --noEmit lib/index.ts 2>&1 | head -20Expected: No errors.
Task 6: Run Convex codegen to regenerate types
Files:
-
Regenerate:
packages/backend/convex/_generated/ -
Step 1: Run Convex codegen
cd packages/backend/convex && npx convex codegenExpected output:
Generating _generated directory...
Done.- Step 2: Verify _generated directory updated
ls -la packages/backend/convex/_generated/Expected: api.d.ts, dataModel.d.ts, server.d.ts exist with recent timestamps.
Task 7: Verify setup end-to-end
Files:
-
Create:
packages/backend/convex/functions/testHelpers.ts(temporary test) -
Step 1: Create a test function using Zod builders
Create packages/backend/convex/functions/testHelpers.ts:
import { zMutation, zQuery, z } from "../lib/zod";
import { getReservationWithDetails } from "../lib/relationships";
import { Id } from "../_generated/dataModel";
// Test zQuery with Zod args
export const testGetReservation = zQuery({
args: z.object({
reservationId: z.string(),
}),
handler: async (ctx, { reservationId }) => {
return await getReservationWithDetails(
ctx,
reservationId as Id<"reservations">,
);
},
});
// Test zMutation with Zod args
export const testCreate = zMutation({
args: z.object({
title: z.string().min(1).max(200),
}),
handler: async (ctx, { title }) => {
const id = await ctx.db.insert("showTemplates", {
title,
slug: title.toLowerCase().replace(/\s+/g, "-"),
description: "",
status: "DRAFT",
duration: 60,
venue: "",
images: [],
});
return id;
},
});- Step 2: Verify TypeScript compiles
cd packages/backend/convex && npx tsc --noEmit 2>&1 | head -30Expected: No TypeScript errors (or only pre-existing errors unrelated to our new code).
- Step 3: Run Convex dev briefly to verify
cd packages/backend/convex && timeout 30 npx convex dev --once 2>&1 | tail -20Expected: "Running migrations..." then "Dev server running" or similar success message.
- Step 4: Remove test function
Delete packages/backend/convex/functions/testHelpers.ts after verification.
Task 8: Commit changes
Files:
-
Stage and commit all new/modified files
-
Step 1: Check git status
git status --short packages/backend/convex/Expected: Modified package.json, pnpm-lock.yaml, new lib/zod.ts, lib/relationships.ts, lib/auth.ts, lib/index.ts, functions/testHelpers.ts (to be deleted).
- Step 2: Stage and commit
git add packages/backend/convex/package.json packages/backend/convex/pnpm-lock.yaml packages/backend/convex/lib/
git commit -m "$(cat <<'EOF'
feat(convex): add convex-helpers lib with Zod, relationships, auth wrappers
- Add convex-helpers and zod dependencies
- Create lib/zod.ts with zQuery, zMutation, zAction builders
- Create lib/relationships.ts with getOccurrenceWithShow, getReservationWithDetails
- Create lib/auth.ts with authedQuery, authedMutation wrappers
- Create lib/index.ts barrel export
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EOF
)"Expected: Commit successful.
Verification Checklist
After implementation, verify:
-
packages/backend/convex/lib/zod.tsexportszQuery,zMutation,zAction,z,zid,withSystemFields -
packages/backend/convex/lib/relationships.tsexportsgetOrThrow,getManyFrom,getOccurrenceWithShow,getReservationWithDetails -
packages/backend/convex/lib/auth.tsexportsauthedQuery,authedMutation - New functions can use Zod args syntax:
args: z.object({ ... }) - TypeScript compiles without new errors
-
npx convex devstarts without errors
Rollback Plan
If issues arise:
# Revert the commit
git revert HEAD --no-edit
# Or reset to previous state
git reset --hard HEAD~Dependencies
{
"convex-helpers": "^0.1.115",
"zod": "^3.25.0",
"@types/node": "latest"
}Key imports:
convex-helpers/server/zod3—zCustomQuery,zCustomMutation,zCustomAction,zid,withSystemFieldsconvex-helpers/server/customFunctions—NoOp,customQuery,customMutationconvex-helpers/server/relationships—getOrThrow,getOneFrom,getManyFrom,getAllzod—z.object(),z.string(),z.number(), etc.