David Alonso
David Alonso4mo ago

prefix validator keys helper 🙏

I'm playing around with this code:
// Helper function to prefix object keys for the Convex validator
function prefixValidatorKeys<T extends Record<string, any>>(obj: T, prefix: z.infer<typeof zBlockType>) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [`${prefix}_${key}`, value])
);
}

// Create the Geopoint block properties validator
export const vGeopointBlockProperties = v.object({
...vCommonProps.fields,
...prefixValidatorKeys(vGeopointSpecificProps.fields, "geopoint"),
});

// Create a type that includes both common and prefixed specific properties
type PrefixProperties<T, P extends z.infer<typeof zBlockType>> = {
[K in keyof T as `${P}_${string & K}`]: T[K]
};

export type GeopointBlockProperties =
Infer<typeof vCommonProps> &
PrefixProperties<Infer<typeof vGeopointSpecificProps>, 'geopoint'>;

const testGeopointBlock: GeopointBlockProperties = {
gridLayout: { x: 0, y: 0 },
geopoint_mapType: "roadmap",
geopoint_zoom: 10,
};
// Helper function to prefix object keys for the Convex validator
function prefixValidatorKeys<T extends Record<string, any>>(obj: T, prefix: z.infer<typeof zBlockType>) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [`${prefix}_${key}`, value])
);
}

// Create the Geopoint block properties validator
export const vGeopointBlockProperties = v.object({
...vCommonProps.fields,
...prefixValidatorKeys(vGeopointSpecificProps.fields, "geopoint"),
});

// Create a type that includes both common and prefixed specific properties
type PrefixProperties<T, P extends z.infer<typeof zBlockType>> = {
[K in keyof T as `${P}_${string & K}`]: T[K]
};

export type GeopointBlockProperties =
Infer<typeof vCommonProps> &
PrefixProperties<Infer<typeof vGeopointSpecificProps>, 'geopoint'>;

const testGeopointBlock: GeopointBlockProperties = {
gridLayout: { x: 0, y: 0 },
geopoint_mapType: "roadmap",
geopoint_zoom: 10,
};
The part that uses regular TS types works, but I'm having issues with the validators part, namely prefixValidatorKeys. Any idea of how this could be implemented?
9 Replies
Convex Bot
Convex Bot4mo 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. - Ask in the <#1228095053885476985> channel to get a response from <@1072591948499664996>. - Avoid tagging staff unless specifically instructed. Thank you!
jamalsoueidan
jamalsoueidan4mo ago
You can use the new v.record()
David Alonso
David AlonsoOP4mo ago
with v.record I can't set different keys to different types, so don't think that would work...
ian
ian4mo ago
You mean you want the better return type for prefixValidatorKeys ? I think you'd return something like VObject with a similar mapped type to PrefixProperties. The Object.fromEntries with map will lose specific types unfortunately
David Alonso
David AlonsoOP4mo ago
yeah, that's the issue I'm running into with these bulk operations.. I'm trying to do something like this with validators as well but I think I might run into the same limitation?
// Helper function to make all fields in a Zod object schema optional
function makeAllOptional<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
return schema.extend(
Object.fromEntries(
Object.entries(schema.shape).map(([key, value]) => [key, value.optional()])
) as { [K in keyof T]: z.ZodOptional<T[K]> }
);
}
// Helper function to make all fields in a Zod object schema optional
function makeAllOptional<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
return schema.extend(
Object.fromEntries(
Object.entries(schema.shape).map(([key, value]) => [key, value.optional()])
) as { [K in keyof T]: z.ZodOptional<T[K]> }
);
}
ian
ian4mo ago
Yeah I've found it's sometimes easier to tell TypeScript the end state with a return type, then just do the convenient JS with an occasional any, when the types are hard for TypeScript to keep up with. In general I feel like the TS types for .map and Object.* are lacking. So doing something like this for me works:
function prefixValidatorKeys3<T extends Record<string, any>, P extends string>(
obj: T,
prefix: P
) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [`${prefix}_${key}`, value])
) as PrefixProperties<T, P>;
}
function prefixValidatorKeys3<T extends Record<string, any>, P extends string>(
obj: T,
prefix: P
) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [`${prefix}_${key}`, value])
) as PrefixProperties<T, P>;
}
And if you want the hover description to also have the prefixes shown, you can use Expand:
import { Expand } from "convex/server";
//...
) as Expand<PrefixProperties<T, typeof prefix>>;
}
import { Expand } from "convex/server";
//...
) as Expand<PrefixProperties<T, typeof prefix>>;
}
David Alonso
David AlonsoOP4mo ago
thanks Ian, that solved the issue with prefixing. For the makeAllOptional helper I realized that in Zod you can just do .partial() on an object. Is there something similar for Convex validators?
ian
ian4mo ago
There's a partial helper I put in convex-helpers/validators.
jamalsoueidan
jamalsoueidan4mo ago
True