#Fix tasks output type
13 messages · Page 1 of 1 (latest)
I notice you're having an issue with the undefined value in your Convex mutation. This is a common problem when working with Convex because undefined values cannot be properly serialized in JSON when sent to the backend.
The specific issue is in your patch operation where you're conditionally setting progressPourcent: status === "COMPLETED" ? 100 : undefined. When status is not "COMPLETED", you're trying to set progressPourcent to undefined, which Convex cannot handle properly in patches sent from clients.
According to the Convex documentation and support discussions, there are a few ways to fix this:
- Use
nullinstead ofundefinedwhen sending data to Convex, then convert it toundefinedin your mutation:
export const _internal = internalMutation({
args,
handler: async (ctx, args): Promise<DataModel['tasks']> => {
const status = args.status ?? 'COMPLETED';
const completedAt = args.completedAt ?? Date.now();
const results = Array.from(new Set([...args.resultIds].filter(notEmpty)));
// Create a patch object
const patch: any = {
completedAt: completedAt,
resultIds: results,
status: status,
ended: true,
};
// Only add progressPourcent if status is COMPLETED
if (status === "COMPLETED") {
patch.progressPourcent = 100;
}
await ctx.db.patch(args.id, patch);
const task = await ctx.db.get(args.id);
if (!task) throw new Error("Task not found");
// If the task is a temporary task (no projectId), we delete it
// to avoid cluttering the database with old tasks
if (!task.workspaceId) await ctx.db.delete(args.id);
return task;
}
})
This approach avoids using undefined entirely by only including the fields you want to update in the patch object.
As explained in the Discord support thread, undefined is not a value that can be stringified in JSON, which is why it causes issues when sending data to Convex:
The arguments object can't carry undefined values to the backend, as it needs to be stringified first:
JSON.stringify({ foo: 'bar', baz: undefined }) '{"foo":"bar"}'
By constructing your patch object without the fields you don't want to update, you avoid this serialization issue entirely.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@sharp jasper Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@sterile gate the problem is that it lacks Type '{ _id: Id<"tasks">; _creationTime: number; workspaceId?: Id<"workspaces"> | undefined; completedAt?: number | undefined; type: "PHOTOSHOOT" | "REMOVE_BACKGROUND" | "QUICK_PHOTOSHOOT" | "GENERATE_IMAGE" | "UPSCALE" | "MAGIC_ERASER"; ... 6 more ...; updatedAt: number; }' is missing the following properties from type '{ document: { _id: Id<"tasks">; _creationTime: number; workspaceId?: Id<"workspaces"> | undefined; completedAt?: number | undefined; type: "PHOTOSHOOT" | "REMOVE_BACKGROUND" | "QUICK_PHOTOSHOOT" | "GENERATE_IMAGE" | "UPSCALE" | "MAGIC_ERASER"; ... 6 more ...; updatedAt: number; }; fieldPaths: ExtractFieldPaths<...> ...': document, fieldPaths, indexes, searchIndexes, vectorIndexes
Let me check the knowledge base for you again. One moment! :books:
I see the issue now. The error is related to TypeScript types in your Convex function. The problem is that your function is returning a document directly, but the return type DataModel['tasks'] is expecting a more complex type that includes document metadata.
Based on the error message, it looks like DataModel['tasks'] is a complex type that includes properties like document, fieldPaths, indexes, etc., but your function is just returning the document itself.
There are a few ways to fix this:
- Use the correct return type from the generated types:
export const _internal = internalMutation({
args,
handler: async (ctx, args): Promise<Doc<"tasks">> => {
// Your existing code...
return task;
}
})
The Doc<"tasks"> type represents just the document itself, which matches what your function is returning.
This approach is supported by several documents in the knowledge sources. According to the TypeScript best practices documentation:
The
DocTypeScript type fromdataModel.d.tsprovides document types for all of your tables.
TypeScript types documentation
Additionally, from the Types Cookbook:
Convex code generation automatically creates types for all the documents in your tables, exposed via the
Doc<"tablename">generic type fromconvex/_generated/dataModel.
If you need to work with document fields without system fields, you can use WithoutSystemFields:
import { WithoutSystemFields } from "convex/server";
import { Doc } from "./_generated/dataModel";
// For creating new documents
type TaskInput = WithoutSystemFields<Doc<"tasks">>;
[TypeScript best practices](<https://docs.convex.dev/understanding/best-practices/typescript#document-types-without-system-fields>)
This should resolve your type error while maintaining proper type safety in your Convex function.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
@sharp jasper Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@sterile gate
import { v } from "convex/values";
import { serverMutation } from "../../procedures";
import { internalMutation } from "../../_generated/server";
import { internal } from "../../_generated/api";
import { TASK_ENDED_STATUS } from "../../schema/task";
import { Doc, Id } from "../../_generated/dataModel";
import { notEmpty } from "@utils/typescript.helpers";
const args = {
id: v.id("tasks"),
resultIds: v.array(v.id("workspaceImages")),
progressPourcent: v.number(),
status: TASK_ENDED_STATUS,
completedAt: v.optional(v.number()),
}
export const _internal = internalMutation({
args,
handler: async (ctx, args): Promise<Doc<'tasks'>> => {
const status = args.status ?? 'COMPLETED';
const completedAt = args.completedAt ?? Date.now();
const results = Array.from(new Set([...args.resultIds].filter(notEmpty)));
await ctx.db.patch(args.id, {
progressPourcent: status === "COMPLETED" ? 100 : undefined,
completedAt: completedAt,
resultIds: results,
status: status,
ended: true,
})
const task = await ctx.db.get(args.id);
if (!task) throw new Error("Task not found");
// If the task is a temporary task (no projectId), we delete it
// to avoid cluttering the database with old tasks
if (!task.workspaceId) await ctx.db.delete(args.id);
return task;
}
})
export const server = serverMutation({
args,
handler: async (ctx, args): Promise<void> => {
const x = await ctx.runMutation(internal.router.tasks.updateTask._internal, args)
}
})
x is a string somehow..
Let me check the knowledge base for you again. One moment! :books:
I see the issue you're encountering. The problem is related to type inference when using ctx.runMutation in Convex.
Looking at your code, you're running a mutation from within another mutation using ctx.runMutation, but the return type isn't being properly inferred. Even though your _internal mutation returns Promise<Doc<'tasks'>>, the variable x is somehow being typed as a string.
This appears to be related to a known issue with the TypeScript type system in Convex when using zod wrappers or when dealing with return types from runMutation. According to a Discord support thread, there was a problem where the output validator wasn't being passed through to the Convex returns validator:
Trusted Support Staff 53: thanks for catching that - I wasn't passing through the output validator to the convex
returnsvalidator. I was worried about missing fields causing the convex validator to choke, but I can make that change.
To fix this issue, you have a few options:
- Explicitly type the return value of
ctx.runMutation:
export const server = serverMutation({
args,
handler: async (ctx, args): Promise<void> => {
const x: Doc<'tasks'> = await ctx.runMutation(internal.router.tasks.updateTask._internal, args);
// Now x is properly typed
}
})
- Add a
returnsvalidator to your mutation to explicitly define the return type:
export const _internal = internalMutation({
args,
returns: v.object(/* define your task object schema here */),
handler: async (ctx, args): Promise<Doc<'tasks'>> => {
// Your existing code...
}
})
According to the Discord thread, there was a change from using output to returns in a newer version:
Trusted Support Staff 53: If you use the alpha release, note you'll have to change your validators from
outputtoreturns- I made it a new key so it would be less likely to break any existing apps, and so it matches the convex naming.
The FunctionReturnType utility from Convex should be correctly inferring the return type of your mutation, but there seems to be an issue with the type inference in this specific case. Explicitly typing the return value or using a returns validator should help resolve this issue.
Remember that when using ctx.runMutation, you're calling a mutation within the same transaction, and the return type should match what's defined in the target mutation.
Also, be aware that there's a note in the documentation about using runMutation:
NOTE: often you can call the mutation's function directly instead of using this.
runMutationincurs overhead of running argument and return value validation, and creating a new isolated JS context.