@usebruno/js 0.45.1 → 0.46.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.45.1",
3
+ "version": "0.46.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -13,7 +13,7 @@
13
13
  "prepack": "npm run test"
14
14
  },
15
15
  "dependencies": {
16
- "@usebruno/common": "0.18.0",
16
+ "@usebruno/common": "0.20.0",
17
17
  "@usebruno/query": "0.2.0",
18
18
  "ajv": "^8.12.0",
19
19
  "ajv-formats": "^2.1.1",
@@ -35,7 +35,8 @@
35
35
  "tv4": "^1.3.0",
36
36
  "uuid": "^9.0.0",
37
37
  "xml-formatter": "^3.5.0",
38
- "xml2js": "^0.6.2"
38
+ "xml2js": "^0.6.2",
39
+ "yaml": "^2.3.4"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@rollup/plugin-commonjs": "^23.0.2",
package/src/bru.js CHANGED
@@ -84,12 +84,19 @@ class Bru {
84
84
  deleteCookie: (url, cookieName, callback) => {
85
85
  const interpolatedUrl = this.interpolate(url);
86
86
  return cookieJar.deleteCookie(interpolatedUrl, cookieName, callback);
87
+ },
88
+
89
+ hasCookie: (url, cookieName, callback) => {
90
+ const interpolatedUrl = this.interpolate(url);
91
+ return cookieJar.hasCookie(interpolatedUrl, cookieName, callback);
87
92
  }
88
93
  };
89
94
  }
90
95
  };
91
96
  // Holds variables that are marked as persistent by scripts
92
97
  this.persistentEnvVariables = {};
98
+ // Holds credential IDs to be reset after script execution
99
+ this.oauth2CredentialsToReset = [];
93
100
  this.runner = {
94
101
  skipRequest: () => {
95
102
  this.skipRequest = true;
@@ -211,15 +218,6 @@ class Bru {
211
218
  throw new Error(`Persistent environment variables must be strings. Received ${typeof value} for key "${key}".`);
212
219
  }
213
220
 
214
- if (this.historyLogger) {
215
- this.historyLogger({
216
- uid: uuid(),
217
- type: 'setEnvVar()',
218
- data: { key, value },
219
- createdAt: new Date().toISOString()
220
- });
221
- }
222
-
223
221
  this.envVariables[key] = value;
224
222
 
225
223
  if (options?.persist) {
@@ -235,6 +233,24 @@ class Bru {
235
233
  delete this.envVariables[key];
236
234
  }
237
235
 
236
+ getAllEnvVars() {
237
+ const vars = Object.assign({}, this.envVariables);
238
+ delete vars.__name__;
239
+ return vars;
240
+ }
241
+
242
+ deleteAllEnvVars() {
243
+ const envName = this.envVariables.__name__;
244
+ for (let key in this.envVariables) {
245
+ if (this.envVariables.hasOwnProperty(key)) {
246
+ delete this.envVariables[key];
247
+ }
248
+ }
249
+ if (envName !== undefined) {
250
+ this.envVariables.__name__ = envName;
251
+ }
252
+ }
253
+
238
254
  getGlobalEnvVar(key) {
239
255
  return this.interpolate(this.globalEnvironmentVariables[key]);
240
256
  }
@@ -247,10 +263,48 @@ class Bru {
247
263
  this.globalEnvironmentVariables[key] = value;
248
264
  }
249
265
 
266
+ // TODO: deleteGlobalEnvVar works in the request lifecycle but does not update the UI.
267
+ // Re-enable once the UI sync issue is resolved.
268
+ // deleteGlobalEnvVar(key) {
269
+ // delete this.globalEnvironmentVariables[key];
270
+ // }
271
+
272
+ getAllGlobalEnvVars() {
273
+ return Object.assign({}, this.globalEnvironmentVariables);
274
+ }
275
+
276
+ // TODO: deleteAllGlobalEnvVars works in the request lifecycle but does not update the UI.
277
+ // Re-enable once the UI sync issue is resolved.
278
+ // deleteAllGlobalEnvVars() {
279
+ // for (let key in this.globalEnvironmentVariables) {
280
+ // if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
281
+ // delete this.globalEnvironmentVariables[key];
282
+ // }
283
+ // }
284
+ // }
285
+
250
286
  getOauth2CredentialVar(key) {
251
287
  return this.interpolate(this.oauth2CredentialVariables[key]);
252
288
  }
253
289
 
290
+ resetOauth2Credential(credentialId) {
291
+ if (!credentialId || typeof credentialId !== 'string') {
292
+ throw new Error('credentialId must be a non-empty string');
293
+ }
294
+
295
+ if (!this.oauth2CredentialsToReset.includes(credentialId)) {
296
+ this.oauth2CredentialsToReset.push(credentialId);
297
+ }
298
+
299
+ // Remove matching credential variables so subsequent getOauth2CredentialVar() calls return undefined
300
+ const prefix = `$oauth2.${credentialId}.`;
301
+ for (const key of Object.keys(this.oauth2CredentialVariables)) {
302
+ if (key.startsWith(prefix)) {
303
+ delete this.oauth2CredentialVariables[key];
304
+ }
305
+ }
306
+ }
307
+
254
308
  hasVar(key) {
255
309
  return Object.hasOwn(this.runtimeVariables, key);
256
310
  }
@@ -267,15 +321,6 @@ class Bru {
267
321
  );
268
322
  }
269
323
 
270
- if (this.historyLogger) {
271
- this.historyLogger({
272
- uid: uuid(),
273
- type: 'setVar()',
274
- data: { key, value: value },
275
- createdAt: new Date().toISOString()
276
- });
277
- }
278
-
279
324
  this.runtimeVariables[key] = value;
280
325
  }
281
326
 
@@ -302,10 +347,57 @@ class Bru {
302
347
  }
303
348
  }
304
349
 
350
+ getAllVars() {
351
+ return Object.assign({}, this.runtimeVariables);
352
+ }
353
+
305
354
  getCollectionVar(key) {
306
355
  return this.interpolate(this.collectionVariables[key]);
307
356
  }
308
357
 
358
+ // TODO: setCollectionVar works in the request lifecycle but does not update the UI.
359
+ // Re-enable once the UI sync issue is resolved.
360
+ // setCollectionVar(key, value) {
361
+ // if (!key) {
362
+ // throw new Error('Creating a variable without specifying a name is not allowed.');
363
+ // }
364
+ //
365
+ // if (variableNameRegex.test(key) === false) {
366
+ // throw new Error(
367
+ // `Variable name: "${key}" contains invalid characters!`
368
+ // + ' Names must only contain alpha-numeric characters, "-", "_", "."'
369
+ // );
370
+ // }
371
+ //
372
+ // this.collectionVariables[key] = value;
373
+ // }
374
+
375
+ hasCollectionVar(key) {
376
+ return Object.hasOwn(this.collectionVariables, key);
377
+ }
378
+
379
+ // TODO: deleteCollectionVar works in the request lifecycle but does not update the UI.
380
+ // Re-enable once the UI sync issue is resolved.
381
+ // deleteCollectionVar(key) {
382
+ // delete this.collectionVariables[key];
383
+ // }
384
+
385
+ // TODO: deleteAllCollectionVars works in the request lifecycle but does not update the UI.
386
+ // Re-enable once the UI sync issue is resolved.
387
+ // deleteAllCollectionVars() {
388
+ // for (let key in this.collectionVariables) {
389
+ // if (this.collectionVariables.hasOwnProperty(key)) {
390
+ // delete this.collectionVariables[key];
391
+ // }
392
+ // }
393
+ // }
394
+
395
+ // TODO: getAllCollectionVars works in the request lifecycle but does not update the UI.
396
+ // Re-enable once the UI sync issue is resolved.
397
+ // getAllCollectionVars() {
398
+ // return Object.assign({}, this.collectionVariables);
399
+ // }
400
+
309
401
  getFolderVar(key) {
310
402
  return this.interpolate(this.folderVariables[key]);
311
403
  }
