-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add inline function support for t.query, t.mutation, and t.action #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Allow t.query to accept an inline async function (ctx) => {...} with query context
- Allow t.mutation to accept an inline async function (ctx) => {...} with mutation context
- Allow t.action to accept an inline async function (ctx) => {...} with action context
- Add comprehensive tests for inline function functionality
- Update type definitions with function overloads for both function references and inline functions
Co-Authored-By: Ian Macartney <ian@convex.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
The function overloads for query/mutation/action caused TypeScript to infer 'unknown' return type when the overload resolution was ambiguous. Added explicit 'as Value' type assertions at the call sites in asyncSyscallImpl where results are passed to convexToJson(). Co-Authored-By: Ian Macartney <ian@convex.dev>
commit: |
convex/inlineFunctions.test.ts
Outdated
| test("inline query with first", async () => { | ||
| const t = convexTest(schema); | ||
| await t.run(async (ctx) => { | ||
| await ctx.db.insert("messages", { author: "sarah", body: "hello" }); | ||
| }); | ||
| const message = await t.query(async (ctx) => { | ||
| return await ctx.db.query("messages").first(); | ||
| }); | ||
| expect(message).toMatchObject({ author: "sarah", body: "hello" }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| test("inline query with first", async () => { | |
| const t = convexTest(schema); | |
| await t.run(async (ctx) => { | |
| await ctx.db.insert("messages", { author: "sarah", body: "hello" }); | |
| }); | |
| const message = await t.query(async (ctx) => { | |
| return await ctx.db.query("messages").first(); | |
| }); | |
| expect(message).toMatchObject({ author: "sarah", body: "hello" }); | |
| }); |
I don't think this query adds much value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed this test as suggested.
convex/inlineFunctions.test.ts
Outdated
| await ctx.runMutation(async (mutCtx) => { | ||
| await mutCtx.db.insert("messages", { author: "action", body: "test" }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ctx.runMutation does not support an inline function in regular convex, so it doesn't need to work here and we shouldn't document / encourage it. only t.mutation / t.action / t.query please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point - removed this test and updated the implementation so that ctx.runMutation, ctx.runQuery, and ctx.runAction inside actions only accept function references (not inline functions). The inline function support is now limited to the top-level t.query, t.mutation, and t.action methods only.
index.ts
Outdated
| const runInlineQuery = async <T>( | ||
| componentPath: string, | ||
| handler: (ctx: any) => T, | ||
| ): Promise<T> => { | ||
| const q = queryGeneric({ | ||
| handler: (ctx: any) => { | ||
| const testCtx = { ...ctx, auth }; | ||
| return handler(testCtx); | ||
| }, | ||
| }); | ||
| const transactionManager = getTransactionManager(); | ||
| const functionPath = { componentPath, udfPath: "inline" }; | ||
| await transactionManager.begin(functionPath, false); | ||
| try { | ||
| const rawResult = await ( | ||
| q as unknown as { invokeQuery: (args: string) => Promise<string> } | ||
| ).invokeQuery(JSON.stringify(convexToJson([{}]))); | ||
| return jsonToConvex(JSON.parse(rawResult)) as T; | ||
| } finally { | ||
| transactionManager.rollback(false); | ||
| } | ||
| }; | ||
|
|
||
| const runInlineMutation = async <T>( | ||
| componentPath: string, | ||
| handler: (ctx: any) => T, | ||
| ): Promise<T> => { | ||
| return await runTransaction( | ||
| handler, | ||
| {}, | ||
| {}, | ||
| { componentPath, udfPath: "inline" }, | ||
| false, | ||
| ); | ||
| }; | ||
|
|
||
| const runInlineAction = async <T>( | ||
| componentPath: string, | ||
| handler: (ctx: any) => T, | ||
| ): Promise<T> => { | ||
| const a = actionGeneric({ | ||
| handler: (ctx: any) => { | ||
| const testCtx = { | ||
| ...ctx, | ||
| runQuery: byType.query, | ||
| runMutation: byType.mutation, | ||
| runAction: byType.action, | ||
| auth, | ||
| }; | ||
| return handler(testCtx); | ||
| }, | ||
| }); | ||
| const functionPath = { componentPath, udfPath: "inline" }; | ||
| getTransactionManager().beginAction(functionPath); | ||
| const requestId = "" + Math.random(); | ||
| const rawResult = await ( | ||
| a as unknown as { | ||
| invokeAction: (requestId: string, args: string) => Promise<string>; | ||
| } | ||
| ).invokeAction(requestId, JSON.stringify(convexToJson([{}]))); | ||
| getTransactionManager().finishAction(); | ||
| return jsonToConvex(JSON.parse(rawResult)) as T; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks duplicative with other code in this file. how might we share more code here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored to reduce duplication by extracting shared helpers:
runQueryWithHandler- shared query execution logic used by bothbyTypeWithPath.queryFromPathand inline queriesrunActionWithHandler- shared action execution logic used by bothbyTypeWithPath.actionFromPathand inline actions
Also created refOnlyQuery, refOnlyMutation, and refOnlyAction helpers that only accept function references (no inline support) for use inside action contexts.
- Remove redundant 'inline query with first' test - Remove test that used inline functions inside ctx.runMutation (not supported in real Convex) - Refactor to reduce code duplication with shared runQueryWithHandler and runActionWithHandler helpers - Ensure ctx.runQuery/runMutation/runAction inside actions only accept function references (not inline functions) Co-Authored-By: Ian Macartney <ian@convex.dev>
feat: add inline function support for t.query, t.mutation, and t.action
Summary
This PR extends
t.query,t.mutation, andt.actionto accept either a function reference with args (existing behavior) or an inline async function(ctx) => {...}that runs directly.Before:
After (both work):
The implementation adds shared helper functions (
runQueryWithHandler,runActionWithHandler,runInlineMutation) and updates thebyTypemethods to detect inline functions viatypeof === "function"check. Type definitions use function overloads to provide proper typing for both signatures.Important design decision: Inside inline actions,
ctx.runQuery,ctx.runMutation, andctx.runActiononly accept function references (not inline functions). This matches real Convex behavior where these methods require registered functions.Updates since last revision
as Valuetype assertions inasyncSyscallImplto fix TypeScript build errorsctx.runMutation(not supported in real Convex)runQueryWithHandlerandrunActionWithHandlerhelpersctx.runQuery/runMutation/runActioninside actions only accept function references viarefOnly*helpersReview & Testing Checklist for Human
typeof === "function"check correctly distinguishes FunctionReference objects from inline functions in all casesdb.insert)ctx.runQuery/runMutation/runActioninside inline actions correctly rejects inline functions and only accepts function referenceswithIdentity()works correctly with inline functionsRecommended test plan:
npm run test:onceto verify all 124 tests pass (1 skipped)await t.query(async (ctx) => { return ctx.db.query("messages").first() });ctx.runMutation(api.mutations.insert, {...})worksNotes