httpyac icon indicating copy to clipboard operation
httpyac copied to clipboard

Feature Request: Allow `provideVariables` Hooks to Access Dotenv Variables

Open waynehearn opened this issue 3 months ago • 1 comments

Summary

I'd like to enhance the provideVariables hook system to support chaining, where hooks can access and build upon variables provided by previous hooks in the series. Specifically, this would allow custom hooks to use dotenv-loaded variables for lookups and derived variable injection before HTTP file substitution occurs.

What I'm Trying to Do

I'm building a custom .httpyac.js provideVariables hook that:

  1. Reads values from dotenv files (e.g., .env)
  2. Uses those values to perform lookups (e.g., against a database or API)
  3. Injects the resulting derived variables into the context
  4. Ensures these variables are available for substitution in .http files

This enables dynamic variable resolution based on environment-specific config.

Current Attempts and Issues

Attempt 1: Using .httpyac.json

  • Created .httpyac.json with environment variables
  • Added a provideVariables hook in .httpyac.js to access context.config.variables
  • Issue: .httpyac.json and .httpyac.js can't coexist (.httpyac.js takes precedence), so I couldn't use both for config and hooks

Attempt 2: Moving to .httpyac.js Only

  • Consolidated everything into .httpyac.js (environments + hook)
  • Issue: Dotenv variables are still not available in context.config.variables during hook execution
  • Root cause: Dotenv loading happens via the provideVariables hook itself, but context.config.variables only contains .httpyac.js variables at that time

Attempt 3: Manual Dotenv Loading in Hook

  • Modified my hook to directly call the internal provideDotenvVariables function
  • Issue: This duplicates loading logic, creates maintenance burden, and could break if internal implementations change

Why It Doesn't Work Cleanly

The current provideVariables implementation uses a SeriesHook where:

  • Each hook receives identical input (environments, context)
  • Hooks run sequentially but independently
  • Outputs are collected and merged at the end

This prevents chaining: my hook can't access dotenv variables loaded by the dotenv plugin's hook, even though they run in the same series.

Dotenv variables are loaded during hook execution, not pre-loaded into context.config.variables, so they're unavailable for cross-hook dependencies.

Proposed Feature Improvement

Change provideVariables from a SeriesHook to a WaterfallHook that:

  • Passes accumulated variables through each hook in sequence
  • Allows downstream hooks to access and build upon upstream results
  • Maintains backward compatibility by still merging all outputs

Alternatively, pre-load dotenv variables into context.config.variables before provideVariables hooks execute, making them available for all hooks.

Benefits

  • Enables plugin chaining and complex variable resolution workflows
  • Reduces code duplication in custom hooks
  • Improves extensibility for dynamic configuration scenarios
  • Maintains the existing API while adding power

Example Use Case

module.exports = {
  configureHooks: (api) => {
    api.hooks.provideVariables.addHook(async (environments, context, accumulatedVars) => {
      // With waterfall: accumulatedVars would include dotenv vars from previous hooks
      const apiKey = accumulatedVars.API_KEY; // From .env
      const derivedToken = await lookupToken(apiKey); // Dynamic lookup
      return { authToken: derivedToken };
    });
  }
};

This would allow seamless integration between built-in dotenv loading and custom variable processing.

Environment

  • httpyac version: 6.16.7
  • Use case: Custom plugin for dynamic variable resolution based on dotenv + external lookups

Thank you for considering this enhancement! I'm happy to provide more details or help test implementations.

waynehearn avatar Oct 11 '25 00:10 waynehearn

Hi, I'm surprised at how deeply you know my code. But I don't think WaterfallHook will work, since it only returns the first result. You could use an interceptor to capture the results. In the context, there would be a results property, and you could access it using beforeLoop.


class CustomJdbcVariables {
private _results?: Array<Variables>;
private id = "custom_jdbc"
  public provideDotenvVariables(
  env: string[] | undefined,
  context: VariableProviderContext
): Promise<Variables> {
....
// use results
}

public async beforeLoop(
hookContext: HookTriggerContext<unknown>
): Promise<boolean | undefined> {
this._results = context.results;
return undefined;
}

export function registerJdbc(api: models.HttpyacHooksApi) {
const foo = new CustomJdbcVariables ();
api.hooks.provideVariables.addInterceptor(foo );
  api.hooks.provideVariables.addHook('jdbc', foo.provideVariables.bind(foo ) );
}

AnWeber avatar Oct 12 '25 19:10 AnWeber