Skip to content

Add composeCtx and composeCtxAndArgs helpers#902

Open
SamHoque wants to merge 1 commit intoget-convex:mainfrom
SamHoque:add-compose-ctx-helpers
Open

Add composeCtx and composeCtxAndArgs helpers#902
SamHoque wants to merge 1 commit intoget-convex:mainfrom
SamHoque:add-compose-ctx-helpers

Conversation

@SamHoque
Copy link
Copy Markdown

@SamHoque SamHoque commented Jan 22, 2026

These composition helpers allow building middleware layers by:

  • composeCtx: adding context transformation to existing middleware
  • composeCtxAndArgs: extending middleware with additional args and transformation

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Summary by CodeRabbit

  • New Features

    • Added middleware composition utilities for custom functions, enabling context transformation and argument validation with type-safe wrappers.
  • Documentation

    • Added "Composing Custom Functions" guide covering middleware composition patterns, argument merging, and error handling examples.

✏️ Tip: You can customize this high-level summary in your review settings.

These composition helpers allow building middleware layers by:
- composeCtx: adding context transformation to existing middleware
- composeCtxAndArgs: extending middleware with additional args and transformation
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Two new composition utilities, composeCtx and composeCtxAndArgs, are added to the customFunctions module, enabling advanced middleware composition patterns. Documentation explaining their usage with type-safe context and argument transformation is included in the README.

Changes

