@usebruno/js 0.51.0 → 0.52.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.
@@ -2,11 +2,28 @@ const addBruShimToContext = require('./shims/bru');
2
2
  const addBrunoRequestShimToContext = require('./shims/bruno-request');
3
3
  const addConsoleShimToContext = require('./shims/console');
4
4
  const addBrunoResponseShimToContext = require('./shims/bruno-response');
5
+ const addBrunoGrpcShimToContext = require('./shims/bruno-grpc');
5
6
  const addTestShimToContext = require('./shims/test');
6
7
  const addLibraryShimsToContext = require('./shims/lib');
7
8
  const addLocalModuleLoaderShimToContext = require('./shims/local-module');
8
9
  const { getRequireCode } = require('./shims/require');
9
- const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscripten');
10
+ const { newQuickJSWASMModuleFromVariant, newVariant, RELEASE_SYNC } = require('quickjs-emscripten');
11
+
12
+ // The engine prints its dispose-abort assertion to stderr on its own. Swallow
13
+ // that line on the CLI, where stderr is the user's screen and a handled trap
14
+ // would read as a crash; keep it in the app, whose console is not user facing.
15
+ const isElectronHost = Boolean(process.versions.electron);
16
+ const isContainedAbortLine = (line) =>
17
+ String(line).includes('list_empty(&rt->gc_obj_list)') && String(line).includes('JS_FreeRuntime');
18
+ const quietEngineVariant = newVariant(RELEASE_SYNC, {
19
+ emscriptenModule: {
20
+ printErr: (line) => {
21
+ if (isElectronHost || !isContainedAbortLine(line)) {
22
+ console.error(line);
23
+ }
24
+ }
25
+ }
26
+ });
10
27
 
11
28
  // execute `npm run sandbox:bundle-libraries` if the below file doesn't exist
12
29
  const getBundledCode = require('../bundle-browser-rollup');
@@ -16,8 +33,54 @@ const addCryptoUtilsShimToContext = require('./shims/lib/crypto-utils');
16
33
  const { wrapScriptInClosure, SANDBOX } = require('../../utils/sandbox');
17
34
 
18
35
  let QuickJSModule;
19
- const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
20
- loader().then((mod) => (QuickJSModule = mod));
36
+ let quickJSModulePromise;
37
+ let quickJSModuleLoading = false;
38
+ let quickJSModuleRecycleCount = 0;
39
+
40
+ // Memoized WASM module for sync + async. reload swaps with no gap; failed
41
+ // reload restores the old memo, failed initial load clears it.
42
+ const loader = ({ reload = false } = {}) => {
43
+ if (!quickJSModulePromise || (reload && !quickJSModuleLoading)) {
44
+ const previousPromise = quickJSModulePromise;
45
+ quickJSModuleLoading = true;
46
+ quickJSModulePromise = newQuickJSWASMModuleFromVariant(quietEngineVariant)
47
+ .then((mod) => {
48
+ QuickJSModule = mod;
49
+ return mod;
50
+ })
51
+ .catch((loadError) => {
52
+ console.error(reload ? 'QuickJS module reload failed' : 'QuickJS module load failed', loadError);
53
+ quickJSModulePromise = reload ? previousPromise : null;
54
+ if (quickJSModulePromise) {
55
+ return quickJSModulePromise;
56
+ }
57
+ throw loadError;
58
+ })
59
+ .finally(() => {
60
+ quickJSModuleLoading = false;
61
+ });
62
+ }
63
+ return quickJSModulePromise;
64
+ };
65
+ loader().catch(() => {});
66
+
67
+ // On dispose WASM trap, recycle the module (old one keeps serving until ready).
68
+ const recycleQuickJSModuleOnAbort = (teardownError, ownerModule) => {
69
+ if (!(teardownError instanceof WebAssembly.RuntimeError)) {
70
+ return false;
71
+ }
72
+ // Skip if this module was already replaced.
73
+ if (!ownerModule || ownerModule === QuickJSModule) {
74
+ quickJSModuleRecycleCount += 1;
75
+ console.warn(
76
+ quickJSModuleRecycleCount === 1
77
+ ? 'QuickJS engine crashed during cleanup and was replaced; the run was not affected'
78
+ : `QuickJS engine replaced again (${quickJSModuleRecycleCount} this session)`
79
+ );
80
+ loader({ reload: true }).catch(() => {});
81
+ }
82
+ return true;
83
+ };
21
84
 
