Delveroff
Delveroff5mo ago

Schema Intersection

I try to create the following model:
type Asset = {
status: 'created' | 'removed'
} & (
| {
type: 'image'
src: string
}
| {
type: 'text'
value: string
}
)
type Asset = {
status: 'created' | 'removed'
} & (
| {
type: 'image'
src: string
}
| {
type: 'text'
value: string
}
)
I've read about unions at the top level here: https://docs.convex.dev/database/schemas#unions However, as you can see, I need the status field that is true for all asset types. I tried to do this:
defineSchema({
assets: defineTable({
status: v.string(), // union of literals
...v.union( // Argument of type is not assignable to parameter of type Record<string, GenericValidator>
v.object({type: v.literal('image'), src: v.string()}),
v.object({type: v.literal('text'), value: v.string()})
)
})
})
defineSchema({
assets: defineTable({
status: v.string(), // union of literals
...v.union( // Argument of type is not assignable to parameter of type Record<string, GenericValidator>
v.object({type: v.literal('image'), src: v.string()}),
v.object({type: v.literal('text'), value: v.string()})
)
})
})
For now, I can't understand how to implement the aforementioned type. Seems like it lacks v.intersection() API.
Schemas | Convex Developer Hub
Schema validation keeps your Convex data neat and tidy. It also gives you end-to-end TypeScript type safety!
5 Replies
Delveroff
DelveroffOP5mo ago
Also, I wouldn't like to create a nested object since I have to index by type
Michal Srb
Michal Srb5mo ago
Simple:
assets: defineTable(
v.union(
v.object({
status: v.string(),
type: v.literal("image"),
src: v.string(),
}),
v.object({
status: v.string(),
type: v.literal("text"),
value: v.string(),
}),
),
),
assets: defineTable(
v.union(
v.object({
status: v.string(),
type: v.literal("image"),
src: v.string(),
}),
v.object({
status: v.string(),
type: v.literal("text"),
value: v.string(),
}),
),
),
Fancy:
const shared = {
status: v.string(),
};

export default defineSchema({
assets: defineTable(
v.union(
v.object({
...shared,
type: v.literal("image"),
src: v.string(),
}),
v.object({
...shared,
type: v.literal("text"),
value: v.string(),
}),
),
),
});
const shared = {
status: v.string(),
};

export default defineSchema({
assets: defineTable(
v.union(
v.object({
...shared,
type: v.literal("image"),
src: v.string(),
}),
v.object({
...shared,
type: v.literal("text"),
value: v.string(),
}),
),
),
});
Fanciest:
const shared = v.object({
status: v.string(),
});

export default defineSchema({
assets: defineTable(
v.union(
v.object({
...shared.fields,
type: v.literal("image"),
src: v.string(),
}),
v.object({
...shared.fields,
type: v.literal("text"),
value: v.string(),
}),
),
),
});
const shared = v.object({
status: v.string(),
});

export default defineSchema({
assets: defineTable(
v.union(
v.object({
...shared.fields,
type: v.literal("image"),
src: v.string(),
}),
v.object({
...shared.fields,
type: v.literal("text"),
value: v.string(),
}),
),
),
});
Delveroff
DelveroffOP5mo ago
Thought exactly the same thing. Thanks! Also thought about the following:
export const assetObject(defs: any) => v.object({ // still figure out typings
status: v.string(),
...defs
})

export default defineSchema({
assets: defineTable(
v.union(
assetObject({
type: v.literal('video')
})
)
)
})
export const assetObject(defs: any) => v.object({ // still figure out typings
status: v.string(),
...defs
})

export default defineSchema({
assets: defineTable(
v.union(
assetObject({
type: v.literal('video')
})
)
)
})
Michal Srb
Michal Srb5mo ago
Doable but harder to type annotate Maybe @ian could build this into convex-helpers, I can see this being a fairly general pattern.
sshader
sshader5mo ago
Also, I wouldn't like to create a nested object since I have to index by type
If you're talking about Convex indexes, you can define indexes on nested properties!

Did you find this page helpful?