@n8n/expression-runtime 0.21.0 → 0.23.0
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/dist/cjs/bridge/bridge-messages.d.ts +8 -8
- package/dist/cjs/bridge/bridge-messages.js +234 -244
- package/dist/cjs/bridge/bridge-messages.js.map +1 -1
- package/dist/cjs/bridge/isolated-vm-bridge.d.ts.map +1 -1
- package/dist/cjs/bridge/isolated-vm-bridge.js +633 -643
- package/dist/cjs/bridge/isolated-vm-bridge.js.map +1 -1
- package/dist/cjs/build.tsbuildinfo +1 -1
- package/dist/cjs/evaluator/error-classification.js +15 -25
- package/dist/cjs/evaluator/error-classification.js.map +1 -1
- package/dist/cjs/evaluator/expression-evaluator.d.ts +7 -1
- package/dist/cjs/evaluator/expression-evaluator.d.ts.map +1 -1
- package/dist/cjs/evaluator/expression-evaluator.js +152 -155
- package/dist/cjs/evaluator/expression-evaluator.js.map +1 -1
- package/dist/cjs/evaluator/lru-cache.d.ts.map +1 -1
- package/dist/cjs/evaluator/lru-cache.js +35 -45
- package/dist/cjs/evaluator/lru-cache.js.map +1 -1
- package/dist/cjs/extensions/array-extensions.js +589 -599
- package/dist/cjs/extensions/array-extensions.js.map +1 -1
- package/dist/cjs/extensions/boolean-extensions.js +38 -48
- package/dist/cjs/extensions/boolean-extensions.js.map +1 -1
- package/dist/cjs/extensions/date-extensions.js +500 -510
- package/dist/cjs/extensions/date-extensions.js.map +1 -1
- package/dist/cjs/extensions/expression-extension-error.d.ts.map +1 -1
- package/dist/cjs/extensions/expression-extension-error.js +12 -22
- package/dist/cjs/extensions/expression-extension-error.js.map +1 -1
- package/dist/cjs/extensions/extend.js +162 -172
- package/dist/cjs/extensions/extend.js.map +1 -1
- package/dist/cjs/extensions/extensions.js +2 -12
- package/dist/cjs/extensions/function-extensions.d.ts +10 -18
- package/dist/cjs/extensions/function-extensions.d.ts.map +1 -1
- package/dist/cjs/extensions/function-extensions.js +78 -88
- package/dist/cjs/extensions/function-extensions.js.map +1 -1
- package/dist/cjs/extensions/number-extensions.js +225 -235
- package/dist/cjs/extensions/number-extensions.js.map +1 -1
- package/dist/cjs/extensions/object-extensions.js +286 -296
- package/dist/cjs/extensions/object-extensions.js.map +1 -1
- package/dist/cjs/extensions/string-extensions.d.ts +1 -1
- package/dist/cjs/extensions/string-extensions.d.ts.map +1 -1
- package/dist/cjs/extensions/string-extensions.js +758 -768
- package/dist/cjs/extensions/string-extensions.js.map +1 -1
- package/dist/cjs/extensions/utils.d.ts.map +1 -1
- package/dist/cjs/extensions/utils.js +28 -38
- package/dist/cjs/extensions/utils.js.map +1 -1
- package/dist/cjs/index.js +32 -42
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/observability/metrics.d.ts +27 -27
- package/dist/cjs/observability/metrics.d.ts.map +1 -1
- package/dist/cjs/observability/metrics.js +59 -69
- package/dist/cjs/observability/metrics.js.map +1 -1
- package/dist/cjs/observability/noop-provider.js +28 -38
- package/dist/cjs/observability/noop-provider.js.map +1 -1
- package/dist/cjs/pool/idle-scaling-pool.d.ts.map +1 -1
- package/dist/cjs/pool/idle-scaling-pool.js +134 -144
- package/dist/cjs/pool/idle-scaling-pool.js.map +1 -1
- package/dist/cjs/pool/isolate-pool.d.ts.map +1 -1
- package/dist/cjs/pool/isolate-pool.js +101 -111
- package/dist/cjs/pool/isolate-pool.js.map +1 -1
- package/dist/cjs/runtime/context.js +418 -428
- package/dist/cjs/runtime/context.js.map +1 -1
- package/dist/cjs/runtime/index.js +26 -36
- package/dist/cjs/runtime/index.js.map +1 -1
- package/dist/cjs/runtime/jmespath.js +72 -82
- package/dist/cjs/runtime/jmespath.js.map +1 -1
- package/dist/cjs/runtime/lazy-proxy.js +238 -248
- package/dist/cjs/runtime/lazy-proxy.js.map +1 -1
- package/dist/cjs/runtime/safe-globals.d.ts.map +1 -1
- package/dist/cjs/runtime/safe-globals.js +134 -144
- package/dist/cjs/runtime/safe-globals.js.map +1 -1
- package/dist/cjs/runtime/serialize.js +54 -64
- package/dist/cjs/runtime/serialize.js.map +1 -1
- package/dist/cjs/runtime/utils.js +20 -30
- package/dist/cjs/runtime/utils.js.map +1 -1
- package/dist/cjs/types/bridge.js +15 -25
- package/dist/cjs/types/bridge.js.map +1 -1
- package/dist/cjs/types/evaluator.d.ts +5 -1
- package/dist/cjs/types/evaluator.d.ts.map +1 -1
- package/dist/cjs/types/evaluator.js +35 -45
- package/dist/cjs/types/evaluator.js.map +1 -1
- package/dist/cjs/types/index.js +14 -24
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/runtime.d.ts.map +1 -1
- package/dist/cjs/types/runtime.js +27 -37
- package/dist/cjs/types/runtime.js.map +1 -1
- package/dist/esm/bridge/bridge-messages.d.ts +8 -8
- package/dist/esm/bridge/isolated-vm-bridge.d.ts.map +1 -1
- package/dist/esm/bridge/isolated-vm-bridge.js.map +1 -1
- package/dist/esm/build.tsbuildinfo +1 -1
- package/dist/esm/evaluator/expression-evaluator.d.ts +7 -1
- package/dist/esm/evaluator/expression-evaluator.d.ts.map +1 -1
- package/dist/esm/evaluator/expression-evaluator.js +8 -1
- package/dist/esm/evaluator/expression-evaluator.js.map +1 -1
- package/dist/esm/evaluator/lru-cache.d.ts.map +1 -1
- package/dist/esm/evaluator/lru-cache.js.map +1 -1
- package/dist/esm/extensions/expression-extension-error.d.ts.map +1 -1
- package/dist/esm/extensions/function-extensions.d.ts +10 -18
- package/dist/esm/extensions/function-extensions.d.ts.map +1 -1
- package/dist/esm/extensions/number-extensions.js.map +1 -1
- package/dist/esm/extensions/string-extensions.d.ts +1 -1
- package/dist/esm/extensions/string-extensions.d.ts.map +1 -1
- package/dist/esm/extensions/string-extensions.js.map +1 -1
- package/dist/esm/extensions/utils.d.ts.map +1 -1
- package/dist/esm/observability/metrics.d.ts +27 -27
- package/dist/esm/observability/metrics.d.ts.map +1 -1
- package/dist/esm/pool/idle-scaling-pool.d.ts.map +1 -1
- package/dist/esm/pool/idle-scaling-pool.js.map +1 -1
- package/dist/esm/pool/isolate-pool.d.ts.map +1 -1
- package/dist/esm/pool/isolate-pool.js.map +1 -1
- package/dist/esm/runtime/lazy-proxy.js.map +1 -1
- package/dist/esm/runtime/safe-globals.d.ts.map +1 -1
- package/dist/esm/types/evaluator.d.ts +5 -1
- package/dist/esm/types/evaluator.d.ts.map +1 -1
- package/dist/esm/types/evaluator.js.map +1 -1
- package/dist/esm/types/runtime.d.ts.map +1 -1
- package/dist/esm/types/runtime.js.map +1 -1
- package/package.json +7 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
2
3
|
if (k2 === undefined) k2 = k;
|
|
3
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -31,233 +32,223 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
31
32
|
return result;
|
|
32
33
|
};
|
|
33
34
|
})();
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.IsolatedVmBridge = void 0;
|
|
37
|
+
const promises_1 = require("node:fs/promises");
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const types_1 = require("../types");
|
|
40
|
+
const bridge_messages_1 = require("./bridge-messages");
|
|
41
|
+
let _ivm = null;
|
|
42
|
+
function getIvm() {
|
|
43
|
+
if (!_ivm) {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
45
|
+
_ivm = require('isolated-vm');
|
|
38
46
|
}
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
return _ivm;
|
|
48
|
+
}
|
|
49
|
+
const BUNDLE_RELATIVE_PATH = path.join('dist', 'bundle', 'runtime.iife.js');
|
|
50
|
+
/** Check if a value is an error sentinel returned by serializeError. */
|
|
51
|
+
function isErrorSentinel(value) {
|
|
52
|
+
return (typeof value === 'object' &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
value.__isError === true);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Serialize an error into a transferable metadata object.
|
|
58
|
+
*
|
|
59
|
+
* Host-side callbacks (getValueAtPath, etc.) catch errors and return this
|
|
60
|
+
* sentinel instead of letting the error cross the isolate boundary (which
|
|
61
|
+
* strips custom class identity and properties). The isolate-side proxy
|
|
62
|
+
* detects __isError and reconstructs a proper Error to throw.
|
|
63
|
+
*/
|
|
64
|
+
function serializeError(err) {
|
|
65
|
+
if (err instanceof Error) {
|
|
66
|
+
const extra = Object.fromEntries(Object.entries(err).filter(([key]) => key !== 'name' && key !== 'message' && key !== 'stack'));
|
|
67
|
+
return {
|
|
68
|
+
__isError: true,
|
|
69
|
+
name: err.name,
|
|
70
|
+
message: err.message,
|
|
71
|
+
stack: err.stack,
|
|
72
|
+
extra,
|
|
73
|
+
};
|
|
41
74
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
return { __isError: true, name: 'Error', message: String(err), extra: {} };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read the runtime IIFE bundle by walking up from `__dirname` until
|
|
79
|
+
* `dist/bundle/runtime.iife.js` is found.
|
|
80
|
+
*
|
|
81
|
+
* This works regardless of where the compiled output lives:
|
|
82
|
+
* - `src/bridge/` (vitest running against source)
|
|
83
|
+
* - `dist/cjs/bridge/` (CJS build)
|
|
84
|
+
*/
|
|
85
|
+
async function readRuntimeBundle() {
|
|
86
|
+
let dir = __dirname;
|
|
87
|
+
while (dir !== path.dirname(dir)) {
|
|
88
|
+
try {
|
|
89
|
+
return await (0, promises_1.readFile)(path.join(dir, BUNDLE_RELATIVE_PATH), 'utf-8');
|
|
55
90
|
}
|
|
56
|
-
|
|
91
|
+
catch { }
|
|
92
|
+
dir = path.dirname(dir);
|
|
57
93
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
throw new Error(`Could not find runtime bundle (${BUNDLE_RELATIVE_PATH}) in any parent of ${__dirname}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* IsolatedVmBridge - Runtime bridge using isolated-vm for secure expression evaluation.
|
|
98
|
+
*
|
|
99
|
+
* This bridge creates a V8 isolate with:
|
|
100
|
+
* - Hard memory limit (128MB default)
|
|
101
|
+
* - No access to Node.js APIs
|
|
102
|
+
* - Timeout enforcement
|
|
103
|
+
* - Complete isolation from host process
|
|
104
|
+
*
|
|
105
|
+
* Context reuse pattern: Create isolate/context once, reset state between evaluations.
|
|
106
|
+
*/
|
|
107
|
+
class IsolatedVmBridge {
|
|
108
|
+
isolate;
|
|
109
|
+
context;
|
|
110
|
+
initialized = false;
|
|
111
|
+
disposed = false;
|
|
112
|
+
config;
|
|
113
|
+
logger;
|
|
114
|
+
constructor(config = {}) {
|
|
115
|
+
this.config = {
|
|
116
|
+
...types_1.DEFAULT_BRIDGE_CONFIG,
|
|
117
|
+
...config,
|
|
118
|
+
};
|
|
119
|
+
this.logger = this.config.logger;
|
|
120
|
+
// Create isolate with memory limit
|
|
121
|
+
// Note: memoryLimit is in MB
|
|
122
|
+
this.isolate = new (getIvm().Isolate)({ memoryLimit: this.config.memoryLimit });
|
|
64
123
|
}
|
|
65
124
|
/**
|
|
66
|
-
*
|
|
125
|
+
* Initialize the isolate and create execution context.
|
|
126
|
+
*
|
|
127
|
+
* Steps:
|
|
128
|
+
* 1. Create context
|
|
129
|
+
* 2. Set up basic globals (global reference)
|
|
130
|
+
* 3. Load runtime bundle (DateTime, extend, proxy system)
|
|
131
|
+
* 4. Verify proxy system
|
|
67
132
|
*
|
|
68
|
-
*
|
|
69
|
-
* sentinel instead of letting the error cross the isolate boundary (which
|
|
70
|
-
* strips custom class identity and properties). The isolate-side proxy
|
|
71
|
-
* detects __isError and reconstructs a proper Error to throw.
|
|
133
|
+
* Must be called before execute().
|
|
72
134
|
*/
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
__isError: true,
|
|
78
|
-
name: err.name,
|
|
79
|
-
message: err.message,
|
|
80
|
-
stack: err.stack,
|
|
81
|
-
extra,
|
|
82
|
-
};
|
|
135
|
+
async initialize() {
|
|
136
|
+
if (this.initialized) {
|
|
137
|
+
return;
|
|
83
138
|
}
|
|
84
|
-
|
|
139
|
+
// Create context in the isolate
|
|
140
|
+
this.context = await this.isolate.createContext();
|
|
141
|
+
// Set up basic globals
|
|
142
|
+
// jail is a reference to the context's global object
|
|
143
|
+
const jail = this.context.global;
|
|
144
|
+
// Set 'global' to reference itself (pattern from POC)
|
|
145
|
+
// This allows code in isolate to access 'global.something'
|
|
146
|
+
await jail.set('global', jail.derefInto());
|
|
147
|
+
// Load runtime bundle (DateTime, extend, SafeObject, proxy system)
|
|
148
|
+
await this.loadVendorLibraries();
|
|
149
|
+
// Verify proxy system loaded correctly
|
|
150
|
+
await this.verifyProxySystem();
|
|
151
|
+
// Inject E() error handler needed by tournament-generated try-catch code
|
|
152
|
+
await this.injectErrorHandler();
|
|
153
|
+
this.initialized = true;
|
|
154
|
+
this.logger.debug('[IsolatedVmBridge] Initialized successfully');
|
|
85
155
|
}
|
|
86
156
|
/**
|
|
87
|
-
*
|
|
88
|
-
* `dist/bundle/runtime.iife.js` is found.
|
|
157
|
+
* Load runtime bundle into the isolate.
|
|
89
158
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
159
|
+
* The runtime bundle includes:
|
|
160
|
+
* - DateTime, extend, extendOptional (expression engine globals)
|
|
161
|
+
* - SafeObject and SafeError wrappers
|
|
162
|
+
* - createDeepLazyProxy function
|
|
163
|
+
* - buildContext function
|
|
164
|
+
*
|
|
165
|
+
* @private
|
|
166
|
+
* @throws {Error} If context not initialized or bundle loading fails
|
|
93
167
|
*/
|
|
94
|
-
async
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
168
|
+
async loadVendorLibraries() {
|
|
169
|
+
if (!this.context) {
|
|
170
|
+
throw new Error('Context not initialized');
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
// Load runtime bundle (includes vendor libraries + proxy system)
|
|
174
|
+
const runtimeBundle = await readRuntimeBundle();
|
|
175
|
+
// Evaluate bundle in isolate context
|
|
176
|
+
// This makes all exported globals available (DateTime, extend, extendOptional, SafeObject, SafeError, createDeepLazyProxy, buildContext)
|
|
177
|
+
await this.context.eval(runtimeBundle);
|
|
178
|
+
this.logger.debug('[IsolatedVmBridge] Runtime bundle loaded');
|
|
179
|
+
// Verify vendor libraries loaded correctly
|
|
180
|
+
const hasDateTime = await this.context.eval('typeof DateTime !== "undefined"');
|
|
181
|
+
const hasExtend = await this.context.eval('typeof extend !== "undefined"');
|
|
182
|
+
if (!hasDateTime || !hasExtend) {
|
|
183
|
+
throw new Error(`Library verification failed: DateTime=${hasDateTime}, extend=${hasExtend}`);
|
|
99
184
|
}
|
|
100
|
-
|
|
101
|
-
|
|
185
|
+
this.logger.debug('[IsolatedVmBridge] Vendor libraries verified successfully');
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
throw new Error(`Failed to load runtime bundle: ${errorMessage}`);
|
|
102
190
|
}
|
|
103
|
-
throw new Error(`Could not find runtime bundle (${BUNDLE_RELATIVE_PATH}) in any parent of ${__dirname}`);
|
|
104
191
|
}
|
|
105
192
|
/**
|
|
106
|
-
*
|
|
193
|
+
* Verify the proxy system loaded correctly.
|
|
107
194
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* - No access to Node.js APIs
|
|
111
|
-
* - Timeout enforcement
|
|
112
|
-
* - Complete isolation from host process
|
|
195
|
+
* The proxy system is loaded as part of the runtime bundle in loadVendorLibraries().
|
|
196
|
+
* This method verifies all required components are available.
|
|
113
197
|
*
|
|
114
|
-
*
|
|
198
|
+
* @private
|
|
199
|
+
* @throws {Error} If context not initialized or proxy system verification fails
|
|
115
200
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
initialized = false;
|
|
120
|
-
disposed = false;
|
|
121
|
-
config;
|
|
122
|
-
logger;
|
|
123
|
-
constructor(config = {}) {
|
|
124
|
-
this.config = {
|
|
125
|
-
...types_1.DEFAULT_BRIDGE_CONFIG,
|
|
126
|
-
...config,
|
|
127
|
-
};
|
|
128
|
-
this.logger = this.config.logger;
|
|
129
|
-
// Create isolate with memory limit
|
|
130
|
-
// Note: memoryLimit is in MB
|
|
131
|
-
this.isolate = new (getIvm().Isolate)({ memoryLimit: this.config.memoryLimit });
|
|
201
|
+
async verifyProxySystem() {
|
|
202
|
+
if (!this.context) {
|
|
203
|
+
throw new Error('Context not initialized');
|
|
132
204
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async initialize() {
|
|
145
|
-
if (this.initialized) {
|
|
146
|
-
return;
|
|
205
|
+
try {
|
|
206
|
+
// Verify proxy system components loaded correctly
|
|
207
|
+
const hasProxyCreator = await this.context.eval('typeof createDeepLazyProxy !== "undefined"');
|
|
208
|
+
const hasSafeObject = await this.context.eval('typeof SafeObject !== "undefined"');
|
|
209
|
+
const hasSafeError = await this.context.eval('typeof SafeError !== "undefined"');
|
|
210
|
+
const hasBuildContext = await this.context.eval('typeof buildContext !== "undefined"');
|
|
211
|
+
if (!hasProxyCreator || !hasSafeObject || !hasSafeError || !hasBuildContext) {
|
|
212
|
+
throw new Error(`Proxy system verification failed: ` +
|
|
213
|
+
`createDeepLazyProxy=${hasProxyCreator}, ` +
|
|
214
|
+
`SafeObject=${hasSafeObject}, SafeError=${hasSafeError}, ` +
|
|
215
|
+
`buildContext=${hasBuildContext}`);
|
|
147
216
|
}
|
|
148
|
-
|
|
149
|
-
this.context = await this.isolate.createContext();
|
|
150
|
-
// Set up basic globals
|
|
151
|
-
// jail is a reference to the context's global object
|
|
152
|
-
const jail = this.context.global;
|
|
153
|
-
// Set 'global' to reference itself (pattern from POC)
|
|
154
|
-
// This allows code in isolate to access 'global.something'
|
|
155
|
-
await jail.set('global', jail.derefInto());
|
|
156
|
-
// Load runtime bundle (DateTime, extend, SafeObject, proxy system)
|
|
157
|
-
await this.loadVendorLibraries();
|
|
158
|
-
// Verify proxy system loaded correctly
|
|
159
|
-
await this.verifyProxySystem();
|
|
160
|
-
// Inject E() error handler needed by tournament-generated try-catch code
|
|
161
|
-
await this.injectErrorHandler();
|
|
162
|
-
this.initialized = true;
|
|
163
|
-
this.logger.debug('[IsolatedVmBridge] Initialized successfully');
|
|
217
|
+
this.logger.debug('[IsolatedVmBridge] Proxy system verified successfully');
|
|
164
218
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* The runtime bundle includes:
|
|
169
|
-
* - DateTime, extend, extendOptional (expression engine globals)
|
|
170
|
-
* - SafeObject and SafeError wrappers
|
|
171
|
-
* - createDeepLazyProxy function
|
|
172
|
-
* - buildContext function
|
|
173
|
-
*
|
|
174
|
-
* @private
|
|
175
|
-
* @throws {Error} If context not initialized or bundle loading fails
|
|
176
|
-
*/
|
|
177
|
-
async loadVendorLibraries() {
|
|
178
|
-
if (!this.context) {
|
|
179
|
-
throw new Error('Context not initialized');
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
// Load runtime bundle (includes vendor libraries + proxy system)
|
|
183
|
-
const runtimeBundle = await readRuntimeBundle();
|
|
184
|
-
// Evaluate bundle in isolate context
|
|
185
|
-
// This makes all exported globals available (DateTime, extend, extendOptional, SafeObject, SafeError, createDeepLazyProxy, buildContext)
|
|
186
|
-
await this.context.eval(runtimeBundle);
|
|
187
|
-
this.logger.debug('[IsolatedVmBridge] Runtime bundle loaded');
|
|
188
|
-
// Verify vendor libraries loaded correctly
|
|
189
|
-
const hasDateTime = await this.context.eval('typeof DateTime !== "undefined"');
|
|
190
|
-
const hasExtend = await this.context.eval('typeof extend !== "undefined"');
|
|
191
|
-
if (!hasDateTime || !hasExtend) {
|
|
192
|
-
throw new Error(`Library verification failed: DateTime=${hasDateTime}, extend=${hasExtend}`);
|
|
193
|
-
}
|
|
194
|
-
this.logger.debug('[IsolatedVmBridge] Vendor libraries verified successfully');
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
198
|
-
throw new Error(`Failed to load runtime bundle: ${errorMessage}`);
|
|
199
|
-
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
221
|
+
throw new Error(`Failed to verify proxy system: ${errorMessage}`);
|
|
200
222
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
catch (error) {
|
|
229
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
230
|
-
throw new Error(`Failed to verify proxy system: ${errorMessage}`);
|
|
231
|
-
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Inject the E() error handler into the isolate context.
|
|
226
|
+
*
|
|
227
|
+
* There are two exception-handling layers inside the isolate:
|
|
228
|
+
*
|
|
229
|
+
* 1. **Inner layer (this handler, `E()`)** — Tournament wraps each
|
|
230
|
+
* expression with try-catch that calls `E(error, this)`. This handler
|
|
231
|
+
* must match the legacy engine's behavior (set in expression.ts via
|
|
232
|
+
* setErrorHandler):
|
|
233
|
+
* - Re-throw ExpressionError / ExpressionExtensionError
|
|
234
|
+
* - Swallow everything else (TypeErrors, generic Errors, etc.)
|
|
235
|
+
*
|
|
236
|
+
* 2. **Outer layer (`wrappedCode` try-catch in `execute()`)** — Catches
|
|
237
|
+
* anything that escaped `E()` (e.g. re-thrown ExpressionErrors) and
|
|
238
|
+
* serializes it into a sentinel object so the host can reconstruct it.
|
|
239
|
+
*
|
|
240
|
+
* Inside the isolate, errors from host callbacks arrive as sentinel
|
|
241
|
+
* objects ({ __isError, name, message, ... }) rather than class instances,
|
|
242
|
+
* so we match by name instead of instanceof.
|
|
243
|
+
*
|
|
244
|
+
* @private
|
|
245
|
+
* @throws {Error} If context not initialized
|
|
246
|
+
*/
|
|
247
|
+
async injectErrorHandler() {
|
|
248
|
+
if (!this.context) {
|
|
249
|
+
throw new Error('Context not initialized');
|
|
232
250
|
}
|
|
233
|
-
|
|
234
|
-
* Inject the E() error handler into the isolate context.
|
|
235
|
-
*
|
|
236
|
-
* There are two exception-handling layers inside the isolate:
|
|
237
|
-
*
|
|
238
|
-
* 1. **Inner layer (this handler, `E()`)** — Tournament wraps each
|
|
239
|
-
* expression with try-catch that calls `E(error, this)`. This handler
|
|
240
|
-
* must match the legacy engine's behavior (set in expression.ts via
|
|
241
|
-
* setErrorHandler):
|
|
242
|
-
* - Re-throw ExpressionError / ExpressionExtensionError
|
|
243
|
-
* - Swallow everything else (TypeErrors, generic Errors, etc.)
|
|
244
|
-
*
|
|
245
|
-
* 2. **Outer layer (`wrappedCode` try-catch in `execute()`)** — Catches
|
|
246
|
-
* anything that escaped `E()` (e.g. re-thrown ExpressionErrors) and
|
|
247
|
-
* serializes it into a sentinel object so the host can reconstruct it.
|
|
248
|
-
*
|
|
249
|
-
* Inside the isolate, errors from host callbacks arrive as sentinel
|
|
250
|
-
* objects ({ __isError, name, message, ... }) rather than class instances,
|
|
251
|
-
* so we match by name instead of instanceof.
|
|
252
|
-
*
|
|
253
|
-
* @private
|
|
254
|
-
* @throws {Error} If context not initialized
|
|
255
|
-
*/
|
|
256
|
-
async injectErrorHandler() {
|
|
257
|
-
if (!this.context) {
|
|
258
|
-
throw new Error('Context not initialized');
|
|
259
|
-
}
|
|
260
|
-
await this.context.eval(`
|
|
251
|
+
await this.context.eval(`
|
|
261
252
|
if (typeof E === 'undefined') {
|
|
262
253
|
globalThis.E = function(error, _context) {
|
|
263
254
|
// Re-throw ExpressionError / ExpressionExtensionError to match
|
|
@@ -272,395 +263,395 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
272
263
|
};
|
|
273
264
|
}
|
|
274
265
|
`);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
const dollarFn = data.$;
|
|
310
|
-
if (path.length >= 2 && path[0] === '$' && typeof dollarFn === 'function') {
|
|
311
|
-
value = dollarFn(path[1]);
|
|
312
|
-
startIndex = 2;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
for (let i = startIndex; i < path.length; i++) {
|
|
316
|
-
value = value?.[path[i]];
|
|
317
|
-
if (value === undefined || value === null) {
|
|
318
|
-
return value;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
// Functions are not reachable via the lazy-proxy data path —
|
|
322
|
-
// every callable on the host data surface routes through the
|
|
323
|
-
// typed-RPC dispatcher. Return undefined so any residual
|
|
324
|
-
// access surfaces as missing rather than as a stale metadata
|
|
325
|
-
// marker the runtime no longer knows how to interpret.
|
|
326
|
-
if (typeof value === 'function') {
|
|
327
|
-
return undefined;
|
|
266
|
+
this.logger.debug('[IsolatedVmBridge] Error handler injected successfully');
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Create an ivm.Reference callback for getting value/metadata at a path.
|
|
270
|
+
*
|
|
271
|
+
* Used by createDeepLazyProxy when accessing properties. Returns metadata
|
|
272
|
+
* markers for arrays and objects, or the primitive value directly.
|
|
273
|
+
*
|
|
274
|
+
* Function-typed values are returned as `undefined` — every callable on
|
|
275
|
+
* the host data surface (`$('Foo').first()`, `$items()`, `$fromAI()`,
|
|
276
|
+
* `$evaluateExpression()`, `$getPairedItem()`) is wired in-isolate via
|
|
277
|
+
* the typed-RPC dispatcher (`callHost`). No expression form should
|
|
278
|
+
* reach a function through this path.
|
|
279
|
+
*
|
|
280
|
+
* @param data - Current workflow data to use for callback responses
|
|
281
|
+
* @private
|
|
282
|
+
*/
|
|
283
|
+
createGetValueAtPathRef(data) {
|
|
284
|
+
return new (getIvm().Reference)((path) => {
|
|
285
|
+
try {
|
|
286
|
+
// Navigate to value
|
|
287
|
+
// Special-case: paths starting with ['$item', index] call data.$item(index)
|
|
288
|
+
// to get the sub-proxy for that item, then continue navigating the rest.
|
|
289
|
+
let value = data;
|
|
290
|
+
let startIndex = 0;
|
|
291
|
+
const itemFn = data.$item;
|
|
292
|
+
if (path.length >= 2 && path[0] === '$item' && typeof itemFn === 'function') {
|
|
293
|
+
const itemIndex = parseInt(path[1], 10);
|
|
294
|
+
if (!isNaN(itemIndex)) {
|
|
295
|
+
value = itemFn(itemIndex);
|
|
296
|
+
startIndex = 2;
|
|
328
297
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const dollarFn = data.$;
|
|
301
|
+
if (path.length >= 2 && path[0] === '$' && typeof dollarFn === 'function') {
|
|
302
|
+
value = dollarFn(path[1]);
|
|
303
|
+
startIndex = 2;
|
|
336
304
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
305
|
+
}
|
|
306
|
+
for (let i = startIndex; i < path.length; i++) {
|
|
307
|
+
value = value?.[path[i]];
|
|
308
|
+
if (value === undefined || value === null) {
|
|
340
309
|
return value;
|
|
341
310
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
311
|
+
}
|
|
312
|
+
// Functions are not reachable via the lazy-proxy data path —
|
|
313
|
+
// every callable on the host data surface routes through the
|
|
314
|
+
// typed-RPC dispatcher. Return undefined so any residual
|
|
315
|
+
// access surfaces as missing rather than as a stale metadata
|
|
316
|
+
// marker the runtime no longer knows how to interpret.
|
|
317
|
+
if (typeof value === 'function') {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
// Handle arrays - always lazy, only transfer length
|
|
321
|
+
if (Array.isArray(value)) {
|
|
322
|
+
return {
|
|
323
|
+
__isArray: true,
|
|
324
|
+
__length: value.length,
|
|
325
|
+
__data: null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// Dates have no enumerable own keys; pass through instead of
|
|
329
|
+
// marshaling as an empty object.
|
|
330
|
+
if (value instanceof Date) {
|
|
350
331
|
return value;
|
|
351
332
|
}
|
|
352
|
-
|
|
353
|
-
|
|
333
|
+
// Handle objects - return metadata with keys
|
|
334
|
+
if (value !== null && typeof value === 'object') {
|
|
335
|
+
return {
|
|
336
|
+
__isObject: true,
|
|
337
|
+
__keys: Object.keys(value),
|
|
338
|
+
};
|
|
354
339
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
startIndex = 2;
|
|
385
|
-
}
|
|
340
|
+
// Primitive value
|
|
341
|
+
return value;
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
return serializeError(err);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Create an ivm.Reference callback for getting array elements at an index.
|
|
350
|
+
*
|
|
351
|
+
* Used by array proxy when accessing numeric indices.
|
|
352
|
+
*
|
|
353
|
+
* @param data - Current workflow data to use for callback responses
|
|
354
|
+
* @private
|
|
355
|
+
*/
|
|
356
|
+
createGetArrayElementRef(data) {
|
|
357
|
+
return new (getIvm().Reference)((path, index) => {
|
|
358
|
+
try {
|
|
359
|
+
// Navigate to array
|
|
360
|
+
// Special-case: paths starting with ['$item', index] call data.$item(index)
|
|
361
|
+
let arr = data;
|
|
362
|
+
let startIndex = 0;
|
|
363
|
+
const itemFn = data.$item;
|
|
364
|
+
if (path.length >= 2 && path[0] === '$item' && typeof itemFn === 'function') {
|
|
365
|
+
const itemIndex = parseInt(path[1], 10);
|
|
366
|
+
if (!isNaN(itemIndex)) {
|
|
367
|
+
arr = itemFn(itemIndex);
|
|
368
|
+
startIndex = 2;
|
|
386
369
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
const dollarFn = data.$;
|
|
373
|
+
if (path.length >= 2 && path[0] === '$' && typeof dollarFn === 'function') {
|
|
374
|
+
arr = dollarFn(path[1]);
|
|
375
|
+
startIndex = 2;
|
|
392
376
|
}
|
|
393
|
-
|
|
377
|
+
}
|
|
378
|
+
for (let i = startIndex; i < path.length; i++) {
|
|
379
|
+
arr = arr?.[path[i]];
|
|
380
|
+
if (arr === undefined || arr === null) {
|
|
394
381
|
return undefined;
|
|
395
382
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
};
|
|
410
|
-
}
|
|
383
|
+
}
|
|
384
|
+
if (!Array.isArray(arr)) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
const element = arr[index];
|
|
388
|
+
// Dates have no enumerable own keys; pass through instead of
|
|
389
|
+
// marshaling as an empty object.
|
|
390
|
+
if (element instanceof Date) {
|
|
391
|
+
return element;
|
|
392
|
+
}
|
|
393
|
+
// If element is object/array, return metadata
|
|
394
|
+
if (element !== null && typeof element === 'object') {
|
|
395
|
+
if (Array.isArray(element)) {
|
|
411
396
|
return {
|
|
412
|
-
|
|
413
|
-
|
|
397
|
+
__isArray: true,
|
|
398
|
+
__length: element.length,
|
|
399
|
+
__data: null,
|
|
414
400
|
};
|
|
415
401
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return serializeError(err);
|
|
402
|
+
return {
|
|
403
|
+
__isObject: true,
|
|
404
|
+
__keys: Object.keys(element),
|
|
405
|
+
};
|
|
421
406
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
407
|
+
// Primitive element
|
|
408
|
+
return element;
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
return serializeError(err);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Create the single typed-RPC dispatcher.
|
|
417
|
+
*
|
|
418
|
+
* The isolate sends one envelope per typed RPC invocation:
|
|
419
|
+
* `callHost({ type: 'getNodeFirst', nodeName, branchIndex?, runIndex? })`
|
|
420
|
+
*
|
|
421
|
+
* Inputs cross a trust boundary, so the dispatcher parses every envelope
|
|
422
|
+
* with the host-side zod schema (`bridgeMessageSchema`) before any
|
|
423
|
+
* dispatch happens. Anything that deviates from the declared shape —
|
|
424
|
+
* unknown `type`, missing required fields, extra unexpected fields,
|
|
425
|
+
* wrong field types — fails the parse and an error sentinel is returned
|
|
426
|
+
* to the caller.
|
|
427
|
+
*
|
|
428
|
+
* After parsing, `switch (msg.type)` dispatches to a private handler with
|
|
429
|
+
* a fully narrowed message type. The operation set is exactly the cases
|
|
430
|
+
* in this switch; the `type` field selects a static branch in source,
|
|
431
|
+
* not a property lookup on a runtime object.
|
|
432
|
+
*
|
|
433
|
+
* @param data - Current workflow data
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
createCallHostRef(data) {
|
|
437
|
+
return new (getIvm().Reference)((rawMsg) => {
|
|
438
|
+
try {
|
|
439
|
+
const msg = bridge_messages_1.bridgeMessageSchema.parse(rawMsg);
|
|
440
|
+
switch (msg.type) {
|
|
441
|
+
case 'getNodeFirst':
|
|
442
|
+
return this.handleGetNodeFirst(msg, data);
|
|
443
|
+
case 'getNodeLast':
|
|
444
|
+
return this.handleGetNodeLast(msg, data);
|
|
445
|
+
case 'getNodeAll':
|
|
446
|
+
return this.handleGetNodeAll(msg, data);
|
|
447
|
+
case 'getInputFirst':
|
|
448
|
+
return this.handleGetInputFirst(data);
|
|
449
|
+
case 'getInputLast':
|
|
450
|
+
return this.handleGetInputLast(data);
|
|
451
|
+
case 'getInputAll':
|
|
452
|
+
return this.handleGetInputAll(data);
|
|
453
|
+
case 'getItems':
|
|
454
|
+
return this.handleGetItems(msg, data);
|
|
455
|
+
case 'fromAi':
|
|
456
|
+
return this.handleFromAi(msg, data);
|
|
457
|
+
case 'getNodePairedItem':
|
|
458
|
+
return this.handleGetNodePairedItem(msg, data);
|
|
459
|
+
case 'getNodeItemMatching':
|
|
460
|
+
return this.handleGetNodeItemMatching(msg, data);
|
|
461
|
+
case 'getNodeItem':
|
|
462
|
+
return this.handleGetNodeItem(msg, data);
|
|
463
|
+
case 'evaluateExpression':
|
|
464
|
+
return this.handleEvaluateExpression(msg, data);
|
|
465
|
+
case 'getPairedItem':
|
|
466
|
+
return this.handleGetPairedItem(msg, data);
|
|
467
|
+
default: {
|
|
468
|
+
// Unreachable at runtime — zod rejects unknown `type` values
|
|
469
|
+
// before the switch. The `never` assignment is the compile-time
|
|
470
|
+
// guard: a new schema added to `bridgeMessageSchema` without a
|
|
471
|
+
// matching case here becomes a type error.
|
|
472
|
+
const exhaustive = msg;
|
|
473
|
+
void exhaustive;
|
|
474
|
+
throw new Error('Unhandled bridge message');
|
|
485
475
|
}
|
|
486
476
|
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
return serializeError(err);
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Handlers for the `$('Foo').{first,last,all}` typed RPCs.
|
|
494
|
-
*
|
|
495
|
-
* Each handler reads a fixed literal property name off the host-side node
|
|
496
|
-
* proxy — the isolate cannot influence which property is dereferenced.
|
|
497
|
-
* Eliminating `data.$` as a host-callable entirely would require reaching
|
|
498
|
-
* the `WorkflowDataProxy` internals (e.g. `getNodeExecutionOrPinnedData`)
|
|
499
|
-
* rather than the public `$()` API; that's a follow-up.
|
|
500
|
-
*
|
|
501
|
-
* `data.$` is a host-wired function (`WorkflowDataProxy`'s `$`). If it
|
|
502
|
-
* ever isn't, optional chaining short-circuits to `undefined` — the same
|
|
503
|
-
* observable result the runtime's `E()` handler produces from any thrown
|
|
504
|
-
* error here.
|
|
505
|
-
*
|
|
506
|
-
* @private
|
|
507
|
-
*/
|
|
508
|
-
handleGetNodeFirst(msg, data) {
|
|
509
|
-
return data.$?.(msg.nodeName)?.first?.(msg.branchIndex, msg.runIndex);
|
|
510
|
-
}
|
|
511
|
-
handleGetNodeLast(msg, data) {
|
|
512
|
-
return data.$?.(msg.nodeName)?.last?.(msg.branchIndex, msg.runIndex);
|
|
513
|
-
}
|
|
514
|
-
handleGetNodeAll(msg, data) {
|
|
515
|
-
return data.$?.(msg.nodeName)?.all?.(msg.branchIndex, msg.runIndex);
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Handlers for the `$input.{first,last,all}` typed RPCs.
|
|
519
|
-
*
|
|
520
|
-
* Each reads a fixed literal property name off `data.$input` (the host's
|
|
521
|
-
* `WorkflowDataProxy` input proxy). The host enforces zero arguments on
|
|
522
|
-
* these methods — the schemas have no fields besides `type`, so the
|
|
523
|
-
* isolate cannot pass anything that would trigger the "should have no
|
|
524
|
-
* arguments" error path on the host side.
|
|
525
|
-
*
|
|
526
|
-
* @private
|
|
527
|
-
*/
|
|
528
|
-
handleGetInputFirst(data) {
|
|
529
|
-
return data.$input?.first?.();
|
|
530
|
-
}
|
|
531
|
-
handleGetInputLast(data) {
|
|
532
|
-
return data.$input?.last?.();
|
|
533
|
-
}
|
|
534
|
-
handleGetInputAll(data) {
|
|
535
|
-
return data.$input?.all?.();
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Handler for `$items(nodeName?, outputIndex?, runIndex?)` — the
|
|
539
|
-
* global accessor for a node's execution data. Reads the literal
|
|
540
|
-
* `$items` property off `data` (host-wired by `WorkflowDataProxy`)
|
|
541
|
-
* and forwards the validated args verbatim. The host applies its own
|
|
542
|
-
* defaults when fields are `undefined`.
|
|
543
|
-
*
|
|
544
|
-
* @private
|
|
545
|
-
*/
|
|
546
|
-
handleGetItems(msg, data) {
|
|
547
|
-
return data.$items?.(msg.nodeName, msg.outputIndex, msg.runIndex);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Handler for `$fromAI(name, description?, type?, defaultValue?)` and its
|
|
551
|
-
* `$fromAi` / `$fromai` aliases. Reads the literal `$fromAI` property
|
|
552
|
-
* off `data` (host-wired) and forwards the args. The host validates
|
|
553
|
-
* `name` (required + regex) and applies its own resolution / fallback
|
|
554
|
-
* logic, so empty / invalid names surface as the host's structured
|
|
555
|
-
* `ExpressionError` rather than a generic zod parse error.
|
|
556
|
-
*
|
|
557
|
-
* Note: `msg.valueType` maps to the host's third positional parameter
|
|
558
|
-
* (`_type` in `WorkflowDataProxy.handleFromAi`). The bridge protocol
|
|
559
|
-
* renames it to avoid collision with the `type` discriminator on the
|
|
560
|
-
* envelope — the host parameter currently goes unused, but if it ever
|
|
561
|
-
* gains a name (`type`), this mapping should stay explicit.
|
|
562
|
-
*
|
|
563
|
-
* @private
|
|
564
|
-
*/
|
|
565
|
-
handleFromAi(msg, data) {
|
|
566
|
-
return data.$fromAI?.(msg.name, msg.description, msg.valueType, msg.defaultValue);
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Handlers for the `$('Foo').pairedItem(itemIndex?)` / `.itemMatching(...)` /
|
|
570
|
-
* `.item` cluster. Three separate typed RPCs, each reading exactly one
|
|
571
|
-
* literal property off the host node proxy.
|
|
572
|
-
*
|
|
573
|
-
* The split is load-bearing: the host's `pairedItemMethod` closure
|
|
574
|
-
* captures which property name the proxy `get` trap saw, and uses
|
|
575
|
-
* that to pick the right error message (e.g. "Missing item index for
|
|
576
|
-
* .itemMatching()") and to decide between method-call vs getter
|
|
577
|
-
* semantics for `.item`. Reading the matching property here lets
|
|
578
|
-
* those host-side branches fire exactly as they do in the legacy
|
|
579
|
-
* engine; no in-isolate validation needed.
|
|
580
|
-
*
|
|
581
|
-
* @private
|
|
582
|
-
*/
|
|
583
|
-
handleGetNodePairedItem(msg, data) {
|
|
584
|
-
return data.$?.(msg.nodeName)?.pairedItem?.(msg.itemIndex);
|
|
585
|
-
}
|
|
586
|
-
handleGetNodeItemMatching(msg, data) {
|
|
587
|
-
return data.$?.(msg.nodeName)?.itemMatching?.(msg.itemIndex);
|
|
588
|
-
}
|
|
589
|
-
handleGetNodeItem(msg, data) {
|
|
590
|
-
// `.item` is a host getter — accessing it invokes the resolver and
|
|
591
|
-
// returns the value immediately. Optional chaining only short-
|
|
592
|
-
// circuits on null/undefined; the getter still fires on access.
|
|
593
|
-
return data.$?.(msg.nodeName)?.item;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Handler for `$evaluateExpression(expression, itemIndex?)`. Forwards
|
|
597
|
-
* the string to the host's nested-evaluation helper, which re-enters
|
|
598
|
-
* the expression engine on the inner expression. Under the VM engine
|
|
599
|
-
* this round-trips through the bridge again on a fresh evaluation
|
|
600
|
-
* cycle, which is the same shape the legacy engine supports.
|
|
601
|
-
*
|
|
602
|
-
* @private
|
|
603
|
-
*/
|
|
604
|
-
handleEvaluateExpression(msg, data) {
|
|
605
|
-
return data.$evaluateExpression?.(msg.expression, msg.itemIndex);
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Handler for `$getPairedItem(destinationNodeName, incomingSourceData,
|
|
609
|
-
* initialPairedItem)`. Forwards directly to the host binding, which
|
|
610
|
-
* walks the paired-item ancestry chain back to the named upstream node
|
|
611
|
-
* and returns the matching execution item.
|
|
612
|
-
*
|
|
613
|
-
* The two trailing host parameters — `usedMethodName` and
|
|
614
|
-
* `nodeBeforeLast` — are deliberately not part of the wire protocol:
|
|
615
|
-
* the host's default for `usedMethodName` is already `$getPairedItem`,
|
|
616
|
-
* and `nodeBeforeLast` is an internal recursion argument the host sets
|
|
617
|
-
* during traversal.
|
|
618
|
-
*
|
|
619
|
-
* @private
|
|
620
|
-
*/
|
|
621
|
-
handleGetPairedItem(msg, data) {
|
|
622
|
-
return data.$getPairedItem?.(msg.destinationNodeName, msg.incomingSourceData, msg.initialPairedItem);
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Execute JavaScript code in the isolated context.
|
|
626
|
-
*
|
|
627
|
-
* Flow:
|
|
628
|
-
* 1. Create three ivm.Reference callbacks scoped to the current data:
|
|
629
|
-
* `getValueAtPath`, `getArrayElement`, `callHost`.
|
|
630
|
-
* 2. Use evalClosureSync to run the code in a closure where `$0`/`$1`/`$2`
|
|
631
|
-
* are the callback references — no global mutable state.
|
|
632
|
-
* 3. buildContext() inside the isolate creates a fresh evaluation context
|
|
633
|
-
* from the closure-scoped references.
|
|
634
|
-
*
|
|
635
|
-
* Each call gets its own closure, so nested and concurrent evaluations
|
|
636
|
-
* cannot interfere with each other.
|
|
637
|
-
*
|
|
638
|
-
* @param code - JavaScript expression to evaluate
|
|
639
|
-
* @param data - Workflow data (e.g., { $json: {...}, $runIndex: 0 })
|
|
640
|
-
* @returns Result of the expression
|
|
641
|
-
* @throws {Error} If bridge not initialized or execution fails
|
|
642
|
-
*/
|
|
643
|
-
execute(code, data, options) {
|
|
644
|
-
if (!this.initialized || !this.context) {
|
|
645
|
-
throw new Error('Bridge not initialized. Call initialize() first.');
|
|
646
477
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
478
|
+
catch (err) {
|
|
479
|
+
return serializeError(err);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Handlers for the `$('Foo').{first,last,all}` typed RPCs.
|
|
485
|
+
*
|
|
486
|
+
* Each handler reads a fixed literal property name off the host-side node
|
|
487
|
+
* proxy — the isolate cannot influence which property is dereferenced.
|
|
488
|
+
* Eliminating `data.$` as a host-callable entirely would require reaching
|
|
489
|
+
* the `WorkflowDataProxy` internals (e.g. `getNodeExecutionOrPinnedData`)
|
|
490
|
+
* rather than the public `$()` API; that's a follow-up.
|
|
491
|
+
*
|
|
492
|
+
* `data.$` is a host-wired function (`WorkflowDataProxy`'s `$`). If it
|
|
493
|
+
* ever isn't, optional chaining short-circuits to `undefined` — the same
|
|
494
|
+
* observable result the runtime's `E()` handler produces from any thrown
|
|
495
|
+
* error here.
|
|
496
|
+
*
|
|
497
|
+
* @private
|
|
498
|
+
*/
|
|
499
|
+
handleGetNodeFirst(msg, data) {
|
|
500
|
+
return data.$?.(msg.nodeName)?.first?.(msg.branchIndex, msg.runIndex);
|
|
501
|
+
}
|
|
502
|
+
handleGetNodeLast(msg, data) {
|
|
503
|
+
return data.$?.(msg.nodeName)?.last?.(msg.branchIndex, msg.runIndex);
|
|
504
|
+
}
|
|
505
|
+
handleGetNodeAll(msg, data) {
|
|
506
|
+
return data.$?.(msg.nodeName)?.all?.(msg.branchIndex, msg.runIndex);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Handlers for the `$input.{first,last,all}` typed RPCs.
|
|
510
|
+
*
|
|
511
|
+
* Each reads a fixed literal property name off `data.$input` (the host's
|
|
512
|
+
* `WorkflowDataProxy` input proxy). The host enforces zero arguments on
|
|
513
|
+
* these methods — the schemas have no fields besides `type`, so the
|
|
514
|
+
* isolate cannot pass anything that would trigger the "should have no
|
|
515
|
+
* arguments" error path on the host side.
|
|
516
|
+
*
|
|
517
|
+
* @private
|
|
518
|
+
*/
|
|
519
|
+
handleGetInputFirst(data) {
|
|
520
|
+
return data.$input?.first?.();
|
|
521
|
+
}
|
|
522
|
+
handleGetInputLast(data) {
|
|
523
|
+
return data.$input?.last?.();
|
|
524
|
+
}
|
|
525
|
+
handleGetInputAll(data) {
|
|
526
|
+
return data.$input?.all?.();
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Handler for `$items(nodeName?, outputIndex?, runIndex?)` — the
|
|
530
|
+
* global accessor for a node's execution data. Reads the literal
|
|
531
|
+
* `$items` property off `data` (host-wired by `WorkflowDataProxy`)
|
|
532
|
+
* and forwards the validated args verbatim. The host applies its own
|
|
533
|
+
* defaults when fields are `undefined`.
|
|
534
|
+
*
|
|
535
|
+
* @private
|
|
536
|
+
*/
|
|
537
|
+
handleGetItems(msg, data) {
|
|
538
|
+
return data.$items?.(msg.nodeName, msg.outputIndex, msg.runIndex);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Handler for `$fromAI(name, description?, type?, defaultValue?)` and its
|
|
542
|
+
* `$fromAi` / `$fromai` aliases. Reads the literal `$fromAI` property
|
|
543
|
+
* off `data` (host-wired) and forwards the args. The host validates
|
|
544
|
+
* `name` (required + regex) and applies its own resolution / fallback
|
|
545
|
+
* logic, so empty / invalid names surface as the host's structured
|
|
546
|
+
* `ExpressionError` rather than a generic zod parse error.
|
|
547
|
+
*
|
|
548
|
+
* Note: `msg.valueType` maps to the host's third positional parameter
|
|
549
|
+
* (`_type` in `WorkflowDataProxy.handleFromAi`). The bridge protocol
|
|
550
|
+
* renames it to avoid collision with the `type` discriminator on the
|
|
551
|
+
* envelope — the host parameter currently goes unused, but if it ever
|
|
552
|
+
* gains a name (`type`), this mapping should stay explicit.
|
|
553
|
+
*
|
|
554
|
+
* @private
|
|
555
|
+
*/
|
|
556
|
+
handleFromAi(msg, data) {
|
|
557
|
+
return data.$fromAI?.(msg.name, msg.description, msg.valueType, msg.defaultValue);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Handlers for the `$('Foo').pairedItem(itemIndex?)` / `.itemMatching(...)` /
|
|
561
|
+
* `.item` cluster. Three separate typed RPCs, each reading exactly one
|
|
562
|
+
* literal property off the host node proxy.
|
|
563
|
+
*
|
|
564
|
+
* The split is load-bearing: the host's `pairedItemMethod` closure
|
|
565
|
+
* captures which property name the proxy `get` trap saw, and uses
|
|
566
|
+
* that to pick the right error message (e.g. "Missing item index for
|
|
567
|
+
* .itemMatching()") and to decide between method-call vs getter
|
|
568
|
+
* semantics for `.item`. Reading the matching property here lets
|
|
569
|
+
* those host-side branches fire exactly as they do in the legacy
|
|
570
|
+
* engine; no in-isolate validation needed.
|
|
571
|
+
*
|
|
572
|
+
* @private
|
|
573
|
+
*/
|
|
574
|
+
handleGetNodePairedItem(msg, data) {
|
|
575
|
+
return data.$?.(msg.nodeName)?.pairedItem?.(msg.itemIndex);
|
|
576
|
+
}
|
|
577
|
+
handleGetNodeItemMatching(msg, data) {
|
|
578
|
+
return data.$?.(msg.nodeName)?.itemMatching?.(msg.itemIndex);
|
|
579
|
+
}
|
|
580
|
+
handleGetNodeItem(msg, data) {
|
|
581
|
+
// `.item` is a host getter — accessing it invokes the resolver and
|
|
582
|
+
// returns the value immediately. Optional chaining only short-
|
|
583
|
+
// circuits on null/undefined; the getter still fires on access.
|
|
584
|
+
return data.$?.(msg.nodeName)?.item;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Handler for `$evaluateExpression(expression, itemIndex?)`. Forwards
|
|
588
|
+
* the string to the host's nested-evaluation helper, which re-enters
|
|
589
|
+
* the expression engine on the inner expression. Under the VM engine
|
|
590
|
+
* this round-trips through the bridge again on a fresh evaluation
|
|
591
|
+
* cycle, which is the same shape the legacy engine supports.
|
|
592
|
+
*
|
|
593
|
+
* @private
|
|
594
|
+
*/
|
|
595
|
+
handleEvaluateExpression(msg, data) {
|
|
596
|
+
return data.$evaluateExpression?.(msg.expression, msg.itemIndex);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Handler for `$getPairedItem(destinationNodeName, incomingSourceData,
|
|
600
|
+
* initialPairedItem)`. Forwards directly to the host binding, which
|
|
601
|
+
* walks the paired-item ancestry chain back to the named upstream node
|
|
602
|
+
* and returns the matching execution item.
|
|
603
|
+
*
|
|
604
|
+
* The two trailing host parameters — `usedMethodName` and
|
|
605
|
+
* `nodeBeforeLast` — are deliberately not part of the wire protocol:
|
|
606
|
+
* the host's default for `usedMethodName` is already `$getPairedItem`,
|
|
607
|
+
* and `nodeBeforeLast` is an internal recursion argument the host sets
|
|
608
|
+
* during traversal.
|
|
609
|
+
*
|
|
610
|
+
* @private
|
|
611
|
+
*/
|
|
612
|
+
handleGetPairedItem(msg, data) {
|
|
613
|
+
return data.$getPairedItem?.(msg.destinationNodeName, msg.incomingSourceData, msg.initialPairedItem);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Execute JavaScript code in the isolated context.
|
|
617
|
+
*
|
|
618
|
+
* Flow:
|
|
619
|
+
* 1. Create three ivm.Reference callbacks scoped to the current data:
|
|
620
|
+
* `getValueAtPath`, `getArrayElement`, `callHost`.
|
|
621
|
+
* 2. Use evalClosureSync to run the code in a closure where `$0`/`$1`/`$2`
|
|
622
|
+
* are the callback references — no global mutable state.
|
|
623
|
+
* 3. buildContext() inside the isolate creates a fresh evaluation context
|
|
624
|
+
* from the closure-scoped references.
|
|
625
|
+
*
|
|
626
|
+
* Each call gets its own closure, so nested and concurrent evaluations
|
|
627
|
+
* cannot interfere with each other.
|
|
628
|
+
*
|
|
629
|
+
* @param code - JavaScript expression to evaluate
|
|
630
|
+
* @param data - Workflow data (e.g., { $json: {...}, $runIndex: 0 })
|
|
631
|
+
* @returns Result of the expression
|
|
632
|
+
* @throws {Error} If bridge not initialized or execution fails
|
|
633
|
+
*/
|
|
634
|
+
execute(code, data, options) {
|
|
635
|
+
if (!this.initialized || !this.context) {
|
|
636
|
+
throw new Error('Bridge not initialized. Call initialize() first.');
|
|
637
|
+
}
|
|
638
|
+
const getValueAtPath = this.createGetValueAtPathRef(data);
|
|
639
|
+
const getArrayElement = this.createGetArrayElementRef(data);
|
|
640
|
+
const callHost = this.createCallHostRef(data);
|
|
641
|
+
try {
|
|
642
|
+
const timezone = options?.timezone ? JSON.stringify(options.timezone) : 'undefined';
|
|
643
|
+
// Wrap transformed code so 'this' === the closure-scoped context.
|
|
644
|
+
// Tournament generates: this.$json.email, this.$items(), etc.
|
|
645
|
+
// buildContext() creates a fresh context with lazy proxies from the
|
|
646
|
+
// closure-scoped callback references — no globals touched. The bundle
|
|
647
|
+
// is passed as a single object so adding typed RPCs doesn't churn the
|
|
648
|
+
// evalClosureSync signature; new operations land as new schemas in
|
|
649
|
+
// bridge-messages.ts and new cases in the callHost dispatcher.
|
|
650
|
+
// The outer try-catch serializes errors into a sentinel object and returns
|
|
651
|
+
// it as the result. Errors from host callbacks arrive as sentinels already
|
|
652
|
+
// (via serializeError), so we pass them through. This avoids a round-trip
|
|
653
|
+
// callback and keeps Error reconstruction on the host side only.
|
|
654
|
+
const wrappedCode = `
|
|
664
655
|
var __ctx = buildContext({
|
|
665
656
|
getValueAtPath: $0,
|
|
666
657
|
getArrayElement: $1,
|
|
@@ -686,82 +677,81 @@ try {
|
|
|
686
677
|
extra: extra
|
|
687
678
|
};
|
|
688
679
|
}`;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
this.logger.debug('[IsolatedVmBridge] Expression executed successfully');
|
|
694
|
-
return result;
|
|
695
|
-
}
|
|
696
|
-
catch (error) {
|
|
697
|
-
// Re-throw reconstructed errors as-is.
|
|
698
|
-
// Note: TypeError is intentionally NOT included here — the isolate's
|
|
699
|
-
// E() handler swallows TypeErrors (failed attack attempts return undefined),
|
|
700
|
-
// so TypeErrors from host callbacks should also go through the generic
|
|
701
|
-
// wrapping for consistent behavior.
|
|
702
|
-
if (error instanceof Error &&
|
|
703
|
-
(error.name === 'ExpressionError' || error.name === 'ExpressionExtensionError')) {
|
|
704
|
-
throw error;
|
|
705
|
-
}
|
|
706
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
707
|
-
if (errorMessage.includes('Script execution timed out')) {
|
|
708
|
-
throw new types_1.TimeoutError(`Expression timed out after ${this.config.timeout}ms`, {});
|
|
709
|
-
}
|
|
710
|
-
if (errorMessage.includes('memory limit')) {
|
|
711
|
-
throw new types_1.MemoryLimitError(`Expression exceeded memory limit of ${this.config.memoryLimit}MB`, {});
|
|
712
|
-
}
|
|
713
|
-
throw new Error(`Expression evaluation failed: ${errorMessage}`);
|
|
714
|
-
}
|
|
715
|
-
finally {
|
|
716
|
-
getValueAtPath.release();
|
|
717
|
-
getArrayElement.release();
|
|
718
|
-
callHost.release();
|
|
680
|
+
const result = this.context.evalClosureSync(wrappedCode, [getValueAtPath, getArrayElement, callHost], { result: { copy: true }, timeout: this.config.timeout });
|
|
681
|
+
if (isErrorSentinel(result)) {
|
|
682
|
+
throw this.reconstructError(result);
|
|
719
683
|
}
|
|
684
|
+
this.logger.debug('[IsolatedVmBridge] Expression executed successfully');
|
|
685
|
+
return result;
|
|
720
686
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (data.stack) {
|
|
731
|
-
error.stack = data.stack;
|
|
732
|
-
}
|
|
733
|
-
// Restore custom properties transferred via copy: true
|
|
734
|
-
if (data.extra) {
|
|
735
|
-
Object.assign(error, data.extra);
|
|
687
|
+
catch (error) {
|
|
688
|
+
// Re-throw reconstructed errors as-is.
|
|
689
|
+
// Note: TypeError is intentionally NOT included here — the isolate's
|
|
690
|
+
// E() handler swallows TypeErrors (failed attack attempts return undefined),
|
|
691
|
+
// so TypeErrors from host callbacks should also go through the generic
|
|
692
|
+
// wrapping for consistent behavior.
|
|
693
|
+
if (error instanceof Error &&
|
|
694
|
+
(error.name === 'ExpressionError' || error.name === 'ExpressionExtensionError')) {
|
|
695
|
+
throw error;
|
|
736
696
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
* Dispose of the isolate and free resources.
|
|
741
|
-
*
|
|
742
|
-
* After disposal, the bridge cannot be used again.
|
|
743
|
-
*/
|
|
744
|
-
async dispose() {
|
|
745
|
-
if (this.disposed) {
|
|
746
|
-
return;
|
|
697
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
698
|
+
if (errorMessage.includes('Script execution timed out')) {
|
|
699
|
+
throw new types_1.TimeoutError(`Expression timed out after ${this.config.timeout}ms`, {});
|
|
747
700
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
this.isolate.dispose();
|
|
701
|
+
if (errorMessage.includes('memory limit')) {
|
|
702
|
+
throw new types_1.MemoryLimitError(`Expression exceeded memory limit of ${this.config.memoryLimit}MB`, {});
|
|
751
703
|
}
|
|
752
|
-
|
|
753
|
-
this.initialized = false;
|
|
754
|
-
this.logger.debug('[IsolatedVmBridge] Disposed');
|
|
704
|
+
throw new Error(`Expression evaluation failed: ${errorMessage}`);
|
|
755
705
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
*/
|
|
761
|
-
isDisposed() {
|
|
762
|
-
return this.disposed || this.isolate.isDisposed;
|
|
706
|
+
finally {
|
|
707
|
+
getValueAtPath.release();
|
|
708
|
+
getArrayElement.release();
|
|
709
|
+
callHost.release();
|
|
763
710
|
}
|
|
764
711
|
}
|
|
765
|
-
|
|
766
|
-
|
|
712
|
+
/**
|
|
713
|
+
* Reconstruct an error from serialized isolate data.
|
|
714
|
+
*
|
|
715
|
+
* Maps error names back to their host-side classes and restores
|
|
716
|
+
* custom properties that would otherwise be lost crossing the boundary.
|
|
717
|
+
*/
|
|
718
|
+
reconstructError(data) {
|
|
719
|
+
const error = new Error(data.message);
|
|
720
|
+
error.name = data.name || 'Error';
|
|
721
|
+
if (data.stack) {
|
|
722
|
+
error.stack = data.stack;
|
|
723
|
+
}
|
|
724
|
+
// Restore custom properties transferred via copy: true
|
|
725
|
+
if (data.extra) {
|
|
726
|
+
Object.assign(error, data.extra);
|
|
727
|
+
}
|
|
728
|
+
return error;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Dispose of the isolate and free resources.
|
|
732
|
+
*
|
|
733
|
+
* After disposal, the bridge cannot be used again.
|
|
734
|
+
*/
|
|
735
|
+
async dispose() {
|
|
736
|
+
if (this.disposed) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
// Dispose isolate (this also disposes all contexts, references, etc.)
|
|
740
|
+
if (!this.isolate.isDisposed) {
|
|
741
|
+
this.isolate.dispose();
|
|
742
|
+
}
|
|
743
|
+
this.disposed = true;
|
|
744
|
+
this.initialized = false;
|
|
745
|
+
this.logger.debug('[IsolatedVmBridge] Disposed');
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Check if the bridge has been disposed.
|
|
749
|
+
*
|
|
750
|
+
* @returns true if disposed, false otherwise
|
|
751
|
+
*/
|
|
752
|
+
isDisposed() {
|
|
753
|
+
return this.disposed || this.isolate.isDisposed;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
exports.IsolatedVmBridge = IsolatedVmBridge;
|
|
767
757
|
//# sourceMappingURL=isolated-vm-bridge.js.map
|