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 +8 -2
- package/index.js +7 -16
- package/inputValidation.js +91 -70
- package/package.json +1 -1
- package/tables/ga4EventsEnhanced.js +1 -1
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'
|
|
567
|
-
throw new Error("aggregateValue: 'column'
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
validateBaseConfig,
|
|
24
|
+
validateEnhancedEventsConfig
|
|
34
25
|
};
|
package/inputValidation.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
265
|
+
validateBaseConfig,
|
|
266
|
+
validateEnhancedEventsConfig
|
|
246
267
|
};
|
package/package.json
CHANGED
|
@@ -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.
|
|
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.");
|