plans
2026-05-04
2026 05 04 Convex Helpers Setup

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/node

Expected 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 -20

Expected: 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 -20

Expected: 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 -20

Expected: 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 -20

Expected: 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 codegen

Expected 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 -30

Expected: 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 -20

Expected: "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.ts exports zQuery, zMutation, zAction, z, zid, withSystemFields
  • packages/backend/convex/lib/relationships.ts exports getOrThrow, getManyFrom, getOccurrenceWithShow, getReservationWithDetails
  • packages/backend/convex/lib/auth.ts exports authedQuery, authedMutation
  • New functions can use Zod args syntax: args: z.object({ ... })
  • TypeScript compiles without new errors
  • npx convex dev starts 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/zod3zCustomQuery, zCustomMutation, zCustomAction, zid, withSystemFields
  • convex-helpers/server/customFunctionsNoOp, customQuery, customMutation
  • convex-helpers/server/relationshipsgetOrThrow, getOneFrom, getManyFrom, getAll
  • zodz.object(), z.string(), z.number(), etc.