refactor: centralize BrunoRequest and BrunoResponse creation in HooksRuntime

This commit is contained in:
sanish-bruno
2026-01-23 00:35:01 +05:30
parent 10408a344a
commit 57351b74ec
4 changed files with 77 additions and 43 deletions

View File

@@ -7,9 +7,6 @@ const prepareRequest = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
const { interpolateString, interpolateObject } = require('./interpolate-string');
const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime, HooksRuntime, HooksExecutor } = require('@usebruno/js');
const HookManager = require('@usebruno/js/src/hook-manager');
const BrunoRequest = require('@usebruno/js/src/bruno-request');
const BrunoResponse = require('@usebruno/js/src/bruno-response');
const { stripExtension } = require('../utils/filesystem');
const { getOptions } = require('../utils/bru');
const { extractHooks, getTreePathFromCollectionToItem, HOOK_EVENTS } = require('../utils/collection');
@@ -238,7 +235,8 @@ const runSingleRequest = async function (
// Call beforeRequest hooks before running pre-request scripts
// Hooks are called in registration order: collection -> folder(s) -> request
const beforeRequestEventData = { request, req: new BrunoRequest(request), collection };
// Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime
const beforeRequestEventData = { request, collection };
const beforeRequestHooksResult = await executeAllHooksConsolidated(
{ collectionHooks, folderHooks, requestHooks },
@@ -717,13 +715,8 @@ const runSingleRequest = async function (
// Call afterResponse hooks after response is received but before post-response scripts
// Hooks are called in registration order: collection -> folder(s) -> request
// Uses consolidated execution when multiple levels have hooks (more efficient)
const afterResponseEventData = {
request,
response,
req: new BrunoRequest(request),
res: new BrunoResponse(response),
collection
};
// Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime
const afterResponseEventData = { request, response, collection };
const afterResponseHooksResult = await executeAllHooksConsolidated(
{ collectionHooks, folderHooks, requestHooks },

View File

@@ -9,9 +9,6 @@ const { ipcMain } = require('electron');
const { each, get, extend, cloneDeep, merge } = require('lodash');
const { NtlmClient } = require('axios-ntlm');
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime, HooksRuntime, HooksExecutor } = require('@usebruno/js');
// BrunoRequest and BrunoResponse are not exported from main index, require directly
const BrunoRequest = require('@usebruno/js/src/bruno-request');
const BrunoResponse = require('@usebruno/js/src/bruno-response');
const { encodeUrl } = require('@usebruno/common').utils;
const { extractPromptVariables } = require('@usebruno/common').utils;
const { interpolateString } = require('./interpolate-string');
@@ -750,7 +747,8 @@ const registerNetworkIpc = (mainWindow) => {
// Call beforeRequest hooks before running pre-request scripts
// Hooks are called in registration order: collection -> folder(s) -> request
const beforeRequestEventData = { request, req: new BrunoRequest(request), collection, collectionUid };
// Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime
const beforeRequestEventData = { request, collection, collectionUid };
const hookOptions = {
request,
envVars,
@@ -943,14 +941,8 @@ const registerNetworkIpc = (mainWindow) => {
// Call afterResponse hooks after response is received but before post-response scripts
// Hooks are called in registration order: collection -> folder(s) -> request
const afterResponseEventData = {
request,
response,
req: new BrunoRequest(request),
res: new BrunoResponse(response),
collection,
collectionUid
};
// Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime
const afterResponseEventData = { request, response, collection, collectionUid };
// Call afterResponse hooks using consolidated approach when multiple levels have hooks
await executeAllHooksConsolidated(
@@ -1476,7 +1468,8 @@ const registerNetworkIpc = (mainWindow) => {
try {
// Call beforeRequest hooks using consolidated approach when multiple levels have hooks
const beforeRequestEventData = { request, req: new BrunoRequest(request), collection, collectionUid };
// Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime
const beforeRequestEventData = { request, collection, collectionUid };
const beforeRequestHooksResult = await executeAllHooksConsolidated(
{ collectionHooks, folderHooks, requestHooks },
@@ -1731,14 +1724,8 @@ const registerNetworkIpc = (mainWindow) => {
}
// Call afterResponse hooks using consolidated approach when multiple levels have hooks
const afterResponseEventData = {
request,
response,
req: new BrunoRequest(request),
res: new BrunoResponse(response),
collection,
collectionUid
};
// Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime
const afterResponseEventData = { request, response, collection, collectionUid };
const afterResponseHooksResult = await executeAllHooksConsolidated(
{ collectionHooks, folderHooks, requestHooks },

View File

@@ -80,11 +80,15 @@ const executeHooksForLevel = async (hooksFile, hookEvent, eventData, options) =>
collectionName
} = options;
// Extract response from eventData if available (for afterResponse hooks)
const response = eventData?.response;
try {
const hooksRuntime = new HooksRuntime({ runtime: scriptingConfig?.runtime });
const result = await hooksRuntime.runHooks({
hooksFile: decomment(hooksFile),
request,
response,
envVariables,
runtimeVariables,
collectionPath,
@@ -96,7 +100,13 @@ const executeHooksForLevel = async (hooksFile, hookEvent, eventData, options) =>
});
if (result?.hookManager) {
await result.hookManager.call(hookEvent, eventData);
// Enrich eventData with runtime-created req/res wrappers
const enrichedEventData = {
...eventData,
req: result.req || eventData.req,
res: result.res || eventData.res
};
await result.hookManager.call(hookEvent, enrichedEventData);
// Dispose HookManager to free VM resources
if (typeof result.hookManager.dispose === 'function') {
result.hookManager.dispose();
@@ -139,12 +149,16 @@ const executeConsolidatedHooks = async (extractedHooks, hookEvent, eventData, op
collectionName
} = options;
// Extract response from eventData if available (for afterResponse hooks)
const response = eventData?.response;
try {
const hooksRuntime = new HooksRuntime({ runtime: scriptingConfig?.runtime });
const result = await hooksRuntime.runHooks({
consolidated: true,
consolidatedHooks: extractedHooks,
request,
response,
envVariables,
runtimeVariables,
collectionPath,
@@ -156,7 +170,13 @@ const executeConsolidatedHooks = async (extractedHooks, hookEvent, eventData, op
});
if (result?.hookManager) {
await result.hookManager.call(hookEvent, eventData);
// Enrich eventData with runtime-created req/res wrappers
const enrichedEventData = {
...eventData,
req: result.req || eventData.req,
res: result.res || eventData.res
};
await result.hookManager.call(hookEvent, enrichedEventData);
// IMPORTANT: Re-capture runner control values AFTER hooks have been called
// The hooks may have called bru.runner.setNextRequest(), bru.runner.skipRequest(), etc.

View File

@@ -1,5 +1,7 @@
const { runScriptInNodeVm } = require('../sandbox/node-vm');
const Bru = require('../bru');
const BrunoRequest = require('../bruno-request');
const BrunoResponse = require('../bruno-response');
const HookManager = require('../hook-manager');
const { cleanJson } = require('../utils');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
@@ -54,7 +56,8 @@ class HooksRuntime {
* Run hooks script to register event handlers
* @param {object} options - Configuration options
* @param {string} [options.hooksFile] - The hooks script content (for single-level execution)
* @param {object} options.request - The request object (used for variable extraction only)
* @param {object} options.request - The request object (used for variable extraction and BrunoRequest creation)
* @param {object} [options.response] - The response object (used for BrunoResponse creation, only for afterResponse hooks)
* @param {object} options.envVariables - Environment variables
* @param {object} options.runtimeVariables - Runtime variables
* @param {string} options.collectionPath - Collection path
@@ -69,12 +72,13 @@ class HooksRuntime {
* @param {string} [options.consolidatedHooks.collectionHooks] - Collection-level hooks script
* @param {Array<object>} [options.consolidatedHooks.folderHooks] - Array of folder hooks
* @param {string} [options.consolidatedHooks.requestHooks] - Request-level hooks script
* @returns {object} Result containing the hookManager instance
* @returns {object} Result containing the hookManager instance, and req/res wrapper objects
*/
async runHooks(options) {
const {
hooksFile,
request,
response,
envVariables,
runtimeVariables,
collectionPath,
@@ -100,6 +104,7 @@ class HooksRuntime {
return this._runConsolidatedHooks({
consolidatedHooks,
request,
response,
envVariables,
runtimeVariables,
collectionPath,
@@ -122,8 +127,14 @@ class HooksRuntime {
// Pass activeHookManager to Bru so it uses the same instance (whether provided or newly created)
const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName, promptVariables, activeHookManager);
// Create BrunoRequest and BrunoResponse wrappers (similar to ScriptRuntime)
const req = request ? new BrunoRequest(request) : null;
const res = response ? new BrunoResponse(response) : null;
const context = {
bru
bru,
req,
res
};
if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -157,7 +168,9 @@ class HooksRuntime {
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
__bru: bru
__bru: bru,
req,
res
};
}
@@ -184,7 +197,9 @@ class HooksRuntime {
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
__bru: bru
__bru: bru,
req,
res
};
}
@@ -211,7 +226,9 @@ class HooksRuntime {
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
__bru: bru
__bru: bru,
req,
res
};
}
@@ -225,6 +242,8 @@ class HooksRuntime {
async _runConsolidatedHooks(options) {
const {
consolidatedHooks,
request,
response,
envVariables,
runtimeVariables,
collectionPath,
@@ -264,6 +283,9 @@ class HooksRuntime {
promptVariables,
activeHookManager
);
// Create BrunoRequest and BrunoResponse wrappers
const req = request ? new BrunoRequest(request) : null;
const res = response ? new BrunoResponse(response) : null;
return {
hookManager: activeHookManager,
envVariables: cleanJson(envVariables),
@@ -273,7 +295,9 @@ class HooksRuntime {
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
__bru: bru
__bru: bru,
req,
res
};
}
@@ -298,9 +322,15 @@ class HooksRuntime {
activeHookManager
);
// Create BrunoRequest and BrunoResponse wrappers (similar to ScriptRuntime)
const req = request ? new BrunoRequest(request) : null;
const res = response ? new BrunoResponse(response) : null;
// Prepare context with error handling callback
const context = {
bru,
req,
res,
__hookResult: null,
__onHookError: (level, error) => {
if (onConsoleLog) {
@@ -348,7 +378,9 @@ class HooksRuntime {
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
__bru: bru
__bru: bru,
req,
res
};
}
@@ -375,7 +407,9 @@ class HooksRuntime {
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution,
// Include bru reference so callers can read updated values after hook execution
__bru: bru
__bru: bru,
req,
res
};
}
}