convex-helpers
Version:
A collection of useful code to complement the official convex package.
235 lines (234 loc) • 12.8 kB
TypeScript
import { ZodTypeDef, z } from "zod";
import { v, GenericId, ObjectType, PropertyValidators, Validator } from "convex/values";
import { FunctionVisibility, GenericDataModel, GenericActionCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, GenericMutationCtx, ActionBuilder } from "convex/server";
import { Mod, Registration, UnvalidatedBuilder } from "./customFunctions";
import { EmptyObject } from "..";
export type ZodValidator = Record<string, z.ZodTypeAny>;
/**
* Create a validator for a Convex `Id`.
*
* When used as a validator, it will check that it's for the right table.
* When used as a parser, it will only check that the Id is a string.
*
* @param tableName - The table that the `Id` references. i.e.` Id<tableName>`
* @returns - A Zod object representing a Convex `Id`
*/
export declare const zid: <TableName extends string>(tableName: TableName) => Zid<TableName>;
/**
* zCustomQuery is like customQuery, but allows validation via zod.
* You can define custom behavior on top of `query` or `internalQuery`
* by passing a function that modifies the ctx and args. Or NoOp to do nothing.
*
* Example usage:
* ```js
* const myQueryBuilder = zCustomQuery(query, {
* args: { sessionId: v.id("sessions") },
* input: async (ctx, args) => {
* const user = await getUserOrNull(ctx);
* const session = await db.get(sessionId);
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
* return { ctx: { db, user, session }, args: {} };
* },
* });
*
* // Using the custom builder
* export const getSomeData = myQueryBuilder({
* args: { someArg: z.string() },
* handler: async (ctx, args) => {
* const { db, user, session, scheduler } = ctx;
* const { someArg } = args;
* // ...
* }
* });
* ```
*
* Simple usage only modifying ctx:
* ```js
* const myInternalQuery = zCustomQuery(
* internalQuery,
* customCtx(async (ctx) => {
* return {
* // Throws an exception if the user isn't logged in
* user: await getUserByTokenIdentifier(ctx),
* };
* })
* );
*
* // Using it
* export const getUser = myInternalQuery({
* args: { email: z.string().email() },
* handler: async (ctx, args) => {
* console.log(args.email);
* return ctx.user;
* },
* });
*
* @param query The query to be modified. Usually `query` or `internalQuery`
* from `_generated/server`.
* @param mod The modifier to be applied to the query, changing ctx and args.
* @returns A new query builder using zod validation to define queries.
*/
export declare function zCustomQuery<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(query: QueryBuilder<DataModel, Visibility>, mod: Mod<GenericQueryCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"query", ModArgsValidator, ModCtx, ModMadeArgs, GenericQueryCtx<DataModel>, Visibility>;
/**
* zCustomMutation is like customMutation, but allows validation via zod.
* You can define custom behavior on top of `mutation` or `internalMutation`
* by passing a function that modifies the ctx and args. Or NoOp to do nothing.
*
* Example usage:
* ```js
* const myMutationBuilder = zCustomMutation(mutation, {
* args: { sessionId: v.id("sessions") },
* input: async (ctx, args) => {
* const user = await getUserOrNull(ctx);
* const session = await db.get(sessionId);
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
* return { ctx: { db, user, session }, args: {} };
* },
* });
*
* // Using the custom builder
* export const getSomeData = myMutationBuilder({
* args: { someArg: z.string() },
* handler: async (ctx, args) => {
* const { db, user, session, scheduler } = ctx;
* const { someArg } = args;
* // ...
* }
* });
* ```
*
* Simple usage only modifying ctx:
* ```js
* const myInternalMutation = zCustomMutation(
* internalMutation,
* customCtx(async (ctx) => {
* return {
* // Throws an exception if the user isn't logged in
* user: await getUserByTokenIdentifier(ctx),
* };
* })
* );
*
* // Using it
* export const getUser = myInternalMutation({
* args: { email: z.string().email() },
* handler: async (ctx, args) => {
* console.log(args.email);
* return ctx.user;
* },
* });
*
* @param mutation The mutation to be modified. Usually `mutation` or `internalMutation`
* from `_generated/server`.
* @param mod The modifier to be applied to the mutation, changing ctx and args.
* @returns A new mutation builder using zod validation to define queries.
*/
export declare function zCustomMutation<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(mutation: MutationBuilder<DataModel, Visibility>, mod: Mod<GenericMutationCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"mutation", ModArgsValidator, ModCtx, ModMadeArgs, GenericMutationCtx<DataModel>, Visibility>;
/**
* zCustomAction is like customAction, but allows validation via zod.
* You can define custom behavior on top of `action` or `internalAction`
* by passing a function that modifies the ctx and args. Or NoOp to do nothing.
*
* Example usage:
* ```js
* const myActionBuilder = zCustomAction(action, {
* args: { sessionId: v.id("sessions") },
* input: async (ctx, args) => {
* const user = await getUserOrNull(ctx);
* const session = await db.get(sessionId);
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
* return { ctx: { db, user, session }, args: {} };
* },
* });
*
* // Using the custom builder
* export const getSomeData = myActionBuilder({
* args: { someArg: z.string() },
* handler: async (ctx, args) => {
* const { db, user, session, scheduler } = ctx;
* const { someArg } = args;
* // ...
* }
* });
* ```
*
* Simple usage only modifying ctx:
* ```js
* const myInternalAction = zCustomAction(
* internalAction,
* customCtx(async (ctx) => {
* return {
* // Throws an exception if the user isn't logged in
* user: await getUserByTokenIdentifier(ctx),
* };
* })
* );
*
* // Using it
* export const getUser = myInternalAction({
* args: { email: z.string().email() },
* handler: async (ctx, args) => {
* console.log(args.email);
* return ctx.user;
* },
* });
*
* @param action The action to be modified. Usually `action` or `internalAction`
* from `_generated/server`.
* @param mod The modifier to be applied to the action, changing ctx and args.
* @returns A new action builder using zod validation to define queries.
*/
export declare function zCustomAction<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(action: ActionBuilder<DataModel, Visibility>, mod: Mod<GenericActionCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"action", ModArgsValidator, ModCtx, ModMadeArgs, GenericActionCtx<DataModel>, Visibility>;
/**
* A builder that customizes a Convex function using argument validation.
* e.g. `query({ args: {}, handler: async (ctx, args) => {} })`
*/
type ValidatedBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = <ExistingArgsValidator extends ZodValidator, Output, ZodOutput extends z.ZodTypeAny | undefined = undefined>(fn: {
args: ExistingArgsValidator;
handler: (ctx: InputCtx & ModCtx, args: z.output<z.ZodObject<ExistingArgsValidator>> & ModMadeArgs) => ZodOutput extends z.ZodTypeAny ? z.input<ZodOutput> | Promise<z.input<ZodOutput>> : Output;
output?: ZodOutput;
}) => Registration<FuncType, Visibility, z.input<z.ZodObject<ExistingArgsValidator>> & ObjectType<ModArgsValidator>, ZodOutput extends z.ZodTypeAny ? Promise<z.output<ZodOutput>> : Output>;
/**
* A builder that customizes a Convex function, whether or not it validates
* arguments. If the customization requires arguments, however, the resulting
* builder will require argument validation too.
*/
type CustomBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = ModArgsValidator extends EmptyObject ? ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility> & UnvalidatedBuilder<FuncType, ModCtx, ModMadeArgs, InputCtx, Visibility> : ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility>;
type ConvexValidatorFromZod<Z extends z.ZodTypeAny> = Z extends Zid<infer TableName> ? Validator<GenericId<TableName>> : Z extends z.ZodString ? Validator<string> : Z extends z.ZodNumber ? Validator<number> : Z extends z.ZodNaN ? Validator<number> : Z extends z.ZodBigInt ? Validator<bigint> : Z extends z.ZodBoolean ? Validator<boolean> : Z extends z.ZodNull ? Validator<null> : Z extends z.ZodUnknown ? Validator<any, false, string> : Z extends z.ZodAny ? Validator<any, false, string> : Z extends z.ZodArray<infer Inner> ? Validator<ConvexValidatorFromZod<Inner>["type"][]> : Z extends z.ZodObject<infer ZodShape> ? ReturnType<typeof v.object<{
[key in keyof ZodShape]: ConvexValidatorFromZod<ZodShape[key]>;
}>> : Z extends z.ZodUnion<infer T> ? Validator<ConvexValidatorFromZod<T[number]>["type"], false, ConvexValidatorFromZod<T[number]>["fieldPaths"]> : Z extends z.ZodDiscriminatedUnion<any, infer T> ? Validator<ConvexValidatorFromZod<T[number]>["type"], false, ConvexValidatorFromZod<T[number]>["fieldPaths"]> : Z extends z.ZodTuple<infer Inner> ? Validator<ConvexValidatorFromZod<Inner[number]>["type"][]> : Z extends z.ZodLazy<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodLiteral<infer Literal> ? Validator<Literal> : Z extends z.ZodEnum<infer T> ? Validator<T[number]> : Z extends z.ZodEffects<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodOptional<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, false, infer InnerFieldPaths> ? Validator<InnerConvex | undefined, true, InnerFieldPaths> : never : Z extends z.ZodNullable<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, infer InnerOptional, infer InnerFieldPaths> ? Validator<null | InnerConvex, InnerOptional, InnerFieldPaths> : never : Z extends z.ZodBranded<infer Inner, any> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodDefault<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, false, infer InnerFieldPaths> ? Validator<InnerConvex | undefined, true, InnerFieldPaths> : never : Z extends z.ZodReadonly<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodPipeline<infer Inner, any> ? ConvexValidatorFromZod<Inner> : never;
/**
* Turn a Zod validator into a Convex Validator.
* @param zod Zod validator can be a Zod object, or a Zod type like `z.string()`
* @returns Convex Validator (e.g. `v.string()` from "convex/values")
*/
export declare function zodToConvex<Z extends z.ZodTypeAny>(zod: Z): ConvexValidatorFromZod<Z>;
/**
* Like zodToConvex, but it takes in a bare object, as expected by Convex
* function arguments, or the argument to defineTable.
*
* @param zod Object with string keys and Zod validators as values
* @returns Object with the same keys, but with Convex validators as values
*/
export declare function zodToConvexFields<Z extends ZodValidator>(zod: Z): { [k in keyof Z]: ConvexValidatorFromZod<Z[k]>; };
interface ZidDef<TableName extends string> extends ZodTypeDef {
typeName: "ConvexId";
tableName: TableName;
}
export declare class Zid<TableName extends string> extends z.ZodType<GenericId<TableName>, ZidDef<TableName>> {
_parse(input: z.ParseInput): z.ParseReturnType<GenericId<TableName>>;
}
/**
* Zod helper for adding Convex system fields to a record to return.
*
* @param tableName - The table where records are from, i.e. Doc<tableName>
* @param zObject - Validators for the user-defined fields on the document.
* @returns - Zod shape for use with `z.object(shape)` that includes system fields.
*/
export declare const withSystemFields: <Table extends string, T extends {
[key: string]: z.ZodTypeAny;
}>(tableName: Table, zObject: T) => T & {
_id: Zid<Table>;
_creationTime: z.ZodNumber;
};
export {};