@@ -1,5 +1,3 @@
1
- const { uuid } = require('./utils');
2
-
3
1
  class BrunoRequest {
4
2
  /**
5
3
  * The following properties are available as shorthand:
@@ -129,23 +127,36 @@ class BrunoRequest {
129
127
  this.req.headers = headers;
130
128
  }
131
129
 
130
+ deleteHeaders(headers) {
131
+ headers.forEach((name) => this.deleteHeader(name));
132
+ }
133
+
132
134
  getHeader(name) {
133
135
  return this.req.headers[name];
134
136
  }
135
137
 
136
138
  setHeader(name, value) {
137
- if (this.historyLogger) {
138
- this.historyLogger({
139
- uid: uuid(),
140
- type: 'setHeader()',
141
- data: { name, value },
142
- createdAt: new Date().toISOString()
143
- });
144
- }
145
139
  this.headers[name] = value;
146
140
  this.req.headers[name] = value;
147
141
  }
148
142
 
143
+ deleteHeader(name) {
144
+ delete this.headers[name];
145
+ delete this.req.headers[name];
146
+
147
+ /**
148
+ Store header name to be applied in the axios request interceptor.
149
+ Default headers (user-agent, accept, accept-encoding, etc.) are added after
150
+ the pre-request script runs, so we track them here and delete them later.
151
+ */
152
+ if (!this.req.__headersToDelete) {
153
+ this.req.__headersToDelete = [];
154
+ }
155
+ if (!this.req.__headersToDelete.includes(name)) {
156
+ this.req.__headersToDelete.push(name);
157
+ }
158
+ }
159
+
149
160
  hasJSONContentType(headers) {
150
161
  const contentType = headers?.['Content-Type'] || headers?.['content-type'] || '';
151
162
  return contentType.includes('json');
@@ -181,15 +192,6 @@ class BrunoRequest {
181
192
  * If the user wants to override this behavior, they can pass the raw option as true
182
193
  */
183
194
  setBody(data, options = {}) {
184
- if (this.historyLogger) {
185
- this.historyLogger({
186
- uid: uuid(),
187
- type: 'setBody()',
188
- data: { body: data },
189
- createdAt: new Date().toISOString()
190
- });
191
- }
192
-
193
195
  if (options.raw) {
194
196
  this.req.data = data;
195
197
  this.body = data;
package/src/index.js CHANGED
@@ -3,11 +3,14 @@ const TestRuntime = require('./runtime/test-runtime');
3
3
  const VarsRuntime = require('./runtime/vars-runtime');
4
4
  const AssertRuntime = require('./runtime/assert-runtime');
5
5
  const { runScriptInNodeVm } = require('./sandbox/node-vm');
6
+ const { formatErrorWithContext, SCRIPT_TYPES } = require('./utils/error-formatter');
6
7
 
7
8
  module.exports = {
8
9
  ScriptRuntime,
9
10
  TestRuntime,
10
11
  VarsRuntime,
11
12
  AssertRuntime,
12
- runScriptInNodeVm
13
+ runScriptInNodeVm,
14
+ formatErrorWithContext,
15
+ SCRIPT_TYPES
13
16
  };
@@ -6,6 +6,7 @@ const { cleanJson } = require('../utils');
6
6
  const { createBruTestResultMethods } = require('../utils/results');
7
7
  const { runScriptInNodeVm } = require('../sandbox/node-vm');
8
8
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
9
+ const { SANDBOX } = require('../utils/sandbox');
9
10
 
10
11
  class ScriptRuntime {
11
12
  constructor(props) {
@@ -41,6 +42,7 @@ class ScriptRuntime {
41
42
  const iterationDetails = request?.runnerIterationDetails || {};
42
43
  const assertionResults = request?.assertionResults || [];
43
44
  const certsAndProxyConfig = request?.certsAndProxyConfig;
45
+ const scriptPath = request?.pathname;
44
46
  const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables, certsAndProxyConfig);
45
47
  const req = new BrunoRequest(request);
46
48
 
@@ -75,47 +77,68 @@ class ScriptRuntime {
75
77
  context.bru.runRequest = runRequestByItemPathname;
76
78
  }
77
79
 
78
- if (this.runtime === 'nodevm') {
79
- await runScriptInNodeVm({
80
- script,
81
- context,
82
- collectionPath,
83
- scriptingConfig
84
- });
85
-
86
- return {
87
- request,
88
- envVariables: cleanJson(envVariables),
89
- runtimeVariables: cleanJson(runtimeVariables),
90
- visualizations,
91
- persistentEnvVariables: bru.persistentEnvVariables,
92
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
93
- results: cleanJson(__brunoTestResults.getResults()),
94
- nextRequestName: bru.nextRequest,
95
- skipRequest: bru.skipRequest,
96
- stopExecution: bru.stopExecution
97
- };
98
- }
99
-
100
- // default runtime is `quickjs`
101
- await executeQuickJsVmAsync({
102
- script: script,
103
- context: context,
104
- collectionPath
105
- });
106
-
107
- return {
80
+ // Helper to build the result object for pre-request scripts
81
+ // Extracted to avoid duplication across runtime branches
82
+ const buildRequestScriptResult = () => ({
108
83
  request,
109
84
  envVariables: cleanJson(envVariables),
110
85
  runtimeVariables: cleanJson(runtimeVariables),
111
86
  visualizations,
112
87
  persistentEnvVariables: bru.persistentEnvVariables,
113
88
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
89
+ oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
114
90
  results: cleanJson(__brunoTestResults.getResults()),
115
91
  nextRequestName: bru.nextRequest,
116
92
  skipRequest: bru.skipRequest,
117
93
  stopExecution: bru.stopExecution
118
- };
94
+ });
95
+
96
+ // Track script errors to attach partial results before re-throwing
97
+ // This ensures that any test() calls that passed before the error are preserved
98
+ // Similar pattern to test-runtime.js which already handles this correctly
99
+ let scriptError = null;
100
+
101
+ if (this.runtime === SANDBOX.NODEVM) {
102
+ try {
103
+ await runScriptInNodeVm({
104
+ script,
105
+ context,
106
+ collectionPath,
107
+ scriptingConfig,
108
+ scriptPath
109
+ });
110
+ } catch (error) {
111
+ scriptError = error;
112
+ }
113
+
114
+ // If script errored, attach partial results so callers can display passed tests
115
+ // before the error occurred (e.g., 2 tests pass, then script throws)
116
+ if (scriptError) {
117
+ scriptError.partialResults = buildRequestScriptResult();
118
+ throw scriptError;
119
+ }
120
+
121
+ return buildRequestScriptResult();
122
+ }
123
+
124
+ // default runtime is `quickjs`
125
+ try {
126
+ await executeQuickJsVmAsync({
127
+ script: script,
128
+ context: context,
129
+ collectionPath,
130
+ scriptPath
131
+ });
132
+ } catch (error) {
133
+ scriptError = error;
134
+ }
135
+
136
+ if (scriptError) {
137
+ scriptError.partialResults = buildRequestScriptResult();
138
+ throw scriptError;
139
+ }
140
+
141
+ return buildRequestScriptResult();
119
142
  }
120
143
 
121
144
  async runResponseScript(
@@ -146,6 +169,7 @@ class ScriptRuntime {
146
169
  const iterationDetails = request?.runnerIterationDetails || {};
147
170
  const assertionResults = request?.assertionResults || [];
148
171
  const certsAndProxyConfig = request?.certsAndProxyConfig;
172
+ const scriptPath = request?.pathname;
149
173
  const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables, certsAndProxyConfig);
150
174
  const req = new BrunoRequest(request);
151
175
  const res = new BrunoResponse(response);
@@ -182,47 +206,68 @@ class ScriptRuntime {
182
206
  context.bru.runRequest = runRequestByItemPathname;
183
207
  }
184
208
 
185
- if (this.runtime === 'nodevm') {
186
- await runScriptInNodeVm({
187
- script,
188
- context,
189
- collectionPath,
190
- scriptingConfig
191
- });
192
-
193
- return {
194
- response,
195
- envVariables: cleanJson(envVariables),
196
- persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
197
- runtimeVariables: cleanJson(runtimeVariables),
198
- visualizations,
199
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
200
- results: cleanJson(__brunoTestResults.getResults()),
201
- nextRequestName: bru.nextRequest,
202
- skipRequest: bru.skipRequest,
203
- stopExecution: bru.stopExecution
204
- };
205
- }
206
-
207
- // default runtime is `quickjs`
208
- await executeQuickJsVmAsync({
209
- script: script,
210
- context: context,
211
- collectionPath
212
- });
213
-
214
- return {
209
+ // Helper to build the result object for post-response scripts
210
+ // Extracted to avoid duplication across runtime branches
211
+ const buildResponseScriptResult = () => ({
215
212
  response,
216
213
  envVariables: cleanJson(envVariables),
217
214
  persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
218
215
  runtimeVariables: cleanJson(runtimeVariables),
219
216
  visualizations,
220
217
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
218
+ oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
221
219
  results: cleanJson(__brunoTestResults.getResults()),
222
220
  nextRequestName: bru.nextRequest,
223
221
  skipRequest: bru.skipRequest,
224
222
  stopExecution: bru.stopExecution
225
- };
223
+ });
224
+
225
+ // Track script errors to attach partial results before re-throwing
226
+ // This ensures that any test() calls that passed before the error are preserved
227
+ // Similar pattern to test-runtime.js which already handles this correctly
228
+ let scriptError = null;
229
+
230
+ if (this.runtime === SANDBOX.NODEVM) {
231
+ try {
232
+ await runScriptInNodeVm({
233
+ script,
234
+ context,
235
+ collectionPath,
236
+ scriptingConfig,
237
+ scriptPath
238
+ });
239
+ } catch (error) {
240
+ scriptError = error;
241
+ }
242
+
243
+ // If script errored, attach partial results so callers can display passed tests
244
+ // before the error occurred (e.g., 2 tests pass, then script throws)
245
+ if (scriptError) {
246
+ scriptError.partialResults = buildResponseScriptResult();
247
+ throw scriptError;
248
+ }
249
+
250
+ return buildResponseScriptResult();
251
+ }
252
+
253
+ // default runtime is `quickjs`
254
+ try {
255
+ await executeQuickJsVmAsync({
256
+ script: script,
257
+ context: context,
258
+ collectionPath,
259
+ scriptPath
260
+ });
261
+ } catch (error) {
262
+ scriptError = error;
263
+ }
264
+
265
+ if (scriptError) {
266
+ scriptError.partialResults = buildResponseScriptResult();
267
+ throw scriptError;
268
+ }
269
+
270
+ return buildResponseScriptResult();
226
271
  }
227
272
  }
228
273
 
@@ -8,6 +8,7 @@ const { createBruTestResultMethods } = require('../utils/results');
8
8
  const { runScriptInNodeVm } = require('../sandbox/node-vm');
9
9
  const jsonwebtoken = require('jsonwebtoken');
10
10
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
11
+ const { SANDBOX } = require('../utils/sandbox');
11
12
 
12
13
  class TestRuntime {
13
14
  constructor(props) {
@@ -38,6 +39,7 @@ class TestRuntime {
38
39
  const iterationDetails = request?.runnerIterationDetails || {};
39
40
  const assertionResults = request?.assertionResults || [];
40
41
  const certsAndProxyConfig = request?.certsAndProxyConfig;
42
+ const scriptPath = request?.pathname;
41
43
  const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables, certsAndProxyConfig);
42
44
  const req = new BrunoRequest(request, historyLogger);
43
45
  const res = new BrunoResponse(response);
@@ -89,19 +91,21 @@ class TestRuntime {
89
91
  let scriptError = null;
90
92
 
91
93
  try {
92
- if (this.runtime === 'nodevm') {
94
+ if (this.runtime === SANDBOX.NODEVM) {
93
95
  await runScriptInNodeVm({
94
96
  script: testsFile,
95
97
  context,
96
98
  collectionPath,
97
- scriptingConfig
99
+ scriptingConfig,
100
+ scriptPath
98
101
  });
99
102
  } else {
100
103
  // default runtime is `quickjs`
101
104
  await executeQuickJsVmAsync({
102
105
  script: testsFile,
103
106
  context: context,
104
- collectionPath
107
+ collectionPath,
108
+ scriptPath
105
109
  });
106
110
  }
107
111
  } catch (error) {
@@ -123,6 +127,7 @@ class TestRuntime {
123
127
  runtimeVariables: cleanJson(runtimeVariables),
124
128
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
125
129
  persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
130
+ oauth2CredentialsToReset: bru.oauth2CredentialsToReset,
126
131
  results: cleanJson(__brunoTestResults.getResults()),
127
132
  nextRequestName: bru.nextRequest
128
133
  };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Gets the type tag of a value using Object.prototype.toString
3
+ * This works across VM context boundaries unlike instanceof
4
+ * @param {*} value - The value to check
5
+ * @returns {string} The type tag (e.g., 'Set', 'Map', 'Array', 'Object')
6
+ */
7
+ function getTypeTag(value) {
8
+ return Object.prototype.toString.call(value).slice(8, -1);
9
+ }
10
+
11
+ /**
12
+ * Transforms a value, converting Set and Map to a special format for display
13
+ * Uses Object.prototype.toString for cross-context type detection
14
+ * @param {*} value - The value to transform
15
+ * @param {WeakSet} seen - Set of already visited objects for circular ref detection
16
+ * @returns {*} Transformed value with Set/Map converted to __brunoType format
17
+ */
18
+ function transformValue(value, seen = new WeakSet()) {
19
+ // Return primitives as-is
20
+ if (value === null || value === undefined || typeof value !== 'object' && typeof value !== 'function') {
21
+ return value;
22
+ }
23
+
24
+ // Circular reference check for objects
25
+ if (typeof value === 'object') {
26
+ if (seen.has(value)) {
27
+ return '[Circular]';
28
+ }
29
+ seen.add(value);
30
+ }
31
+
32
+ const typeTag = getTypeTag(value);
33
+
34
+ if (typeTag === 'Set') {
35
+ return {
36
+ __brunoType: 'Set',
37
+ __brunoValue: Array.from(value).map((item) => transformValue(item, seen))
38
+ };
39
+ }
40
+
41
+ if (typeTag === 'Map') {
42
+ return {
43
+ __brunoType: 'Map',
44
+ __brunoValue: Array.from(value.entries()).map(([k, v]) => [
45
+ transformValue(k, seen),
46
+ transformValue(v, seen)
47
+ ])
48
+ };
49
+ }
50
+
51
+ if (typeTag === 'Array') {
52
+ return value.map((item) => transformValue(item, seen));
53
+ }
54
+
55
+ if (typeTag === 'Object') {
56
+ const transformed = {};
57
+ for (const [key, val] of Object.entries(value)) {
58
+ transformed[key] = transformValue(val, seen);
59
+ }
60
+ return transformed;
61
+ }
62
+
63
+ // Handle functions - show clean wrapper
64
+ if (typeTag === 'Function' || typeof value === 'function') {
65
+ const name = value.name || 'anonymous';
66
+ return `function ${name}() {\n [native code]\n}`;
67
+ }
68
+
69
+ // Handle other built-in types (Date, RegExp, Error, etc.) - convert to string representation
70
+ try {
71
+ return value?.toString?.() ?? String(value);
72
+ } catch {
73
+ return `[${typeTag}]`;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Wraps a console object to add Set/Map support for logging
79
+ * @param {Object} originalConsole - The original console object
80
+ * @returns {Object} Wrapped console with Set/Map transformation
81
+ */
82
+ function wrapConsoleWithSerializers(originalConsole) {
83
+ if (!originalConsole) return originalConsole;
84
+
85
+ const methodsToWrap = ['log', 'debug', 'info', 'warn', 'error'];
86
+ const wrappedConsole = { ...originalConsole };
87
+
88
+ for (const method of methodsToWrap) {
89
+ if (typeof originalConsole[method] === 'function') {
90
+ wrappedConsole[method] = (...args) => {
91
+ const transformedArgs = args.map((arg) => transformValue(arg));
92
+ originalConsole[method](...transformedArgs);
93
+ };
94
+ }
95
+ }
96
+
97
+ return wrappedConsole;
98
+ }
99
+
100
+ module.exports = {
101
+ wrapConsoleWithSerializers
102
+ };