Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Nov 28, 2025

feat: add inline function support for t.query, t.mutation, and t.action

Summary

This PR extends t.query, t.mutation, and t.action to accept either a function reference with args (existing behavior) or an inline async function (ctx) => {...} that runs directly.

Before:

await t.query(api.queries.list);

After (both work):

await t.query(api.queries.list);
await t.query(async (ctx) => {
  return await ctx.db.query("messages").first();
});

The implementation adds shared helper functions (runQueryWithHandler, runActionWithHandler, runInlineMutation) and updates the byType methods to detect inline functions via typeof === "function" check. Type definitions use function overloads to provide proper typing for both signatures.

Important design decision: Inside inline actions, ctx.runQuery, ctx.runMutation, and ctx.runAction only accept function references (not inline functions). This matches real Convex behavior where these methods require registered functions.

Updates since last revision

  • Added explicit as Value type assertions in asyncSyscallImpl to fix TypeScript build errors
  • Removed redundant "inline query with first" test
  • Removed test that used inline functions inside ctx.runMutation (not supported in real Convex)
  • Refactored to reduce code duplication with shared runQueryWithHandler and runActionWithHandler helpers
  • Ensured ctx.runQuery/runMutation/runAction inside actions only accept function references via refOnly* helpers

Review & Testing Checklist for Human

  • Verify the typeof === "function" check correctly distinguishes FunctionReference objects from inline functions in all cases
  • Confirm inline query context is properly read-only (no write methods like db.insert)
  • Verify ctx.runQuery/runMutation/runAction inside inline actions correctly rejects inline functions and only accepts function references
  • Test withIdentity() works correctly with inline functions

Recommended test plan:

  1. Run npm run test:once to verify all 124 tests pass (1 skipped)
  2. Try the example from the request: await t.query(async (ctx) => { return ctx.db.query("messages").first() });
  3. Verify inline action calling a function reference via ctx.runMutation(api.mutations.insert, {...}) works

Notes

- 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-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1764356372-inline-functions

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 28, 2025

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/convex-test@59

commit: 65f0794

Comment on lines 20 to 30
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" });
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Copy link
Contributor Author

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.

Comment on lines 74 to 76
await ctx.runMutation(async (mutCtx) => {
await mutCtx.db.insert("messages", { author: "action", body: "test" });
});
Copy link
Contributor

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.

Copy link
Contributor Author

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
Comment on lines 1975 to 2037
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;
};
Copy link
Contributor

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?

Copy link
Contributor Author

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 both byTypeWithPath.queryFromPath and inline queries
  • runActionWithHandler - shared action execution logic used by both byTypeWithPath.actionFromPath and 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants