Tom Redman
Tom Redman
CCConvex Community
Created by rgz on 3/18/2025 in #show-and-tell
Convex Panel
That is epic. I would def use it for admins
4 replies
CCConvex Community
Created by rgz on 3/18/2025 in #show-and-tell
Convex Panel
Ok this is sick! Is the idea that we can embed this as “admin” component in our app? Like the official Convex dashboard “to go”?
4 replies
CCConvex Community
Created by ianpaschal on 2/25/2025 in #support-community
How to re-organize api structure?
@ianpaschal For context I have about 150 functions, and have been partial to this pattern:
/table
- actions.ts
- mutations.ts
- queries.ts
/table
- actions.ts
- mutations.ts
- queries.ts
But I'm not religious about it. I find if one of my table/functions.ts files starts to grow large, there's usually a more semantic way to chunk it out -- I'm usually "mixing metaphors" and I'll create a new folder that has a more narrow scope, and move some things there. That said, sometimes I'll throw caution to the wind and create what feels like the most ergonomic setup (see google/places.ts below). Here's how my setup looks:
/convex
├── README.md
├── _generated
│   ├── api.d.ts
│   ├── api.js
│   ├── dataModel.d.ts
│   ├── server.d.ts
│   └── server.js
├── admin
│   ├── mutations.ts
│   └── queries.ts
├── aggregates
│   ├── campaigns.ts
│   └── migrations.ts
├── auth.config.ts
├── auth.ts
├── authProviders
│   └── Password.ts
├── authentication
│   ├── mutations.ts
│   ├── queries.ts
│   └── user.ts
├── campaigns
│   ├── actions.ts
│   ├── migrations.ts
│   ├── mutations.ts
│   ├── queries.ts
│   └── utils.ts
├── convex.config.ts
├── crons.ts
├── domains
│   ├── actions.ts
│   ├── mutations.ts
│   └── queries.ts
├── dub
│   ├── mutations.ts
│   └── webhooks.ts
├── emailEvents
│   └── mutations.ts
├── emailableLists
│   └── queries.ts
├── emails
│   ├── actions.ts
│   ├── mutations.ts
│   ├── parser.ts
│   └── utils.ts
├── fub
│   ├── contacts
│   └── lists
├── google
│   └── places.ts
...
/convex
├── README.md
├── _generated
│   ├── api.d.ts
│   ├── api.js
│   ├── dataModel.d.ts
│   ├── server.d.ts
│   └── server.js
├── admin
│   ├── mutations.ts
│   └── queries.ts
├── aggregates
│   ├── campaigns.ts
│   └── migrations.ts
├── auth.config.ts
├── auth.ts
├── authProviders
│   └── Password.ts
├── authentication
│   ├── mutations.ts
│   ├── queries.ts
│   └── user.ts
├── campaigns
│   ├── actions.ts
│   ├── migrations.ts
│   ├── mutations.ts
│   ├── queries.ts
│   └── utils.ts
├── convex.config.ts
├── crons.ts
├── domains
│   ├── actions.ts
│   ├── mutations.ts
│   └── queries.ts
├── dub
│   ├── mutations.ts
│   └── webhooks.ts
├── emailEvents
│   └── mutations.ts
├── emailableLists
│   └── queries.ts
├── emails
│   ├── actions.ts
│   ├── mutations.ts
│   ├── parser.ts
│   └── utils.ts
├── fub
│   ├── contacts
│   └── lists
├── google
│   └── places.ts
...
Edit: Just did the math: 139 functions in 39 files. So 3-4 functions per file on average.
6 replies
CCConvex Community
Created by uzamaki21 on 2/28/2025 in #support-community
Value does not match validator
@uzamaki21 - you can cast your URL parameter as required. This is how I do the same thing (using tanstack router & tanstack query in this example):
function CampaignsEdit() {
const { data: campaign } = useSuspenseQuery(
convexQuery(api.campaigns.queries.getCampaign, {
id: Route.useParams().campaignId as Id<"campaigns">, // <-- this makes the validator happy
})
);

return <CampaignEditor campaign={campaign} />;
}
function CampaignsEdit() {
const { data: campaign } = useSuspenseQuery(
convexQuery(api.campaigns.queries.getCampaign, {
id: Route.useParams().campaignId as Id<"campaigns">, // <-- this makes the validator happy
})
);

return <CampaignEditor campaign={campaign} />;
}
10 replies
CCConvex Community
Created by Tom Redman on 3/1/2025 in #support-community
Creating a validator for [key: string]: any
Cool, thanks @lee! Team v.any() for the time being 🙂
4 replies
CCConvex Community
Created by Tom Redman on 2/18/2025 in #support-community
"use node" is present in node runtime file but still getting "you are using node APIs" error
OH I guess it'd be ideal to have that error message show the import trace, if possible. E.g., "a node runtime file (mailchimp.ts) is being imported by a non-node runtime bundle: myCrazyMutations.ts"
7 replies
CCConvex Community
Created by Tom Redman on 2/18/2025 in #support-community
"use node" is present in node runtime file but still getting "you are using node APIs" error
Yeah I have node in all of them. Thank you both! @Tom i will tripppple check – I suspect this is the culprit. Wha'ts really weird is that this was all working –– I was using this package for the last few months. But also, to be fair, the mailchimp package is a bit of a mess and is not typescript, so I've been cajoling it to work. To get a better experience overall, I just generated a new schema & client from an openAPI spec and a created my own client for it. 🙂 Thanks so much guys
7 replies
CCConvex Community
Created by Lucas Couto on 1/20/2025 in #support-community
Query arguments issue?
I didn’t know about this. Would love to see it @Lucas Couto!
12 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
Is there a better way in Typescript to check if the ctx is either a QueryCtx or an ActionCtx? (Better than checking for the presence of 'db')
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
Here's the final form for posterity:
import { getAuthUserId } from "@convex-dev/auth/server";
import { Doc } from "../_generated/dataModel";
import { ActionCtx, internalQuery, QueryCtx } from "../_generated/server";
import { get } from "../base/ query";
import { internal } from "../_generated/api";

