@port-labs/jq-node-bindings 1.1.0-dev → 1.1.0-dev.1

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/lib/index.js CHANGED
@@ -1,11 +1,12 @@
1
- const jq = require('./jq');
2
- const template = require('./template');
3
- const templateAsync = require('./templateAsync');
4
-
1
+ const jq = require("./jq");
2
+ const template = require("./template");
3
+ const templateAsync = require("./templateAsync");
5
4
 
6
5
  module.exports = {
7
6
  exec: jq.exec,
8
7
  execAsync: jq.execAsync,
8
+ execRaw: jq.execRaw,
9
+ execRawAsync: jq.execRawAsync,
9
10
  setCacheSize: jq.setCacheSize,
10
11
  renderRecursively: template.renderRecursively,
11
12
  renderRecursivelyAsync: templateAsync.renderRecursivelyAsync,
package/lib/jq.js CHANGED
@@ -1,47 +1,83 @@
1
- const nativeJq = require('bindings')('jq-node-bindings')
2
- nativeJq.setCacheSize(1000)
1
+ const nativeJq = require("bindings")("jq-node-bindings");
2
+ nativeJq.setCacheSize(1000);
3
3
 
4
- const formatFilter = (filter, {enableEnv = false} = {}) => {
5
- // Escape single quotes only if they are opening or closing a string
6
- let formattedFilter = filter.replace(/(^|\s)'(?!\s|")|(?<!\s|")'(\s|$)/g, '$1"$2');
7
- // Conditionally enable access to env
8
- return enableEnv ? formattedFilter : `def env: {}; {} as $ENV | ${formattedFilter}`;
9
- }
4
+ const SINGLE_QUOTE_RE = /(^|\s)'(?!\s|")|(?<!\s|")'(\s|$)/g;
5
+ const COMPILE_ERROR_PREFIX = "jq: compile error";
6
+ const ENV_DISABLE_PREFIX = "def env: {}; {} as $ENV | ";
10
7
 
8
+ const formatFilter = (filter, enableEnv) => {
9
+ const formattedFilter = filter.replace(SINGLE_QUOTE_RE, '$1"$2');
10
+ return enableEnv ? formattedFilter : ENV_DISABLE_PREFIX + formattedFilter;
11
+ };
11
12
 
12
- class JqExecError extends Error {
13
- }
13
+ class JqExecError extends Error {}
14
14
 
15
- class JqExecCompileError extends JqExecError {
16
- }
15
+ class JqExecCompileError extends JqExecError {}
17
16
 
18
- const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) => {
17
+ const execRaw = (jsonStr, filter, execOptions) => {
19
18
  try {
20
- const data = nativeJq.execSync(JSON.stringify(object), formatFilter(filter, {enableEnv}))
21
-
19
+ const data = nativeJq.execSync(
20
+ jsonStr,
21
+ formatFilter(filter, execOptions.enableEnv)
22
+ );
22
23
  return data?.value;
23
24
  } catch (err) {
24
- if (throwOnError) {
25
- throw new (err?.message?.startsWith('jq: compile error') ? JqExecCompileError : JqExecError)(err.message);
25
+ if (execOptions.throwOnError) {
26
+ const msg = err?.message;
27
+ throw new (
28
+ msg && msg.startsWith(COMPILE_ERROR_PREFIX)
29
+ ? JqExecCompileError
30
+ : JqExecError
31
+ )(msg);
26
32
  }
27
- return null
33
+ return null;
28
34
  }
29
- }
35
+ };
30
36
 
31
- const execAsync = async (object, filter, {enableEnv = false, throwOnError = false, timeoutSec} = {}) => {
37
+ const execRawAsync = async (jsonStr, filter, execOptions) => {
32
38
  try {
33
- const data = await nativeJq.execAsync(JSON.stringify(object), formatFilter(filter, {enableEnv}), timeoutSec)
39
+ const data = await nativeJq.execAsync(
40
+ jsonStr,
41
+ formatFilter(filter, execOptions.enableEnv),
42
+ execOptions.timeoutSec
43
+ );
34
44
  return data?.value;
35
45
  } catch (err) {
36
- if (throwOnError) {
37
- throw new (err?.message?.startsWith('jq: compile error') ? JqExecCompileError : JqExecError)(err.message);
46
+ if (execOptions.throwOnError) {
47
+ const msg = err?.message;
48
+ throw new (
49
+ msg && msg.startsWith(COMPILE_ERROR_PREFIX)
50
+ ? JqExecCompileError
51
+ : JqExecError
52
+ )(msg);
38
53
  }
39
- return null
54
+ return null;
40
55
  }
41
- }
56
+ };
57
+
58
+ const DEFAULT_OPTIONS = { enableEnv: false, throwOnError: false };
59
+
60
+ const exec = (object, filter, execOptions) => {
61
+ return execRaw(
62
+ JSON.stringify(object),
63
+ filter,
64
+ execOptions || DEFAULT_OPTIONS
65
+ );
66
+ };
67
+
68
+ const execAsync = async (object, filter, execOptions) => {
69
+ return execRawAsync(
70
+ JSON.stringify(object),
71
+ filter,
72
+ execOptions || DEFAULT_OPTIONS
73
+ );
74
+ };
75
+
42
76
  module.exports = {
43
77
  exec,
44
78
  execAsync,
79
+ execRaw,
80
+ execRawAsync,
45
81
  setCacheSize: nativeJq.setCacheSize,
46
82
  JqExecError,
47
83
  JqExecCompileError,
package/lib/parse.js ADDED
@@ -0,0 +1,79 @@
1
+ const CH_BACKSLASH = 92; // '\'
2
+ const CH_DOUBLE_QUOTE = 34; // '"'
3
+ const CH_SINGLE_QUOTE = 39; // "'"
4
+ const CH_OPEN_BRACE = 123; // '{'
5
+ const CH_CLOSE_BRACE = 125; // '}'
6
+
7
+ const findInsideDoubleBracesIndices = (input) => {
8
+ let wrappingQuote = 0;
9
+ let insideDoubleBracesStart = -1;
10
+ const indices = [];
11
+ const len = input.length;
12
+
13
+ for (let i = 0; i < len; i++) {
14
+ const cc = input.charCodeAt(i);
15
+
16
+ if (insideDoubleBracesStart !== -1 && cc === CH_BACKSLASH) {
17
+ i++;
18
+ continue;
19
+ }
20
+ if (
21
+ insideDoubleBracesStart !== -1 &&
22
+ (cc === CH_DOUBLE_QUOTE || cc === CH_SINGLE_QUOTE)
23
+ ) {
24
+ if (!wrappingQuote) {
25
+ wrappingQuote = cc;
26
+ } else if (wrappingQuote === cc) {
27
+ wrappingQuote = 0;
28
+ }
29
+ } else if (
30
+ !wrappingQuote &&
31
+ cc === CH_OPEN_BRACE &&
32
+ i > 0 &&
33
+ input.charCodeAt(i - 1) === CH_OPEN_BRACE
34
+ ) {
35
+ if (insideDoubleBracesStart !== -1) {
36
+ throw new Error(
37
+ `Found double braces in index ${i - 1} inside other one in index ${
38
+ insideDoubleBracesStart - 2
39
+ }`
40
+ );
41
+ }
42
+ insideDoubleBracesStart = i + 1;
43
+ if (i + 1 < len && input.charCodeAt(i + 1) === CH_OPEN_BRACE) {
44
+ i++;
45
+ }
46
+ } else if (
47
+ !wrappingQuote &&
48
+ cc === CH_CLOSE_BRACE &&
49
+ i > 0 &&
50
+ input.charCodeAt(i - 1) === CH_CLOSE_BRACE
51
+ ) {
52
+ if (insideDoubleBracesStart !== -1) {
53
+ indices.push({ start: insideDoubleBracesStart, end: i - 1 });
54
+ insideDoubleBracesStart = -1;
55
+ if (i + 1 < len && input.charCodeAt(i + 1) === CH_CLOSE_BRACE) {
56
+ i++;
57
+ }
58
+ } else {
59
+ throw new Error(
60
+ `Found closing double braces in index ${
61
+ i - 1
62
+ } without opening double braces`
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ if (insideDoubleBracesStart !== -1) {
69
+ throw new Error(
70
+ `Found opening double braces in index ${
71
+ insideDoubleBracesStart - 2
72
+ } without closing double braces`
73
+ );
74
+ }
75
+
76
+ return indices;
77
+ };
78
+
79
+ module.exports = findInsideDoubleBracesIndices;
package/lib/template.js CHANGED
@@ -1,125 +1,123 @@
1
- const jq = require('./jq');
1
+ const jq = require("./jq");
2
+ const findInsideDoubleBracesIndices = require("./parse");
2
3
 
3
- const findInsideDoubleBracesIndices = (input) => {
4
- let wrappingQuote = null;
5
- let insideDoubleBracesStart = null;
6
- const indices = [];
4
+ const CH_OPEN_BRACE = 123; // '{'
5
+ const CH_CLOSE_BRACE = 125; // '}'
6
+ const BRACE_PAIR_LEN = 2; // length of '{{' or '}}'
7
7
 
8
- for (let i = 0; i < input.length; i += 1) {
9
- const char = input[i];
8
+ const SPREAD_KEYWORD_PATTERN = /^\s*\{\{\s*spreadValue\(\s*\)\s*\}\}\s*$/;
10
9
 
11
- if (insideDoubleBracesStart && char === '\\') {
12
- // If next character is escaped, skip it
13
- i += 1;
14
- }
15
- if (insideDoubleBracesStart && (char === '"' || char === "'")) {
16
- // If inside double braces and inside quotes, ignore braces
17
- if (!wrappingQuote) {
18
- wrappingQuote = char;
19
- } else if (wrappingQuote === char) {
20
- wrappingQuote = null;
21
- }
22
- } else if (!wrappingQuote && char === '{' && i > 0 && input[i - 1] === '{') {
23
- // if opening double braces that not wrapped with quotes
24
- if (insideDoubleBracesStart) {
25
- throw new Error(`Found double braces in index ${i - 1} inside other one in index ${insideDoubleBracesStart - '{{'.length}`);
26
- }
27
- insideDoubleBracesStart = i + 1;
28
- if (input[i + 1] === '{') {
29
- // To overcome three "{" in a row considered as two different opening double braces
30
- i += 1;
31
- }
32
- } else if (!wrappingQuote && char === '}' && i > 0 && input[i - 1] === '}') {
33
- // if closing double braces that not wrapped with quotes
34
- if (insideDoubleBracesStart) {
35
- indices.push({start: insideDoubleBracesStart, end: i - 1});
36
- insideDoubleBracesStart = null;
37
- if (input[i + 1] === '}') {
38
- // To overcome three "}" in a row considered as two different closing double braces
39
- i += 1;
40
- }
41
- } else {
42
- throw new Error(`Found closing double braces in index ${i - 1} without opening double braces`);
43
- }
44
- }
45
- }
46
-
47
- if (insideDoubleBracesStart) {
48
- throw new Error(`Found opening double braces in index ${insideDoubleBracesStart - '{{'.length} without closing double braces`);
49
- }
50
-
51
- return indices;
52
- }
53
-
54
- const render = (inputJson, template, execOptions = {}) => {
55
- if (typeof template !== 'string') {
10
+ const render = (jsonStr, template, execOptions) => {
11
+ if (typeof template !== "string") {
56
12
  return null;
57
13
  }
58
14
  const indices = findInsideDoubleBracesIndices(template);
59
15
  if (!indices.length) {
60
- // If no jq templates in string, return it
61
16
  return template;
62
17
  }
63
18
 
64
19
  const firstIndex = indices[0];
65
- if (indices.length === 1 && template.trim().startsWith('{{') && template.trim().endsWith('}}')) {
66
- // If entire string is a template, evaluate and return the result with the original type
67
- return jq.exec(inputJson, template.slice(firstIndex.start, firstIndex.end), execOptions);
20
+ if (indices.length === 1) {
21
+ const trimmed = template.trim();
22
+ if (
23
+ trimmed.charCodeAt(0) === CH_OPEN_BRACE &&
24
+ trimmed.charCodeAt(1) === CH_OPEN_BRACE &&
25
+ trimmed.charCodeAt(trimmed.length - 1) === CH_CLOSE_BRACE &&
26
+ trimmed.charCodeAt(trimmed.length - BRACE_PAIR_LEN) === CH_CLOSE_BRACE
27
+ ) {
28
+ return jq.execRaw(
29
+ jsonStr,
30
+ template.slice(firstIndex.start, firstIndex.end),
31
+ execOptions
32
+ );
33
+ }
68
34
  }
69
35
 
70
- let result = template.slice(0, firstIndex.start - '{{'.length); // Initiate result with string until first template start index
71
- indices.forEach((index, i) => {
72
- const jqResult = jq.exec(inputJson, template.slice(index.start, index.end), execOptions);
36
+ let result = template.slice(0, firstIndex.start - BRACE_PAIR_LEN);
37
+ for (let i = 0; i < indices.length; i++) {
38
+ const index = indices[i];
39
+ const jqResult = jq.execRaw(
40
+ jsonStr,
41
+ template.slice(index.start, index.end),
42
+ execOptions
43
+ );
73
44
  result +=
74
- // Add to the result the stringified evaluated jq of the current template
75
- (typeof jqResult === 'string' ? jqResult : JSON.stringify(jqResult)) +
76
- // Add to the result from template end index. if last template index - until the end of string, else until next start index
77
- template.slice(
78
- index.end + '}}'.length,
79
- i + 1 === indices.length ? template.length : indices[i + 1].start - '{{'.length,
80
- );
81
- });
45
+ typeof jqResult === "string" ? jqResult : JSON.stringify(jqResult);
46
+ result += template.slice(
47
+ index.end + BRACE_PAIR_LEN,
48
+ i + 1 === indices.length
49
+ ? template.length
50
+ : indices[i + 1].start - BRACE_PAIR_LEN
51
+ );
52
+ }
82
53
 
83
54
  return result;
84
- }
55
+ };
56
+
57
+ const renderRecursively = (inputJson, template, execOptions) => {
58
+ if (!execOptions) execOptions = {};
59
+ const jsonStr = JSON.stringify(inputJson);
60
+ return _renderRecursively(jsonStr, template, execOptions);
61
+ };
85
62
 
86
- const renderRecursively = (inputJson, template, execOptions = {}) => {
87
- if (typeof template === 'string') {
88
- return render(inputJson, template, execOptions);
63
+ const _renderRecursively = (jsonStr, template, execOptions) => {
64
+ if (typeof template === "string") {
65
+ return render(jsonStr, template, execOptions);
89
66
  }
90
67
  if (Array.isArray(template)) {
91
- return template.map((value) => renderRecursively(inputJson, value, execOptions));
68
+ const len = template.length;
69
+ const result = new Array(len);
70
+ for (let i = 0; i < len; i++) {
71
+ result[i] = _renderRecursively(jsonStr, template[i], execOptions);
72
+ }
73
+ return result;
92
74
  }
93
- if (typeof template === 'object' && template !== null) {
94
- return Object.fromEntries(
95
- Object.entries(template).flatMap(([key, value]) => {
96
- const SPREAD_KEYWORD = "spreadValue";
97
- const keywordMatcher = `^\\{\\{\\s*${SPREAD_KEYWORD}\\(\\s*\\)\\s*\\}\\}$`; // matches {{ <Keyword>() }} with white spaces where you'd expect them
98
-
99
- if (key.trim().match(keywordMatcher)) {
100
- const evaluatedValue = renderRecursively(inputJson, value, execOptions);
101
- if (typeof evaluatedValue !== "object") {
102
- throw new Error(
103
- `Evaluated value should be an object if the key is ${key}. Original value: ${value}, evaluated to: ${JSON.stringify(evaluatedValue)}`
104
- );
105
- }
106
- return Object.entries(evaluatedValue);
107
- }
75
+ if (typeof template === "object" && template !== null) {
76
+ const keys = Object.keys(template);
77
+ const result = {};
78
+ for (let i = 0; i < keys.length; i++) {
79
+ const key = keys[i];
80
+ const value = template[key];
108
81
 
109
- const evaluatedKey = renderRecursively(inputJson, key, execOptions);
110
- if (!['undefined', 'string'].includes(typeof evaluatedKey) && evaluatedKey !== null) {
82
+ if (SPREAD_KEYWORD_PATTERN.test(key)) {
83
+ const evaluatedValue = _renderRecursively(jsonStr, value, execOptions);
84
+ if (typeof evaluatedValue !== "object") {
111
85
  throw new Error(
112
- `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(evaluatedKey)}`,
86
+ `Evaluated value should be an object if the key is ${key}. Original value: ${value}, evaluated to: ${JSON.stringify(
87
+ evaluatedValue
88
+ )}`
113
89
  );
114
90
  }
115
- return evaluatedKey ? [[evaluatedKey, renderRecursively(inputJson, value, execOptions)]] : [];
116
- }),
117
- );
91
+ const spreadKeys = Object.keys(evaluatedValue);
92
+ for (let j = 0; j < spreadKeys.length; j++) {
93
+ result[spreadKeys[j]] = evaluatedValue[spreadKeys[j]];
94
+ }
95
+ continue;
96
+ }
97
+
98
+ const evaluatedKey = _renderRecursively(jsonStr, key, execOptions);
99
+ const keyType = typeof evaluatedKey;
100
+ if (
101
+ keyType !== "undefined" &&
102
+ keyType !== "string" &&
103
+ evaluatedKey !== null
104
+ ) {
105
+ throw new Error(
106
+ `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(
107
+ evaluatedKey
108
+ )}`
109
+ );
110
+ }
111
+ if (evaluatedKey) {
112
+ result[evaluatedKey] = _renderRecursively(jsonStr, value, execOptions);
113
+ }
114
+ }
115
+ return result;
118
116
  }
119
117
 
120
118
  return template;
121
- }
119
+ };
122
120
 
123
121
  module.exports = {
124
- renderRecursively
122
+ renderRecursively,
125
123
  };
@@ -1,129 +1,145 @@
1
- const jq = require('./jq');
1
+ const jq = require("./jq");
2
+ const findInsideDoubleBracesIndices = require("./parse");
2
3
 
3
- const SPREAD_KEYWORD_PATTERN = /^\s*\{\{\s*spreadValue\(\s*\)\s*\}\}\s*$/; // matches {{ <Keyword>() }} with white spaces where you'd expect them
4
+ const CH_OPEN_BRACE = 123; // '{'
5
+ const CH_CLOSE_BRACE = 125; // '}'
6
+ const BRACE_PAIR_LEN = 2; // length of '{{' or '}}'
4
7
 
5
- const findInsideDoubleBracesIndices = (input) => {
6
- let wrappingQuote = null;
7
- let insideDoubleBracesStart = null;
8
- const indices = [];
8
+ const SPREAD_KEYWORD_PATTERN = /^\s*\{\{\s*spreadValue\(\s*\)\s*\}\}\s*$/;
9
9
 
10
- for (let i = 0; i < input.length; i += 1) {
11
- const char = input[i];
12
-
13
- if (insideDoubleBracesStart && char === '\\') {
14
- // If next character is escaped, skip it
15
- i += 1;
16
- }
17
- if (insideDoubleBracesStart && (char === '"' || char === "'")) {
18
- // If inside double braces and inside quotes, ignore braces
19
- if (!wrappingQuote) {
20
- wrappingQuote = char;
21
- } else if (wrappingQuote === char) {
22
- wrappingQuote = null;
23
- }
24
- } else if (!wrappingQuote && char === '{' && i > 0 && input[i - 1] === '{') {
25
- // if opening double braces that not wrapped with quotes
26
- if (insideDoubleBracesStart) {
27
- throw new Error(`Found double braces in index ${i - 1} inside other one in index ${insideDoubleBracesStart - '{{'.length}`);
28
- }
29
- insideDoubleBracesStart = i + 1;
30
- if (input[i + 1] === '{') {
31
- // To overcome three "{" in a row considered as two different opening double braces
32
- i += 1;
33
- }
34
- } else if (!wrappingQuote && char === '}' && i > 0 && input[i - 1] === '}') {
35
- // if closing double braces that not wrapped with quotes
36
- if (insideDoubleBracesStart) {
37
- indices.push({start: insideDoubleBracesStart, end: i - 1});
38
- insideDoubleBracesStart = null;
39
- if (input[i + 1] === '}') {
40
- // To overcome three "}" in a row considered as two different closing double braces
41
- i += 1;
42
- }
43
- } else {
44
- throw new Error(`Found closing double braces in index ${i - 1} without opening double braces`);
45
- }
46
- }
47
- }
48
-
49
- if (insideDoubleBracesStart) {
50
- throw new Error(`Found opening double braces in index ${insideDoubleBracesStart - '{{'.length} without closing double braces`);
51
- }
52
-
53
- return indices;
54
- }
55
-
56
- const renderAsync =async (inputJson, template, execOptions = {}) => {
57
- if (typeof template !== 'string') {
10
+ const renderAsync = async (jsonStr, template, execOptions) => {
11
+ if (typeof template !== "string") {
58
12
  return null;
59
13
  }
60
14
  const indices = findInsideDoubleBracesIndices(template);
61
15
  if (!indices.length) {
62
- // If no jq templates in string, return it
63
16
  return template;
64
17
  }
65
18
 
66
19
  const firstIndex = indices[0];
67
- if (indices.length === 1 && template.trim().startsWith('{{') && template.trim().endsWith('}}')) {
68
- // If entire string is a template, evaluate and return the result with the original type
69
- return jq.execAsync(inputJson, template.slice(firstIndex.start, firstIndex.end), execOptions);
20
+ if (indices.length === 1) {
21
+ const trimmed = template.trim();
22
+ if (
23
+ trimmed.charCodeAt(0) === CH_OPEN_BRACE &&
24
+ trimmed.charCodeAt(1) === CH_OPEN_BRACE &&
25
+ trimmed.charCodeAt(trimmed.length - 1) === CH_CLOSE_BRACE &&
26
+ trimmed.charCodeAt(trimmed.length - BRACE_PAIR_LEN) === CH_CLOSE_BRACE
27
+ ) {
28
+ return jq.execRawAsync(
29
+ jsonStr,
30
+ template.slice(firstIndex.start, firstIndex.end),
31
+ execOptions
32
+ );
33
+ }
70
34
  }
71
35
 
72
- let result = template.slice(0, firstIndex.start - '{{'.length); // Initiate result with string until first template start index
36
+ let result = template.slice(0, firstIndex.start - BRACE_PAIR_LEN);
73
37
  for (let i = 0; i < indices.length; i++) {
74
38
  const index = indices[i];
75
- const jqResult = await jq.execAsync(inputJson, template.slice(index.start, index.end), execOptions);
39
+ const jqResult = await jq.execRawAsync(
40
+ jsonStr,
41
+ template.slice(index.start, index.end),
42
+ execOptions
43
+ );
76
44
  result +=
77
- // Add to the result the stringified evaluated jq of the current template
78
- (typeof jqResult === 'string' ? jqResult : JSON.stringify(jqResult)) +
79
- // Add to the result from template end index. if last template index - until the end of string, else until next start index
80
- template.slice(
81
- index.end + '}}'.length,
82
- i + 1 === indices.length ? template.length : indices[i + 1].start - '{{'.length,
83
- );
84
- }
45
+ typeof jqResult === "string" ? jqResult : JSON.stringify(jqResult);
46
+ result += template.slice(
47
+ index.end + BRACE_PAIR_LEN,
48
+ i + 1 === indices.length
49
+ ? template.length
50
+ : indices[i + 1].start - BRACE_PAIR_LEN
51
+ );
52
+ }
85
53
 
86
54
  return result;
87
- }
55
+ };
56
+
57
+ const renderRecursivelyAsync = async (inputJson, template, execOptions) => {
58
+ if (!execOptions) execOptions = {};
59
+ const jsonStr = JSON.stringify(inputJson);
60
+ return _renderRecursivelyAsync(jsonStr, template, execOptions);
61
+ };
88
62
 
89
- const renderRecursivelyAsync = async(inputJson, template, execOptions = {}) => {
90
- if (typeof template === 'string') {
91
- return renderAsync(inputJson, template, execOptions);
63
+ const _renderRecursivelyAsync = async (jsonStr, template, execOptions) => {
64
+ if (typeof template === "string") {
65
+ return renderAsync(jsonStr, template, execOptions);
92
66
  }
93
67
  if (Array.isArray(template)) {
94
- return Promise.all(template.map((value) => renderRecursivelyAsync(inputJson, value, execOptions)));
68
+ const len = template.length;
69
+ const promises = new Array(len);
70
+ for (let i = 0; i < len; i++) {
71
+ promises[i] = _renderRecursivelyAsync(jsonStr, template[i], execOptions);
72
+ }
73
+ return Promise.all(promises);
95
74
  }
96
- if (typeof template === 'object' && template !== null) {
97
- const t = Object.entries(template).map(async([key, value]) => {
98
-
99
- if (SPREAD_KEYWORD_PATTERN.test(key)) {
100
- const evaluatedValue = await renderRecursivelyAsync(inputJson, value, execOptions);
101
- if (typeof evaluatedValue !== "object") {
102
- throw new Error(
103
- `Evaluated value should be an object if the key is ${key}. Original value: ${value}, evaluated to: ${JSON.stringify(evaluatedValue)}`
104
- );
105
- }
106
- return Object.entries(evaluatedValue);
107
- }
108
-
109
- const evaluatedKey = await renderRecursivelyAsync(inputJson, key, execOptions);
110
- if (typeof evaluatedKey !== 'string' && evaluatedKey != null) {
111
- throw new Error(
112
- `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(evaluatedKey)}`,
113
- );
75
+ if (typeof template === "object" && template !== null) {
76
+ const keys = Object.keys(template);
77
+ const len = keys.length;
78
+ const promises = new Array(len);
79
+ for (let i = 0; i < len; i++) {
80
+ const key = keys[i];
81
+ promises[i] = _processKeyAsync(jsonStr, key, template[key], execOptions);
82
+ }
83
+ const entries = await Promise.all(promises);
84
+ const result = {};
85
+ for (let i = 0; i < entries.length; i++) {
86
+ const entry = entries[i];
87
+ if (entry) {
88
+ const pairs = entry.pairs;
89
+ for (let j = 0; j < pairs.length; j++) {
90
+ result[pairs[j][0]] = pairs[j][1];
114
91
  }
115
- return evaluatedKey ? [[evaluatedKey, await renderRecursivelyAsync(inputJson, value, execOptions)]] : [];
116
- });
117
-
118
-
119
- return Object.fromEntries((await Promise.all(t)).flat());
92
+ }
93
+ }
94
+ return result;
95
+ }
120
96
 
97
+ return template;
98
+ };
121
99
 
100
+ const _processKeyAsync = async (jsonStr, key, value, execOptions) => {
101
+ if (SPREAD_KEYWORD_PATTERN.test(key)) {
102
+ const evaluatedValue = await _renderRecursivelyAsync(
103
+ jsonStr,
104
+ value,
105
+ execOptions
106
+ );
107
+ if (typeof evaluatedValue !== "object") {
108
+ throw new Error(
109
+ `Evaluated value should be an object if the key is ${key}. Original value: ${value}, evaluated to: ${JSON.stringify(
110
+ evaluatedValue
111
+ )}`
112
+ );
113
+ }
114
+ const spreadKeys = Object.keys(evaluatedValue);
115
+ const pairs = new Array(spreadKeys.length);
116
+ for (let i = 0; i < spreadKeys.length; i++) {
117
+ pairs[i] = [spreadKeys[i], evaluatedValue[spreadKeys[i]]];
118
+ }
119
+ return { pairs };
122
120
  }
123
121
 
124
- return template;
125
- }
122
+ const evaluatedKey = await _renderRecursivelyAsync(jsonStr, key, execOptions);
123
+ if (typeof evaluatedKey !== "string" && evaluatedKey != null) {
124
+ throw new Error(
125
+ `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(
126
+ evaluatedKey
127
+ )}`
128
+ );
129
+ }
130
+ if (evaluatedKey) {
131
+ return {
132
+ pairs: [
133
+ [
134
+ evaluatedKey,
135
+ await _renderRecursivelyAsync(jsonStr, value, execOptions),
136
+ ],
137
+ ],
138
+ };
139
+ }
140
+ return null;
141
+ };
126
142
 
127
143
  module.exports = {
128
- renderRecursivelyAsync
144
+ renderRecursivelyAsync,
129
145
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@port-labs/jq-node-bindings",
3
- "version": "v1.1.0-dev",
3
+ "version": "v1.1.0-dev.1",
4
4
  "description": "Node.js bindings for JQ",
5
5
  "jq-node-bindings": "v1.1.0-dev",
6
6
  "main": "lib/index.js",
@@ -45,4 +45,3 @@
45
45
  "node": ">=6.0.0"
46
46
  }
47
47
  }
48
-
@@ -164,15 +164,15 @@ describe('template', () => {
164
164
  expect(await jq.renderRecursivelyAsync({}, '{{env}}')).toEqual({});
165
165
  })
166
166
  it('test throw on error', async () => {
167
- expect(async () => { await jq.renderRecursivelyAsync({}, '{{foo}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
167
+ expect(async () => { await jq.renderRecursivelyAsync({}, '{{foo}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1");
168
168
  expect(async () => { await jq.renderRecursivelyAsync({}, '{{1/0}}', {throwOnError: true}) }).rejects.toThrow("number (1) and number (0) cannot be divided because the divisor is zero");
169
- expect(async () => { await jq.renderRecursivelyAsync({}, '{{{}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: syntax error, unexpected end of file (Unix shell quoting issues?) at <top-level>, line 1:");
170
- expect(async () => { await jq.renderRecursivelyAsync({}, '{{ {(0):1} }}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: Cannot use number (0) as object key at <top-level>, line 1:");
171
- expect(async () => { await jq.renderRecursivelyAsync({}, '{{if true then 1 else 0}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: Possibly unterminated 'if' statement at <top-level>, line 1:");
169
+ expect(async () => { await jq.renderRecursivelyAsync({}, '{{{}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: syntax error, unexpected end of file");
170
+ expect(async () => { await jq.renderRecursivelyAsync({}, '{{ {(0):1} }}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: Cannot use number (0) as object key at <top-level>, line 1");
171
+ expect(async () => { await jq.renderRecursivelyAsync({}, '{{if true then 1 else 0}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: Possibly unterminated 'if' statement at <top-level>, line 1");
172
172
  expect(async () => { await jq.renderRecursivelyAsync({}, '{{null | map(.+1)}}', {throwOnError: true}) }).rejects.toThrow("jq: error: Cannot iterate over null (null)");
173
173
  expect(async () => { await jq.renderRecursivelyAsync({foo: "bar"}, '{{.foo + 1}}', {throwOnError: true}) }).rejects.toThrow("jq: error: string (\"bar\") and number (1) cannot be added");
174
- expect(async () => { await jq.renderRecursivelyAsync({}, '{{foo}}/{{bar}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
175
- expect(async () => { await jq.renderRecursivelyAsync({}, '/{{foo}}/', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
174
+ expect(async () => { await jq.renderRecursivelyAsync({}, '{{foo}}/{{bar}}', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1");
175
+ expect(async () => { await jq.renderRecursivelyAsync({}, '/{{foo}}/', {throwOnError: true}) }).rejects.toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1");
176
176
  expect(async () => { await jq.renderRecursivelyAsync({}, { "{{ spreadValue() }}": "str" }, { throwOnError: true }) })
177
177
  .rejects.toThrow('Evaluated value should be an object if the key is {{ spreadValue() }}. Original value: str, evaluated to: "str"');
178
178
  expect(async () => { await jq.renderRecursivelyAsync({}, { "{{ spreadValue() }}": "{{ \"str\" }}" }, { throwOnError: true }) })