@usebruno/js 0.1.0 → 0.2.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.1.0",
3
+ "version": "0.2.0",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src",
@@ -9,7 +9,11 @@
9
9
  "peerDependencies": {
10
10
  "vm2": "^3.9.13"
11
11
  },
12
+ "scripts": {
13
+ "test": "jest --testPathIgnorePatterns test.js"
14
+ },
12
15
  "dependencies": {
16
+ "@usebruno/query": "0.1.0",
13
17
  "atob": "^2.1.2",
14
18
  "ajv": "^8.12.0",
15
19
  "btoa": "^1.2.1",
@@ -20,4 +24,4 @@
20
24
  "nanoid": "3.3.4",
21
25
  "uuid": "^9.0.0"
22
26
  }
23
- }
27
+ }
@@ -1,6 +1,5 @@
1
1
  const _ = require('lodash');
2
2
  const chai = require('chai');
3
- const { nanoid } = require('nanoid');
4
3
  const Bru = require('../bru');
5
4
  const BrunoRequest = require('../bruno-request');
6
5
  const { evaluateJsExpression, createResponseParser } = require('../utils');
@@ -40,7 +39,7 @@ const { expect } = chai;
40
39
  * isBoolean : is boolean
41
40
  */
42
41
  const parseAssertionOperator = (str = '') => {
43
- if(!str || typeof str !== 'string' || str.length) {
42
+ if(!str || typeof str !== 'string' || !str.length) {
44
43
  return {
45
44
  operator: 'eq',
46
45
  value: str
package/src/utils.js CHANGED
@@ -1,25 +1,85 @@
1
1
  const jsonQuery = require('json-query');
2
+ const { get } = require("@usebruno/query");
3
+
4
+ const JS_KEYWORDS = `
5
+ break case catch class const continue debugger default delete do
6
+ else export extends false finally for function if import in instanceof
7
+ new null return super switch this throw true try typeof var void while with
8
+ undefined let static yield arguments of
9
+ `.split(/\s+/).filter(word => word.length > 0);
10
+
11
+ /**
12
+ * Creates a function from a Javascript expression
13
+ *
14
+ * When the function is called, the variables used in this expression are picked up from the context
15
+ *
16
+ * ```js
17
+ * res.data.pets.map(pet => pet.name.toUpperCase())
18
+ *
19
+ * function(context) {
20
+ * const { res, pet } = context;
21
+ * return res.data.pets.map(pet => pet.name.toUpperCase())
22
+ * }
23
+ * ```
24
+ */
25
+ const compileJsExpression = (expr) => {
26
+ // get all dotted identifiers (foo, bar.baz, .baz)
27
+ const matches = expr.match(/([\w\.$]+)/g) ?? [];
28
+
29
+ // get valid js identifiers (foo, bar)
30
+ const vars = new Set(
31
+ matches
32
+ .filter(match => /^[a-zA-Z$_]/.test(match)) // starts with valid js identifier (foo.bar)
33
+ .map(match => match.split('.')[0]) // top level identifier (foo)
34
+ .filter(name => !JS_KEYWORDS.includes(name)) // exclude js keywords
35
+ );
36
+
37
+ // globals such as Math
38
+ const globals = [...vars].filter(name => name in globalThis);
39
+
40
+ const code = {
41
+ vars: [...vars].join(", "),
42
+ // pick global from context or globalThis
43
+ globals: globals
44
+ .map(name => ` ${name} = ${name} ?? globalThis.${name};`)
45
+ .join('')
46
+ };
47
+
48
+ const body = `let { ${code.vars} } = context; ${code.globals}; return ${expr}`;
49
+
50
+ return new Function("context", body);
51
+ };
52
+
53
+ const internalExpressionCache = new Map();
2
54
 
3
55
  const evaluateJsExpression = (expression, context) => {
4
- const fn = new Function(...Object.keys(context), `return ${expression}`);
5
- return fn(...Object.values(context));
56
+ let fn = internalExpressionCache.get(expression);
57
+ if (fn == null) {
58
+ internalExpressionCache.set(expression, fn = compileJsExpression(expression));
59
+ }
60
+ return fn(context);
6
61
  };
7
62
 
8
63
  const createResponseParser = (response = {}) => {
9
- const res = (expr) => {
10
- const output = jsonQuery(expr, { data: response.data });
11
- return output ? output.value : null;
12
- }
64
+ const res = (expr, ...fns) => {
65
+ return get(response.data, expr, ...fns);
66
+ };
13
67
 
14
68
  res.status = response.status;
15
69
  res.statusText = response.statusText;
16
70
  res.headers = response.headers;
17
71
  res.body = response.data;
18
72
 
73
+ res.jq = (expr) => {
74
+ const output = jsonQuery(expr, { data: response.data });
75
+ return output ? output.value : null;
76
+ };
77
+
19
78
  return res;
20
79
  };
21
80
 
22
81
  module.exports = {
23
82
  evaluateJsExpression,
24
- createResponseParser
83
+ createResponseParser,
84
+ internalExpressionCache
25
85
  };