#use nodejs function in another file

9 messages · Page 1 of 1 (latest)

ancient glade
#

Let's say I have file B containing function B which depends on a library that needs to be run in a node.js environment, and at the top of this file I have the useNode directive.

Function B is used in file A, where function A calls function B. This means that file A also needs to have this useNode directive, right?

This fits my observation because if I don't have the useNode directive in file A, npx convex dev will fail.

tired sigilBOT
#

Hi @ancient glade. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Your observation is partially correct, but there's an important distinction to make. Let me explain the correct way to handle this situation in Convex:

  1. File B (with function B) that uses a Node.js-specific library should indeed have the "use node" directive at the top of the file.

  2. However, File A (with function A) that calls function B should NOT have the "use node" directive. In fact, you cannot directly import and use a "use node" function in a non-"use node" file in Convex.

The correct approach is to use Convex's action system to bridge between the Node.js environment and the default Convex runtime. Here's how you should structure this:

  1. In File B (Node.js environment):

    "use node";
    import { internalAction } from "./_generated/server";
    import nodeLibrary from "node-specific-library";
    
    export const functionB = internalAction({
      handler: async (ctx, args) => {
        // Use nodeLibrary here
        // ...
      }
    });
    
  2. In File A (default Convex runtime):

    import { action } from "./_generated/server";
    import { internal } from "./_generated/api";
    
    export const functionA = action({
      handler: async (ctx, args) => {
        // Call functionB using ctx.runAction
        const result = await ctx.runAction(internal.fileB.functionB, args);
        // ...
      }
    });
    

This approach allows you to keep File A in the default Convex runtime while still being able to call the Node.js-specific function B.

The reason npx convex dev fails when you don't have "use node" in File A is that you're likely trying to directly import and use the Node.js function, which is not allowed in the default Convex runtime.

#

@ancient glade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

ancient glade
#

@tired sigil Function A in fileA is an action, but function B in file B is a normal function. Does that change anything in your analysis?

tired sigilBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Your question brings up an important point about the structure of Convex functions. Let me clarify the situation based on this new information:

  1. If Function A in fileA is an action, it's already running in a context that can interact with both the Convex runtime and Node.js environments.

  2. However, Function B in fileB, being a "normal" function (not a Convex function like query, mutation, or action) that requires Node.js, still needs special handling.

Here's how this scenario should be approached:

  1. FileB (with Function B) should still have the "use node" directive at the top, as it depends on Node.js-specific libraries.

  2. FileA (with Function A) should not have the "use node" directive. Instead, it should be structured as follows:

import { action } from "./_generated/server";
import { internal } from "./_generated/api";

export const functionA = action({
  handler: async (ctx, args) => {
    // Call functionB using ctx.runAction
    const result = await ctx.runAction(internal.fileB.wrapperForFunctionB, args);
    // ... rest of functionA logic
  }
});
  1. In FileB, you need to wrap Function B in an internal action:
"use node";
import { internalAction } from "./_generated/server";
import nodeLibrary from "node-specific-library";

// Your original Function B
function functionB(args) {
  // Use nodeLibrary here
  // ...
}

// Wrapper action for Function B
export const wrapperForFunctionB = internalAction({
  handler: async (ctx, args) => {
    return functionB(args);
  }
});
#

This structure allows you to:

  1. Keep Function B in a Node.js environment where it can use Node-specific libraries.
  2. Provide a Convex-compatible wrapper (wrapperForFunctionB) that can be called from other Convex functions.
  3. Allow Function A to remain in the default Convex runtime while still being able to use Function B's functionality.

The key here is that you can't directly call a Node.js function from the Convex runtime. You need to wrap it in a Convex action (in this case, an internal action) to bridge between the two environments.

This approach is consistent with Convex's design for handling Node.js-specific code (https://docs.convex.dev/functions/actions#calling-third-party-apis-and-using-npm-packages) and internal functions (https://docs.convex.dev/functions/internal-functions).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: