@usebruno/js 0.49.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebruno/js",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -14,7 +14,7 @@
14
14
  "prepack": "npm run test"
15
15
  },
16
16
  "dependencies": {
17
- "@usebruno/common": "0.23.0",
17
+ "@usebruno/common": "0.24.0",
18
18
  "@usebruno/query": "0.2.2",
19
19
  "ajv": "^8.12.0",
20
20
  "ajv-formats": "^2.1.1",
@@ -28,7 +28,7 @@
28
28
  "handlebars": "^4.7.9",
29
29
  "json-query": "^2.2.2",
30
30
  "jsonwebtoken": "^9.0.3",
31
- "lodash": "^4.17.21",
31
+ "lodash": "4.18.1",
32
32
  "moment": "^2.29.4",
33
33
  "nanoid": "3.3.8",
34
34
  "node-fetch": "^2.7.0",
package/src/bru.js CHANGED
@@ -1,4 +1,4 @@
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');
@@ -121,8 +121,11 @@ class Bru {
121
121
  createCookieJar,
122
122
  getCookiesForUrl
123
123
  });
124
- // Holds variables that are marked as persistent by scripts
125
- this.persistentEnvVariables = {};
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;
126
129
  // Holds credential IDs to be reset after script execution
127
130
  this.oauth2CredentialsToReset = [];
128
131
  this.runner = {
@@ -240,7 +243,7 @@ class Bru {
240
243
  return this.interpolate(this.envVariables[key]);
241
244
  }
242
245
 
243
- setEnvVar(key, value, options = {}) {
246
+ setEnvVar(key, value) {
244
247
  if (!key) {
245
248
  throw new Error('Creating a env variable without specifying a name is not allowed.');
246
249
  }
@@ -251,24 +254,21 @@ class Bru {
251
254
  );
252
255
  }
253
256
 
254
- // When persist is true, only string values are allowed
255
- if (options?.persist && typeof value !== 'string') {
256
- throw new Error(`Persistent environment variables must be strings. Received ${typeof value} for key "${key}".`);
257
- }
258
-
259
- this.envVariables[key] = value;
260
-
261
- if (options?.persist) {
262
- this.persistentEnvVariables[key] = value;
263
- } else {
264
- if (this.persistentEnvVariables[key]) {
265
- delete this.persistentEnvVariables[key];
266
- }
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;
267
263
  }
268
264
  }
269
265
 
270
266
  deleteEnvVar(key) {
271
- delete this.envVariables[key];
267
+ if (key === '__name__') return;
268
+ if (Object.hasOwn(this.envVariables, key)) {
269
+ delete this.envVariables[key];
270
+ this._envDirty = true;
271
+ }
272
272
  }
273
273
 
274
274
  getAllEnvVars() {
@@ -278,15 +278,15 @@ class Bru {
278
278
  }
279
279
 
280
280
  deleteAllEnvVars() {
281
- const envName = this.envVariables.__name__;
282
- for (let key in this.envVariables) {
283
- if (this.envVariables.hasOwnProperty(key)) {
284
- delete this.envVariables[key];
285
- }
286
- }
287
- if (envName !== undefined) {
288
- 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;
289
288
  }
289
+ if (removed) this._envDirty = true;
290
290
  }
291
291
 
292
292
  hasGlobalEnvVar(key) {
@@ -302,28 +302,31 @@ class Bru {
302
302
  throw new Error('Creating a env variable without specifying a name is not allowed.');
303
303
  }
304
304
 
305
- this.globalEnvironmentVariables[key] = value;
305
+ if (!Object.hasOwn(this.globalEnvironmentVariables, key) || !isEqual(this.globalEnvironmentVariables[key], value)) {
306
+ this.globalEnvironmentVariables[key] = value;
307
+ this._globalEnvDirty = true;
308
+ }
306
309
  }
307
310
 
308
- // TODO: deleteGlobalEnvVar works in the request lifecycle but does not update the UI.
309
- // Re-enable once the UI sync issue is resolved.
310
- // deleteGlobalEnvVar(key) {
311
- // delete this.globalEnvironmentVariables[key];
312
- // }
311
+ deleteGlobalEnvVar(key) {
312
+ if (Object.hasOwn(this.globalEnvironmentVariables, key)) {
313
+ delete this.globalEnvironmentVariables[key];
314
+ this._globalEnvDirty = true;
315
+ }
316
+ }
313
317
 
314
318
  getAllGlobalEnvVars() {
315
319
  return Object.assign({}, this.globalEnvironmentVariables);
316
320
  }
317
321
 
318
- // TODO: deleteAllGlobalEnvVars works in the request lifecycle but does not update the UI.
319
- // Re-enable once the UI sync issue is resolved.
320
- // deleteAllGlobalEnvVars() {
321
- // for (let key in this.globalEnvironmentVariables) {
322
- // if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
323
- // delete this.globalEnvironmentVariables[key];
324
- // }
325
- // }
326
- // }
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
+ }
327
330
 
328
331
  getOauth2CredentialVar(key) {
329
332
  return this.interpolate(this.oauth2CredentialVariables[key]);
@@ -363,7 +366,10 @@ class Bru {
363
366
  );
364
367
  }
365
368
 
366
- this.runtimeVariables[key] = value;
369
+ if (!Object.hasOwn(this.runtimeVariables, key) || !isEqual(this.runtimeVariables[key], value)) {
370
+ this.runtimeVariables[key] = value;
371
+ this._runtimeVarsDirty = true;
372
+ }
367
373
  }
