@usebruno/js 0.48.0 → 0.50.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/package.json +5 -4
- package/src/bru.js +119 -87
- package/src/bruno-request.js +7 -1
- package/src/runtime/script-runtime.js +19 -18
- package/src/runtime/scripted-entries.js +16 -0
- package/src/runtime/test-runtime.js +14 -12
- package/src/runtime/vars-runtime.js +4 -4
- package/src/sandbox/quickjs/index.js +22 -7
- package/src/sandbox/quickjs/shims/bru.js +50 -46
- package/src/sandbox/quickjs/utils/index.js +131 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebruno/js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
|
|
12
|
+
"test:ci": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --testPathIgnorePatterns test.js",
|
|
12
13
|
"sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js",
|
|
13
14
|
"prepack": "npm run test"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@usebruno/common": "0.
|
|
17
|
+
"@usebruno/common": "0.24.0",
|
|
17
18
|
"@usebruno/query": "0.2.2",
|
|
18
19
|
"ajv": "^8.12.0",
|
|
19
20
|
"ajv-formats": "^2.1.1",
|
|
20
21
|
"atob": "^2.1.2",
|
|
21
|
-
"axios": "1.
|
|
22
|
+
"axios": "1.16.0",
|
|
22
23
|
"btoa": "^1.2.1",
|
|
23
24
|
"chai": "^4.3.7",
|
|
24
25
|
"chai-string": "^1.5.0",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"handlebars": "^4.7.9",
|
|
28
29
|
"json-query": "^2.2.2",
|
|
29
30
|
"jsonwebtoken": "^9.0.3",
|
|
30
|
-
"lodash": "
|
|
31
|
+
"lodash": "4.18.1",
|
|
31
32
|
"moment": "^2.29.4",
|
|
32
33
|
"nanoid": "3.3.8",
|
|
33
34
|
"node-fetch": "^2.7.0",
|
package/src/bru.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const { cloneDeep } = require('lodash');
|
|
1
|
+
const { cloneDeep, isEqual } = require('lodash');
|
|
2
2
|
const { uuid } = require('./utils');
|
|
3
3
|
const xmlFormat = require('xml-formatter');
|
|
4
4
|
const { interpolate: _interpolate } = require('@usebruno/common');
|
|
5
|
-
const {
|
|
5
|
+
const { createSendRequest } = require('@usebruno/requests').scripting;
|
|
6
6
|
const { jar: createCookieJar, getCookiesForUrl } = require('@usebruno/requests').cookies;
|
|
7
7
|
const CookieList = require('./cookie-list');
|
|
8
8
|
const Handlebars = require('handlebars');
|
|
@@ -102,8 +102,17 @@ class Bru {
|
|
|
102
102
|
this.setVisualizations = setVisualizations;
|
|
103
103
|
this.onConsoleLog = onConsoleLog || null;
|
|
104
104
|
this.collectionName = collectionName;
|
|
105
|
-
//
|
|
106
|
-
this.
|
|
105
|
+
// Set by the host-side __bruSetScope global at the top of each segment's IIFE.
|
|
106
|
+
this._currentScope = null;
|
|
107
|
+
this.scriptedRequestEntries = [];
|
|
108
|
+
this.sendRequest = (...args) => {
|
|
109
|
+
const scopeSnapshot = this._currentScope ? { ...this._currentScope } : null;
|
|
110
|
+
const send = createSendRequest(certsAndProxyConfig, {
|
|
111
|
+
onComplete: (entry) =>
|
|
112
|
+
this._recordScriptedRequest({ source: 'sendRequest', scope: scopeSnapshot, ...entry })
|
|
113
|
+
});
|
|
114
|
+
return send(...args);
|
|
115
|
+
};
|
|
107
116
|
this.runtime = runtime;
|
|
108
117
|
this.requestUrl = requestUrl;
|
|
109
118
|
this.cookies = new CookieList({
|
|
@@ -112,8 +121,11 @@ class Bru {
|
|
|
112
121
|
createCookieJar,
|
|
113
122
|
getCookiesForUrl
|
|
114
123
|
});
|
|
115
|
-
//
|
|
116
|
-
this.
|
|
124
|
+
// Dirty flags — set by mutators so runtimes can skip IPC/disk writes for unchanged scopes
|
|
125
|
+
this._envDirty = false;
|
|
126
|
+
this._globalEnvDirty = false;
|
|
127
|
+
this._collVarsDirty = false;
|
|
128
|
+
this._runtimeVarsDirty = false;
|
|
117
129
|
// Holds credential IDs to be reset after script execution
|
|
118
130
|
this.oauth2CredentialsToReset = [];
|
|
119
131
|
this.runner = {
|
|
@@ -205,6 +217,16 @@ class Bru {
|
|
|
205
217
|
return this.collectionPath;
|
|
206
218
|
}
|
|
207
219
|
|
|
220
|
+
_recordScriptedRequest(entry) {
|
|
221
|
+
// Prefer scope passed in by the caller (snapshot at call time). Fall back to
|
|
222
|
+
// _currentScope for callers that don't supply one (e.g. bru.runRequest).
|
|
223
|
+
const { scope: providedScope, ...rest } = entry;
|
|
224
|
+
const scope = providedScope !== undefined
|
|
225
|
+
? providedScope
|
|
226
|
+
: (this._currentScope ? { ...this._currentScope } : null);
|
|
227
|
+
this.scriptedRequestEntries.push({ ...rest, scope });
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
getEnvName() {
|
|
209
231
|
return this.envVariables.__name__;
|
|
210
232
|
}
|
|
@@ -221,7 +243,7 @@ class Bru {
|
|
|
221
243
|
return this.interpolate(this.envVariables[key]);
|
|
222
244
|
}
|
|
223
245
|
|
|
224
|
-
setEnvVar(key, value
|
|
246
|
+
setEnvVar(key, value) {
|
|
225
247
|
if (!key) {
|
|
226
248
|
throw new Error('Creating a env variable without specifying a name is not allowed.');
|
|
227
249
|
}
|
|
@@ -232,24 +254,21 @@ class Bru {
|
|
|
232
254
|
);
|
|
233
255
|
}
|
|
234
256
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (options?.persist) {
|
|
243
|
-
this.persistentEnvVariables[key] = value;
|
|
244
|
-
} else {
|
|
245
|
-
if (this.persistentEnvVariables[key]) {
|
|
246
|
-
delete this.persistentEnvVariables[key];
|
|
247
|
-
}
|
|
257
|
+
// Deep-equal compare so object/array writes that mutate in place
|
|
258
|
+
// (e.g. `const c = bru.getEnvVar('cfg'); c.port = 4000; bru.setEnvVar('cfg', c);`)
|
|
259
|
+
// still flip the dirty flag — strict `!==` returned false for same-reference writes.
|
|
260
|
+
if (!Object.hasOwn(this.envVariables, key) || !isEqual(this.envVariables[key], value)) {
|
|
261
|
+
this.envVariables[key] = value;
|
|
262
|
+
this._envDirty = true;
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
265
|
|
|
251
266
|
deleteEnvVar(key) {
|
|
252
|
-
|
|
267
|
+
if (key === '__name__') return;
|
|
268
|
+
if (Object.hasOwn(this.envVariables, key)) {
|
|
269
|
+
delete this.envVariables[key];
|
|
270
|
+
this._envDirty = true;
|
|
271
|
+
}
|
|
253
272
|
}
|
|
254
273
|
|
|
255
274
|
getAllEnvVars() {
|
|
@@ -259,15 +278,19 @@ class Bru {
|
|
|
259
278
|
}
|
|
260
279
|
|
|
261
280
|
deleteAllEnvVars() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.envVariables.__name__ = envName;
|
|
281
|
+
// Iterate via Object.keys (own enumerable) so a user-set `hasOwnProperty` var
|
|
282
|
+
// can't shadow Object.prototype.hasOwnProperty and crash the loop.
|
|
283
|
+
let removed = false;
|
|
284
|
+
for (const key of Object.keys(this.envVariables)) {
|
|
285
|
+
if (key === '__name__') continue;
|
|
286
|
+
delete this.envVariables[key];
|
|
287
|
+
removed = true;
|
|
270
288
|
}
|
|
289
|
+
if (removed) this._envDirty = true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
hasGlobalEnvVar(key) {
|
|
293
|
+
return Object.hasOwn(this.globalEnvironmentVariables, key);
|
|
271
294
|
}
|
|
272
295
|
|
|
273
296
|
getGlobalEnvVar(key) {
|
|
@@ -279,28 +302,31 @@ class Bru {
|
|
|
279
302
|
throw new Error('Creating a env variable without specifying a name is not allowed.');
|
|
280
303
|
}
|
|
281
304
|
|
|
282
|
-
this.globalEnvironmentVariables[key]
|
|
305
|
+
if (!Object.hasOwn(this.globalEnvironmentVariables, key) || !isEqual(this.globalEnvironmentVariables[key], value)) {
|
|
306
|
+
this.globalEnvironmentVariables[key] = value;
|
|
307
|
+
this._globalEnvDirty = true;
|
|
308
|
+
}
|
|
283
309
|
}
|
|
284
310
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
deleteGlobalEnvVar(key) {
|
|
312
|
+
if (Object.hasOwn(this.globalEnvironmentVariables, key)) {
|
|
313
|
+
delete this.globalEnvironmentVariables[key];
|
|
314
|
+
this._globalEnvDirty = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
290
317
|
|
|
291
318
|
getAllGlobalEnvVars() {
|
|
292
319
|
return Object.assign({}, this.globalEnvironmentVariables);
|
|
293
320
|
}
|
|
294
321
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// }
|
|
322
|
+
deleteAllGlobalEnvVars() {
|
|
323
|
+
const keys = Object.keys(this.globalEnvironmentVariables);
|
|
324
|
+
if (!keys.length) return;
|
|
325
|
+
for (const key of keys) {
|
|
326
|
+
delete this.globalEnvironmentVariables[key];
|
|
327
|
+
}
|
|
328
|
+
this._globalEnvDirty = true;
|
|
329
|
+
}
|
|
304
330
|
|
|
305
331
|
getOauth2CredentialVar(key) {
|
|
306
332
|
return this.interpolate(this.oauth2CredentialVariables[key]);
|
|
@@ -340,7 +366,10 @@ class Bru {
|
|
|
340
366
|
);
|
|
341
367
|
}
|
|
342
368
|
|
|
343
|
-
this.runtimeVariables[key]
|
|
369
|
+
if (!Object.hasOwn(this.runtimeVariables, key) || !isEqual(this.runtimeVariables[key], value)) {
|
|
370
|
+
this.runtimeVariables[key] = value;
|
|
371
|
+
this._runtimeVarsDirty = true;
|
|
372
|
+
}
|
|
344
373
|
}
|
|
345
374
|
|
|
346
375
|
getVar(key) {
|
|
@@ -355,15 +384,19 @@ class Bru {
|
|
|
355
384
|
}
|
|
356
385
|
|
|
357
386
|
deleteVar(key) {
|
|
358
|
-
|
|
387
|
+
if (Object.hasOwn(this.runtimeVariables, key)) {
|
|
388
|
+
delete this.runtimeVariables[key];
|
|
389
|
+
this._runtimeVarsDirty = true;
|
|
390
|
+
}
|
|
359
391
|
}
|
|
360
392
|
|
|
361
393
|
deleteAllVars() {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
394
|
+
const keys = Object.keys(this.runtimeVariables);
|
|
395
|
+
if (!keys.length) return;
|
|
396
|
+
for (const key of keys) {
|
|
397
|
+
delete this.runtimeVariables[key];
|
|
366
398
|
}
|
|
399
|
+
this._runtimeVarsDirty = true;
|
|
367
400
|
}
|
|
368
401
|
|
|
369
402
|
getAllVars() {
|
|
@@ -374,48 +407,47 @@ class Bru {
|
|
|
374
407
|
return this.interpolate(this.collectionVariables[key]);
|
|
375
408
|
}
|
|
376
409
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
410
|
+
setCollectionVar(key, value) {
|
|
411
|
+
if (!key) {
|
|
412
|
+
throw new Error('Creating a variable without specifying a name is not allowed.');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (variableNameRegex.test(key) === false) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`Variable name: "${key}" contains invalid characters!`
|
|
418
|
+
+ ' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!Object.hasOwn(this.collectionVariables, key) || !isEqual(this.collectionVariables[key], value)) {
|
|
423
|
+
this.collectionVariables[key] = value;
|
|
424
|
+
this._collVarsDirty = true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
393
427
|
|
|
394
428
|
hasCollectionVar(key) {
|
|
395
429
|
return Object.hasOwn(this.collectionVariables, key);
|
|
396
430
|
}
|
|
397
431
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
// return Object.assign({}, this.collectionVariables);
|
|
418
|
-
// }
|
|
432
|
+
deleteCollectionVar(key) {
|
|
433
|
+
if (Object.hasOwn(this.collectionVariables, key)) {
|
|
434
|
+
delete this.collectionVariables[key];
|
|
435
|
+
this._collVarsDirty = true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
deleteAllCollectionVars() {
|
|
440
|
+
const keys = Object.keys(this.collectionVariables);
|
|
441
|
+
if (!keys.length) return;
|
|
442
|
+
for (const key of keys) {
|
|
443
|
+
delete this.collectionVariables[key];
|
|
444
|
+
}
|
|
445
|
+
this._collVarsDirty = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
getAllCollectionVars() {
|
|
449
|
+
return Object.assign({}, this.collectionVariables);
|
|
450
|
+
}
|
|
419
451
|
|
|
420
452
|
getFolderVar(key) {
|
|
421
453
|
return this.interpolate(this.folderVariables[key]);
|
package/src/bruno-request.js
CHANGED
|
@@ -70,7 +70,13 @@ class BrunoRequest {
|
|
|
70
70
|
if (segment.startsWith(':')) {
|
|
71
71
|
const paramName = segment.slice(1);
|
|
72
72
|
const pathParam = this.req.pathParams.find((param) => param.name === paramName);
|
|
73
|
-
if (
|
|
73
|
+
if (
|
|
74
|
+
pathParam
|
|
75
|
+
&& pathParam.enabled !== false
|
|
76
|
+
&& pathParam.value !== null
|
|
77
|
+
&& pathParam.value !== undefined
|
|
78
|
+
&& (typeof pathParam.value !== 'string' || pathParam.value.trim() !== '')
|
|
79
|
+
) {
|
|
74
80
|
return pathParam.value;
|
|
75
81
|
}
|
|
76
82
|
}
|
|
@@ -8,6 +8,7 @@ const { createBruTestResultMethods } = require('../utils/results');
|
|
|
8
8
|
const { runScriptInNodeVm } = require('../sandbox/node-vm');
|
|
9
9
|
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
|
10
10
|
const { SANDBOX } = require('../utils/sandbox');
|
|
11
|
+
const { bindRunRequest, createScopeSetter } = require('./scripted-entries');
|
|
11
12
|
|
|
12
13
|
class ScriptRuntime {
|
|
13
14
|
constructor(props) {
|
|
@@ -80,7 +81,8 @@ class ScriptRuntime {
|
|
|
80
81
|
test,
|
|
81
82
|
expect: chai.expect,
|
|
82
83
|
assert: chai.assert,
|
|
83
|
-
__brunoTestResults: __brunoTestResults
|
|
84
|
+
__brunoTestResults: __brunoTestResults,
|
|
85
|
+
__bruSetScope: createScopeSetter(bru)
|
|
84
86
|
};
|
|
85
87
|
|
|
86
88
|
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
|
@@ -98,24 +100,23 @@ class ScriptRuntime {
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
context.bru.runRequest = runRequestByItemPathname;
|
|
103
|
-
}
|
|
103
|
+
bindRunRequest(bru, runRequestByItemPathname);
|
|
104
104
|
|
|
105
105
|
// Helper to build the result object for pre-request scripts
|
|
106
106
|
// Extracted to avoid duplication across runtime branches
|
|
107
107
|
const buildRequestScriptResult = () => ({
|
|
108
108
|
request,
|
|
109
|
-
envVariables: cleanJson(envVariables),
|
|
110
|
-
runtimeVariables: cleanJson(runtimeVariables),
|
|
109
|
+
envVariables: bru._envDirty ? cleanJson(envVariables) : null,
|
|
110
|
+
runtimeVariables: bru._runtimeVarsDirty ? cleanJson(runtimeVariables) : null,
|
|
111
|
+
collectionVariables: bru._collVarsDirty ? cleanJson(collectionVariables) : null,
|
|
112
|
+
globalEnvironmentVariables: bru._globalEnvDirty ? cleanJson(globalEnvironmentVariables) : null,
|
|
111
113
|
visualizations,
|
|
112
|
-
persistentEnvVariables: bru.persistentEnvVariables,
|
|
113
|
-
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
|
114
114
|
oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
|
|
115
115
|
results: cleanJson(__brunoTestResults.getResults()),
|
|
116
116
|
nextRequestName: bru.nextRequest,
|
|
117
117
|
skipRequest: bru.skipRequest,
|
|
118
|
-
stopExecution: bru.stopExecution
|
|
118
|
+
stopExecution: bru.stopExecution,
|
|
119
|
+
scriptedRequestEntries: cleanJson(bru.scriptedRequestEntries || [])
|
|
119
120
|
});
|
|
120
121
|
|
|
121
122
|
// Track script errors to attach partial results before re-throwing
|
|
@@ -233,7 +234,8 @@ class ScriptRuntime {
|
|
|
233
234
|
test,
|
|
234
235
|
expect: chai.expect,
|
|
235
236
|
assert: chai.assert,
|
|
236
|
-
__brunoTestResults: __brunoTestResults
|
|
237
|
+
__brunoTestResults: __brunoTestResults,
|
|
238
|
+
__bruSetScope: createScopeSetter(bru)
|
|
237
239
|
};
|
|
238
240
|
|
|
239
241
|
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
|
@@ -251,24 +253,23 @@ class ScriptRuntime {
|
|
|
251
253
|
};
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
context.bru.runRequest = runRequestByItemPathname;
|
|
256
|
-
}
|
|
256
|
+
bindRunRequest(bru, runRequestByItemPathname);
|
|
257
257
|
|
|
258
258
|
// Helper to build the result object for post-response scripts
|
|
259
259
|
// Extracted to avoid duplication across runtime branches
|
|
260
260
|
const buildResponseScriptResult = () => ({
|
|
261
261
|
response,
|
|
262
|
-
envVariables: cleanJson(envVariables),
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
envVariables: bru._envDirty ? cleanJson(envVariables) : null,
|
|
263
|
+
runtimeVariables: bru._runtimeVarsDirty ? cleanJson(runtimeVariables) : null,
|
|
264
|
+
collectionVariables: bru._collVarsDirty ? cleanJson(collectionVariables) : null,
|
|
265
|
+
globalEnvironmentVariables: bru._globalEnvDirty ? cleanJson(globalEnvironmentVariables) : null,
|
|
265
266
|
visualizations,
|
|
266
|
-
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
|
267
267
|
oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
|
|
268
268
|
results: cleanJson(__brunoTestResults.getResults()),
|
|
269
269
|
nextRequestName: bru.nextRequest,
|
|
270
270
|
skipRequest: bru.skipRequest,
|
|
271
|
-
stopExecution: bru.stopExecution
|
|
271
|
+
stopExecution: bru.stopExecution,
|
|
272
|
+
scriptedRequestEntries: cleanJson(bru.scriptedRequestEntries || [])
|
|
272
273
|
});
|
|
273
274
|
|
|
274
275
|
// Track script errors to attach partial results before re-throwing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Forwards the caller's bru as a second arg so the host can attribute the call.
|
|
2
|
+
const bindRunRequest = (bru, runRequestByItemPathname) => {
|
|
3
|
+
if (!runRequestByItemPathname) return;
|
|
4
|
+
bru.runRequest = (relativePathname) =>
|
|
5
|
+
runRequestByItemPathname(relativePathname, bru);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Kept off bru to stay out of user-facing autocomplete.
|
|
9
|
+
const createScopeSetter = (bru) => (scope) => {
|
|
10
|
+
bru._currentScope = scope || null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
bindRunRequest,
|
|
15
|
+
createScopeSetter
|
|
16
|
+
};
|
|
@@ -9,6 +9,7 @@ const { runScriptInNodeVm } = require('../sandbox/node-vm');
|
|
|
9
9
|
const jsonwebtoken = require('jsonwebtoken');
|
|
10
10
|
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
|
11
11
|
const { SANDBOX } = require('../utils/sandbox');
|
|
12
|
+
const { bindRunRequest, createScopeSetter } = require('./scripted-entries');
|
|
12
13
|
|
|
13
14
|
class TestRuntime {
|
|
14
15
|
constructor(props) {
|
|
@@ -68,9 +69,10 @@ class TestRuntime {
|
|
|
68
69
|
if (!testsFile || !testsFile.length) {
|
|
69
70
|
return {
|
|
70
71
|
request,
|
|
71
|
-
envVariables,
|
|
72
|
-
runtimeVariables,
|
|
73
|
-
|
|
72
|
+
envVariables: null,
|
|
73
|
+
runtimeVariables: null,
|
|
74
|
+
collectionVariables: null,
|
|
75
|
+
globalEnvironmentVariables: null,
|
|
74
76
|
results: __brunoTestResults.getResults(),
|
|
75
77
|
nextRequestName: bru.nextRequest
|
|
76
78
|
};
|
|
@@ -84,7 +86,8 @@ class TestRuntime {
|
|
|
84
86
|
expect: chai.expect,
|
|
85
87
|
assert: chai.assert,
|
|
86
88
|
__brunoTestResults: __brunoTestResults,
|
|
87
|
-
jwt: jsonwebtoken
|
|
89
|
+
jwt: jsonwebtoken,
|
|
90
|
+
__bruSetScope: createScopeSetter(bru)
|
|
88
91
|
};
|
|
89
92
|
|
|
90
93
|
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
|
@@ -102,9 +105,7 @@ class TestRuntime {
|
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
context.bru.runRequest = runRequestByItemPathname;
|
|
107
|
-
}
|
|
108
|
+
bindRunRequest(bru, runRequestByItemPathname);
|
|
108
109
|
|
|
109
110
|
let scriptError = null;
|
|
110
111
|
|
|
@@ -141,13 +142,14 @@ class TestRuntime {
|
|
|
141
142
|
|
|
142
143
|
const result = {
|
|
143
144
|
request,
|
|
144
|
-
envVariables: cleanJson(envVariables),
|
|
145
|
-
runtimeVariables: cleanJson(runtimeVariables),
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
envVariables: bru._envDirty ? cleanJson(envVariables) : null,
|
|
146
|
+
runtimeVariables: bru._runtimeVarsDirty ? cleanJson(runtimeVariables) : null,
|
|
147
|
+
collectionVariables: bru._collVarsDirty ? cleanJson(collectionVariables) : null,
|
|
148
|
+
globalEnvironmentVariables: bru._globalEnvDirty ? cleanJson(globalEnvironmentVariables) : null,
|
|
148
149
|
oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
|
|
149
150
|
results: cleanJson(__brunoTestResults.getResults()),
|
|
150
|
-
nextRequestName: bru.nextRequest
|
|
151
|
+
nextRequestName: bru.nextRequest,
|
|
152
|
+
scriptedRequestEntries: cleanJson(bru.scriptedRequestEntries || [])
|
|
151
153
|
};
|
|
152
154
|
|
|
153
155
|
if (scriptError) {
|
|
@@ -92,10 +92,10 @@ class VarsRuntime {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
return {
|
|
95
|
-
envVariables,
|
|
96
|
-
runtimeVariables,
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
envVariables: bru._envDirty ? cleanJson(envVariables) : null,
|
|
96
|
+
runtimeVariables: bru._runtimeVarsDirty ? cleanJson(runtimeVariables) : null,
|
|
97
|
+
collectionVariables: bru._collVarsDirty ? cleanJson(collectionVariables) : null,
|
|
98
|
+
globalEnvironmentVariables: bru._globalEnvDirty ? cleanJson(globalEnvironmentVariables) : null,
|
|
99
99
|
error
|
|
100
100
|
};
|
|
101
101
|
}
|
|
@@ -11,7 +11,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip
|
|
|
11
11
|
// execute `npm run sandbox:bundle-libraries` if the below file doesn't exist
|
|
12
12
|
const getBundledCode = require('../bundle-browser-rollup');
|
|
13
13
|
const addPathShimToContext = require('./shims/lib/path');
|
|
14
|
-
const { marshallToVm } = require('./utils');
|
|
14
|
+
const { marshallToVm, createManagedQuickJsContext } = require('./utils');
|
|
15
15
|
const addCryptoUtilsShimToContext = require('./shims/lib/crypto-utils');
|
|
16
16
|
const { wrapScriptInClosure, SANDBOX } = require('../../utils/sandbox');
|
|
17
17
|
|
|
@@ -56,9 +56,10 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
|
|
|
56
56
|
|
|
57
57
|
externalScript = removeQuotes(externalScript);
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
let managedQuickJsContext;
|
|
60
60
|
try {
|
|
61
|
-
|
|
61
|
+
managedQuickJsContext = createManagedQuickJsContext(QuickJSModule);
|
|
62
|
+
const vm = managedQuickJsContext.vm;
|
|
62
63
|
const { bru, req, res, ...variables } = externalContext;
|
|
63
64
|
|
|
64
65
|
bru && addBruShimToContext(vm, bru);
|
|
@@ -74,7 +75,7 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
|
|
|
74
75
|
|
|
75
76
|
let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;
|
|
76
77
|
|
|
77
|
-
const result = vm.
|
|
78
|
+
const result = vm.evalCodeRetained(scriptText);
|
|
78
79
|
if (result.error) {
|
|
79
80
|
let e = vm.dump(result.error);
|
|
80
81
|
result.error.dispose();
|
|
@@ -86,6 +87,8 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
|
|
|
86
87
|
}
|
|
87
88
|
} catch (error) {
|
|
88
89
|
console.error('Error executing the script!', error);
|
|
90
|
+
} finally {
|
|
91
|
+
managedQuickJsContext?.dispose();
|
|
89
92
|
}
|
|
90
93
|
};
|
|
91
94
|
|
|
@@ -95,9 +98,11 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
|
|
|
95
98
|
}
|
|
96
99
|
externalScript = externalScript?.trim();
|
|
97
100
|
|
|
101
|
+
let managedQuickJsContext;
|
|
98
102
|
try {
|
|
99
103
|
const module = await loader();
|
|
100
|
-
|
|
104
|
+
managedQuickJsContext = createManagedQuickJsContext(module);
|
|
105
|
+
const vm = managedQuickJsContext.vm;
|
|
101
106
|
|
|
102
107
|
// add crypto utilities required by the crypto-js library in bundledCode
|
|
103
108
|
await addCryptoUtilsShimToContext(vm);
|
|
@@ -126,17 +131,27 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
|
|
|
126
131
|
|
|
127
132
|
const script = wrapScriptInClosure(externalScript, SANDBOX.QUICKJS);
|
|
128
133
|
|
|
129
|
-
const result = vm.
|
|
134
|
+
const result = vm.evalCodeRetained(script, scriptPath);
|
|
130
135
|
const promiseHandle = vm.unwrapResult(result);
|
|
131
136
|
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
|
132
137
|
promiseHandle.dispose();
|
|
133
138
|
const resolvedHandle = vm.unwrapResult(resolvedResult);
|
|
134
139
|
resolvedHandle.dispose();
|
|
135
|
-
// vm.dispose();
|
|
136
140
|
return;
|
|
137
141
|
} catch (error) {
|
|
138
142
|
error.__isQuickJS = true;
|
|
139
143
|
throw error;
|
|
144
|
+
} finally {
|
|
145
|
+
// Wait for any in-flight async work (sendRequest, axios, cookie jar, timers,
|
|
146
|
+
// un-awaited promises) to settle before tearing down the VM. Disposing while
|
|
147
|
+
// a deferred is still pending lets its later host callback touch a freed
|
|
148
|
+
// context, throwing `QuickJSUseAfterFree`.
|
|
149
|
+
try {
|
|
150
|
+
await managedQuickJsContext?.waitForPendingDeferreds?.();
|
|
151
|
+
managedQuickJsContext?.dispose();
|
|
152
|
+
} catch (teardownError) {
|
|
153
|
+
throw teardownError;
|
|
154
|
+
}
|
|
140
155
|
}
|
|
141
156
|
};
|
|
142
157
|
|
|
@@ -55,8 +55,8 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
55
55
|
vm.setProp(bruObject, 'getEnvVar', getEnvVar);
|
|
56
56
|
getEnvVar.dispose();
|
|
57
57
|
|
|
58
|
-
let setEnvVar = vm.newFunction('setEnvVar', function (key, value
|
|
59
|
-
bru.setEnvVar(vm.dump(key), vm.dump(value)
|
|
58
|
+
let setEnvVar = vm.newFunction('setEnvVar', function (key, value) {
|
|
59
|
+
bru.setEnvVar(vm.dump(key), vm.dump(value));
|
|
60
60
|
});
|
|
61
61
|
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
|
62
62
|
setEnvVar.dispose();
|
|
@@ -103,13 +103,11 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
103
103
|
vm.setProp(bruObject, 'setGlobalEnvVar', setGlobalEnvVar);
|
|
104
104
|
setGlobalEnvVar.dispose();
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// vm.setProp(bruObject, 'deleteGlobalEnvVar', deleteGlobalEnvVar);
|
|
112
|
-
// deleteGlobalEnvVar.dispose();
|
|
106
|
+
let deleteGlobalEnvVar = vm.newFunction('deleteGlobalEnvVar', function (key) {
|
|
107
|
+
bru.deleteGlobalEnvVar(vm.dump(key));
|
|
108
|
+
});
|
|
109
|
+
vm.setProp(bruObject, 'deleteGlobalEnvVar', deleteGlobalEnvVar);
|
|
110
|
+
deleteGlobalEnvVar.dispose();
|
|
113
111
|
|
|
114
112
|
let getAllGlobalEnvVars = vm.newFunction('getAllGlobalEnvVars', function () {
|
|
115
113
|
return marshallToVm(bru.getAllGlobalEnvVars(), vm);
|
|
@@ -117,13 +115,17 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
117
115
|
vm.setProp(bruObject, 'getAllGlobalEnvVars', getAllGlobalEnvVars);
|
|
118
116
|
getAllGlobalEnvVars.dispose();
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
let hasGlobalEnvVar = vm.newFunction('hasGlobalEnvVar', function (key) {
|
|
119
|
+
return marshallToVm(bru.hasGlobalEnvVar(vm.dump(key)), vm);
|
|
120
|
+
});
|
|
121
|
+
vm.setProp(bruObject, 'hasGlobalEnvVar', hasGlobalEnvVar);
|
|
122
|
+
hasGlobalEnvVar.dispose();
|
|
123
|
+
|
|
124
|
+
let deleteAllGlobalEnvVars = vm.newFunction('deleteAllGlobalEnvVars', function () {
|
|
125
|
+
bru.deleteAllGlobalEnvVars();
|
|
126
|
+
});
|
|
127
|
+
vm.setProp(bruObject, 'deleteAllGlobalEnvVars', deleteAllGlobalEnvVars);
|
|
128
|
+
deleteAllGlobalEnvVars.dispose();
|
|
127
129
|
|
|
128
130
|
let hasVar = vm.newFunction('hasVar', function (key) {
|
|
129
131
|
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
|
|
@@ -277,13 +279,11 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
277
279
|
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
|
|
278
280
|
getCollectionVar.dispose();
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// vm.setProp(bruObject, 'setCollectionVar', setCollectionVar);
|
|
286
|
-
// setCollectionVar.dispose();
|
|
282
|
+
let setCollectionVar = vm.newFunction('setCollectionVar', function (key, value) {
|
|
283
|
+
bru.setCollectionVar(vm.dump(key), vm.dump(value));
|
|
284
|
+
});
|
|
285
|
+
vm.setProp(bruObject, 'setCollectionVar', setCollectionVar);
|
|
286
|
+
setCollectionVar.dispose();
|
|
287
287
|
|
|
288
288
|
let hasCollectionVar = vm.newFunction('hasCollectionVar', function (key) {
|
|
289
289
|
return marshallToVm(bru.hasCollectionVar(vm.dump(key)), vm);
|
|
@@ -291,29 +291,23 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
291
291
|
vm.setProp(bruObject, 'hasCollectionVar', hasCollectionVar);
|
|
292
292
|
hasCollectionVar.dispose();
|
|
293
293
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Re-enable once the UI sync issue is resolved.
|
|
312
|
-
// let getAllCollectionVars = vm.newFunction('getAllCollectionVars', function () {
|
|
313
|
-
// return marshallToVm(bru.getAllCollectionVars(), vm);
|
|
314
|
-
// });
|
|
315
|
-
// vm.setProp(bruObject, 'getAllCollectionVars', getAllCollectionVars);
|
|
316
|
-
// getAllCollectionVars.dispose();
|
|
294
|
+
let deleteCollectionVar = vm.newFunction('deleteCollectionVar', function (key) {
|
|
295
|
+
bru.deleteCollectionVar(vm.dump(key));
|
|
296
|
+
});
|
|
297
|
+
vm.setProp(bruObject, 'deleteCollectionVar', deleteCollectionVar);
|
|
298
|
+
deleteCollectionVar.dispose();
|
|
299
|
+
|
|
300
|
+
let deleteAllCollectionVars = vm.newFunction('deleteAllCollectionVars', function () {
|
|
301
|
+
bru.deleteAllCollectionVars();
|
|
302
|
+
});
|
|
303
|
+
vm.setProp(bruObject, 'deleteAllCollectionVars', deleteAllCollectionVars);
|
|
304
|
+
deleteAllCollectionVars.dispose();
|
|
305
|
+
|
|
306
|
+
let getAllCollectionVars = vm.newFunction('getAllCollectionVars', function () {
|
|
307
|
+
return marshallToVm(bru.getAllCollectionVars(), vm);
|
|
308
|
+
});
|
|
309
|
+
vm.setProp(bruObject, 'getAllCollectionVars', getAllCollectionVars);
|
|
310
|
+
getAllCollectionVars.dispose();
|
|
317
311
|
|
|
318
312
|
let getTestResults = vm.newFunction('getTestResults', () => {
|
|
319
313
|
const promise = vm.newPromise();
|
|
@@ -401,10 +395,20 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
401
395
|
});
|
|
402
396
|
sendRequestHandle.consume((handle) => vm.setProp(bruObject, '_sendRequest', handle));
|
|
403
397
|
|
|
398
|
+
// On vm.global, not bru, to stay off user-facing autocomplete.
|
|
399
|
+
let setScopeHandle = vm.newFunction('__bruSetScope', (scopeArg) => {
|
|
400
|
+
bru._currentScope = vm.dump(scopeArg) || null;
|
|
401
|
+
});
|
|
402
|
+
setScopeHandle.consume((handle) => vm.setProp(vm.global, '__bruSetScope', handle));
|
|
403
|
+
|
|
404
404
|
const sleep = vm.newFunction('sleep', (timer) => {
|
|
405
405
|
const t = vm.getString(timer);
|
|
406
406
|
const promise = vm.newPromise();
|
|
407
407
|
setTimeout(() => {
|
|
408
|
+
// The VM may have been disposed while this native timer was pending
|
|
409
|
+
// (e.g. a setTimeout/sleep whose promise the script never awaited).
|
|
410
|
+
// Touching the VM after teardown throws QuickJSUseAfterFree, so bail out.
|
|
411
|
+
if (!vm.alive) return;
|
|
408
412
|
promise.resolve(vm.newString('slept'));
|
|
409
413
|
}, t);
|
|
410
414
|
promise.settled.then(vm.runtime.executePendingJobs);
|
|
@@ -1,3 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a QuickJS context with centralized lifecycle management:
|
|
3
|
+
* - vm.evalCode() auto-disposes result handles (for shim setup code)
|
|
4
|
+
* - vm.evalCodeRetained() returns the raw result (for user script execution)
|
|
5
|
+
* - all newObject/newFunction/newArray handles are tracked and disposed on teardown
|
|
6
|
+
*/
|
|
7
|
+
const createManagedQuickJsContext = (module) => {
|
|
8
|
+
const vm = module.newContext();
|
|
9
|
+
const disposeTracked = trackQuickJsContext(vm);
|
|
10
|
+
const evalCodeRetained = vm.evalCode.bind(vm);
|
|
11
|
+
const waitForPendingDeferreds = trackPendingDeferreds(vm);
|
|
12
|
+
|
|
13
|
+
vm.evalCode = (code, filename = 'eval.js') => {
|
|
14
|
+
const result = evalCodeRetained(code, filename);
|
|
15
|
+
if (result.error) {
|
|
16
|
+
const error = vm.dump(result.error);
|
|
17
|
+
result.error.dispose();
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
result.value.dispose();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
vm.evalCodeRetained = evalCodeRetained;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
vm,
|
|
27
|
+
waitForPendingDeferreds,
|
|
28
|
+
dispose: () => disposeQuickJsContext(vm, disposeTracked)
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Track every deferred created by the async shims (sendRequest, axios, cookie
|
|
34
|
+
* jar, sleep, ...) so teardown can wait for them to settle. A user script that
|
|
35
|
+
* fires-and-forgets async work (e.g. an un-awaited setTimeout) resolves the
|
|
36
|
+
* wrapping closure immediately; without this, the VM is disposed before the
|
|
37
|
+
* deferred's host callback runs, and touching the freed context throws
|
|
38
|
+
* `QuickJSUseAfterFree`. Each `.settled` resolves once the deferred is
|
|
39
|
+
* resolved/rejected, so awaiting them keeps the context alive long enough.
|
|
40
|
+
*
|
|
41
|
+
* The hook is installed now (at context creation) so it captures promises as
|
|
42
|
+
* the script runs. Returns a function that drains the captured deferreds at
|
|
43
|
+
* teardown; new deferreds can be created while we wait (a chained timer), so it
|
|
44
|
+
* drains in place until none remain.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const trackPendingDeferreds = (vm) => {
|
|
48
|
+
const pendingDeferreds = [];
|
|
49
|
+
const originalNewPromise = vm.newPromise.bind(vm);
|
|
50
|
+
vm.newPromise = (...args) => {
|
|
51
|
+
const deferred = originalNewPromise(...args);
|
|
52
|
+
pendingDeferreds.push(deferred.settled.catch(() => { }));
|
|
53
|
+
return deferred;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return async () => {
|
|
57
|
+
while (pendingDeferreds.length) {
|
|
58
|
+
const batch = pendingDeferreds.splice(0);
|
|
59
|
+
await Promise.all(batch);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Tracks handles created via newObject/newFunction/newArray so they can all be
|
|
66
|
+
* disposed before the context. quickjs-emscripten requires every heap handle to
|
|
67
|
+
* be disposed individually; shims attach then drop their ref via .dispose().
|
|
68
|
+
*/
|
|
69
|
+
const trackQuickJsContext = (vm) => {
|
|
70
|
+
const handles = [];
|
|
71
|
+
|
|
72
|
+
const track = (handle) => {
|
|
73
|
+
handles.push(handle);
|
|
74
|
+
return handle;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Replace an allocator with a wrapper that records every handle it returns,
|
|
78
|
+
// so teardown can dispose them all. Behaviour is otherwise identical.
|
|
79
|
+
const trackAllocations = (method) => {
|
|
80
|
+
const original = vm[method]?.bind(vm);
|
|
81
|
+
if (!original) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
vm[method] = (...args) => track(original(...args));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
['newObject', 'newFunction', 'newArray'].forEach(trackAllocations);
|
|
89
|
+
|
|
90
|
+
// Dispose newest-first: later handles may reference earlier ones.
|
|
91
|
+
return () => {
|
|
92
|
+
for (const handle of handles.reverse()) {
|
|
93
|
+
if (handle?.alive) {
|
|
94
|
+
handle.dispose();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clears shim globals, drains pending QuickJS jobs, and disposes the context.
|
|
102
|
+
* Pass disposeTracked from trackQuickJsContext() to free shim handles first.
|
|
103
|
+
*/
|
|
104
|
+
const disposeQuickJsContext = (vm, disposeTracked) => {
|
|
105
|
+
if (!vm?.alive) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof disposeTracked === 'function') {
|
|
110
|
+
disposeTracked();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Drain the runtime's pending job queue (resolved/rejected promise callbacks)
|
|
114
|
+
// before disposing. Executing a job can schedule more jobs (chained `.then()`s),
|
|
115
|
+
// so we keep going until `hasPendingJob()` reports the queue is empty or a job
|
|
116
|
+
// throws.
|
|
117
|
+
while (vm.runtime?.hasPendingJob?.()) {
|
|
118
|
+
const result = vm.runtime.executePendingJobs();
|
|
119
|
+
// On error, dispose the error handle and stop draining.
|
|
120
|
+
if (result.error) {
|
|
121
|
+
result.error.dispose();
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
vm.dispose();
|
|
126
|
+
};
|
|
127
|
+
|
|
1
128
|
const marshallToVm = (value, vm) => {
|
|
2
129
|
if (value === undefined) {
|
|
3
130
|
return vm.undefined;
|
|
@@ -79,5 +206,8 @@ async function invokeFunction(vm, quickFn, args = []) {
|
|
|
79
206
|
|
|
80
207
|
module.exports = {
|
|
81
208
|
marshallToVm,
|
|
82
|
-
invokeFunction
|
|
209
|
+
invokeFunction,
|
|
210
|
+
createManagedQuickJsContext,
|
|
211
|
+
disposeQuickJsContext,
|
|
212
|
+
trackQuickJsContext
|
|
83
213
|
};
|