22
85
  const toNumber = (value) => {
23
86
  const num = Number(value);
@@ -57,8 +120,9 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
57
120
  externalScript = removeQuotes(externalScript);
58
121
  }
59
122
  let managedQuickJsContext;
123
+ const quickJsModule = QuickJSModule;
60
124
  try {
61
- managedQuickJsContext = createManagedQuickJsContext(QuickJSModule);
125
+ managedQuickJsContext = createManagedQuickJsContext(quickJsModule);
62
126
  const vm = managedQuickJsContext.vm;
63
127
  const { bru, req, res, ...variables } = externalContext;
64
128
 
@@ -88,7 +152,13 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
88
152
  } catch (error) {
89
153
  console.error('Error executing the script!', error);
90
154
  } finally {
91
- managedQuickJsContext?.dispose();
155
+ try {
156
+ managedQuickJsContext?.dispose();
157
+ } catch (teardownError) {
158
+ if (!recycleQuickJSModuleOnAbort(teardownError, quickJsModule)) {
159
+ console.error('Error disposing QuickJS context', teardownError);
160
+ }
161
+ }
92
162
  }
93
163
  };
94
164
 
@@ -99,9 +169,11 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
99
169
  externalScript = externalScript?.trim();
100
170
 
101
171
  let managedQuickJsContext;
