UNPKG

convex-helpers

Version:

A collection of useful code to complement the official convex package.

261 lines (260 loc) 12.1 kB
/** * This file contains helpers for defining custom functions that modify the * context and arguments of a Convex function. Allows you to: * * - Run authentication logic before the request starts. * - Look up commonly used data and add it to the ctx argument. * - Replace a ctx or argument field with a different value, such as a version * of `db` that runs custom functions on data access. * - Consume arguments from the client that are not passed to the query, such * as taking in an authentication parameter like an API key or session ID. * These arguments must be sent up by the client along with each request. */ import { ObjectType, PropertyValidators } from "convex/values"; import { ActionBuilder, FunctionVisibility, GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, RegisteredAction, RegisteredMutation, RegisteredQuery, UnvalidatedFunction } from "convex/server"; import { EmptyObject } from ".."; /** * A modifier for a query, mutation, or action. * * This defines what arguments are required for the modifier, and how to modify * the ctx and args. If the required args are not returned, they will not be * provided for the modified function. All returned ctx and args will show up * in the type signature for the modified function. * To remove something from `ctx`, you can return it as `undefined`. */ export type Mod<Ctx extends Record<string, any>, ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>> = { args: ModArgsValidator; input: (ctx: Ctx, args: ObjectType<ModArgsValidator>) => Promise<{ ctx: ModCtx; args: ModMadeArgs; }> | { ctx: ModCtx; args: ModMadeArgs; }; }; /** * A helper for defining a Mod when your mod doesn't need to add or remove * anything from args. * @param mod A function that defines how to modify the ctx. * @returns A ctx delta to be applied to the original ctx. */ export declare function customCtx<InCtx extends Record<string, any>, OutCtx extends Record<string, any>>(mod: (original: InCtx) => Promise<OutCtx> | OutCtx): Mod<InCtx, {}, OutCtx, {}>; /** * A Mod that doesn't add or remove any context or args. */ export declare const NoOp: { args: {}; input(): { args: {}; ctx: {}; }; }; /** * customQuery helps define custom behavior on top of `query` or `internalQuery` * by passing a function that modifies the ctx and args. * * Example usage: * ```js * const myQueryBuilder = customQuery(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: v.string() }, * handler: async (ctx, args) => { * const { db, user, session, scheduler } = ctx; * const { someArg } = args; * // ... * } * }); * ``` * * Simple usage only modifying ctx: * ```js * const myInternalQuery = customQuery( * 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: {}, * handler: async (ctx, args) => { * 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 to define queries with modified ctx and args. */ export declare function customQuery<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>; /** * customMutation helps define custom behavior on top of `mutation` * or `internalMutation` by passing a function that modifies the ctx and args. * * Example usage: * ```js * const myMutationBuilder = customMutation(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 setSomeData = myMutationBuilder({ * args: { someArg: v.string() }, * handler: async (ctx, args) => { * const { db, user, session, scheduler } = ctx; * const { someArg } = args; * // ... * } * }); * ``` * * Simple usage only modifying ctx: * ```js * const myUserMutation = customMutation( * mutation, * customCtx(async (ctx) => { * return { * // Throws an exception if the user isn't logged in * user: await getUserByTokenIdentifier(ctx), * }; * }) * ); * * // Using it * export const setMyName = myUserMutation({ * args: { name: v.string() }, * handler: async (ctx, args) => { * await ctx.db.patch(ctx.user._id, { name: args.name }); * }, * }); * * @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 to define queries with modified ctx and args. */ export declare function customMutation<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>; /** * customAction helps define custom behavior on top of `action` * or `internalAction` by passing a function that modifies the ctx and args. * * Example usage: * ```js * const myActionBuilder = customAction(action, { * args: { secretKey: v.string() }, * input: async (ctx, args) => { * // Very basic authorization, e.g. from trusted backends. * if (args.secretKey !== process.env.SECRET_KEY) { * throw new Error("Invalid secret key"); * } * const user = await ctx.runQuery(internal.users.getUser, {}); * return { ctx: { user }, args: {} }; * }, * }); * * // Using the custom builder * export const runSomeAction = myActionBuilder({ * args: { someArg: v.string() }, * handler: async (ctx, args) => { * const { user, scheduler } = ctx; * const { someArg } = args; * // ... * } * }); * ``` * * Simple usage only modifying ctx: * ```js * const myUserAction = customAction( * internalAction, * customCtx(async (ctx) => { * return { * // Throws an exception if the user isn't logged in * user: await ctx.runQuery(internal.users.getUser, {}); * }; * }) * ); * * // Using it * export const sendUserEmail = myUserAction({ * args: { subject: v.string(), body: v.string() }, * handler: async (ctx, args) => { * await sendEmail(ctx.user.email, args.subject, args.body); * }, * }); * * @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 to define queries with modified ctx and args. */ export declare function customAction<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>; /** * * @param splitArgsValidator The args that should be split out from the rest. * As an object mapping arg names to validators (v.* from convex/values). * @param args The arguments to a function, including values to be split out. * @returns The args split into two objects: `split` and `rest` based on keys. */ export declare function splitArgs<SplitArgsValidator extends PropertyValidators, Args extends Record<string, any>>(splitArgsValidator: SplitArgsValidator, args: Args & ObjectType<SplitArgsValidator>): { split: ObjectType<SplitArgsValidator>; rest: { [k in Exclude<keyof Args, keyof SplitArgsValidator>]: Args[k]; }; }; /** * A Convex function (query, mutation, or action) to be registered for the API. * Convenience to specify the registration type based on function type. */ export type Registration<FuncType extends "query" | "mutation" | "action", Visibility extends FunctionVisibility, Args extends DefaultFunctionArgs, Output> = { query: RegisteredQuery<Visibility, Args, Output>; mutation: RegisteredMutation<Visibility, Args, Output>; action: RegisteredAction<Visibility, Args, Output>; }[FuncType]; /** * 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 PropertyValidators, Output>(fn: { args: ExistingArgsValidator; handler: (ctx: Overwrite<InputCtx, ModCtx>, args: Overwrite<ObjectType<ExistingArgsValidator>, ModMadeArgs>) => Output; }) => Registration<FuncType, Visibility, ObjectType<ExistingArgsValidator & ModArgsValidator>, Output>; /** * A builder that customizes a Convex function which doesn't validate arguments. * e.g. `query(async (ctx, args) => {})` * or `query({ handler: async (ctx, args) => {} })` */ export type UnvalidatedBuilder<FuncType extends "query" | "mutation" | "action", ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = <Output, ExistingArgs extends DefaultFunctionArgs = DefaultFunctionArgs>(fn: UnvalidatedFunction<Overwrite<InputCtx, ModCtx>, [ ExistingArgs & ModMadeArgs ], Output>) => Registration<FuncType, Visibility, ExistingArgs, 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>; export type CustomCtx<Builder> = Builder extends ValidatedBuilder<any, any, infer ModCtx, any, infer InputCtx, any> ? Overwrite<InputCtx, ModCtx> : never; type Overwrite<T, U> = Omit<T, keyof U> & U; type DefaultFunctionArgs = Record<string, unknown>; export {};