Štosdenfer
Štosdenfer6d ago

import.meta not supported - calling helper fn

I'm getting import.meta not supported error when trying to run this:
// convex/tasks.ts
export const deleteTask = mutation({
args: {
id: v.id("tasks"),
fileKeys: v.array(v.string()),
},
handler: async (ctx, args) => {
const user = await ctx.auth.getUserIdentity();
if (!user) {
throw new Error("not-authorized");
}

await ctx.db.delete(args.id);
await deleteFiles(args.fileKeys);
},
});

// server/uploadthing.ts
"use server";

import { UTApi } from "uploadthing/server";

const utapi = new UTApi({});

export const deleteFiles = async (fileKeys: string[]) => {
await utapi.deleteFiles(fileKeys);
};
// convex/tasks.ts
export const deleteTask = mutation({
args: {
id: v.id("tasks"),
fileKeys: v.array(v.string()),
},
handler: async (ctx, args) => {
const user = await ctx.auth.getUserIdentity();
if (!user) {
throw new Error("not-authorized");
}

await ctx.db.delete(args.id);
await deleteFiles(args.fileKeys);
},
});

// server/uploadthing.ts
"use server";

import { UTApi } from "uploadthing/server";

const utapi = new UTApi({});

export const deleteFiles = async (fileKeys: string[]) => {
await utapi.deleteFiles(fileKeys);
};
If I remove line await deleteFiles(args.fileKeys); in convex/tasks.ts everything works. Can I not call outside fns inside mutations?
12 Replies
Convex Bot
Convex Bot6d ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
Štosdenfer
ŠtosdenferOP6d ago
For this to work, similarly to queries, mutations must be deterministic, and cannot call third party APIs. To call third party APIs, use actions.
is this the issue?
Clever Tagline
Yup. The recommendation in a case like this is to have the mutation schedule an internal action. Converting deleteFiles to an internal action should be pretty simple.
Štosdenfer
ŠtosdenferOP5d ago
Action didn't work initially as well because it couldn't use the deleteFile from the uploadthing package. I had two options then: use the "use node" directive which made everything quite a bit slower or use the UploadThing REST API via fetch in the native Convex env - for that I needed to add the UT env var secret in the convex dashboard. I couldn't get it to work with fetch, but axios for some reason worked. Anyways, it's resolved and here's the final code for future reference:
// convex/tasks.ts
export const deleteTask = internalMutation({
args: {
id: v.id("tasks"),
},
handler: async (ctx, args) => {
const user = await ctx.auth.getUserIdentity();
if (!user) {
throw new Error("not-authorized");
}

await ctx.db.delete(args.id);
},
});
export const deleteTaskWithFiles = action({
args: { id: v.id("tasks"), fileKeys: v.array(v.string()) },
handler: async (ctx, args) => {
const { id, fileKeys } = args;

await ctx.runMutation(internal.tasks.deleteTask, { id });

const utApiKey = process.env.UPLOADTHING_SECRET;
if (!utApiKey) {
throw new Error("ut-secret-not-set");
}

const { data } = await axios.post(
"https://api.uploadthing.com/v6/deleteFiles",
{
fileKeys: fileKeys,
},
{
headers: {
"X-Uploadthing-Api-Key": utApiKey,
"Content-Type": "application/json",
},
},
);
console.log("ut response", data);
},
});
// convex/tasks.ts
export const deleteTask = internalMutation({
args: {
id: v.id("tasks"),
},
handler: async (ctx, args) => {
const user = await ctx.auth.getUserIdentity();
if (!user) {
throw new Error("not-authorized");
}

await ctx.db.delete(args.id);
},
});
export const deleteTaskWithFiles = action({
args: { id: v.id("tasks"), fileKeys: v.array(v.string()) },
handler: async (ctx, args) => {
const { id, fileKeys } = args;

await ctx.runMutation(internal.tasks.deleteTask, { id });

const utApiKey = process.env.UPLOADTHING_SECRET;
if (!utApiKey) {
throw new Error("ut-secret-not-set");
}

const { data } = await axios.post(
"https://api.uploadthing.com/v6/deleteFiles",
{
fileKeys: fileKeys,
},
{
headers: {
"X-Uploadthing-Api-Key": utApiKey,
"Content-Type": "application/json",
},
},
);
console.log("ut response", data);
},
});
actually, reading this again - was I supposed to call the action inside the internalMutation and not the other way around like I did?
Clever Tagline
Correct. The mutation deletes the document, then schedules the execution of an internal action to make the call to uploadThing.
Štosdenfer
ŠtosdenferOP5d ago
And if the action fails inside the internal mutation, will it still mutate the data?
Clever Tagline
To be clear, you don't run the internal action from within the mutation, you schedule it. Scheduling an action always succeeds because you're just saying, "Run this thing after X delay, and using these arguments." The scheduler sets up that action to run after the indicated delay, and the mutation continues. This separates the success of the action from the success of the mutation because it's technically a different process running the action once it's scheduled.
Štosdenfer
ŠtosdenferOP5d ago
Hm, okay. Can you help me understand then why is it more correct to call the action from the mutation, instead of vice versa?
Clever Tagline
Again, you're not calling the action, you're scheduling it. There's an important difference. If you were to call it directly from the mutation, then the mutation would be impacted if the action failed. By scheduling the action, they're separated, and the action's success/failure has no impact on the mutation. The reason why it's vital to use actions for calling external APIs is explained here: https://docs.convex.dev/functions/mutation-functions#transactions
Mutations | Convex Developer Hub
Insert, update, and remove data from the database
Clever Tagline
Read more about scheduled functions here: https://docs.convex.dev/scheduling/scheduled-functions
Scheduled Functions | Convex Developer Hub
Schedule functions to run in the future
erquhart
erquhart5d ago
This is an important point - actions do not roll back on failure. So it's possible the mutation succeeds but the action does not. As Clever said the mutation scheduling the action only guarantees the action will run, not that it will run successfully. One more on top of the resources Clever linked to: Look for "Architecture" > "Don't misuse actions" section in Zen of Convex: https://docs.convex.dev/understanding/zen
Štosdenfer
ŠtosdenferOP4d ago
Thank you guys! This was helpful, I'm gonna mark this as resolved

Did you find this page helpful?