172
+ let scriptError;
173
+ let quickJsModule;
102
174
  try {
103
- const module = await loader();
104
- managedQuickJsContext = createManagedQuickJsContext(module);
175
+ quickJsModule = await loader();
176
+ managedQuickJsContext = createManagedQuickJsContext(quickJsModule);
105
177
  const vm = managedQuickJsContext.vm;
106
178
 
107
179
  // add crypto utilities required by the crypto-js library in bundledCode
@@ -120,6 +192,7 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
120
192
 
121
193
  consoleFn && addConsoleShimToContext(vm, consoleFn);
122
194
  bru && addBruShimToContext(vm, bru);
195
+ bru?.grpc && addBrunoGrpcShimToContext(vm, bru.grpc);
123
196
  req && addBrunoRequestShimToContext(vm, req);
124
197
  res && addBrunoResponseShimToContext(vm, res);
125
198
  addLocalModuleLoaderShimToContext(vm, collectionPath);
@@ -137,22 +210,33 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
137
210
  promiseHandle.dispose();
138
211
  const resolvedHandle = vm.unwrapResult(resolvedResult);
139
212
  resolvedHandle.dispose();
140
- return;
141
213
  } catch (error) {
142
214
  error.__isQuickJS = true;
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`.
215
+ scriptError = error;
216
+ }
217
+
218
+ // The run waits for every pending deferred before returning: un-awaited
219
+ // async work is the user's choice, and the run is not done until it is.
220
+ // The wait is unbounded by design; cancelling the request is the way out.
221
+ if (managedQuickJsContext) {
222
+ // No try/catch: every awaited settle promise is pre-caught at creation
223
+ // (trackPendingDeferreds), so this await cannot reject and skip dispose.
224
+ await managedQuickJsContext.waitForPendingDeferreds();
149
225
  try {
150
- await managedQuickJsContext?.waitForPendingDeferreds?.();
151
- managedQuickJsContext?.dispose();
226
+ managedQuickJsContext.dispose();
152
227
  } catch (teardownError) {
153
- throw teardownError;
228
+ const recycled = recycleQuickJSModuleOnAbort(teardownError, quickJsModule);
229
+ if (!scriptError && !recycled) {
230
+ scriptError = teardownError;
231
+ } else if (!recycled) {
232
+ console.error('Error disposing QuickJS context', teardownError);
233
+ }
154
234
  }
155
235
  }
236
+
237
+ if (scriptError) {
238
+ throw scriptError;
239
+ }
156
240
  };
157
241
 
158
242
  module.exports = {
@@ -0,0 +1,29 @@
1
+ const addBrunoGrpcRequestShimToContext = require('./grpc/bruno-grpc-request');
2
+ const addBrunoGrpcResponseShimToContext = require('./grpc/bruno-grpc-response');
3
+
4
+ /**
5
+ * Installs `bru.grpc` onto the `bru` object the bru shim has already put on the global, so it has
6
+ * to run after it.
7
+ */
8
+ const addBrunoGrpcShimToContext = (vm, grpc) => {
9
+ const bruObject = vm.getProp(vm.global, 'bru');
10
+ const grpcObject = vm.newObject();
11
+
12
+ const { evalCode: requestEvalCode } = addBrunoGrpcRequestShimToContext(vm, grpc.request, grpcObject);
13
+ // `response` is absent in `beforeCallStart`, which has no response yet.
14
+ const { evalCode: responseEvalCode } = grpc.response
15
+ ? addBrunoGrpcResponseShimToContext(vm, grpc.response, grpcObject)
16
+ : { evalCode: [] };
17
+
18
+ vm.setProp(bruObject, 'grpc', grpcObject);
19
+ grpcObject.dispose();
20
+ bruObject.dispose();
21
+
22
+ // The list code reaches its target through `globalThis.bru.grpc`, so it can only run now that
23
+ // `grpc` is on `bru`. Each block is braced on its own — every bridge declares the same consts.
24
+ for (const code of [...requestEvalCode, ...responseEvalCode].filter(Boolean)) {
25
+ vm.evalCode(`{ ${code} }`);
26
+ }
27
+ };
28
+
29
+ module.exports = addBrunoGrpcShimToContext;
@@ -0,0 +1,47 @@
1
+ const { marshallToVm } = require('../../utils');
2
+ const addGrpcMetadataListShimToContext = require('./grpc-metadata-list');
3
+ const addGrpcMessageListShimToContext = require('./grpc-message-list');
4
+
5
+ // Keep this in step with BrunoGrpcRequest.
6
+ const addBrunoGrpcRequestShimToContext = (vm, request, grpcObject) => {
7
+ const requestObject = vm.newObject();
8
+
9
+ const scalars = ['url', 'method', 'methodType', 'authMode', 'protoPath', 'name'];
10
+
11
+ for (const property of scalars) {
12
+ const value = marshallToVm(request[property], vm);
13
+ vm.setProp(requestObject, property, value);
14
+ value.dispose();
15
+ }
16
+
17
+ // request.metadata — writable in `beforeCallStart`, read-only in `afterCallEnd`
18
+ const metadataEvalCode = addGrpcMetadataListShimToContext(
19
+ vm,
20
+ request.metadata,
21
+ requestObject,
22
+ 'metadata',
23
+ 'globalThis.bru.grpc.request'
24
+ );
25
+
26
+ // request.messages — the messages the call sent, read-only, and empty until it has sent any
27
+ const messagesEvalCode = addGrpcMessageListShimToContext(
28
+ vm,
29
+ request.messages,
30
+ requestObject,
31
+ 'globalThis.bru.grpc.request'
32
+ );
33
+
34
+ // request.message — present only in `beforeMessageSend`.
35
+ if (request.message) {
36
+ const message = marshallToVm(request.message, vm);
37
+ vm.setProp(requestObject, 'message', message);
38
+ message.dispose();
39
+ }
40
+
41
+ vm.setProp(grpcObject, 'request', requestObject);
42
+ requestObject.dispose();
43
+
44
+ return { evalCode: [metadataEvalCode, messagesEvalCode] };
45
+ };
46
+
47
+ module.exports = addBrunoGrpcRequestShimToContext;
@@ -0,0 +1,40 @@
1
+ const { marshallToVm } = require('../../utils');
2
+ const addGrpcMetadataListShimToContext = require('./grpc-metadata-list');
3
+ const addGrpcMessageListShimToContext = require('./grpc-message-list');
4
+
5
+ // Keep this in step with BrunoGrpcResponse.
6
+ const addBrunoGrpcResponseShimToContext = (vm, response, grpcObject) => {
7
+ const responseObject = vm.newObject();
8
+
9
+ // Marshalled once, as on the request: the call is over, so no scalar can change mid-hook.
10
+ const scalars = ['statusCode', 'statusText', 'duration'];
11
+
12
+ for (const property of scalars) {
13
+ const value = marshallToVm(response?.[property], vm);
14
+ vm.setProp(responseObject, property, value);
15
+ value.dispose();
16
+ }
17
+
18
+ // response.metadata / .trailers / .messages — the same lists `bru.grpc.request` gets, read-only here
19
+ const listEvalCode = ['metadata', 'trailers'].map((property) =>
20
+ addGrpcMetadataListShimToContext(vm, response[property], responseObject, property, 'globalThis.bru.grpc.response')
21
+ );
22
+
23
+ listEvalCode.push(
24
+ addGrpcMessageListShimToContext(vm, response.messages, responseObject, 'globalThis.bru.grpc.response')
25
+ );
26
+
27
+ // response.message — present only in `afterMessageReceive`
28
+ if (response.message) {
29
+ const message = marshallToVm(response.message, vm);
30
+ vm.setProp(responseObject, 'message', message);
31
+ message.dispose();
32
+ }
33
+
34
+ vm.setProp(grpcObject, 'response', responseObject);
35
+ responseObject.dispose();
36
+
37
+ return { evalCode: listEvalCode };
38
+ };
39
+
40
+ module.exports = addBrunoGrpcResponseShimToContext;
@@ -0,0 +1,29 @@
1
+ const { createPropertyListBridge } = require('../../utils/property-list-bridge');
2
+
3
+ /**
4
+ * Bridges a GrpcMessageList — `bru.grpc.request.messages`, `bru.grpc.response.messages`
5
+ * — onto a VM object. Keep in sync with GrpcMessageList
6
+ *
7
+ * @param {Object} vm - QuickJS VM instance
8
+ * @param {Object} list - The native GrpcMessageList
9
+ * @param {Object} targetObject - VM object handle the list is attached to
10
+ * @param {string} objectPath - Path to `targetObject` in the VM, e.g. `globalThis.bru.grpc.request`
11
+ * @returns {string} Code the caller must eval once `objectPath` resolves
12
+ */
13
+ const addGrpcMessageListShimToContext = (vm, list, targetObject, objectPath) => {
14
+ const listObject = vm.newObject();
15
+
16
+ const { evalCode } = createPropertyListBridge(vm, list, listObject, {
17
+ globalPath: `${objectPath}.messages`,
18
+ syncReadMethods: ['count'],
19
+ syncReadObjectMethods: ['get', 'all', 'toJSON'],
20
+ withIterators: true
21
+ });
22
+
23
+ vm.setProp(targetObject, 'messages', listObject);
24
+ listObject.dispose();
25
+
26
+ return evalCode;
27
+ };
28
+
29
+ module.exports = addGrpcMessageListShimToContext;
@@ -0,0 +1,31 @@
1
+ const { createPropertyListBridge } = require('../../utils/property-list-bridge');
2
+
3
+ /**
4
+ * Bridges a GrpcMetadataList — `bru.grpc.request.metadata`, `bru.grpc.response.metadata`,
5
+ * `bru.grpc.response.trailers` — onto a VM object. Keep in sync with GrpcMetadataList
6
+ *
7
+ * @param {Object} vm - QuickJS VM instance
8
+ * @param {Object} list - The native GrpcMetadataList
9
+ * @param {Object} targetObject - VM object handle the list is attached to
10
+ * @param {string} property - Property name on `targetObject`
11
+ * @param {string} objectPath - Path to `targetObject` in the VM, e.g. `globalThis.bru.grpc.request`
12
+ * @returns {string} Code the caller must eval once `objectPath` resolves
13
+ */
14
+ const addGrpcMetadataListShimToContext = (vm, list, targetObject, property, objectPath) => {
15
+ const listObject = vm.newObject();
16
+
17
+ const { evalCode } = createPropertyListBridge(vm, list, listObject, {
18
+ globalPath: `${objectPath}.${property}`,
19
+ syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'],
20
+ syncReadObjectMethods: ['one', 'all', 'toJSON'],
21
+ syncWriteMethods: ['upsert', 'add', 'remove', 'clear'],
22
+ withIterators: true
23
+ });
24
+
25
+ vm.setProp(targetObject, property, listObject);
26
+ listObject.dispose();
27
+
28
+ return evalCode;
29
+ };
30
+
31
+ module.exports = addGrpcMetadataListShimToContext;
@@ -8,7 +8,7 @@ const createManagedQuickJsContext = (module) => {
8
8
  const vm = module.newContext();
9
9
  const disposeTracked = trackQuickJsContext(vm);
10
10
  const evalCodeRetained = vm.evalCode.bind(vm);
11
- const waitForPendingDeferreds = trackPendingDeferreds(vm);
11
+ const { waitForPendingDeferreds, disposePendingDeferreds } = trackPendingDeferreds(vm);
12
12
 
13
13
  vm.evalCode = (code, filename = 'eval.js') => {
14
14
  const result = evalCodeRetained(code, filename);
@@ -25,7 +25,7 @@ const createManagedQuickJsContext = (module) => {
25
25
  return {
26
26
  vm,
27
27
  waitForPendingDeferreds,
28
- dispose: () => disposeQuickJsContext(vm, disposeTracked)
28
+ dispose: () => disposeQuickJsContext(vm, disposeTracked, disposePendingDeferreds)
29
29
  };
30
30
  };
31
31
 
@@ -38,27 +38,41 @@ const createManagedQuickJsContext = (module) => {
38
38
  * `QuickJSUseAfterFree`. Each `.settled` resolves once the deferred is
39
39
  * resolved/rejected, so awaiting them keeps the context alive long enough.
40
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.
41
+ * The hook installs at context creation. `waitForPendingDeferreds` drains in
42
+ * place (waiting can chain new deferreds) with no timeout; `disposePendingDeferreds`
43
+ * frees unsettled resolve/reject handles, which left alive abort JS_FreeRuntime.
45
44
  */
46
-
47
45
  const trackPendingDeferreds = (vm) => {
48
- const pendingDeferreds = [];
46
+ const pendingSettles = [];
47
+ const deferreds = [];
49
48
  const originalNewPromise = vm.newPromise.bind(vm);
50
49
  vm.newPromise = (...args) => {
51
50
  const deferred = originalNewPromise(...args);
52
- pendingDeferreds.push(deferred.settled.catch(() => { }));
51
+ deferreds.push(deferred);
52
+ pendingSettles.push(deferred.settled.catch(() => { }));
53
53
  return deferred;
54
54
  };
55
55
 
56
- return async () => {
57
- while (pendingDeferreds.length) {
58
- const batch = pendingDeferreds.splice(0);
56
+ const waitForPendingDeferreds = async () => {
57
+ while (pendingSettles.length) {
58
+ const batch = pendingSettles.splice(0);
59
59
  await Promise.all(batch);
60
60
  }
61
61
  };
62
+
63
+ const disposePendingDeferreds = () => {
64
+ while (deferreds.length) {
65
+ const deferred = deferreds.pop();
66
+ if (deferred.alive) {
67
+ deferred.dispose();
68
+ }
69
+ }
70
+ };
71
+
72
+ return {
73
+ waitForPendingDeferreds,
74
+ disposePendingDeferreds
75
+ };
62
76
  };
63
77
 
64
78
  /**
@@ -98,22 +112,18 @@ const trackQuickJsContext = (vm) => {
98
112
  };
99
113
 
100
114
  /**
101
- * Clears shim globals, drains pending QuickJS jobs, and disposes the context.
102
- * Pass disposeTracked from trackQuickJsContext() to free shim handles first.
115
+ * Drains pending QuickJS jobs, frees leftover deferreds and tracked handles,
116
+ * and disposes the context. Jobs are drained BEFORE the handle flush: a job
117
+ * can call allocating shims or start new async work, and anything it creates
118
+ * must still be freed or JS_FreeRuntime aborts on the leftover GC objects.
103
119
  */
104
- const disposeQuickJsContext = (vm, disposeTracked) => {
120
+ const disposeQuickJsContext = (vm, disposeTracked, disposePendingDeferreds) => {
105
121
  if (!vm?.alive) {
106
122
  return;
107
123
  }
108
124
 
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.
125
+ // Executing a job can schedule more jobs (chained `.then()`s), so keep going
126
+ // until `hasPendingJob()` reports the queue is empty or a job throws.
117
127
  while (vm.runtime?.hasPendingJob?.()) {
118
128
  const result = vm.runtime.executePendingJobs();
119
129
  // On error, dispose the error handle and stop draining.
@@ -122,6 +132,15 @@ const disposeQuickJsContext = (vm, disposeTracked) => {
122
132
  break;
123
133
  }
124
134
  }
135
+
136
+ if (typeof disposePendingDeferreds === 'function') {
137
+ disposePendingDeferreds();
138
+ }
139
+
140
+ if (typeof disposeTracked === 'function') {
141
+ disposeTracked();
142
+ }
143
+
125
144
  vm.dispose();
126
145
  };
127
146
 
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const YAML = require('yaml');
4
+ const { SCRIPT_TYPES } = require('@usebruno/common');
4
5
  const { NODEVM_SCRIPT_WRAPPER_OFFSET, QUICKJS_SCRIPT_WRAPPER_OFFSET } = require('./sandbox');
5
6
 
6
7
  const posixifyPath = (p) => (p ? p.replace(/\\/g, '/') : p);
@@ -11,17 +12,15 @@ const ALLOWED_SOURCE_EXTENSIONS = ['.bru', '.yml'];
11
12
  const isAllowedSourceFile = (filePath) =>
12
13
  typeof filePath === 'string' && ALLOWED_SOURCE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
13
14
 
14
- const SCRIPT_TYPES = Object.freeze({
15
- PRE_REQUEST: 'pre-request',
16
- POST_RESPONSE: 'post-response',
17
- TEST: 'test'
18
- });
19
-
20
15
  // Bruno script types → OpenCollection YAML script types
21
16
  const SCRIPT_TYPE_TO_YML = {
22
17
  [SCRIPT_TYPES.PRE_REQUEST]: 'before-request',
23
18
  [SCRIPT_TYPES.POST_RESPONSE]: 'after-response',
24
- [SCRIPT_TYPES.TEST]: 'tests'
19
+ [SCRIPT_TYPES.TEST]: 'tests',
20
+ [SCRIPT_TYPES.BEFORE_CALL_START]: 'grpc:before-call-start',
21
+ [SCRIPT_TYPES.BEFORE_MESSAGE_SEND]: 'grpc:before-message-send',
22
+ [SCRIPT_TYPES.AFTER_MESSAGE_RECEIVE]: 'grpc:after-message-receive',
23
+ [SCRIPT_TYPES.AFTER_CALL_END]: 'grpc:after-call-end'
25
24
  };
26
25
 
27
26
  const readFile = (filePath, cache = null) => {
@@ -38,7 +37,11 @@ const readFile = (filePath, cache = null) => {
38
37
  const BLOCK_PATTERNS = {
39
38
  [SCRIPT_TYPES.PRE_REQUEST]: /^script:pre-request\s*\{/,
40
39
  [SCRIPT_TYPES.POST_RESPONSE]: /^script:post-response\s*\{/,
41
- [SCRIPT_TYPES.TEST]: /^tests\s*\{/
40
+ [SCRIPT_TYPES.TEST]: /^tests\s*\{/,
41
+ [SCRIPT_TYPES.BEFORE_CALL_START]: /^script:grpc:before-call-start\s*\{/,
42
+ [SCRIPT_TYPES.BEFORE_MESSAGE_SEND]: /^script:grpc:before-message-send\s*\{/,
43
+ [SCRIPT_TYPES.AFTER_MESSAGE_RECEIVE]: /^script:grpc:after-message-receive\s*\{/,
44
+ [SCRIPT_TYPES.AFTER_CALL_END]: /^script:grpc:after-call-end\s*\{/
42
45
  };
43
46
 
44
47
  /** Find the 1-indexed line where a script block's content starts in a .bru file */
@@ -747,5 +750,6 @@ module.exports = {
747
750
  findYmlScriptBlockStartLine,
748
751
  findYmlScriptBlockEndLine,
749
752
  adjustStackTrace,
750
- getErrorTypeName
753
+ getErrorTypeName,
754
+ posixifyPath
751
755
  };