ampp
ampp2w ago

CustomCtx + ents not inferring wrapper for custom ctx.runMutations()

I'm a little lost. I took my code from the action session wrapping for doing custom ctx.runMutation() and tried to do that since we now have ctx.runMutation available in mutations. Goal
export const replayInputRaw = sessionMutation({
args: {
input: v.any(),
eventType: vEventType,
},
handler: async (ctx, args) => {
const fnRef = makeEventFunctionRef(args.eventType)
const returnValue = await ctx.runSessionMutation(fnRef, {
//sessionId: args.sessionId, <-- gets included
...args.input,
})
return returnValue
},
})
export const replayInputRaw = sessionMutation({
args: {
input: v.any(),
eventType: vEventType,
},
handler: async (ctx, args) => {
const fnRef = makeEventFunctionRef(args.eventType)
const returnValue = await ctx.runSessionMutation(fnRef, {
//sessionId: args.sessionId, <-- gets included
...args.input,
})
return returnValue
},
})
customMutation withTriggerCtx doesn't throw (avoiding the ent wrapper) but newCtx gets errors: tried casting it to several things
export const sessionMutation = customMutation(rawMutation, {
args: { sessionId: v.optional(v.union(v.string(), v.null())) },
input: async (baseCtx, { sessionId }) => {
const withTriggersCtx = await triggers.wrapDB(baseCtx)

const newCtx = await mutationCtx(withTriggersCtx, sessionId)
return {
ctx: {
...newCtx,
...runSessionMutationFunctions(withTriggersCtx, sessionId),
},
args: { sessionId },
}
},
})
export const sessionMutation = customMutation(rawMutation, {
args: { sessionId: v.optional(v.union(v.string(), v.null())) },
input: async (baseCtx, { sessionId }) => {
const withTriggersCtx = await triggers.wrapDB(baseCtx)

const newCtx = await mutationCtx(withTriggersCtx, sessionId)
return {
ctx: {
...newCtx,
...runSessionMutationFunctions(withTriggersCtx, sessionId),
},
args: { sessionId },
}
},
})
places i use
export type SessionMutationCtx = CustomCtx<typeof sessionMutation>
export type SessionMutationCtx = CustomCtx<typeof sessionMutation>
i get
const newEvent = await tsCall(ctx, {
Type 'Omit<GenericMutationCtx<{ engines: { document: { _id: Id<"engines">; _creationTime: number; currentTime?: number | undefined; lastStepTs?: number | undefined; processedTime?: number | undefined; sphereId: string; running: boolean; generationNumber: number; }; fieldPaths: ExtractFieldPaths<...> | "_id"; indexes: { .....' runSessionMutationts(2345)
is missing the following properties from type :
'{ runSessionQuery<Query extends SessionFunctionMutation<"query">>(query: Query, ...args: SessionArgsArrayMutation<Query>): Promise<FunctionReturnType<Query>>; ... 14 more ...; skipRules: { ...; }; }':
:
runSessionQuery
runSessionMutation
const newEvent = await tsCall(ctx, {
Type 'Omit<GenericMutationCtx<{ engines: { document: { _id: Id<"engines">; _creationTime: number; currentTime?: number | undefined; lastStepTs?: number | undefined; processedTime?: number | undefined; sphereId: string; running: boolean; generationNumber: number; }; fieldPaths: ExtractFieldPaths<...> | "_id"; indexes: { .....' runSessionMutationts(2345)
is missing the following properties from type :
'{ runSessionQuery<Query extends SessionFunctionMutation<"query">>(query: Query, ...args: SessionArgsArrayMutation<Query>): Promise<FunctionReturnType<Query>>; ... 14 more ...; skipRules: { ...; }; }':
:
runSessionQuery
runSessionMutation
4 Replies
Convex Bot
Convex Bot2w 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!
ampp
amppOP2w ago
reference
type SessionFunctionMutation<
T extends 'query' | 'mutation',
Args = any,
> = FunctionReference<
T,
'public' | 'internal',
{ sessionId: SessionId } & Args,
any
>

type SessionArgsArrayMutation<
Fn extends SessionFunctionMutation<'query' | 'mutation', any>,
> = keyof FunctionArgs<Fn> extends 'sessionId'
? [args?: EmptyObject]
: [args: BetterOmit<FunctionArgs<Fn>, 'sessionId'>]

export interface RunSessionMutationFunctions {
runSessionQuery<Query extends SessionFunctionMutation<'query'>>(
query: Query,
...args: SessionArgsArrayMutation<Query>
): Promise<FunctionReturnType<Query>>

runSessionMutation<Mutation extends SessionFunctionMutation<'mutation'>>(
mutation: Mutation,
...args: SessionArgsArrayMutation<Mutation>
): Promise<FunctionReturnType<Mutation>>
}

export function runSessionMutationFunctions<DataModel extends GenericDataModel>(
ctx: GenericMutationCtx<DataModel>,
sessionId: SessionId
): RunSessionMutationFunctions {
return {
runSessionQuery(fn, ...args) {
const argsWithSession = { ...(args[0] ?? {}), sessionId } as FunctionArgs<
typeof fn
>
return ctx.runQuery(fn, argsWithSession)
},
runSessionMutation(fn, ...args) {
const argsWithSession = { ...(args[0] ?? {}), sessionId } as FunctionArgs<
typeof fn
>
return ctx.runMutation(fn, argsWithSession)
},
}
}
type SessionFunctionMutation<
T extends 'query' | 'mutation',
Args = any,
> = FunctionReference<
T,
'public' | 'internal',
{ sessionId: SessionId } & Args,
any
>

type SessionArgsArrayMutation<
Fn extends SessionFunctionMutation<'query' | 'mutation', any>,
> = keyof FunctionArgs<Fn> extends 'sessionId'
? [args?: EmptyObject]
: [args: BetterOmit<FunctionArgs<Fn>, 'sessionId'>]

export interface RunSessionMutationFunctions {
runSessionQuery<Query extends SessionFunctionMutation<'query'>>(
query: Query,
...args: SessionArgsArrayMutation<Query>
): Promise<FunctionReturnType<Query>>

runSessionMutation<Mutation extends SessionFunctionMutation<'mutation'>>(
mutation: Mutation,
...args: SessionArgsArrayMutation<Mutation>
): Promise<FunctionReturnType<Mutation>>
}

export function runSessionMutationFunctions<DataModel extends GenericDataModel>(
ctx: GenericMutationCtx<DataModel>,
sessionId: SessionId
): RunSessionMutationFunctions {
return {
runSessionQuery(fn, ...args) {
const argsWithSession = { ...(args[0] ?? {}), sessionId } as FunctionArgs<
typeof fn
>
return ctx.runQuery(fn, argsWithSession)
},
runSessionMutation(fn, ...args) {
const argsWithSession = { ...(args[0] ?? {}), sessionId } as FunctionArgs<
typeof fn
>
return ctx.runMutation(fn, argsWithSession)
},
}
}
ents mutationCtx
async function mutationCtx(
baseCtx: RawMutationCtx,
sessionId?: string | null,
requireAdmin?: boolean
) {
//console.time('customMutation')
const ctx = {
db: baseCtx.db as unknown as undefined,
baseCtx,
skipRules: { table: entsTableFactory(baseCtx, entDefinitions) },
}

const entDefinitionsWithRules = getEntDefinitionsWithRules(ctx as any)

const sessionInfo: SessionInfo = await createBackendContextsFromSession(
{ ...baseCtx, ...ctx },
sessionId
)
const table = await entsTableFactory(baseCtx, entDefinitionsWithRules)
return {
...ctx,
table,
}
}
async function mutationCtx(
baseCtx: RawMutationCtx,
sessionId?: string | null,
requireAdmin?: boolean
) {
//console.time('customMutation')
const ctx = {
db: baseCtx.db as unknown as undefined,
baseCtx,
skipRules: { table: entsTableFactory(baseCtx, entDefinitions) },
}

const entDefinitionsWithRules = getEntDefinitionsWithRules(ctx as any)

const sessionInfo: SessionInfo = await createBackendContextsFromSession(
{ ...baseCtx, ...ctx },
sessionId
)
const table = await entsTableFactory(baseCtx, entDefinitionsWithRules)
return {
...ctx,
table,
}
}
Michal Srb
Michal Srb7d ago
Without looking deeply, try not nesting customMutations, make one that creates the ents wrapping and the session stuff? That might help you debug. Also I'd really avoid ctx.runMutation inside mutations unless totally necessary
ampp
amppOP7d ago
Yeah, this is a nice to have as i do try to avoid the use of ctx.runMutation() but it does seem like the best solution i can figure to avoid my circular reference issues if my newer design doesn't work. The use case for this particular function is basically to insure the events are safetly given admin context. I think i'm going to remove the ents context from the first mutation as 99% of the logic is in ctx.runMutation() I'm working towards having at least 300 functions that based on arrays are loop through and created. This seems like it can be a significant performance penalty so id rather not do it twice like with a 2nd set of internal functions.

Did you find this page helpful?