export const getCurrentUserOrThrowQuery = internalQuery({
args: {},
handler: getCurrentUserOrThrow,
});

export function getCurrentUserOrThrow(
ctx: QueryCtx
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;
export function getCurrentUserOrThrow(
ctx: ActionCtx
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;

export async function getCurrentUserOrThrow(ctx: QueryCtx | ActionCtx) {
if (!("db" in ctx)) {
return ctx.runQuery(
internal.authentication.user.getCurrentUserOrThrowQuery
);
}

const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Not signed in");
}

const user = await get(ctx, userId);
if (!user) {
throw new Error("User not found");
}

const teamId = user.teamId;
if (!teamId) {
throw new Error("User is not in a team");
}

const team = await get(ctx, teamId);
if (!team) {
throw new Error("Team not found");
}

return { user, team };
}
import { getAuthUserId } from "@convex-dev/auth/server";
import { Doc } from "../_generated/dataModel";
import { ActionCtx, internalQuery, QueryCtx } from "../_generated/server";
import { get } from "../base/ query";
import { internal } from "../_generated/api";

export const getCurrentUserOrThrowQuery = internalQuery({
args: {},
handler: getCurrentUserOrThrow,
});

export function getCurrentUserOrThrow(
ctx: QueryCtx
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;
export function getCurrentUserOrThrow(
ctx: ActionCtx
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;

export async function getCurrentUserOrThrow(ctx: QueryCtx | ActionCtx) {
if (!("db" in ctx)) {
return ctx.runQuery(
internal.authentication.user.getCurrentUserOrThrowQuery
);
}

const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Not signed in");
}

const user = await get(ctx, userId);
if (!user) {
throw new Error("User not found");
}

const teamId = user.teamId;
if (!teamId) {
throw new Error("User is not in a team");
}

const team = await get(ctx, teamId);
if (!team) {
throw new Error("Team not found");
}

return { user, team };
}
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
@Lee yes you are right! And no, no issues @Tom (other than the bug Lee pointed out), just wanted to make sure this is still the blessed path. If query | mutation -> reuse the ctx, if it's an action, you gotta use ActionCtx.runQuery()
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
export function getCurrentUserOrThrow(
ctx: GenericQueryCtx<DataModel>
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;
export function getCurrentUserOrThrow(): Promise<{
user: Doc<"users">;
team: Doc<"teams">;
}>;

export async function getCurrentUserOrThrow(ctx?: GenericQueryCtx<DataModel>) {
if (!ctx) {
return await internalQuery({
args: {},
handler: async (ctx) => {
return await getCurrentUserOrThrow(ctx);
},
});
}

const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Not signed in");
}

const user = await ctx.db.get(userId);
if (!user) {
throw new Error("User not found");
}

const teamId = user.teamId;
if (!teamId) {
throw new Error("User is not in a team");
}

const team = await ctx.db.get(teamId);
if (!team) {
throw new Error("Team not found");
}

return { user, team };
}
export function getCurrentUserOrThrow(
ctx: GenericQueryCtx<DataModel>
): Promise<{ user: Doc<"users">; team: Doc<"teams"> }>;
export function getCurrentUserOrThrow(): Promise<{
user: Doc<"users">;
team: Doc<"teams">;
}>;

export async function getCurrentUserOrThrow(ctx?: GenericQueryCtx<DataModel>) {
if (!ctx) {
return await internalQuery({
args: {},
handler: async (ctx) => {
return await getCurrentUserOrThrow(ctx);
},
});
}

const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Not signed in");
}

const user = await ctx.db.get(userId);
if (!user) {
throw new Error("User not found");
}

const teamId = user.teamId;
if (!teamId) {
throw new Error("User is not in a team");
}

const team = await ctx.db.get(teamId);
if (!team) {
throw new Error("Team not found");
}

return { user, team };
}
Is this legit in the case where I can't pass a query context? (I could also accept a mutation context?) For example, from an action, I can't pass a usable context:
export const create = action({
args: {
creationState: campaignStateSchema,
sendNow: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const { user, team } = await getCurrentUserOrThrow();

// this function req's `fetch` hence we use an action
const { html, plainText } = await parseEmailTemplate(
args.creationState.emailContent.template
);

const campaignName = getCampaignNameFromState(args.creationState);

const campaignId = (await ctx.runMutation(
internal.campaigns.mutations.insert,
{
campaign: //...
}
)) as Id<"campaigns">;

return campaignId;
},
});
export const create = action({
args: {
creationState: campaignStateSchema,
sendNow: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const { user, team } = await getCurrentUserOrThrow();

// this function req's `fetch` hence we use an action
const { html, plainText } = await parseEmailTemplate(
args.creationState.emailContent.template
);

const campaignName = getCampaignNameFromState(args.creationState);

const campaignId = (await ctx.runMutation(
internal.campaigns.mutations.insert,
{
campaign: //...
}
)) as Id<"campaigns">;

return campaignId;
},
});
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
I'm combing through all these now. Code already feels a lot cleaner.
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
I've got some refactoring to do across my code! Love it. Thanks again
25 replies
CCConvex Community
Created by Tom Redman on 1/20/2025 in #support-community
Type error when NOT directly calling function (new to Convex 1.18.2)
Thanks @Lee 📿 Really appreciate it. The docs are helpful!
25 replies
CCConvex Community
Created by Tom Redman on 1/10/2025 in #support-community
Is this typically a bad practice? v.union(v.id("thisList"), v.id("thatList"))
"A discriminated union" is a great band name too
15 replies
CCConvex Community
Created by Tom Redman on 1/10/2025 in #support-community
Is this typically a bad practice? v.union(v.id("thisList"), v.id("thatList"))
Ah this is perfect! Thank you @RJ !
15 replies
CCConvex Community
Created by Tom Redman on 12/6/2024 in #support-community
Has anybody solved implementing a trigger for _scheduled_functions changes?
Excellent! Thanks @lee 🤗
4 replies
CCConvex Community
Created by Tom Redman on 12/6/2024 in #support-community
Not sure why this is throwing a "multiple paginated queries" error
Ahh thank you @erquhart! Makes sense, if a little unintuitive. I guess I can't paginate through a table in its own query.
10 replies