Michael
Michael4w ago

How to best model an eventually complete record in the schema?

We have a table in which some fields are not ready at creation, but are eventually filled out by scheduled functions. What would be the best way to model the schema for this in Convex and get type safety? Here's a complex approach that we're trying (that would obviously not scale well for more fields):
const commonFields = v.object({
createdBy: v.id("users"),
displayName: v.string(),
});

const eventuallyCompleteField1 = v.object({
projectSlug: v.string(),
teamSlug: v.string()
});

const eventuallyCompleteField2 = v.string();

const tableReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: eventuallyCompleteField1,
eventuallyComplete2: eventuallyCompleteField2
});

const tableNotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: v.null(),
eventuallyComplete2: v.null()
});

const ev1NotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: v.null(),
eventuallyComplete2: eventuallyCompleteField2
});

const ev2NotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: eventuallyCompleteField1,
eventuallyComplete2: v.null()
});

export const vMyTable = v.union(
tableReadyVersion,
tableNotReadyVersion,
ev1NotReadyVersion,
ev2NotReadyVersion,
);
const commonFields = v.object({
createdBy: v.id("users"),
displayName: v.string(),
});

const eventuallyCompleteField1 = v.object({
projectSlug: v.string(),
teamSlug: v.string()
});

const eventuallyCompleteField2 = v.string();

const tableReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: eventuallyCompleteField1,
eventuallyComplete2: eventuallyCompleteField2
});

const tableNotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: v.null(),
eventuallyComplete2: v.null()
});

const ev1NotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: v.null(),
eventuallyComplete2: eventuallyCompleteField2
});

const ev2NotReadyVersion = v.object({
...commonFields.fields,
eventuallyComplete1: eventuallyCompleteField1,
eventuallyComplete2: v.null()
});

export const vMyTable = v.union(
tableReadyVersion,
tableNotReadyVersion,
ev1NotReadyVersion,
ev2NotReadyVersion,
);
Thank you!
2 Replies
Convex Bot
Convex Bot4w 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!
Clever Tagline
The common way to model such a table is by using v.optional() to wrap those field values that don't need to be specified at creation; e.g.
const myTable = defineTable({
createdBy: v.id("users"),
displayName: v.string(),
eventuallyCompleteField1: v.optional(
v.object({
projectSlug: v.string(),
teamSlug: v.string()
})
),
eventuallyCompleteField2: v.optional(
v.string()
)
})
const myTable = defineTable({
createdBy: v.id("users"),
displayName: v.string(),
eventuallyCompleteField1: v.optional(
v.object({
projectSlug: v.string(),
teamSlug: v.string()
})
),
eventuallyCompleteField2: v.optional(
v.string()
)
})
When inserting the document, you're only required to pass values for createdBy and displayName. The others you can pass later in mutations via ctx.db.patch, either singly or at the same time, however your logic needs to work. If passed, only the type you specified will be accepted in each case, or undefined to delete the existing field value.

Did you find this page helpful?