Cohort / File(s) Summary
Documentation
packages/convex-helpers/README.md
Added "Composing Custom Functions" section with base middleware composition patterns, context transformation hooks, and example usage demonstrating error handling and argument extension workflows.
Implementation
packages/convex-helpers/server/customFunctions.ts
Added composeCtx function to compose a base customization with a context-transforming function. Added composeCtxAndArgs function to extend a base customization by merging new argument validators and applying a transform using those arguments. Both include comprehensive documentation comments and type annotations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 With helper hands we weave and blend,
Two new compose tools 'round the bend!
Context transforms and args align,
Middleware chains in types so fine.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: adding two new helper functions (composeCtx and composeCtxAndArgs) to the codebase, which matches the primary modifications in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/convex-helpers/server/customFunctions.ts`:
- Around line 288-334: composeCtxAndArgs currently discards baseResult.onSuccess
so base finalizers never run; update the returned customization from
composeCtxAndArgs to preserve and compose onSuccess handlers: after awaiting
base.input and extension.transform, create a combined onSuccess that first
awaits baseResult.onSuccess (if present) with the original args/ctx, then calls
any extension or new onSuccess (if extension provides one), and return that
combined onSuccess along with ctx and args; locate composeCtxAndArgs,
base.input, baseResult, and extension.transform to implement this merge.
- Around line 235-256: composeCtx currently discards any onSuccess hook returned
by the base input; update the returned object from the input wrapper in
composeCtx to forward baseResult.onSuccess (e.g., include an onSuccess property
referencing baseResult.onSuccess) so base middleware post-success callbacks are
preserved; locate the composeCtx function and modify the return of the input
async function (where baseResult is computed) to also return onSuccess:
baseResult.onSuccess (handling the possibility it's undefined).
🧹 Nitpick comments (1)
packages/convex-helpers/README.md (1)

127-133: Clarify the source of device/staff in the example.

The example references device/staff without showing their derivation; a short lookup line would make it clearer.

💡 Suggested doc tweak
-const withDevice = customCtxAndArgs({
-  args: { token: v.string() },
-  input: async (ctx, { token }) => ({
-    ctx: { device, staff: staff || null },
-    args: {},
-  }),
-});
+const withDevice = customCtxAndArgs({
+  args: { token: v.string() },
+  input: async (ctx, { token }) => {
+    const { device, staff } = await getDeviceAndStaff(token);
+    return { ctx: { device, staff: staff ?? null }, args: {} };
+  },
+});

Comment on lines +235 to +256
export function composeCtx<
Ctx extends Record<string, any>,
CustomArgsValidator extends PropertyValidators,
CustomCtx extends Record<string, any>,
CustomMadeArgs extends Record<string, any>,
ExtraArgs extends Record<string, any>,
TransformedCtx extends Record<string, any>,
>(
base: Customization<Ctx, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>,
transform: (
ctx: Ctx & CustomCtx,
extra: ExtraArgs,
) => TransformedCtx | Promise<TransformedCtx>,
): Customization<Ctx, CustomArgsValidator, TransformedCtx, CustomMadeArgs, ExtraArgs> {
return {
args: base.args,
input: async (ctx, args, extra) => {
const baseResult = await base.input(ctx, args, extra);
const combinedCtx = { ...ctx, ...baseResult.ctx } as Ctx & CustomCtx;
const transformedCtx = await transform(combinedCtx, extra);
return { ctx: transformedCtx, args: baseResult.args };
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve base onSuccess callbacks during composition.

composeCtx drops baseResult.onSuccess, so any post-success hook from the base middleware is lost. Please forward it.

🐛 Proposed fix
-      const transformedCtx = await transform(combinedCtx, extra);
-      return { ctx: transformedCtx, args: baseResult.args };
+      const transformedCtx = await transform(combinedCtx, extra);
+      return {
+        ctx: transformedCtx,
+        args: baseResult.args,
+        onSuccess: baseResult.onSuccess,
+      };
🤖 Prompt for AI Agents
In `@packages/convex-helpers/server/customFunctions.ts` around lines 235 - 256,
composeCtx currently discards any onSuccess hook returned by the base input;
update the returned object from the input wrapper in composeCtx to forward
baseResult.onSuccess (e.g., include an onSuccess property referencing
baseResult.onSuccess) so base middleware post-success callbacks are preserved;
locate the composeCtx function and modify the return of the input async function
(where baseResult is computed) to also return onSuccess: baseResult.onSuccess
(handling the possibility it's undefined).

Comment on lines +288 to +334
export function composeCtxAndArgs<
Ctx extends Record<string, any>,
CustomArgsValidator extends PropertyValidators,
CustomCtx extends Record<string, any>,
CustomMadeArgs extends Record<string, any>,
ExtraArgs extends Record<string, any>,
NewArgsValidator extends PropertyValidators,
TransformedCtx extends Record<string, any>,
>(
base: Customization<Ctx, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>,
extension: {
args: NewArgsValidator;
transform: (
ctx: Ctx & CustomCtx,
args: ObjectType<NewArgsValidator>,
extra: ExtraArgs,
) => TransformedCtx | Promise<TransformedCtx>;
},
): Customization<
Ctx,
CustomArgsValidator & NewArgsValidator,
TransformedCtx,
CustomMadeArgs,
ExtraArgs
> {
return {
args: { ...base.args, ...extension.args },
input: async (ctx, args, extra) => {
const argsRecord = args as Record<string, any>;

// Extract base args
const baseArgKeys = Object.keys(base.args);
const baseArgs = Object.fromEntries(
baseArgKeys.map((k) => [k, argsRecord[k]]),
) as ObjectType<CustomArgsValidator>;

// Extract extension args
const extArgKeys = Object.keys(extension.args);
const extArgs = Object.fromEntries(
extArgKeys.map((k) => [k, argsRecord[k]]),
) as ObjectType<NewArgsValidator>;

const baseResult = await base.input(ctx, baseArgs, extra);
const combinedCtx = { ...ctx, ...baseResult.ctx } as Ctx & CustomCtx;
const transformedCtx = await extension.transform(combinedCtx, extArgs, extra);
return { ctx: transformedCtx, args: baseResult.args };
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve base onSuccess callbacks in composeCtxAndArgs.

Same issue here: baseResult.onSuccess is dropped, so base finalizers won’t run.

🐛 Proposed fix
-      const transformedCtx = await extension.transform(combinedCtx, extArgs, extra);
-      return { ctx: transformedCtx, args: baseResult.args };
+      const transformedCtx = await extension.transform(combinedCtx, extArgs, extra);
+      return {
+        ctx: transformedCtx,
+        args: baseResult.args,
+        onSuccess: baseResult.onSuccess,
+      };
🤖 Prompt for AI Agents
In `@packages/convex-helpers/server/customFunctions.ts` around lines 288 - 334,
composeCtxAndArgs currently discards baseResult.onSuccess so base finalizers
never run; update the returned customization from composeCtxAndArgs to preserve
and compose onSuccess handlers: after awaiting base.input and
extension.transform, create a combined onSuccess that first awaits
baseResult.onSuccess (if present) with the original args/ctx, then calls any
extension or new onSuccess (if extension provides one), and return that combined
onSuccess along with ctx and args; locate composeCtxAndArgs, base.input,
baseResult, and extension.transform to implement this merge.

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.

1 participant