@http-forge/core 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/index.js +165 -165
- package/dist/index.mjs +165 -165
- package/dist/infrastructure/config/config-service.d.ts +1 -0
- package/dist/infrastructure/config/config.interface.d.ts +8 -0
- package/dist/infrastructure/environment/environment-config-service.d.ts +1 -1
- package/dist/infrastructure/environment/environment-resolver.d.ts +9 -0
- package/dist/infrastructure/environment/forge-env.d.ts +34 -1
- package/dist/infrastructure/environment/variable-interpolator.d.ts +25 -3
- package/dist/infrastructure/execution/request-executor.d.ts +1 -0
- package/dist/infrastructure/script/async-drain.d.ts +53 -0
- package/dist/infrastructure/script/interfaces.d.ts +3 -3
- package/dist/infrastructure/script/request-script-session.d.ts +8 -1
- package/dist/infrastructure/script/script-executor.d.ts +13 -1
- package/dist/infrastructure/test-suite/result-storage.d.ts +0 -2
- package/dist/types/types.d.ts +0 -2
- package/package.json +1 -1
|
@@ -66,6 +66,7 @@ export declare class ConfigService implements IConfigService {
|
|
|
66
66
|
getSuitesPath(): string;
|
|
67
67
|
getModulePaths(): string[];
|
|
68
68
|
getScriptScope(): 'shared' | 'isolated';
|
|
69
|
+
getScriptTimeout(): number;
|
|
69
70
|
getWorkspacePath(): string;
|
|
70
71
|
reload(): void;
|
|
71
72
|
configExists(): boolean;
|
|
@@ -44,6 +44,12 @@ export interface ScriptsConfig {
|
|
|
44
44
|
* must pass through pm.variables / pm.environment / pm.globals.
|
|
45
45
|
*/
|
|
46
46
|
scope?: 'shared' | 'isolated';
|
|
47
|
+
/**
|
|
48
|
+
* Per-script timeout in milliseconds (default 5000). Bounds both the synchronous
|
|
49
|
+
* script CPU guard and the async event-loop drain budget (pending setTimeout /
|
|
50
|
+
* un-awaited Promises) that the runner waits on before advancing to the next request.
|
|
51
|
+
*/
|
|
52
|
+
timeout?: number;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Runner configuration
|
|
@@ -212,6 +218,8 @@ export interface IConfigService {
|
|
|
212
218
|
getModulePaths(): string[];
|
|
213
219
|
/** Get the script scope mode ('shared' default, or 'isolated' for Postman parity) */
|
|
214
220
|
getScriptScope(): 'shared' | 'isolated';
|
|
221
|
+
/** Get the per-script timeout in milliseconds (default 5000) */
|
|
222
|
+
getScriptTimeout(): number;
|
|
215
223
|
/** Get the workspace root path */
|
|
216
224
|
getWorkspacePath(): string;
|
|
217
225
|
/** Reload configuration from disk */
|
|
@@ -113,7 +113,7 @@ export declare class EnvironmentConfigService implements IEnvironmentConfigServi
|
|
|
113
113
|
private getEnvLocalConfigPath;
|
|
114
114
|
private loadFolderConfigs;
|
|
115
115
|
/**
|
|
116
|
-
* Create a VariableResolver pre-loaded with environment
|
|
116
|
+
* Create a VariableResolver pre-loaded with environment variables.
|
|
117
117
|
*/
|
|
118
118
|
private createResolver;
|
|
119
119
|
private saveFolderSharedConfig;
|
|
@@ -77,6 +77,15 @@ export declare class EnvironmentResolver implements IEnvironmentStore {
|
|
|
77
77
|
* Set a global variable
|
|
78
78
|
*/
|
|
79
79
|
setGlobal(key: string, value: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Remove a session global variable. File-defined globals (globalVariables)
|
|
82
|
+
* are defaults and cannot be deleted here — only session overrides are removed.
|
|
83
|
+
*/
|
|
84
|
+
unsetGlobal(key: string): void;
|
|
85
|
+
/**
|
|
86
|
+
* Clear all session global variables. File-defined globals are left intact.
|
|
87
|
+
*/
|
|
88
|
+
clearGlobals(): void;
|
|
80
89
|
/**
|
|
81
90
|
* Resolve environment for execution
|
|
82
91
|
*/
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
* env.get('token'); // 'abc'
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
import { EnvironmentResolver } from './environment-resolver';
|
|
34
33
|
import type { IEnvironmentStore, IVariableInterpolator } from '../../types/environment-config';
|
|
34
|
+
import { EnvironmentResolver } from './environment-resolver';
|
|
35
35
|
/**
|
|
36
36
|
* ForgeEnv interface - the public API for environment management
|
|
37
37
|
*/
|
|
@@ -69,6 +69,22 @@ export interface IForgeEnv {
|
|
|
69
69
|
* Get list of available environment names
|
|
70
70
|
*/
|
|
71
71
|
getEnvironments(): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Get all global variables (environment-agnostic variable layer)
|
|
74
|
+
*/
|
|
75
|
+
getGlobals(): Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* Set a global variable value
|
|
78
|
+
*/
|
|
79
|
+
setGlobal(key: string, value: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Remove a global variable
|
|
82
|
+
*/
|
|
83
|
+
unsetGlobal(key: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Clear all (session) global variables
|
|
86
|
+
*/
|
|
87
|
+
clearGlobals(): void;
|
|
72
88
|
/**
|
|
73
89
|
* Resolve {{variable}} placeholders in a string
|
|
74
90
|
*/
|
|
@@ -127,6 +143,23 @@ export declare class ForgeEnv implements IForgeEnv {
|
|
|
127
143
|
* Get list of available environment names
|
|
128
144
|
*/
|
|
129
145
|
getEnvironments(): string[];
|
|
146
|
+
/**
|
|
147
|
+
* Get all global variables. Globals are the lowest-priority, environment-agnostic
|
|
148
|
+
* variable layer — treated like environment variables with no environment scope.
|
|
149
|
+
*/
|
|
150
|
+
getGlobals(): Record<string, string>;
|
|
151
|
+
/**
|
|
152
|
+
* Set a global variable value.
|
|
153
|
+
*/
|
|
154
|
+
setGlobal(key: string, value: string): void;
|
|
155
|
+
/**
|
|
156
|
+
* Remove a (session) global variable.
|
|
157
|
+
*/
|
|
158
|
+
unsetGlobal(key: string): void;
|
|
159
|
+
/**
|
|
160
|
+
* Clear all (session) global variables.
|
|
161
|
+
*/
|
|
162
|
+
clearGlobals(): void;
|
|
130
163
|
/**
|
|
131
164
|
* Resolve {{variable}} placeholders
|
|
132
165
|
*/
|
|
@@ -28,20 +28,28 @@ export interface VariableResolverConfig {
|
|
|
28
28
|
globals: Record<string, string>;
|
|
29
29
|
collectionVariables: Record<string, string>;
|
|
30
30
|
environmentVariables: Record<string, string>;
|
|
31
|
-
sessionVariables: Record<string, string>;
|
|
32
31
|
variables: Record<string, string>;
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
34
|
* Variable Resolver - Resolves {{varName}} patterns in strings and objects
|
|
36
35
|
* Uses a 5-step pipeline: $dynamic → filter chains → simple lookup → JS expressions → passthrough
|
|
37
|
-
* Variable precedence: globals > collection > environment >
|
|
36
|
+
* Variable precedence: globals > collection > environment > request > extra
|
|
38
37
|
*/
|
|
39
38
|
export declare class VariableResolver {
|
|
40
39
|
private readonly allVariables;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum nesting depth for recursive {{var}} resolution.
|
|
42
|
+
* Postman-compatible cap that guards against reference cycles
|
|
43
|
+
* (e.g. a -> {{b}}, b -> {{a}}).
|
|
44
|
+
*/
|
|
45
|
+
private static readonly MAX_RESOLUTION_DEPTH;
|
|
41
46
|
constructor(config: VariableResolverConfig);
|
|
42
47
|
/**
|
|
43
48
|
* Resolve variables in a string ({{varName}} syntax)
|
|
44
|
-
* Uses the full 5-step pipeline with optional escaping for quoted strings
|
|
49
|
+
* Uses the full 5-step pipeline with optional escaping for quoted strings.
|
|
50
|
+
* Nested references — a variable whose value itself contains {{...}} — are
|
|
51
|
+
* resolved recursively (Postman-style) by repeating the substitution until
|
|
52
|
+
* the string stabilizes or the depth cap is reached.
|
|
45
53
|
*/
|
|
46
54
|
resolveString(str: string, escape?: boolean): string;
|
|
47
55
|
/**
|
|
@@ -49,6 +57,20 @@ export declare class VariableResolver {
|
|
|
49
57
|
* Extra variables take highest precedence
|
|
50
58
|
*/
|
|
51
59
|
resolveStringWithExtra(str: string, extraVariables: Record<string, string>, escape?: boolean): string;
|
|
60
|
+
/**
|
|
61
|
+
* Repeatedly apply a single substitution pass until the result stops changing
|
|
62
|
+
* (fixed point) or the nesting depth cap is hit. This resolves variable values
|
|
63
|
+
* that themselves contain {{...}} references. Undefined tokens are left as-is,
|
|
64
|
+
* so a pass that resolves nothing produces no change and terminates the loop;
|
|
65
|
+
* the depth cap guards against reference cycles (a -> {{b}}, b -> {{a}}).
|
|
66
|
+
*/
|
|
67
|
+
private resolveUntilStable;
|
|
68
|
+
/**
|
|
69
|
+
* Single substitution pass over a string using the 5-step pipeline.
|
|
70
|
+
* With escaping enabled, resolved values are escaped for their surrounding
|
|
71
|
+
* quote context.
|
|
72
|
+
*/
|
|
73
|
+
private resolveSinglePass;
|
|
52
74
|
/**
|
|
53
75
|
* Resolve variables in an object recursively
|
|
54
76
|
*/
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Drain — Postman-compatible event-loop draining for script sandboxes
|
|
3
|
+
*
|
|
4
|
+
* Postman/Newman keep a script's sandbox alive after its synchronous body returns,
|
|
5
|
+
* pumping the event loop until all pending timers (`setTimeout`/`setInterval`) and
|
|
6
|
+
* microtasks (un-awaited Promises) have settled — bounded by a script timeout. Only
|
|
7
|
+
* then does the runner advance, committing any variable mutations made by those late
|
|
8
|
+
* callbacks so the next request can see them.
|
|
9
|
+
*
|
|
10
|
+
* This module provides an instrumented timer set plus a `drain()` loop that replicates
|
|
11
|
+
* that behavior. The timers wrap the real Node timers, track outstanding handles, and
|
|
12
|
+
* swallow errors thrown inside deferred callbacks (reporting them as non-fatal) so a
|
|
13
|
+
* late failure never rejects the surrounding request — matching Postman, where such
|
|
14
|
+
* callbacks run on a later tick outside the script's try/catch.
|
|
15
|
+
*/
|
|
16
|
+
/** Sandbox-facing timer functions, drop-in replacements for the Node globals. */
|
|
17
|
+
export interface SandboxTimers {
|
|
18
|
+
setTimeout: (handler: (...args: any[]) => void, timeout?: number, ...args: any[]) => any;
|
|
19
|
+
clearTimeout: (handle: any) => void;
|
|
20
|
+
setInterval: (handler: (...args: any[]) => void, timeout?: number, ...args: any[]) => any;
|
|
21
|
+
clearInterval: (handle: any) => void;
|
|
22
|
+
}
|
|
23
|
+
/** Result of a drain operation. */
|
|
24
|
+
export interface DrainResult {
|
|
25
|
+
/** True if the timeout cap was hit while timers were still pending. */
|
|
26
|
+
timedOut: boolean;
|
|
27
|
+
/** Approximate wall-clock time spent draining, in milliseconds. */
|
|
28
|
+
elapsedMs: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A registry of instrumented timers bound to a single script session.
|
|
32
|
+
*/
|
|
33
|
+
export interface TimerRegistry {
|
|
34
|
+
/** Timer functions to expose inside the VM sandbox. */
|
|
35
|
+
readonly timers: SandboxTimers;
|
|
36
|
+
/** Number of timers still pending (not yet fired or cleared, or active intervals). */
|
|
37
|
+
pendingCount(): number;
|
|
38
|
+
/** Cancel every outstanding timer. Used at the drain cap and on dispose. */
|
|
39
|
+
clearAll(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Pump the event loop until no timers are pending (and a final microtask flush
|
|
42
|
+
* yields nothing new) or until `timeoutMs` of wall-clock time elapses, whichever
|
|
43
|
+
* comes first. On timeout, all remaining timers are force-cleared.
|
|
44
|
+
*/
|
|
45
|
+
drain(timeoutMs: number): Promise<DrainResult>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a timer registry whose deferred-callback errors are routed to `onTimerError`.
|
|
49
|
+
*
|
|
50
|
+
* @param onTimerError Invoked (never throws) when a timer callback throws. The error is
|
|
51
|
+
* swallowed relative to the request so the run continues.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createTimerRegistry(onTimerError: (error: unknown) => void): TimerRegistry;
|
|
@@ -61,7 +61,6 @@ export interface CommonScriptContext {
|
|
|
61
61
|
variables: Record<string, string>;
|
|
62
62
|
collectionVariables?: Record<string, string>;
|
|
63
63
|
globals?: Record<string, string>;
|
|
64
|
-
sessionVariables?: Record<string, string>;
|
|
65
64
|
environmentVariables?: Record<string, string>;
|
|
66
65
|
environmentName?: string;
|
|
67
66
|
sendRequest?: (options: any) => Promise<any>;
|
|
@@ -70,6 +69,7 @@ export interface CommonScriptContext {
|
|
|
70
69
|
iterationCount?: number;
|
|
71
70
|
iterationData?: Record<string, any>;
|
|
72
71
|
onEnvironmentChange?: (action: 'set' | 'unset' | 'clear', key?: string, value?: string) => void;
|
|
72
|
+
onGlobalsChange?: (action: 'set' | 'unset' | 'clear', key?: string, value?: string) => void;
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* Script execution context for pre-request scripts
|
|
@@ -95,7 +95,6 @@ export interface PreRequestScriptResult {
|
|
|
95
95
|
modifiedVariables?: Record<string, string>;
|
|
96
96
|
modifiedCollectionVariables?: Record<string, string>;
|
|
97
97
|
modifiedGlobals?: Record<string, string>;
|
|
98
|
-
modifiedSessionVariables?: Record<string, string>;
|
|
99
98
|
modifiedEnvironmentVariables?: Record<string, string>;
|
|
100
99
|
consoleOutput?: string[];
|
|
101
100
|
/** Postman-compatible: pm.execution.setNextRequest() value. null = stop runner. */
|
|
@@ -110,7 +109,8 @@ export interface PostResponseScriptResult {
|
|
|
110
109
|
testResults: TestAssertion[];
|
|
111
110
|
consoleOutput?: string[];
|
|
112
111
|
modifiedEnvironmentVariables?: Record<string, string>;
|
|
113
|
-
|
|
112
|
+
modifiedGlobals?: Record<string, string>;
|
|
113
|
+
modifiedCollectionVariables?: Record<string, string>;
|
|
114
114
|
/** Postman-compatible: pm.execution.setNextRequest() value. null = stop runner. */
|
|
115
115
|
nextRequest?: string | null;
|
|
116
116
|
/** Postman-compatible: pm.visualizer.set(template, data) output */
|
|
@@ -8,16 +8,22 @@
|
|
|
8
8
|
* This matches Postman's behavior for per-request script execution.
|
|
9
9
|
*/
|
|
10
10
|
import * as vm from 'vm';
|
|
11
|
+
import { SandboxTimers } from './async-drain';
|
|
11
12
|
import { CommonScriptContext, IRequestScriptSession, PostResponseScriptResult, PreRequestScriptContext, PreRequestScriptResult, ResponseContext } from './interfaces';
|
|
12
13
|
/**
|
|
13
14
|
* Dependencies injected from ScriptExecutor
|
|
14
15
|
* Interface Segregation: Only the methods needed by the session
|
|
15
16
|
*/
|
|
16
17
|
export interface SessionDependencies {
|
|
17
|
-
createVM: (ctx: any, scriptConsole: any) => vm.Context;
|
|
18
|
+
createVM: (ctx: any, scriptConsole: any, timers?: SandboxTimers) => vm.Context;
|
|
18
19
|
createCommonContext: (context: CommonScriptContext, eventName: 'prerequest' | 'test') => any;
|
|
19
20
|
/** When true, each script level runs in its own scope (Postman-compatible). */
|
|
20
21
|
isolateScripts?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Per-script timeout in milliseconds. Bounds BOTH the synchronous VM CPU guard and
|
|
24
|
+
* the async event-loop drain budget (pending timers / un-awaited Promises).
|
|
25
|
+
*/
|
|
26
|
+
scriptTimeoutMs?: number;
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
23
29
|
* Request Script Session Implementation
|
|
@@ -43,6 +49,7 @@ export declare class RequestScriptSession implements IRequestScriptSession {
|
|
|
43
49
|
private _nextRequest;
|
|
44
50
|
private _skipRequest;
|
|
45
51
|
private _visualizerData;
|
|
52
|
+
private readonly timerRegistry;
|
|
46
53
|
constructor(deps: SessionDependencies, initialContext: PreRequestScriptContext);
|
|
47
54
|
/**
|
|
48
55
|
* Initialize the shared VM context
|
|
@@ -23,8 +23,9 @@ export declare class ScriptExecutor implements IScriptExecutor {
|
|
|
23
23
|
private readonly httpService?;
|
|
24
24
|
private readonly secretRegistry?;
|
|
25
25
|
private readonly scopeMode;
|
|
26
|
+
private readonly scriptTimeoutMs;
|
|
26
27
|
private readonly moduleLoader;
|
|
27
|
-
constructor(httpService?: IHttpRequestService | undefined, modulePaths?: string[], secretRegistry?: SecretResolverRegistry | undefined, scopeMode?: 'shared' | 'isolated');
|
|
28
|
+
constructor(httpService?: IHttpRequestService | undefined, modulePaths?: string[], secretRegistry?: SecretResolverRegistry | undefined, scopeMode?: 'shared' | 'isolated', scriptTimeoutMs?: number);
|
|
28
29
|
/**
|
|
29
30
|
* Create a request execution session
|
|
30
31
|
* Factory method implementing IScriptExecutor
|
|
@@ -33,6 +34,10 @@ export declare class ScriptExecutor implements IScriptExecutor {
|
|
|
33
34
|
/**
|
|
34
35
|
* Create VM context with sandbox configuration
|
|
35
36
|
* Returns a context object that can be used with vm.runInContext()
|
|
37
|
+
*
|
|
38
|
+
* @param timers Optional instrumented timer set. When provided, the sandbox uses
|
|
39
|
+
* these instead of the Node globals so the session can track and
|
|
40
|
+
* drain pending timers (Postman-compatible async behavior).
|
|
36
41
|
*/
|
|
37
42
|
private createVM;
|
|
38
43
|
/**
|
|
@@ -55,6 +60,13 @@ export declare class ScriptExecutor implements IScriptExecutor {
|
|
|
55
60
|
* Create a basic variable scope with get/set/has/unset/clear/toObject
|
|
56
61
|
*/
|
|
57
62
|
private createVariableScope;
|
|
63
|
+
/**
|
|
64
|
+
* Create the globals scope with change callbacks.
|
|
65
|
+
* Globals are a special, environment-agnostic variable layer, so they are
|
|
66
|
+
* persisted live through the same push-callback mechanism as environment
|
|
67
|
+
* variables (see createEnvironmentScope) rather than a post-hoc diff.
|
|
68
|
+
*/
|
|
69
|
+
private createGlobalsScope;
|
|
58
70
|
/**
|
|
59
71
|
* Create merged variable scope (Postman-style cascading lookup)
|
|
60
72
|
*/
|
|
@@ -227,8 +227,6 @@ export interface FullResultDetails {
|
|
|
227
227
|
modifiedEnvironmentVariables?: Record<string, string>;
|
|
228
228
|
/** Collection variables modified by scripts */
|
|
229
229
|
modifiedCollectionVariables?: Record<string, string>;
|
|
230
|
-
/** Session variables modified by scripts */
|
|
231
|
-
modifiedSessionVariables?: Record<string, string>;
|
|
232
230
|
/** pm.execution.setNextRequest() value */
|
|
233
231
|
nextRequest?: string | null;
|
|
234
232
|
/** pm.visualizer.set() payload */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -218,7 +218,6 @@ export interface ExecutionVariables {
|
|
|
218
218
|
variables: Record<string, string>;
|
|
219
219
|
collectionVariables?: Record<string, string>;
|
|
220
220
|
globals?: Record<string, string>;
|
|
221
|
-
sessionVariables?: Record<string, string>;
|
|
222
221
|
environmentVariables?: Record<string, string>;
|
|
223
222
|
}
|
|
224
223
|
/**
|
|
@@ -619,7 +618,6 @@ export interface ExecutionResult {
|
|
|
619
618
|
modifiedVariables?: Record<string, string>;
|
|
620
619
|
modifiedEnvironmentVariables?: Record<string, string>;
|
|
621
620
|
modifiedCollectionVariables?: Record<string, string>;
|
|
622
|
-
modifiedSessionVariables?: Record<string, string>;
|
|
623
621
|
error?: string;
|
|
624
622
|
/** Folder path of the request within its collection (slash-separated; empty for root) */
|
|
625
623
|
folderPath?: string;
|
package/package.json
CHANGED