368
374
 
369
375
  getVar(key) {
@@ -378,15 +384,19 @@ class Bru {
378
384
  }
379
385
 
380
386
  deleteVar(key) {
381
- delete this.runtimeVariables[key];
387
+ if (Object.hasOwn(this.runtimeVariables, key)) {
388
+ delete this.runtimeVariables[key];
389
+ this._runtimeVarsDirty = true;
390
+ }
382
391
  }
383
392
 
384
393
  deleteAllVars() {
385
- for (let key in this.runtimeVariables) {
386
- if (this.runtimeVariables.hasOwnProperty(key)) {
387
- delete this.runtimeVariables[key];
388
- }
394
+ const keys = Object.keys(this.runtimeVariables);
395
+ if (!keys.length) return;
396
+ for (const key of keys) {
397
+ delete this.runtimeVariables[key];
389
398
  }
399
+ this._runtimeVarsDirty = true;
390
400
  }
391
401
 
392
402
  getAllVars() {
@@ -397,48 +407,47 @@ class Bru {
397
407
  return this.interpolate(this.collectionVariables[key]);
398
408
  }
399
409
 
400
- // TODO: setCollectionVar works in the request lifecycle but does not update the UI.
401
- // Re-enable once the UI sync issue is resolved.
402
- // setCollectionVar(key, value) {
403
- // if (!key) {
404
- // throw new Error('Creating a variable without specifying a name is not allowed.');
405
- // }
406
- //
407
- // if (variableNameRegex.test(key) === false) {
408
- // throw new Error(
409
- // `Variable name: "${key}" contains invalid characters!`
410
- // + ' Names must only contain alpha-numeric characters, "-", "_", "."'
411
- // );
412
- // }
413
- //
414
- // this.collectionVariables[key] = value;
415
- // }
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
+ }
416
427
 
417
428
  hasCollectionVar(key) {
418
429
  return Object.hasOwn(this.collectionVariables, key);
419
430
  }
420
431
 
421
- // TODO: deleteCollectionVar works in the request lifecycle but does not update the UI.
422
- // Re-enable once the UI sync issue is resolved.
423
- // deleteCollectionVar(key) {
424
- // delete this.collectionVariables[key];
425
- // }
426
-
427
- // TODO: deleteAllCollectionVars works in the request lifecycle but does not update the UI.
428
- // Re-enable once the UI sync issue is resolved.
429
- // deleteAllCollectionVars() {
430
- // for (let key in this.collectionVariables) {
431
- // if (this.collectionVariables.hasOwnProperty(key)) {
432
- // delete this.collectionVariables[key];
433
- // }
434
- // }
435
- // }
436
-
437
- // TODO: getAllCollectionVars works in the request lifecycle but does not update the UI.
438
- // Re-enable once the UI sync issue is resolved.
439
- // getAllCollectionVars() {
440
- // return Object.assign({}, this.collectionVariables);
441
- // }
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
+ }
442
451
 
443
452
  getFolderVar(key) {
444
453
  return this.interpolate(this.folderVariables[key]);
@@ -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 (pathParam && pathParam.value) {
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
  }
@@ -106,11 +106,11 @@ class ScriptRuntime {
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,
@@ -259,11 +259,11 @@ class ScriptRuntime {
259
259
  // Extracted to avoid duplication across runtime branches
260
260
  const buildResponseScriptResult = () => ({
261
261
  response,
262
- envVariables: cleanJson(envVariables),
263
- persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
264
- runtimeVariables: cleanJson(runtimeVariables),
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,
@@ -69,9 +69,10 @@ class TestRuntime {
69
69
  if (!testsFile || !testsFile.length) {
70
70
  return {
71
71
  request,
72
- envVariables,
73
- runtimeVariables,
74
- globalEnvironmentVariables,
72
+ envVariables: null,
73
+ runtimeVariables: null,
74
+ collectionVariables: null,
75
+ globalEnvironmentVariables: null,
75
76
  results: __brunoTestResults.getResults(),
76
77
  nextRequestName: bru.nextRequest
77
78
  };
@@ -141,10 +142,10 @@ class TestRuntime {
141
142
 
142
143
  const result = {
143
144
  request,
144
- envVariables: cleanJson(envVariables),
145
- runtimeVariables: cleanJson(runtimeVariables),
146
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
147
- persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
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
151
  nextRequestName: bru.nextRequest,
@@ -92,10 +92,10 @@ class VarsRuntime {
92
92
  }
93
93
 
94
94
  return {
95
- envVariables,
96
- runtimeVariables,
97
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
98
- persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
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
  }
@@ -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, options = {}) {
59
- bru.setEnvVar(vm.dump(key), vm.dump(value), vm.dump(options));
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
- // TODO: deleteGlobalEnvVar works in the request lifecycle but does not update the UI.
107
- // Re-enable once the UI sync issue is resolved.
108
- // let deleteGlobalEnvVar = vm.newFunction('deleteGlobalEnvVar', function (key) {
109
- // bru.deleteGlobalEnvVar(vm.dump(key));
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);
@@ -123,13 +121,11 @@ const addBruShimToContext = (vm, bru) => {
123
121
  vm.setProp(bruObject, 'hasGlobalEnvVar', hasGlobalEnvVar);
124
122
  hasGlobalEnvVar.dispose();
125
123
 
126
- // TODO: deleteAllGlobalEnvVars works in the request lifecycle but does not update the UI.
127
- // Re-enable once the UI sync issue is resolved.
128
- // let deleteAllGlobalEnvVars = vm.newFunction('deleteAllGlobalEnvVars', function () {
129
- // bru.deleteAllGlobalEnvVars();
130
- // });
131
- // vm.setProp(bruObject, 'deleteAllGlobalEnvVars', deleteAllGlobalEnvVars);
132
- // deleteAllGlobalEnvVars.dispose();
124
+ let deleteAllGlobalEnvVars = vm.newFunction('deleteAllGlobalEnvVars', function () {
125
+ bru.deleteAllGlobalEnvVars();
126
+ });
127
+ vm.setProp(bruObject, 'deleteAllGlobalEnvVars', deleteAllGlobalEnvVars);
128
+ deleteAllGlobalEnvVars.dispose();
133
129
 
134
130
  let hasVar = vm.newFunction('hasVar', function (key) {
135
131
  return marshallToVm(bru.hasVar(vm.dump(key)), vm);
@@ -283,13 +279,11 @@ const addBruShimToContext = (vm, bru) => {
283
279
  vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
284
280
  getCollectionVar.dispose();
285
281
 
286
- // TODO: setCollectionVar works in the request lifecycle but does not update the UI.
287
- // Re-enable once the UI sync issue is resolved.
288
- // let setCollectionVar = vm.newFunction('setCollectionVar', function (key, value) {
289
- // bru.setCollectionVar(vm.dump(key), vm.dump(value));
290
- // });
291
- // vm.setProp(bruObject, 'setCollectionVar', setCollectionVar);
292
- // 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();
293
287
 
294
288
  let hasCollectionVar = vm.newFunction('hasCollectionVar', function (key) {
295
289
  return marshallToVm(bru.hasCollectionVar(vm.dump(key)), vm);
@@ -297,29 +291,23 @@ const addBruShimToContext = (vm, bru) => {
297
291
  vm.setProp(bruObject, 'hasCollectionVar', hasCollectionVar);
298
292
  hasCollectionVar.dispose();
299
293
 
300
- // TODO: deleteCollectionVar works in the request lifecycle but does not update the UI.
301
- // Re-enable once the UI sync issue is resolved.
302
- // let deleteCollectionVar = vm.newFunction('deleteCollectionVar', function (key) {
303
- // bru.deleteCollectionVar(vm.dump(key));
304
- // });
305
- // vm.setProp(bruObject, 'deleteCollectionVar', deleteCollectionVar);
306
- // deleteCollectionVar.dispose();
307
-
308
- // TODO: deleteAllCollectionVars works in the request lifecycle but does not update the UI.
309
- // Re-enable once the UI sync issue is resolved.
310
- // let deleteAllCollectionVars = vm.newFunction('deleteAllCollectionVars', function () {
311
- // bru.deleteAllCollectionVars();
312
- // });
313
- // vm.setProp(bruObject, 'deleteAllCollectionVars', deleteAllCollectionVars);
314
- // deleteAllCollectionVars.dispose();
315
-
316
- // TODO: getAllCollectionVars works in the request lifecycle but does not update the UI.
317
- // Re-enable once the UI sync issue is resolved.
318
- // let getAllCollectionVars = vm.newFunction('getAllCollectionVars', function () {
319
- // return marshallToVm(bru.getAllCollectionVars(), vm);
320
- // });
321
- // vm.setProp(bruObject, 'getAllCollectionVars', getAllCollectionVars);
322
- // 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();
323
311
 
324
312
  let getTestResults = vm.newFunction('getTestResults', () => {
325
313
  const promise = vm.newPromise();