ga4-export-fixer 0.1.6-dev.5 → 0.1.6-dev.7

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/helpers.js CHANGED
@@ -563,8 +563,14 @@ Aggregation
563
563
  * // => SQL expression for the last user_id by event_timestamp.
564
564
  */
565
565
  const aggregateValue = (column, aggregateType, timestampColumn) => {
566
- if (typeof column === 'undefined' || typeof timestampColumn === 'undefined') {
567
- throw new Error("aggregateValue: 'column' and 'timestampColumn' are required parameters and must be defined.");
566
+ if (typeof column === 'undefined') {
567
+ throw new Error("aggregateValue: 'column' is a required parameter and must be defined.");
568
+ }
569
+ if (typeof aggregateType === 'undefined') {
570
+ throw new Error("aggregateValue: 'aggregateType' is a required parameter and must be defined.");
571
+ }
572
+ if ((aggregateType === 'first' || aggregateType === 'last') && typeof timestampColumn === 'undefined') {
573
+ throw new Error(`aggregateValue: 'timestampColumn' is required when aggregateType is '${aggregateType}'.`);
568
574
  }
569
575
 
570
576
  if (aggregateType === 'max') {
package/index.js CHANGED
@@ -1,28 +1,18 @@
1
1
  const helpers = require('./helpers.js');
2
2
  const ga4EventsEnhanced = require('./tables/ga4EventsEnhanced.js');
3
3
  const preOperations = require('./preOperations.js');
4
- const { validateConfig } = require('./inputValidation.js');
4
+ const { validateBaseConfig, validateEnhancedEventsConfig } = require('./inputValidation.js');
5
5
  const { mergeSQLConfigurations } = require('./utils.js');
6
6
  const { baseConfig } = require('./defaultConfig.js');
7
7
 
8
8
  // export setPreOperations with default configuration for usage with downstream tables
9
9
  const setPreOperations = (config) => {
10
- if (!config || !config.self) {
11
- throw new Error('setPreOperations: config.self is required. Pass the table\'s "self()" reference in the config object.');
12
- }
13
- if (typeof config.incremental !== 'boolean') {
14
- throw new Error('setPreOperations: config.incremental is required. Pass a boolean indicating whether the table uses incremental mode.');
15
- }
16
-
17
- /*
18
- Todo:
19
- - validation for baseConfig -> include in this function
20
- - separate config merge into another function
21
- - don't create a full ga4EnhancedEvents config for this case
22
- */
23
-
10
+ // merge the input config with the defaults
24
11
  const mergedConfig = mergeSQLConfigurations(baseConfig, config);
25
12
 
13
+ // do input validation on the merged config
14
+ validateBaseConfig(mergedConfig);
15
+
26
16
  return preOperations.setPreOperations(mergedConfig);
27
17
  };
28
18
 
@@ -30,5 +20,6 @@ module.exports = {
30
20
  helpers,
31
21
  ga4EventsEnhanced,
32
22
  setPreOperations,
33
- validateConfig
23
+ validateBaseConfig,
24
+ validateEnhancedEventsConfig
34
25
  };
@@ -1,5 +1,86 @@
1
1
  const { isDataformTableReferenceObject } = require('./utils.js');
2
2
 
3
+ /**
4
+ * Validates the base configuration fields shared across all table types.
5
+ * These correspond to the fields defined in baseConfig (defaultConfig.js):
6
+ * self, incremental, test, testConfig, and preOperations.
7
+ *
8
+ * @param {Object} config - The merged configuration object to validate.
9
+ * @throws {Error} If any base configuration value is invalid or missing.
10
+ */
11
+ const validateBaseConfig = (config) => {
12
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
13
+ throw new Error(`config must be a non-null object. Received: ${JSON.stringify(config)}`);
14
+ }
15
+
16
+ // self - required, must be valid format
17
+ if (config.test !== true) {
18
+ if (typeof config.self !== 'string' || !config.self.trim() || !/^`[^`]+`$/.test(config.self.trim())) {
19
+ throw new Error(`config.self is required when config.test !== true and must be a non-empty string in format '\`project.dataset.table\`' (using the ref() function). Received: ${JSON.stringify(config.self)}`);
20
+ }
21
+ }
22
+
23
+ // incremental - required, must be boolean
24
+ if (typeof config.incremental !== 'boolean') {
25
+ throw new Error(`config.incremental must be a boolean. Received: ${JSON.stringify(config.incremental)}`);
26
+ }
27
+
28
+ // test - optional; when defined, must be a boolean
29
+ if (typeof config.test !== 'undefined' && typeof config.test !== 'boolean') {
30
+ throw new Error(`config.test must be a boolean when defined. Received: ${JSON.stringify(config.test)}`);
31
+ }
32
+
33
+ // testConfig - optional; when included, must be an object with optional dateRangeStart and dateRangeEnd
34
+ if (typeof config.testConfig !== 'undefined') {
35
+ if (!config.testConfig || typeof config.testConfig !== 'object' || Array.isArray(config.testConfig)) {
36
+ throw new Error(`config.testConfig must be an object when included. Received: ${JSON.stringify(config.testConfig)}`);
37
+ }
38
+ if (config.testConfig.dateRangeStart !== undefined && (typeof config.testConfig.dateRangeStart !== 'string' || !config.testConfig.dateRangeStart.trim())) {
39
+ throw new Error(`config.testConfig.dateRangeStart must be a non-empty string (SQL date expression) when provided. Received: ${JSON.stringify(config.testConfig.dateRangeStart)}`);
40
+ }
41
+ if (config.testConfig.dateRangeEnd !== undefined && (typeof config.testConfig.dateRangeEnd !== 'string' || !config.testConfig.dateRangeEnd.trim())) {
42
+ throw new Error(`config.testConfig.dateRangeEnd must be a non-empty string (SQL date expression) when provided. Received: ${JSON.stringify(config.testConfig.dateRangeEnd)}`);
43
+ }
44
+ }
45
+
46
+ // preOperations - required
47
+ if (config.preOperations === undefined) {
48
+ throw new Error("config.preOperations is required.");
49
+ }
50
+ if (!config.preOperations || typeof config.preOperations !== 'object' || Array.isArray(config.preOperations)) {
51
+ throw new Error(`config.preOperations must be an object. Received: ${JSON.stringify(config.preOperations)}`);
52
+ }
53
+ if (config.preOperations.numberOfPreviousDaysToScan === undefined) {
54
+ throw new Error("config.preOperations.numberOfPreviousDaysToScan is required.");
55
+ }
56
+ const v = config.preOperations.numberOfPreviousDaysToScan;
57
+ if (typeof v !== 'number' || isNaN(v) || !Number.isInteger(v) || v < 0) {
58
+ throw new Error(`config.preOperations.numberOfPreviousDaysToScan must be a non-negative integer. Received: ${JSON.stringify(v)}`);
59
+ }
60
+ if (config.preOperations.dateRangeStartFullRefresh === undefined || config.preOperations.dateRangeStartFullRefresh === null) {
61
+ throw new Error("config.preOperations.dateRangeStartFullRefresh is required.");
62
+ }
63
+ if (typeof config.preOperations.dateRangeStartFullRefresh !== 'string' || !config.preOperations.dateRangeStartFullRefresh.trim()) {
64
+ throw new Error(`config.preOperations.dateRangeStartFullRefresh must be a non-empty string (SQL date expression). Received: ${JSON.stringify(config.preOperations.dateRangeStartFullRefresh)}`);
65
+ }
66
+ if (config.preOperations.dateRangeEnd === undefined || config.preOperations.dateRangeEnd === null) {
67
+ throw new Error("config.preOperations.dateRangeEnd is required.");
68
+ }
69
+ if (typeof config.preOperations.dateRangeEnd !== 'string' || !config.preOperations.dateRangeEnd.trim()) {
70
+ throw new Error(`config.preOperations.dateRangeEnd must be a non-empty string (SQL date expression). Received: ${JSON.stringify(config.preOperations.dateRangeEnd)}`);
71
+ }
72
+ if (config.preOperations.incrementalStartOverride !== undefined && config.preOperations.incrementalStartOverride !== null && config.preOperations.incrementalStartOverride !== '') {
73
+ if (typeof config.preOperations.incrementalStartOverride !== 'string' || !config.preOperations.incrementalStartOverride.trim()) {
74
+ throw new Error(`config.preOperations.incrementalStartOverride must be a non-empty string when provided. Received: ${JSON.stringify(config.preOperations.incrementalStartOverride)}`);
75
+ }
76
+ }
77
+ if (config.preOperations.incrementalEndOverride !== undefined && config.preOperations.incrementalEndOverride !== null && config.preOperations.incrementalEndOverride !== '') {
78
+ if (typeof config.preOperations.incrementalEndOverride !== 'string' || !config.preOperations.incrementalEndOverride.trim()) {
79
+ throw new Error(`config.preOperations.incrementalEndOverride must be a non-empty string when provided. Received: ${JSON.stringify(config.preOperations.incrementalEndOverride)}`);
80
+ }
81
+ }
82
+ };
83
+
3
84
  /**
4
85
  * Validates a GA4 export fixer configuration object.
5
86
  * Validation is performed on mergedConfig (default values merged with user input).
@@ -9,12 +90,19 @@ const { isDataformTableReferenceObject } = require('./utils.js');
9
90
  * @param {Object} config - The merged configuration object to validate.
10
91
  * @throws {Error} If any configuration value is invalid or missing.
11
92
  */
12
- const validateConfig = (config) => {
93
+ const validateEnhancedEventsConfig = (config) => {
13
94
  try {
14
95
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
15
96
  throw new Error(`config must be a non-null object. Received: ${JSON.stringify(config)}`);
16
97
  }
17
98
 
99
+ // base config fields (self, incremental, test, testConfig, preOperations)
100
+ validateBaseConfig(config);
101
+
102
+ /*
103
+ Rest of the validations are related to ga4_events_enhanced table specific fields
104
+ */
105
+
18
106
  // sourceTable - required; string or Dataform table reference
19
107
  if (config.sourceTable === undefined || config.sourceTable === null) {
20
108
  throw new Error("config.sourceTable is required. Provide a Dataform table reference (using the ref() function) or a string in format '`project.dataset.table`'.");
@@ -32,19 +120,6 @@ const validateConfig = (config) => {
32
120
  throw new Error(`config.sourceTable must be a Dataform table reference object or a string in format '\`project.dataset.table\`'. Received: ${JSON.stringify(config.sourceTable)}`);
33
121
  }
34
122
 
35
- // self - required when using Dataform; must be valid format
36
- // config.self is required when config.test === true and must be a non-empty string in format '\`project.dataset.table\`' (using the ref() function)
37
- if (config.test !== true) {
38
- if (typeof config.self !== 'string' || !config.self.trim() || !/^`[^`]+`$/.test(config.self.trim())) {
39
- throw new Error(`config.self is required when config.test === true and must be a non-empty string in format '\`project.dataset.table\`' (using the ref() function). Received: ${JSON.stringify(config.self)}`);
40
- }
41
- }
42
-
43
- // incremental - required when using Dataform; must be boolean
44
- if (typeof config.incremental !== 'boolean') {
45
- throw new Error(`config.incremental must be a boolean. Received: ${JSON.stringify(config.incremental)}`);
46
- }
47
-
48
123
  // schemaLock - optional; must be undefined or a string in "YYYYMMDD" format (e.g., "20260101")
49
124
  if (typeof config.schemaLock !== 'undefined') {
50
125
  if (typeof config.schemaLock !== 'string' || !/^\d{8}$/.test(config.schemaLock)) {
@@ -132,66 +207,11 @@ const validateConfig = (config) => {
132
207
  throw new Error(`config.dataIsFinal.detectionMethod must be 'DAY_THRESHOLD' when only intraday export is enabled (config.includedExportTypes.daily is false). A dayThreshold of 1 is recommended for intraday-only configurations. Received: ${JSON.stringify(config.dataIsFinal.detectionMethod)}`);
133
208
  }
134
209
 
135
- // test - optional; when defined, must be a boolean
136
- if (typeof config.test !== 'undefined' && typeof config.test !== 'boolean') {
137
- throw new Error(`config.test must be a boolean when defined. Received: ${JSON.stringify(config.test)}`);
138
- }
139
-
140
- // testConfig - optional; when included, must be an object with optional dateRangeStart and dateRangeEnd
141
- if (typeof config.testConfig !== 'undefined') {
142
- if (!config.testConfig || typeof config.testConfig !== 'object' || Array.isArray(config.testConfig)) {
143
- throw new Error(`config.testConfig must be an object when included. Received: ${JSON.stringify(config.testConfig)}`);
144
- }
145
- if (config.testConfig.dateRangeStart !== undefined && (typeof config.testConfig.dateRangeStart !== 'string' || !config.testConfig.dateRangeStart.trim())) {
146
- throw new Error(`config.testConfig.dateRangeStart must be a non-empty string (SQL date expression) when provided. Received: ${JSON.stringify(config.testConfig.dateRangeStart)}`);
147
- }
148
- if (config.testConfig.dateRangeEnd !== undefined && (typeof config.testConfig.dateRangeEnd !== 'string' || !config.testConfig.dateRangeEnd.trim())) {
149
- throw new Error(`config.testConfig.dateRangeEnd must be a non-empty string (SQL date expression) when provided. Received: ${JSON.stringify(config.testConfig.dateRangeEnd)}`);
150
- }
151
- }
152
-
153
210
  // bufferDays - required
154
211
  if (typeof config.bufferDays !== 'number' || !Number.isInteger(config.bufferDays) || config.bufferDays < 0) {
155
212
  throw new Error(`config.bufferDays must be a non-negative integer. Received: ${JSON.stringify(config.bufferDays)}`);
156
213
  }
157
214
 
158
- // preOperations - required
159
- if (config.preOperations === undefined) {
160
- throw new Error("config.preOperations is required.");
161
- }
162
- if (!config.preOperations || typeof config.preOperations !== 'object' || Array.isArray(config.preOperations)) {
163
- throw new Error(`config.preOperations must be an object. Received: ${JSON.stringify(config.preOperations)}`);
164
- }
165
- if (config.preOperations.numberOfPreviousDaysToScan === undefined) {
166
- throw new Error("config.preOperations.numberOfPreviousDaysToScan is required.");
167
- }
168
- const v = config.preOperations.numberOfPreviousDaysToScan;
169
- if (typeof v !== 'number' || isNaN(v) || !Number.isInteger(v) || v < 0) {
170
- throw new Error(`config.preOperations.numberOfPreviousDaysToScan must be a non-negative integer. Received: ${JSON.stringify(v)}`);
171
- }
172
- if (config.preOperations.dateRangeStartFullRefresh === undefined || config.preOperations.dateRangeStartFullRefresh === null) {
173
- throw new Error("config.preOperations.dateRangeStartFullRefresh is required.");
174
- }
175
- if (typeof config.preOperations.dateRangeStartFullRefresh !== 'string' || !config.preOperations.dateRangeStartFullRefresh.trim()) {
176
- throw new Error(`config.preOperations.dateRangeStartFullRefresh must be a non-empty string (SQL date expression). Received: ${JSON.stringify(config.preOperations.dateRangeStartFullRefresh)}`);
177
- }
178
- if (config.preOperations.dateRangeEnd === undefined || config.preOperations.dateRangeEnd === null) {
179
- throw new Error("config.preOperations.dateRangeEnd is required.");
180
- }
181
- if (typeof config.preOperations.dateRangeEnd !== 'string' || !config.preOperations.dateRangeEnd.trim()) {
182
- throw new Error(`config.preOperations.dateRangeEnd must be a non-empty string (SQL date expression). Received: ${JSON.stringify(config.preOperations.dateRangeEnd)}`);
183
- }
184
- if (config.preOperations.incrementalStartOverride !== undefined && config.preOperations.incrementalStartOverride !== null && config.preOperations.incrementalStartOverride !== '') {
185
- if (typeof config.preOperations.incrementalStartOverride !== 'string' || !config.preOperations.incrementalStartOverride.trim()) {
186
- throw new Error(`config.preOperations.incrementalStartOverride must be a non-empty string when provided. Received: ${JSON.stringify(config.preOperations.incrementalStartOverride)}`);
187
- }
188
- }
189
- if (config.preOperations.incrementalEndOverride !== undefined && config.preOperations.incrementalEndOverride !== null && config.preOperations.incrementalEndOverride !== '') {
190
- if (typeof config.preOperations.incrementalEndOverride !== 'string' || !config.preOperations.incrementalEndOverride.trim()) {
191
- throw new Error(`config.preOperations.incrementalEndOverride must be a non-empty string when provided. Received: ${JSON.stringify(config.preOperations.incrementalEndOverride)}`);
192
- }
193
- }
194
-
195
215
  // Array fields - all required
196
216
  const stringArrayKeys = ['defaultExcludedEventParams', 'excludedEventParams', 'sessionParams', 'defaultExcludedEvents', 'excludedEvents', 'excludedColumns'];
197
217
  for (const key of stringArrayKeys) {
@@ -242,5 +262,6 @@ const validateConfig = (config) => {
242
262
  };
243
263
 
244
264
  module.exports = {
245
- validateConfig
265
+ validateBaseConfig,
266
+ validateEnhancedEventsConfig
246
267
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga4-export-fixer",
3
- "version": "0.1.6-dev.5",
3
+ "version": "0.1.6-dev.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -148,7 +148,7 @@ const generateEnhancedEventsSQL = (config) => {
148
148
  const mergedConfig = utils.mergeSQLConfigurations(defaultConfig, config);
149
149
 
150
150
  // validate the config and throw an error if it's invalid
151
- inputValidation.validateConfig(mergedConfig);
151
+ inputValidation.validateEnhancedEventsConfig(mergedConfig);
152
152
 
153
153
  if (!mergedConfig.sourceTable || typeof mergedConfig.sourceTable !== 'string' || mergedConfig.sourceTable.trim() === '') {
154
154
  throw new Error("generateEnhancedEventsSQL: 'sourceTable' is a required parameter in config and must be a non-empty string.");