ga4-export-fixer 0.6.1-dev.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/utils.js +63 -29
package/README.md CHANGED
@@ -157,7 +157,7 @@ Include the package in the package.json file in your Dataform repository.
157
157
  {
158
158
  "dependencies": {
159
159
  "@dataform/core": "3.0.42",
160
- "ga4-export-fixer": "0.6.0"
160
+ "ga4-export-fixer": "0.6.1"
161
161
  }
162
162
  }
163
163
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga4-export-fixer",
3
- "version": "0.6.1-dev.0",
3
+ "version": "0.6.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
package/utils.js CHANGED
@@ -33,53 +33,87 @@ const mergeUniqueArrays = (...arrays) => {
33
33
  * @returns {string} Generated SQL query
34
34
  */
35
35
  const queryBuilder = (steps) => {
36
+ const INDENT = 2;
37
+ const pad = ' '.repeat(INDENT);
38
+
39
+ // Re-indents a multi-line SQL fragment so that continuation lines
40
+ // (lines after the first) have a consistent base indentation.
41
+ // Preserves relative indentation within the fragment.
42
+ const reindent = (sql, targetIndent) => {
43
+ if (!sql.includes('\n')) return sql;
44
+ const lines = sql.split('\n');
45
+ const continuationLines = lines.slice(1).filter(l => l.trim());
46
+ if (continuationLines.length === 0) return sql;
47
+ const minIndent = Math.min(
48
+ ...continuationLines.map(l => l.match(/^ */)[0].length)
49
+ );
50
+ const p = ' '.repeat(targetIndent);
51
+ return lines[0] + '\n' + lines.slice(1)
52
+ .map(l => l.trim() ? p + l.slice(minIndent) : '')
53
+ .join('\n');
54
+ };
55
+
56
+ // Shifts an entire SQL block right by the given number of spaces.
57
+ // Preserves relative indentation within the block.
58
+ const indentBlock = (sql, spaces) => {
59
+ const p = ' '.repeat(spaces);
60
+ return sql.split('\n')
61
+ .map(l => l.trim() ? p + l : '')
62
+ .join('\n');
63
+ };
64
+
36
65
  // Helper function to turn step.columns into SQL string
37
66
  const columnsToSQL = (columns) => {
38
67
  return Object.entries(columns)
39
68
  // exclude all columns that have been explicitly set to undefined
40
69
  .filter(([key, value]) => value !== undefined)
41
70
  .map(([key, value]) => {
71
+ let entry;
42
72
  // if the key and value are the same, return the value as is (i.e. no alias)
43
73
  if (key === value) {
44
- return value;
45
- }
74
+ entry = value;
46
75
  // if the key starts with '[sql]', return the value as is (i.e. no alias)
47
- if (key.startsWith('[sql]')) {
48
- return value;
76
+ } else if (key.startsWith('[sql]')) {
77
+ entry = value;
78
+ } else {
79
+ entry = `${value} as ${key}`;
49
80
  }
50
- return `${value} as ${key}`;
81
+ return reindent(entry, INDENT);
51
82
  })
52
- .join(',\n ');
83
+ .join(',\n' + pad);
53
84
  };
54
85
 
55
86
  const selectSQL = (step) => {
56
- const leftJoinClauses = step.leftJoin ? step.leftJoin.map(join => `left join\n ${join.table} ${join.condition}`) : [];
57
- const whereClause = step.where ? `where\n ${step.where}` : '';
58
- const groupByClause = step.groupBy ? `group by\n ${step.groupBy.join(', ')}` : '';
59
-
60
- return `select
61
- ${columnsToSQL(step.columns)}
62
- from
63
- ${step.from}
64
- ${leftJoinClauses.join('\n')}
65
- ${whereClause}
66
- ${groupByClause}`;
87
+ const parts = [`select\n${pad}${columnsToSQL(step.columns)}`];
88
+ parts.push(`from\n${pad}${step.from}`);
89
+
90
+ if (step.leftJoin) {
91
+ step.leftJoin.forEach(join => {
92
+ parts.push(`left join\n${pad}${join.table} ${join.condition}`);
93
+ });
94
+ }
95
+
96
+ if (step.where) {
97
+ parts.push(`where\n${pad}${reindent(step.where, INDENT)}`);
98
+ }
99
+
100
+ if (step.groupBy) {
101
+ parts.push(`group by\n${pad}${step.groupBy.join(', ')}`);
102
+ }
103
+
104
+ return parts.join('\n');
67
105
  };
68
106
 
69
- let sql = "";
70
107
  if (steps.length === 1) {
71
- // Only one step, no CTE needed
72
- const step = steps[0];
73
- sql = selectSQL(step);
74
- } else {
75
- // Multiple steps, all but last are CTEs
76
- const ctes = steps.slice(0, -1).map(step => {
77
- return `${step.name} as (${selectSQL(step)})`;
78
- });
79
- const lastStep = steps[steps.length - 1];
80
- sql = `with ${ctes.join(',\n ')}\n${selectSQL(lastStep)}`;
108
+ return selectSQL(steps[0]);
81
109
  }
82
- return sql;
110
+
111
+ const ctes = steps.slice(0, -1).map(step => {
112
+ const body = indentBlock(selectSQL(step), INDENT);
113
+ return `${step.name} as (\n${body}\n)`;
114
+ });
115
+ const lastStep = steps[steps.length - 1];
116
+ return `with ${ctes.join(',\n')}\n${selectSQL(lastStep)}`;
83
117
  };
84
118
 
85
119
  /**