@whaly/connector-sdk 0.1.1 → 0.3.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/README.md +92 -0
- package/dist/index.d.mts +568 -200
- package/dist/index.d.ts +568 -200
- package/dist/index.js +1389 -584
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1354 -579
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -5
package/dist/index.js
CHANGED
|
@@ -35,9 +35,17 @@ __export(index_exports, {
|
|
|
35
35
|
Authenticator: () => Authenticator,
|
|
36
36
|
BATCH_INTERVAL_MS: () => BATCH_INTERVAL_MS,
|
|
37
37
|
BigQueryTarget: () => BigQueryTarget,
|
|
38
|
+
CloudStorageService: () => CloudStorageService,
|
|
38
39
|
CounterMetric: () => CounterMetric,
|
|
40
|
+
CsvExtractionConfigBuilder: () => CsvExtractionConfigBuilder,
|
|
39
41
|
DEFAULT_MAX_CONCURRENT_STREAMS: () => DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
40
42
|
EXECUTION_TIME_METRIC_NAME: () => EXECUTION_TIME_METRIC_NAME,
|
|
43
|
+
ExcelExtractionConfigBuilder: () => ExcelExtractionConfigBuilder,
|
|
44
|
+
FileFormat: () => FileFormat,
|
|
45
|
+
FilePatterns: () => FilePatterns,
|
|
46
|
+
FileStream: () => FileStream,
|
|
47
|
+
FileTap: () => FileTap,
|
|
48
|
+
GCSStateProvider: () => GCSStateProvider,
|
|
41
49
|
ITarget: () => ITarget,
|
|
42
50
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
43
51
|
MissingSchemaError: () => MissingSchemaError,
|
|
@@ -45,22 +53,37 @@ __export(index_exports, {
|
|
|
45
53
|
ROWS_SYNCED_METRIC_NAME: () => ROWS_SYNCED_METRIC_NAME,
|
|
46
54
|
RecordMessage: () => RecordMessage,
|
|
47
55
|
RenameColumnStore: () => RenameColumnStore,
|
|
56
|
+
ReplicationMethod: () => ReplicationMethod,
|
|
48
57
|
ReplicationMethodMessage: () => ReplicationMethodMessage,
|
|
49
|
-
Resolver: () => Resolver,
|
|
50
58
|
SchemaMessage: () => SchemaMessage,
|
|
51
59
|
SchemaValidationError: () => SchemaValidationError,
|
|
60
|
+
SftpClient: () => import_ssh2_sftp_client2.default,
|
|
61
|
+
SftpService: () => SftpService,
|
|
52
62
|
StateMessage: () => StateMessage,
|
|
53
63
|
StateService: () => StateService,
|
|
54
64
|
Stream: () => Stream,
|
|
55
|
-
StreamMetadata: () => StreamMetadata,
|
|
56
65
|
StreamWarehouseSyncService: () => StreamWarehouseSyncService,
|
|
57
66
|
Tap: () => Tap,
|
|
67
|
+
VariableExtractors: () => VariableExtractors,
|
|
58
68
|
_greaterThan: () => _greaterThan,
|
|
59
69
|
addWhalyFields: () => addWhalyFields,
|
|
70
|
+
checkCsvHeaderRow: () => checkCsvHeaderRow,
|
|
71
|
+
createCellReference: () => createCellReference,
|
|
72
|
+
createCsvStreamConfig: () => createCsvStreamConfig,
|
|
73
|
+
createExcelGenerator: () => createExcelGenerator,
|
|
74
|
+
createExcelStreamConfig: () => createExcelStreamConfig,
|
|
60
75
|
createTemporaryFileStream: () => createTemporaryFileStream,
|
|
76
|
+
csvFieldsToJsonSchema: () => csvFieldsToJsonSchema,
|
|
77
|
+
excelColumnToIndex: () => excelColumnToIndex,
|
|
78
|
+
excelFieldsToJsonSchema: () => excelFieldsToJsonSchema,
|
|
79
|
+
excelRowToIndex: () => excelRowToIndex,
|
|
80
|
+
extractExcelRows: () => extractExcelRows,
|
|
81
|
+
extractPrimaryKeysFromCsvConfig: () => extractPrimaryKeysFromCsvConfig,
|
|
82
|
+
extractPrimaryKeysFromExcelFields: () => extractPrimaryKeysFromExcelFields,
|
|
61
83
|
extractStateForStream: () => extractStateForStream,
|
|
84
|
+
fieldTypeToJsonSchema: () => fieldTypeToJsonSchema,
|
|
62
85
|
finalizeStateProgressMarkers: () => finalizeStateProgressMarkers,
|
|
63
|
-
|
|
86
|
+
findAllMatchingConfigs: () => findAllMatchingConfigs,
|
|
64
87
|
flattenSchema: () => flattenSchema,
|
|
65
88
|
getAllMetrics: () => getAllMetrics,
|
|
66
89
|
getAxiosInstance: () => getAxiosInstance,
|
|
@@ -73,198 +96,35 @@ __export(index_exports, {
|
|
|
73
96
|
gracefulExit: () => gracefulExit,
|
|
74
97
|
haltAndCatchFire: () => haltAndCatchFire,
|
|
75
98
|
incrementStreamState: () => incrementStreamState,
|
|
99
|
+
indexToExcelColumn: () => indexToExcelColumn,
|
|
100
|
+
indexToExcelRow: () => indexToExcelRow,
|
|
76
101
|
loadJson: () => loadJson,
|
|
77
|
-
loadResolver: () => loadResolver,
|
|
78
102
|
loadSchema: () => loadSchema,
|
|
79
103
|
logger: () => logger,
|
|
104
|
+
parseCellReference: () => parseCellReference,
|
|
80
105
|
postFormDataApiCall: () => postFormDataApiCall,
|
|
81
106
|
postJSONApiCall: () => postJSONApiCall,
|
|
82
107
|
postUrlEncodedApiCall: () => postUrlEncodedApiCall,
|
|
83
108
|
printMemoryFootprint: () => printMemoryFootprint,
|
|
109
|
+
processFileStreams: () => processFileStreams,
|
|
84
110
|
removeParasiteProperties: () => removeParasiteProperties,
|
|
111
|
+
rowGeneratorFromCsv: () => rowGeneratorFromCsv,
|
|
85
112
|
safePath: () => safePath,
|
|
86
|
-
|
|
113
|
+
unzip: () => unzip,
|
|
114
|
+
validateExcelConfig: () => validateExcelConfig,
|
|
115
|
+
writeDataToCsv: () => writeDataToCsv,
|
|
116
|
+
writeGeneratorToCSV: () => writeGeneratorToCSV
|
|
87
117
|
});
|
|
88
118
|
module.exports = __toCommonJS(index_exports);
|
|
89
119
|
|
|
90
120
|
// src/sdk/utils.ts
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
// src/sdk/service/logger.ts
|
|
94
|
-
var winston2 = __toESM(require("winston"));
|
|
95
|
-
|
|
96
|
-
// src/sdk/models/resolver.ts
|
|
97
|
-
var Resolver = class {
|
|
98
|
-
constructor() {
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// src/resolvers/local-file/services/files.ts
|
|
103
|
-
var import_fs = __toESM(require("fs"));
|
|
104
|
-
var writeFile = (path, content) => {
|
|
105
|
-
return Promise.resolve(import_fs.default.writeFileSync(path, content));
|
|
106
|
-
};
|
|
107
|
-
var readAndParseJSONFile = (path) => {
|
|
108
|
-
if (!import_fs.default.existsSync(path)) {
|
|
109
|
-
throw new Error(`File \`${path}\` wasn't found at: ${process.cwd()}`);
|
|
110
|
-
}
|
|
111
|
-
const raw = import_fs.default.readFileSync(path);
|
|
112
|
-
const parsed = JSON.parse(raw.toString());
|
|
113
|
-
return parsed;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// src/resolvers/local-file/hook/schemaHook.ts
|
|
117
|
-
var import_fs_extra = __toESM(require("fs-extra"));
|
|
118
|
-
var LocalSchemaHook = class {
|
|
119
|
-
async writeSchema(input) {
|
|
120
|
-
await import_fs_extra.default.appendFile("./schema.tmp", JSON.stringify(input) + "\n");
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// src/resolvers/local-file/resolver.ts
|
|
125
|
-
var import_winston = __toESM(require("winston"));
|
|
126
|
-
|
|
127
|
-
// src/sdk/service/metric.ts
|
|
128
|
-
var metricStore = {};
|
|
129
|
-
var ALL_STREAM_ID_KEYWORD = "all";
|
|
130
|
-
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
131
|
-
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
132
|
-
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
133
|
-
var getCounterMetrics = (name, streamIds) => {
|
|
134
|
-
return streamIds.map((streamId) => {
|
|
135
|
-
return getCounterMetric(name, streamId);
|
|
136
|
-
});
|
|
137
|
-
};
|
|
138
|
-
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
139
|
-
if (!metricStore[streamId]) {
|
|
140
|
-
metricStore[streamId] = {};
|
|
141
|
-
}
|
|
142
|
-
const streamMetricStore = metricStore[streamId];
|
|
143
|
-
if (!streamMetricStore[name]) {
|
|
144
|
-
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
145
|
-
}
|
|
146
|
-
return streamMetricStore[name];
|
|
147
|
-
};
|
|
148
|
-
var getAllMetrics = () => {
|
|
149
|
-
return Object.keys(metricStore).flatMap((streamId) => {
|
|
150
|
-
const streamMetricStore = metricStore[streamId];
|
|
151
|
-
if (!streamMetricStore) {
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
var CounterMetric = class {
|
|
158
|
-
value = 0;
|
|
159
|
-
name;
|
|
160
|
-
streamId;
|
|
161
|
-
constructor(name, streamId) {
|
|
162
|
-
this.name = name;
|
|
163
|
-
this.streamId = streamId;
|
|
164
|
-
}
|
|
165
|
-
increment(inc = 1) {
|
|
166
|
-
this.value = this.value + inc;
|
|
167
|
-
}
|
|
168
|
-
getStreamId() {
|
|
169
|
-
return this.streamId;
|
|
170
|
-
}
|
|
171
|
-
getName() {
|
|
172
|
-
return this.name;
|
|
173
|
-
}
|
|
174
|
-
getValue() {
|
|
175
|
-
return this.value;
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// src/resolvers/local-file/resolver.ts
|
|
180
|
-
var { combine, prettyPrint, timestamp, errors, splat } = import_winston.default.format;
|
|
181
|
-
var statePath = "./state.json";
|
|
182
|
-
var metricsPath = "./metrics.json";
|
|
183
|
-
var LocalFilesResolver = class extends Resolver {
|
|
184
|
-
checkIfCanSync() {
|
|
185
|
-
return Promise.resolve(true);
|
|
186
|
-
}
|
|
187
|
-
markSyncStarted() {
|
|
188
|
-
return Promise.resolve();
|
|
189
|
-
}
|
|
190
|
-
onError(errorType, errorText, errorDebugText) {
|
|
191
|
-
const path = `./error.json`;
|
|
192
|
-
return writeFile(path, JSON.stringify({
|
|
193
|
-
errorType,
|
|
194
|
-
errorText,
|
|
195
|
-
errorDebugText
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
getSchemaHooks() {
|
|
199
|
-
const wlyHook = new LocalSchemaHook();
|
|
200
|
-
return [wlyHook];
|
|
201
|
-
}
|
|
202
|
-
writeSchema() {
|
|
203
|
-
return Promise.resolve();
|
|
204
|
-
}
|
|
205
|
-
constructor() {
|
|
206
|
-
super();
|
|
207
|
-
}
|
|
208
|
-
getState() {
|
|
209
|
-
logger.info(`\u{1F4C1} Using the LOCAL resolver`);
|
|
210
|
-
const state = readAndParseJSONFile(statePath);
|
|
211
|
-
return Promise.resolve({
|
|
212
|
-
state
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
writeCatalog(catalog) {
|
|
216
|
-
return Promise.resolve();
|
|
217
|
-
}
|
|
218
|
-
writeState(state) {
|
|
219
|
-
return writeFile(statePath, state);
|
|
220
|
-
}
|
|
221
|
-
markSyncFailed() {
|
|
222
|
-
logger.info(`\u{1F622} Sync has failed.`);
|
|
223
|
-
return Promise.resolve();
|
|
224
|
-
}
|
|
225
|
-
markSyncComplete() {
|
|
226
|
-
logger.info(`\u{1F389} Sync is complete.`);
|
|
227
|
-
return Promise.resolve();
|
|
228
|
-
}
|
|
229
|
-
markDiscoveryComplete() {
|
|
230
|
-
logger.info(`\u{1F389} Discovery is complete.`);
|
|
231
|
-
return Promise.resolve();
|
|
232
|
-
}
|
|
233
|
-
async flushMetrics() {
|
|
234
|
-
const allMetrics = getAllMetrics();
|
|
235
|
-
const metricsOuput = allMetrics.map((metric) => {
|
|
236
|
-
return {
|
|
237
|
-
streamId: metric.getStreamId(),
|
|
238
|
-
name: metric.getName(),
|
|
239
|
-
value: metric.getValue()
|
|
240
|
-
};
|
|
241
|
-
}).map((obj) => JSON.stringify(obj)).join(`
|
|
242
|
-
`);
|
|
243
|
-
return writeFile(metricsPath, metricsOuput);
|
|
244
|
-
}
|
|
245
|
-
updateSourceValue(optionKey, optionValue) {
|
|
246
|
-
logger.info(`Updating source value ${optionKey} with value ${optionValue}`, { private: true });
|
|
247
|
-
return Promise.resolve();
|
|
248
|
-
}
|
|
249
|
-
getLogFormat() {
|
|
250
|
-
return combine(
|
|
251
|
-
timestamp(),
|
|
252
|
-
errors({ stack: true }),
|
|
253
|
-
splat(),
|
|
254
|
-
prettyPrint()
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// src/sdk/service/resolver.ts
|
|
260
|
-
var loadResolver = () => {
|
|
261
|
-
return new LocalFilesResolver();
|
|
262
|
-
};
|
|
121
|
+
var import_fs = require("fs");
|
|
263
122
|
|
|
264
123
|
// src/sdk/service/logger.ts
|
|
124
|
+
var winston = __toESM(require("winston"));
|
|
265
125
|
var BATCH_INTERVAL_MS = 100;
|
|
266
126
|
var transports2 = {
|
|
267
|
-
console: new
|
|
127
|
+
console: new winston.transports.Console({
|
|
268
128
|
stderrLevels: [],
|
|
269
129
|
level: "info"
|
|
270
130
|
})
|
|
@@ -274,8 +134,14 @@ var getTransports = () => {
|
|
|
274
134
|
transports2.console
|
|
275
135
|
];
|
|
276
136
|
};
|
|
277
|
-
var
|
|
278
|
-
|
|
137
|
+
var { combine, prettyPrint, timestamp, errors, splat } = winston.format;
|
|
138
|
+
var winstonLogger = winston.createLogger({
|
|
139
|
+
format: combine(
|
|
140
|
+
timestamp(),
|
|
141
|
+
errors({ stack: true }),
|
|
142
|
+
splat(),
|
|
143
|
+
prettyPrint()
|
|
144
|
+
),
|
|
279
145
|
transports: getTransports()
|
|
280
146
|
});
|
|
281
147
|
var isWinstonOpen = true;
|
|
@@ -304,13 +170,9 @@ var logger = {
|
|
|
304
170
|
};
|
|
305
171
|
|
|
306
172
|
// src/sdk/utils.ts
|
|
307
|
-
var writeStateFile = (resolver, state) => {
|
|
308
|
-
logger.info(`\u{1F4DD} Writing the state file.`);
|
|
309
|
-
return resolver.writeState(state);
|
|
310
|
-
};
|
|
311
173
|
function readFile(fileName, filePath) {
|
|
312
174
|
try {
|
|
313
|
-
return (0,
|
|
175
|
+
return (0, import_fs.readFileSync)(filePath);
|
|
314
176
|
} catch (err) {
|
|
315
177
|
logger.error(`Can't open file: \`${fileName}\` at path: \`${filePath}\`. Is it at the proper place?`);
|
|
316
178
|
throw err;
|
|
@@ -337,7 +199,7 @@ var loadSchema = (streamId) => {
|
|
|
337
199
|
// src/sdk/service/network.ts
|
|
338
200
|
var import_axios = __toESM(require("axios"));
|
|
339
201
|
var import_url = require("url");
|
|
340
|
-
var
|
|
202
|
+
var fs = __toESM(require("fs"));
|
|
341
203
|
var qs = __toESM(require("qs"));
|
|
342
204
|
var import_axios_retry = __toESM(require("axios-retry"));
|
|
343
205
|
var shouldRetry = (error) => {
|
|
@@ -450,7 +312,7 @@ var getJSONApiCall = async (endpoint, config, privateLogging) => {
|
|
|
450
312
|
};
|
|
451
313
|
var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) => {
|
|
452
314
|
const instance = getAxiosInstance(config.retryCount);
|
|
453
|
-
const writer =
|
|
315
|
+
const writer = fs.createWriteStream(output);
|
|
454
316
|
const paramsSerializer = (params) => {
|
|
455
317
|
return qs.stringify(params, { arrayFormat: "repeat" });
|
|
456
318
|
};
|
|
@@ -544,6 +406,58 @@ var postUrlEncodedApiCall = async (endpoint, config, payload) => {
|
|
|
544
406
|
}
|
|
545
407
|
};
|
|
546
408
|
|
|
409
|
+
// src/sdk/service/metric.ts
|
|
410
|
+
var metricStore = {};
|
|
411
|
+
var ALL_STREAM_ID_KEYWORD = "all";
|
|
412
|
+
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
413
|
+
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
414
|
+
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
415
|
+
var getCounterMetrics = (name, streamIds) => {
|
|
416
|
+
return streamIds.map((streamId) => {
|
|
417
|
+
return getCounterMetric(name, streamId);
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
421
|
+
if (!metricStore[streamId]) {
|
|
422
|
+
metricStore[streamId] = {};
|
|
423
|
+
}
|
|
424
|
+
const streamMetricStore = metricStore[streamId];
|
|
425
|
+
if (!streamMetricStore[name]) {
|
|
426
|
+
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
427
|
+
}
|
|
428
|
+
return streamMetricStore[name];
|
|
429
|
+
};
|
|
430
|
+
var getAllMetrics = () => {
|
|
431
|
+
return Object.keys(metricStore).flatMap((streamId) => {
|
|
432
|
+
const streamMetricStore = metricStore[streamId];
|
|
433
|
+
if (!streamMetricStore) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
var CounterMetric = class {
|
|
440
|
+
value = 0;
|
|
441
|
+
name;
|
|
442
|
+
streamId;
|
|
443
|
+
constructor(name, streamId) {
|
|
444
|
+
this.name = name;
|
|
445
|
+
this.streamId = streamId;
|
|
446
|
+
}
|
|
447
|
+
increment(inc = 1) {
|
|
448
|
+
this.value = this.value + inc;
|
|
449
|
+
}
|
|
450
|
+
getStreamId() {
|
|
451
|
+
return this.streamId;
|
|
452
|
+
}
|
|
453
|
+
getName() {
|
|
454
|
+
return this.name;
|
|
455
|
+
}
|
|
456
|
+
getValue() {
|
|
457
|
+
return this.value;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
547
461
|
// src/sdk/service/memory.ts
|
|
548
462
|
var formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
|
|
549
463
|
var printMemoryFootprint = (prefix) => {
|
|
@@ -559,9 +473,6 @@ var printMemoryFootprint = (prefix) => {
|
|
|
559
473
|
|
|
560
474
|
// src/sdk/service/exit.ts
|
|
561
475
|
async function gracefulExit(exitCode) {
|
|
562
|
-
if (exitCode !== 0) {
|
|
563
|
-
await loadResolver().markSyncFailed();
|
|
564
|
-
}
|
|
565
476
|
const loggerFinish = new Promise((resolve, reject) => {
|
|
566
477
|
logger.closeWinston(resolve);
|
|
567
478
|
});
|
|
@@ -581,34 +492,16 @@ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
|
|
|
581
492
|
errorText,
|
|
582
493
|
errorDebugText
|
|
583
494
|
);
|
|
584
|
-
const resolver = loadResolver();
|
|
585
|
-
await resolver.onError(errorType, errorText, errorDebugText);
|
|
586
495
|
await gracefulExit(0);
|
|
587
496
|
};
|
|
588
497
|
|
|
589
|
-
// src/sdk/
|
|
590
|
-
var
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
};
|
|
597
|
-
if (rel.left === streamId) {
|
|
598
|
-
acc.right.push({
|
|
599
|
-
streamId: rel.right,
|
|
600
|
-
...base
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
if (rel.right === streamId) {
|
|
604
|
-
acc.left.push({
|
|
605
|
-
streamId: rel.left,
|
|
606
|
-
...base
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
return acc;
|
|
610
|
-
}, { left: [], right: [] });
|
|
611
|
-
};
|
|
498
|
+
// src/sdk/models/replication.ts
|
|
499
|
+
var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
|
|
500
|
+
ReplicationMethod2["INCREMENTAL"] = "INCREMENTAL";
|
|
501
|
+
ReplicationMethod2["FULL_TABLE"] = "FULL_TABLE";
|
|
502
|
+
ReplicationMethod2["APPEND"] = "APPEND";
|
|
503
|
+
return ReplicationMethod2;
|
|
504
|
+
})(ReplicationMethod || {});
|
|
612
505
|
|
|
613
506
|
// src/sdk/models/messages.ts
|
|
614
507
|
var RecordMessage = class {
|
|
@@ -668,7 +561,6 @@ var SchemaMessage = class {
|
|
|
668
561
|
displayLabel;
|
|
669
562
|
description;
|
|
670
563
|
propertiesMetadata;
|
|
671
|
-
relationships;
|
|
672
564
|
constructor(opts) {
|
|
673
565
|
const {
|
|
674
566
|
stream,
|
|
@@ -677,7 +569,6 @@ var SchemaMessage = class {
|
|
|
677
569
|
bookmarkProperties,
|
|
678
570
|
displayLabel,
|
|
679
571
|
description,
|
|
680
|
-
relationships,
|
|
681
572
|
propertiesMetadata
|
|
682
573
|
} = opts;
|
|
683
574
|
this.stream = stream;
|
|
@@ -686,7 +577,6 @@ var SchemaMessage = class {
|
|
|
686
577
|
this.bookmarkProperties = bookmarkProperties;
|
|
687
578
|
this.displayLabel = displayLabel;
|
|
688
579
|
this.description = description;
|
|
689
|
-
this.relationships = relationships;
|
|
690
580
|
this.propertiesMetadata = propertiesMetadata;
|
|
691
581
|
}
|
|
692
582
|
toString() {
|
|
@@ -696,8 +586,7 @@ var SchemaMessage = class {
|
|
|
696
586
|
schema: this.schema,
|
|
697
587
|
key_properties: this.keyProperties,
|
|
698
588
|
label: this.displayLabel,
|
|
699
|
-
description: this.description
|
|
700
|
-
relationships: this.relationships
|
|
589
|
+
description: this.description
|
|
701
590
|
};
|
|
702
591
|
if (this.bookmarkProperties) {
|
|
703
592
|
result["bookmark_properties"] = this.bookmarkProperties;
|
|
@@ -706,128 +595,8 @@ var SchemaMessage = class {
|
|
|
706
595
|
}
|
|
707
596
|
};
|
|
708
597
|
|
|
709
|
-
// src/sdk/models/metadata.ts
|
|
710
|
-
var MetadataMap = class extends Map {
|
|
711
|
-
areEquals(array1, array2) {
|
|
712
|
-
return array1.length === array2.length && array1.every(function(value, index) {
|
|
713
|
-
return value === array2[index];
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
findExistingEntry(key) {
|
|
717
|
-
return Array.from(super.entries()).find((entry) => {
|
|
718
|
-
if (this.areEquals(entry[0], key)) {
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
get(key) {
|
|
724
|
-
const existingEntry = this.findExistingEntry(key);
|
|
725
|
-
return existingEntry?.[1];
|
|
726
|
-
}
|
|
727
|
-
set(key, value) {
|
|
728
|
-
const existingKey = this.findExistingEntry(key);
|
|
729
|
-
if (existingKey) {
|
|
730
|
-
return super.set(existingKey?.[0], value);
|
|
731
|
-
} else {
|
|
732
|
-
return super.set(key, value);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
var StreamMetadata = class {
|
|
737
|
-
metadataByBreadcrumb;
|
|
738
|
-
constructor() {
|
|
739
|
-
this.metadataByBreadcrumb = new MetadataMap();
|
|
740
|
-
}
|
|
741
|
-
fromCatalogStreamMetadata(metadataArr) {
|
|
742
|
-
metadataArr.forEach((metadata) => {
|
|
743
|
-
this.metadataByBreadcrumb.set(metadata.breadcrumb, metadata.metadata);
|
|
744
|
-
});
|
|
745
|
-
return this;
|
|
746
|
-
}
|
|
747
|
-
toString() {
|
|
748
|
-
return this.metadataByBreadcrumb;
|
|
749
|
-
}
|
|
750
|
-
get(breadcrumb) {
|
|
751
|
-
return this.metadataByBreadcrumb.get(breadcrumb);
|
|
752
|
-
}
|
|
753
|
-
write(breadcrumb, key, value) {
|
|
754
|
-
if (value === void 0) {
|
|
755
|
-
throw new Error(`The value you're trying to write on the metadata at breadcrumb: \`${breadcrumb}\` and key: \`${key}\` is undefined.
|
|
756
|
-
|
|
757
|
-
Writing an undefined value is not a valid operation. Is there something wrong with your value?`);
|
|
758
|
-
}
|
|
759
|
-
if (!this.metadataByBreadcrumb.get(breadcrumb)) {
|
|
760
|
-
this.metadataByBreadcrumb.set(breadcrumb, {});
|
|
761
|
-
}
|
|
762
|
-
const previousValue = this.metadataByBreadcrumb.get(breadcrumb);
|
|
763
|
-
const newValue = {};
|
|
764
|
-
newValue[key] = value;
|
|
765
|
-
this.metadataByBreadcrumb.set(breadcrumb, { ...previousValue, ...newValue });
|
|
766
|
-
return this;
|
|
767
|
-
}
|
|
768
|
-
getRootBreadcrumbMetadata() {
|
|
769
|
-
const rootBreadcrumbMetadata = this.metadataByBreadcrumb.get([]);
|
|
770
|
-
if (!rootBreadcrumbMetadata) {
|
|
771
|
-
throw new Error(`No metadata was attached to the root breadcrumb \`[\`] while we need one to store metadata at the table level.
|
|
772
|
-
|
|
773
|
-
Is your catalog properly generated?`);
|
|
774
|
-
}
|
|
775
|
-
return rootBreadcrumbMetadata;
|
|
776
|
-
}
|
|
777
|
-
isStreamSelected() {
|
|
778
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
779
|
-
if (rootBreadcrumbMetadata.selected === false) {
|
|
780
|
-
return false;
|
|
781
|
-
}
|
|
782
|
-
if (rootBreadcrumbMetadata.selected === true || rootBreadcrumbMetadata["selected-by-default"] === true) {
|
|
783
|
-
return true;
|
|
784
|
-
}
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
getKeyProperties() {
|
|
788
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
789
|
-
if (!rootBreadcrumbMetadata["table-key-properties"]) {
|
|
790
|
-
return [];
|
|
791
|
-
}
|
|
792
|
-
return rootBreadcrumbMetadata["table-key-properties"];
|
|
793
|
-
}
|
|
794
|
-
getReplicationKey() {
|
|
795
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
796
|
-
if (!rootBreadcrumbMetadata["replication-key"]) {
|
|
797
|
-
return void 0;
|
|
798
|
-
}
|
|
799
|
-
return rootBreadcrumbMetadata["replication-key"];
|
|
800
|
-
}
|
|
801
|
-
getReplicationMethod() {
|
|
802
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
803
|
-
if (!rootBreadcrumbMetadata["replication-method"]) {
|
|
804
|
-
return void 0;
|
|
805
|
-
}
|
|
806
|
-
return rootBreadcrumbMetadata["replication-method"];
|
|
807
|
-
}
|
|
808
|
-
getForcedReplicationMethod() {
|
|
809
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
810
|
-
if (!rootBreadcrumbMetadata["forced-replication-method"]) {
|
|
811
|
-
return void 0;
|
|
812
|
-
}
|
|
813
|
-
return rootBreadcrumbMetadata["forced-replication-method"];
|
|
814
|
-
}
|
|
815
|
-
toList() {
|
|
816
|
-
return Array.from(this.metadataByBreadcrumb.keys()).map((breadcrumb) => {
|
|
817
|
-
const metadata = this.metadataByBreadcrumb.get(breadcrumb);
|
|
818
|
-
if (!metadata) {
|
|
819
|
-
throw new Error(`There was no metadata entry for breadcrumb: \`${breadcrumb}\` in metadata map: ${this.metadataByBreadcrumb.toString()}`);
|
|
820
|
-
}
|
|
821
|
-
return {
|
|
822
|
-
breadcrumb,
|
|
823
|
-
metadata
|
|
824
|
-
};
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
};
|
|
828
|
-
|
|
829
598
|
// src/sdk/models/state.ts
|
|
830
|
-
var
|
|
599
|
+
var import_dayjs = __toESM(require("dayjs"));
|
|
831
600
|
|
|
832
601
|
// src/sdk/constants/date.ts
|
|
833
602
|
var defaultDateTimeFormat = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ";
|
|
@@ -842,12 +611,12 @@ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
|
842
611
|
if (!newRkValue) {
|
|
843
612
|
throw new Error((0, import_util.format)(`No value for replicationKey: ${replicationKey} in record with keys: %j`, Object.keys(latestRecord)));
|
|
844
613
|
}
|
|
845
|
-
let newRkValueMoment = (0,
|
|
614
|
+
let newRkValueMoment = (0, import_dayjs.default)(newRkValue);
|
|
846
615
|
let prevRkValue;
|
|
847
616
|
let prevRkValueMoment;
|
|
848
617
|
if (isSorted) {
|
|
849
618
|
prevRkValue = state["replicationKeyValue"];
|
|
850
|
-
prevRkValueMoment = (0,
|
|
619
|
+
prevRkValueMoment = (0, import_dayjs.default)(prevRkValue);
|
|
851
620
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
852
621
|
throw new Error(
|
|
853
622
|
`Unsorted data detected in stream. Latest value '${newRkValue}' is
|
|
@@ -862,24 +631,24 @@ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
|
862
631
|
state[PROGRESS_MARKERS] = marker;
|
|
863
632
|
}
|
|
864
633
|
prevRkValue = state[PROGRESS_MARKERS]["replicationKeyValue"];
|
|
865
|
-
prevRkValueMoment = (0,
|
|
634
|
+
prevRkValueMoment = (0, import_dayjs.default)(prevRkValue || state["replicationKeyValue"]);
|
|
866
635
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
867
636
|
newRkValueMoment = prevRkValueMoment;
|
|
868
637
|
newRkValue = prevRkValue;
|
|
869
638
|
}
|
|
870
639
|
}
|
|
871
640
|
const signpostMarker = state[SIGNPOST_MARKER];
|
|
872
|
-
const replicationKeySignpostMoment = (0,
|
|
641
|
+
const replicationKeySignpostMoment = (0, import_dayjs.default)(signpostMarker);
|
|
873
642
|
if (replicationKeySignpostMoment && replicationKeySignpostMoment.isBefore(newRkValueMoment)) {
|
|
874
643
|
newRkValueMoment = replicationKeySignpostMoment;
|
|
875
644
|
newRkValue = replicationKeySignpostMoment.format(defaultDateTimeFormat);
|
|
876
645
|
}
|
|
877
646
|
if (isSorted === false) {
|
|
878
647
|
state[PROGRESS_MARKERS]["replicationKey"] = replicationKey;
|
|
879
|
-
state[PROGRESS_MARKERS]["replicationKeyValue"] = (0,
|
|
648
|
+
state[PROGRESS_MARKERS]["replicationKeyValue"] = (0, import_dayjs.default)(newRkValueMoment).format(defaultDateTimeFormat);
|
|
880
649
|
} else {
|
|
881
650
|
state["replicationKey"] = replicationKey;
|
|
882
|
-
state["replicationKeyValue"] = (0,
|
|
651
|
+
state["replicationKeyValue"] = (0, import_dayjs.default)(newRkValueMoment).format(defaultDateTimeFormat);
|
|
883
652
|
}
|
|
884
653
|
return state;
|
|
885
654
|
};
|
|
@@ -901,7 +670,7 @@ var finalizeStateProgressMarkers = (state) => {
|
|
|
901
670
|
return state;
|
|
902
671
|
};
|
|
903
672
|
var _greaterThan = (aValue, bValue) => {
|
|
904
|
-
return (0,
|
|
673
|
+
return (0, import_dayjs.default)(aValue).isAfter((0, import_dayjs.default)(bValue));
|
|
905
674
|
};
|
|
906
675
|
var extractStateForStream = (state, streamId) => {
|
|
907
676
|
if (!state.bookmarks) {
|
|
@@ -924,26 +693,16 @@ var StateService = class {
|
|
|
924
693
|
}
|
|
925
694
|
return this.instance;
|
|
926
695
|
}
|
|
927
|
-
|
|
928
|
-
setBookmark(streamId, ts) {
|
|
929
|
-
this.bookmarks[streamId] = ts.format(defaultDateTimeFormat);
|
|
930
|
-
}
|
|
931
|
-
setBookmarkV2(streamId, streamState) {
|
|
696
|
+
setBookmark(streamId, streamState) {
|
|
932
697
|
this.bookmarks[streamId] = streamState;
|
|
933
698
|
}
|
|
934
|
-
|
|
699
|
+
setBookmarkSignpost(streamId, signpostValue) {
|
|
935
700
|
if (!this.bookmarks[streamId]) {
|
|
936
701
|
this.bookmarks[streamId] = {};
|
|
937
702
|
}
|
|
938
703
|
this.bookmarks[streamId][SIGNPOST_MARKER] = signpostValue;
|
|
939
704
|
}
|
|
940
705
|
getBookmark(streamId) {
|
|
941
|
-
if (!this.bookmarks[streamId]) {
|
|
942
|
-
return (0, import_moment.default)(0);
|
|
943
|
-
}
|
|
944
|
-
return (0, import_moment.default)(this.bookmarks[streamId]);
|
|
945
|
-
}
|
|
946
|
-
getBookmarkV2(streamId) {
|
|
947
706
|
if (this.bookmarks[streamId]) {
|
|
948
707
|
return this.bookmarks[streamId];
|
|
949
708
|
} else {
|
|
@@ -983,7 +742,7 @@ var import_axios2 = __toESM(require("axios"));
|
|
|
983
742
|
var qs2 = __toESM(require("qs"));
|
|
984
743
|
|
|
985
744
|
// src/sdk/models/tap/stream.ts
|
|
986
|
-
var
|
|
745
|
+
var import_dayjs2 = __toESM(require("dayjs"));
|
|
987
746
|
|
|
988
747
|
// src/sdk/helpers/typing.ts
|
|
989
748
|
var isDatetimeType = (typeDef) => {
|
|
@@ -1006,10 +765,11 @@ var isDatetimeType = (typeDef) => {
|
|
|
1006
765
|
};
|
|
1007
766
|
|
|
1008
767
|
// src/sdk/models/tap/stream.ts
|
|
1009
|
-
var
|
|
768
|
+
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"));
|
|
1010
769
|
var import_bluebird2 = __toESM(require("bluebird"));
|
|
1011
770
|
|
|
1012
771
|
// src/sdk/models/tap/tap.ts
|
|
772
|
+
var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"));
|
|
1013
773
|
var import_bluebird = __toESM(require("bluebird"));
|
|
1014
774
|
var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
|
|
1015
775
|
var Tap = class {
|
|
@@ -1017,12 +777,21 @@ var Tap = class {
|
|
|
1017
777
|
config;
|
|
1018
778
|
streams;
|
|
1019
779
|
concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
|
|
1020
|
-
|
|
780
|
+
stateProvider;
|
|
781
|
+
tapState;
|
|
782
|
+
constructor(target, config, stateProvider) {
|
|
1021
783
|
this.streams = [];
|
|
1022
784
|
this.target = target;
|
|
1023
785
|
this.config = config;
|
|
786
|
+
this.stateProvider = stateProvider;
|
|
787
|
+
this.tapState = { bookmarks: {} };
|
|
1024
788
|
}
|
|
1025
789
|
sync = async (options) => {
|
|
790
|
+
const initialState = await this.stateProvider.getState();
|
|
791
|
+
if (initialState.state && initialState.state.bookmarks) {
|
|
792
|
+
this.tapState = (0, import_cloneDeep.default)(initialState.state);
|
|
793
|
+
}
|
|
794
|
+
await this.init();
|
|
1026
795
|
logger.info(`\u{1F680} Start syncing`);
|
|
1027
796
|
const state = StateService.getInstance().get();
|
|
1028
797
|
logger.info(`\u{1F4CD} Received state: %j`, state);
|
|
@@ -1059,9 +828,7 @@ var Stream = class {
|
|
|
1059
828
|
config;
|
|
1060
829
|
tapState;
|
|
1061
830
|
target;
|
|
1062
|
-
|
|
1063
|
-
// Replication method that can be forced in the stream implemenation. Will prevail over any user/catalog choice
|
|
1064
|
-
forcedReplicationMethod = void 0;
|
|
831
|
+
replicationMethod = void 0;
|
|
1065
832
|
selectedByDefault = true;
|
|
1066
833
|
STATE_MSG_FREQUENCY = 10;
|
|
1067
834
|
streamId = "default";
|
|
@@ -1069,10 +836,9 @@ var Stream = class {
|
|
|
1069
836
|
displayLabel = void 0;
|
|
1070
837
|
description = void 0;
|
|
1071
838
|
primaryKey = [];
|
|
1072
|
-
|
|
839
|
+
replicationKey = void 0;
|
|
1073
840
|
// When set, use the state from another stream for this stream
|
|
1074
841
|
useStateFromStreamId = void 0;
|
|
1075
|
-
relationships = [];
|
|
1076
842
|
children = [];
|
|
1077
843
|
// If true, it means that the records in this stream are sorted by replicationKey.
|
|
1078
844
|
// If false, then the records are coming in an unsorted manner.
|
|
@@ -1084,9 +850,9 @@ var Stream = class {
|
|
|
1084
850
|
// Metrics configuration
|
|
1085
851
|
rowsSyncedMetricsConf;
|
|
1086
852
|
executionTimeMetricsConf;
|
|
1087
|
-
constructor(config,
|
|
853
|
+
constructor(config, tapState, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
1088
854
|
this.config = config;
|
|
1089
|
-
this.tapState = (0,
|
|
855
|
+
this.tapState = (0, import_cloneDeep2.default)(tapState);
|
|
1090
856
|
this.target = target;
|
|
1091
857
|
this.childConcurrency = childConcurrency;
|
|
1092
858
|
this.rowsSyncedMetricsConf = {
|
|
@@ -1159,13 +925,11 @@ var Stream = class {
|
|
|
1159
925
|
if (!this.isSilent) {
|
|
1160
926
|
const schema = await this.getSchema();
|
|
1161
927
|
if (schema !== void 0) {
|
|
1162
|
-
const relatedRels = findRelatedRelationships(this.streamId, this.relationships);
|
|
1163
928
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1164
929
|
const message = new SchemaMessage({
|
|
1165
930
|
keyProperties: this.primaryKey,
|
|
1166
931
|
stream: this.streamId,
|
|
1167
932
|
schema: schema.jsonSchema,
|
|
1168
|
-
relationships: relatedRels,
|
|
1169
933
|
...replicationConfig.replicationKey ? { bookmarkProperties: [replicationConfig.replicationKey] } : {},
|
|
1170
934
|
...this.displayLabel ? { displayLabel: this.displayLabel } : {},
|
|
1171
935
|
...this.description ? { description: this.description } : {},
|
|
@@ -1185,7 +949,7 @@ var Stream = class {
|
|
|
1185
949
|
const signpostMoment = await this.getReplicationKeySignpost();
|
|
1186
950
|
const signpostValue = signpostMoment?.format(defaultDateTimeFormat);
|
|
1187
951
|
if (signpostValue) {
|
|
1188
|
-
StateService.getInstance().
|
|
952
|
+
StateService.getInstance().setBookmarkSignpost(this.streamId, signpostValue);
|
|
1189
953
|
}
|
|
1190
954
|
};
|
|
1191
955
|
/**
|
|
@@ -1201,7 +965,7 @@ var Stream = class {
|
|
|
1201
965
|
if (this.useStateFromStreamId) {
|
|
1202
966
|
return;
|
|
1203
967
|
}
|
|
1204
|
-
if (this.
|
|
968
|
+
if (this.replicationMethod && !this.replicationKey) {
|
|
1205
969
|
return;
|
|
1206
970
|
}
|
|
1207
971
|
if (!replicationConfig.replicationKey) {
|
|
@@ -1211,14 +975,14 @@ var Stream = class {
|
|
|
1211
975
|
);
|
|
1212
976
|
}
|
|
1213
977
|
const stateServiceInst = StateService.getInstance();
|
|
1214
|
-
const state = stateServiceInst.
|
|
978
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1215
979
|
const newState = incrementStreamState(
|
|
1216
980
|
state,
|
|
1217
981
|
replicationConfig.replicationKey,
|
|
1218
982
|
latestRecord,
|
|
1219
983
|
this.isSorted
|
|
1220
984
|
);
|
|
1221
|
-
StateService.getInstance().
|
|
985
|
+
StateService.getInstance().setBookmark(this.streamId, newState);
|
|
1222
986
|
}
|
|
1223
987
|
}
|
|
1224
988
|
};
|
|
@@ -1258,19 +1022,19 @@ var Stream = class {
|
|
|
1258
1022
|
this.replicationKey: ${replicationConfig.replicationKey}`);
|
|
1259
1023
|
if (state.replicationKeyValue) {
|
|
1260
1024
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning replication key value: ${state.replicationKeyValue}`);
|
|
1261
|
-
return (0,
|
|
1025
|
+
return (0, import_dayjs2.default)(state.replicationKeyValue);
|
|
1262
1026
|
} else if (startDate) {
|
|
1263
1027
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning config start date: ${startDate}`);
|
|
1264
|
-
return (0,
|
|
1028
|
+
return (0, import_dayjs2.default)(startDate);
|
|
1265
1029
|
} else {
|
|
1266
1030
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning EPOCH (1970)`);
|
|
1267
|
-
return (0,
|
|
1031
|
+
return (0, import_dayjs2.default)(0);
|
|
1268
1032
|
}
|
|
1269
1033
|
}
|
|
1270
1034
|
/**
|
|
1271
1035
|
* Return the max allowable bookmark value for this stream's replication key.
|
|
1272
1036
|
*
|
|
1273
|
-
* For timestamp-based replication keys, this defaults to `
|
|
1037
|
+
* For timestamp-based replication keys, this defaults to `dayjs()`. For
|
|
1274
1038
|
* non-timestamp replication keys, default to `undefined`.
|
|
1275
1039
|
*
|
|
1276
1040
|
* Override this value to prevent bookmarks from being advanced in cases where we
|
|
@@ -1278,7 +1042,7 @@ var Stream = class {
|
|
|
1278
1042
|
*/
|
|
1279
1043
|
getReplicationKeySignpost = async () => {
|
|
1280
1044
|
if (await this.isTimestampReplicationKey()) {
|
|
1281
|
-
return (0,
|
|
1045
|
+
return (0, import_dayjs2.default)();
|
|
1282
1046
|
}
|
|
1283
1047
|
return void 0;
|
|
1284
1048
|
};
|
|
@@ -1286,22 +1050,22 @@ var Stream = class {
|
|
|
1286
1050
|
* Return the default replication method for the stream that will be used to write the catalog.
|
|
1287
1051
|
*/
|
|
1288
1052
|
defaultReplicationConfig = () => {
|
|
1289
|
-
if (this.
|
|
1053
|
+
if (this.replicationMethod) {
|
|
1290
1054
|
return {
|
|
1291
|
-
replicationMethod: this.
|
|
1292
|
-
replicationKey: this.
|
|
1055
|
+
replicationMethod: this.replicationMethod,
|
|
1056
|
+
replicationKey: this.replicationKey,
|
|
1293
1057
|
isForced: true
|
|
1294
1058
|
};
|
|
1295
1059
|
}
|
|
1296
|
-
if (this.
|
|
1060
|
+
if (this.replicationKey || this.useStateFromStreamId) {
|
|
1297
1061
|
return {
|
|
1298
|
-
replicationMethod: "INCREMENTAL"
|
|
1299
|
-
replicationKey: this.
|
|
1062
|
+
replicationMethod: "INCREMENTAL" /* INCREMENTAL */,
|
|
1063
|
+
replicationKey: this.replicationKey,
|
|
1300
1064
|
isForced: true
|
|
1301
1065
|
};
|
|
1302
1066
|
}
|
|
1303
1067
|
return {
|
|
1304
|
-
replicationMethod: "FULL_TABLE"
|
|
1068
|
+
replicationMethod: "FULL_TABLE" /* FULL_TABLE */,
|
|
1305
1069
|
replicationKey: void 0,
|
|
1306
1070
|
isForced: false
|
|
1307
1071
|
};
|
|
@@ -1362,9 +1126,9 @@ var Stream = class {
|
|
|
1362
1126
|
rowsSent += 1;
|
|
1363
1127
|
}
|
|
1364
1128
|
const stateServiceInst = StateService.getInstance();
|
|
1365
|
-
const state = stateServiceInst.
|
|
1129
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1366
1130
|
const newState = finalizeStateProgressMarkers(state);
|
|
1367
|
-
stateServiceInst.
|
|
1131
|
+
stateServiceInst.setBookmark(this.streamId, newState);
|
|
1368
1132
|
if (!parent) {
|
|
1369
1133
|
logger.info(`\u2705 Completed sync for stream: ${this.streamId} (${rowsSent} records)`);
|
|
1370
1134
|
} else {
|
|
@@ -1470,7 +1234,7 @@ function getTruncatedParamsForLog(params) {
|
|
|
1470
1234
|
}
|
|
1471
1235
|
|
|
1472
1236
|
// src/sdk/models/tap/restStream.ts
|
|
1473
|
-
var
|
|
1237
|
+
var import_cloneDeep3 = __toESM(require("lodash/cloneDeep.js"));
|
|
1474
1238
|
var import_isEqual = __toESM(require("lodash/isEqual.js"));
|
|
1475
1239
|
var RESTStream = class extends Stream {
|
|
1476
1240
|
axiosInstance;
|
|
@@ -1491,13 +1255,24 @@ var RESTStream = class extends Stream {
|
|
|
1491
1255
|
*/
|
|
1492
1256
|
_prepareRequest = async (nextPageToken, parent) => {
|
|
1493
1257
|
const method = this.httpMethod;
|
|
1494
|
-
const url = this.
|
|
1258
|
+
const url = this.getNextUrl(nextPageToken, parent);
|
|
1495
1259
|
if (!url) {
|
|
1496
1260
|
throw new Error(`StreamId: ${this.streamId} - URL is undefined. Did your properly define the URL for this stream?`);
|
|
1497
1261
|
}
|
|
1498
1262
|
const authenticator = this.authenticator;
|
|
1263
|
+
let finalUrl = url;
|
|
1264
|
+
let urlQueryParams = {};
|
|
1265
|
+
const queryIndex = url.indexOf("?");
|
|
1266
|
+
if (queryIndex !== -1) {
|
|
1267
|
+
finalUrl = url.substring(0, queryIndex);
|
|
1268
|
+
const rawQueryString = url.substring(queryIndex + 1);
|
|
1269
|
+
urlQueryParams = qs2.parse(rawQueryString);
|
|
1270
|
+
}
|
|
1499
1271
|
const params = {
|
|
1500
1272
|
...this.getNextUrlParams(nextPageToken, parent),
|
|
1273
|
+
// Query params embedded in URL should override computed pagination params
|
|
1274
|
+
// but should not override auth params which are appended last
|
|
1275
|
+
...urlQueryParams,
|
|
1501
1276
|
...await authenticator?.getAuthQS(this.config)
|
|
1502
1277
|
};
|
|
1503
1278
|
const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
|
|
@@ -1516,7 +1291,7 @@ var RESTStream = class extends Stream {
|
|
|
1516
1291
|
return qs2.stringify(params2, { arrayFormat: "repeat" });
|
|
1517
1292
|
};
|
|
1518
1293
|
const request = {
|
|
1519
|
-
url,
|
|
1294
|
+
url: finalUrl,
|
|
1520
1295
|
method,
|
|
1521
1296
|
data: requestBody,
|
|
1522
1297
|
params,
|
|
@@ -1589,7 +1364,7 @@ var RESTStream = class extends Stream {
|
|
|
1589
1364
|
for await (const row of rows) {
|
|
1590
1365
|
yield row;
|
|
1591
1366
|
}
|
|
1592
|
-
const previousToken = (0,
|
|
1367
|
+
const previousToken = (0, import_cloneDeep3.default)(nextPageToken);
|
|
1593
1368
|
nextPageToken = this.getNextPageToken(resp, previousToken);
|
|
1594
1369
|
if (nextPageToken && (0, import_isEqual.default)(nextPageToken, previousToken)) {
|
|
1595
1370
|
throw new Error(
|
|
@@ -1668,7 +1443,7 @@ var RESTStream = class extends Stream {
|
|
|
1668
1443
|
* TODO: Make URL + Path templatisable and replace those with Extractor (or Tap) config
|
|
1669
1444
|
* @returns
|
|
1670
1445
|
*/
|
|
1671
|
-
|
|
1446
|
+
getNextUrl(previousToken, parent) {
|
|
1672
1447
|
const urlPattern = [this.baseUrl, this.path].join("");
|
|
1673
1448
|
return urlPattern;
|
|
1674
1449
|
}
|
|
@@ -1849,6 +1624,8 @@ var StreamWarehouseSyncService = class {
|
|
|
1849
1624
|
mergeQueries = this.getReplaceQueries();
|
|
1850
1625
|
} else if (replicationMethod === "INCREMENTAL") {
|
|
1851
1626
|
mergeQueries = this.getMergeQueries();
|
|
1627
|
+
} else if (replicationMethod === "APPEND") {
|
|
1628
|
+
mergeQueries = this.getAppendQueries();
|
|
1852
1629
|
} else if (replicationMethod === "LOG_BASED") {
|
|
1853
1630
|
mergeQueries = this.getMergeQueries();
|
|
1854
1631
|
}
|
|
@@ -2020,25 +1797,25 @@ var flattenSchema = (streamId, schema) => {
|
|
|
2020
1797
|
|
|
2021
1798
|
// src/sdk/models/target/target.ts
|
|
2022
1799
|
var import_jsonschema = require("jsonschema");
|
|
2023
|
-
var
|
|
1800
|
+
var import_dayjs4 = __toESM(require("dayjs"));
|
|
2024
1801
|
|
|
2025
1802
|
// src/sdk/models/target/streamDbState.ts
|
|
2026
|
-
var
|
|
1803
|
+
var import_dayjs3 = __toESM(require("dayjs"));
|
|
2027
1804
|
|
|
2028
1805
|
// src/sdk/models/target/temporaryFile.ts
|
|
2029
|
-
var
|
|
1806
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
2030
1807
|
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
2031
1808
|
var createTemporaryFileStream = (streamId) => {
|
|
2032
|
-
|
|
2033
|
-
const
|
|
2034
|
-
const writeStream =
|
|
1809
|
+
import_fs_extra.default.ensureDirSync("./tmp");
|
|
1810
|
+
const path2 = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
1811
|
+
const writeStream = import_fs_extra.default.createWriteStream(path2, { flags: "w" });
|
|
2035
1812
|
return {
|
|
2036
1813
|
stream: writeStream,
|
|
2037
|
-
path
|
|
1814
|
+
path: path2
|
|
2038
1815
|
};
|
|
2039
1816
|
};
|
|
2040
|
-
function safePath(
|
|
2041
|
-
let formattedPath =
|
|
1817
|
+
function safePath(path2) {
|
|
1818
|
+
let formattedPath = path2;
|
|
2042
1819
|
formattedPath = formattedPath.replace(/^The\s+/gi, "");
|
|
2043
1820
|
formattedPath = formattedPath.replace(/\s+(?:\(|\[)?Dis(?:c|k) (?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d)+(?:\)|\])?/gi, "");
|
|
2044
1821
|
formattedPath = formattedPath.replace(/\s+/gi, "_");
|
|
@@ -2079,7 +1856,7 @@ function replaceSymbols(str) {
|
|
|
2079
1856
|
}
|
|
2080
1857
|
|
|
2081
1858
|
// src/sdk/models/target/streamDbState.ts
|
|
2082
|
-
var
|
|
1859
|
+
var fs3 = __toESM(require("fs"));
|
|
2083
1860
|
var StreamState = class {
|
|
2084
1861
|
streamId;
|
|
2085
1862
|
schema;
|
|
@@ -2104,7 +1881,7 @@ var StreamState = class {
|
|
|
2104
1881
|
this.fileToLoad = createTemporaryFileStream(streamId);
|
|
2105
1882
|
this.syncedRowCount = 0;
|
|
2106
1883
|
this.keyProperties = [];
|
|
2107
|
-
this.batchDate = (0,
|
|
1884
|
+
this.batchDate = (0, import_dayjs3.default)();
|
|
2108
1885
|
this.replicationMethod = replicationMethod;
|
|
2109
1886
|
this._hasBeenLoadedYetDuringThisSync = false;
|
|
2110
1887
|
}
|
|
@@ -2129,7 +1906,7 @@ var StreamState = class {
|
|
|
2129
1906
|
resetFileToLoad() {
|
|
2130
1907
|
this.fileToLoad.stream.close();
|
|
2131
1908
|
const oldPath = this.fileToLoad.path;
|
|
2132
|
-
|
|
1909
|
+
fs3.unlinkSync(oldPath);
|
|
2133
1910
|
this.fileToLoad = createTemporaryFileStream(this.streamId);
|
|
2134
1911
|
this.batchedRowCount = 0;
|
|
2135
1912
|
}
|
|
@@ -2160,8 +1937,8 @@ var semaphore = new import_async_mutex.Semaphore(1);
|
|
|
2160
1937
|
var ITarget = class _ITarget {
|
|
2161
1938
|
config;
|
|
2162
1939
|
schemaHooks;
|
|
2163
|
-
resolver;
|
|
2164
1940
|
syncTime;
|
|
1941
|
+
stateProvider;
|
|
2165
1942
|
// Latest state message received from the Tap
|
|
2166
1943
|
// Not yet flushed as we didn't upload the stream data since receiving it
|
|
2167
1944
|
batchedState;
|
|
@@ -2172,15 +1949,15 @@ var ITarget = class _ITarget {
|
|
|
2172
1949
|
streams;
|
|
2173
1950
|
// JSON Schema validator
|
|
2174
1951
|
validator;
|
|
2175
|
-
constructor(config,
|
|
1952
|
+
constructor(config, stateProvider) {
|
|
2176
1953
|
this.config = config;
|
|
2177
|
-
this.
|
|
2178
|
-
this.schemaHooks =
|
|
1954
|
+
this.stateProvider = stateProvider;
|
|
1955
|
+
this.schemaHooks = [];
|
|
2179
1956
|
this.streams = {};
|
|
2180
1957
|
this.batchedState = {};
|
|
2181
1958
|
this.flushedState = {};
|
|
2182
1959
|
this.validator = new import_jsonschema.Validator();
|
|
2183
|
-
this.syncTime = (0,
|
|
1960
|
+
this.syncTime = (0, import_dayjs4.default)();
|
|
2184
1961
|
this.renameColumnStore = new RenameColumnStore();
|
|
2185
1962
|
}
|
|
2186
1963
|
///////////////////////////////////////////
|
|
@@ -2289,7 +2066,7 @@ var ITarget = class _ITarget {
|
|
|
2289
2066
|
this.streams[streamId] = new StreamState(
|
|
2290
2067
|
streamId,
|
|
2291
2068
|
dbSyncInstance,
|
|
2292
|
-
replicationMethod || "FULL_TABLE"
|
|
2069
|
+
replicationMethod || "FULL_TABLE" /* FULL_TABLE */
|
|
2293
2070
|
);
|
|
2294
2071
|
}
|
|
2295
2072
|
replicationMethod = (message) => {
|
|
@@ -2347,7 +2124,8 @@ var ITarget = class _ITarget {
|
|
|
2347
2124
|
}
|
|
2348
2125
|
});
|
|
2349
2126
|
streamState.setSchema(schema);
|
|
2350
|
-
|
|
2127
|
+
const streamReplicationMethod = this.streams[streamId]?.getReplicationMethod();
|
|
2128
|
+
if (message.keyProperties.length === 0 && streamReplicationMethod !== "APPEND" /* APPEND */) {
|
|
2351
2129
|
throw new Error(`StreamId: ${message.stream} - \`key_properties\` field is required and can't be empty in SCHEMA message.
|
|
2352
2130
|
Received SCHEMA message: ${JSON.stringify(message)}`);
|
|
2353
2131
|
}
|
|
@@ -2366,39 +2144,7 @@ var ITarget = class _ITarget {
|
|
|
2366
2144
|
return translation;
|
|
2367
2145
|
}
|
|
2368
2146
|
return k;
|
|
2369
|
-
})
|
|
2370
|
-
relationships: {
|
|
2371
|
-
left: message.relationships.left.flatMap((l) => {
|
|
2372
|
-
if (this.renameColumnStore.isReady(l.streamId) && this.renameColumnStore.isReady(message.stream)) {
|
|
2373
|
-
const from = this.renameColumnStore.getColumnTranslation(l.streamId, l.from);
|
|
2374
|
-
const to = this.renameColumnStore.getColumnTranslation(message.stream, l.to);
|
|
2375
|
-
if (from && to) {
|
|
2376
|
-
return [{
|
|
2377
|
-
type: l.type,
|
|
2378
|
-
streamId: l.streamId,
|
|
2379
|
-
from,
|
|
2380
|
-
to
|
|
2381
|
-
}];
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
return [];
|
|
2385
|
-
}),
|
|
2386
|
-
right: message.relationships.right.flatMap((r) => {
|
|
2387
|
-
if (this.renameColumnStore.isReady(message.stream) && this.renameColumnStore.isReady(r.streamId)) {
|
|
2388
|
-
const from = this.renameColumnStore.getColumnTranslation(message.stream, r.from);
|
|
2389
|
-
const to = this.renameColumnStore.getColumnTranslation(r.streamId, r.to);
|
|
2390
|
-
if (from && to) {
|
|
2391
|
-
return [{
|
|
2392
|
-
type: r.type,
|
|
2393
|
-
streamId: r.streamId,
|
|
2394
|
-
from,
|
|
2395
|
-
to
|
|
2396
|
-
}];
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
return [];
|
|
2400
|
-
})
|
|
2401
|
-
}
|
|
2147
|
+
})
|
|
2402
2148
|
};
|
|
2403
2149
|
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2404
2150
|
const input = {
|
|
@@ -2431,9 +2177,6 @@ var ITarget = class _ITarget {
|
|
|
2431
2177
|
try {
|
|
2432
2178
|
logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
|
|
2433
2179
|
await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
|
|
2434
|
-
const configResolver = loadResolver();
|
|
2435
|
-
await configResolver.markSyncComplete();
|
|
2436
|
-
await configResolver.flushMetrics();
|
|
2437
2180
|
return Promise.resolve();
|
|
2438
2181
|
} catch (err) {
|
|
2439
2182
|
logger.error(`Error while handling complete event.`);
|
|
@@ -2446,7 +2189,8 @@ var ITarget = class _ITarget {
|
|
|
2446
2189
|
return false;
|
|
2447
2190
|
}
|
|
2448
2191
|
const batchedRecords = stream.getBatchedRowCount();
|
|
2449
|
-
|
|
2192
|
+
const method = stream.getReplicationMethod();
|
|
2193
|
+
if ((method === "INCREMENTAL" || method === "APPEND") && batchedRecords > 1e6) {
|
|
2450
2194
|
logger.info(`\u{1F9CA} Stream=${streamId} has reached the maximum amount of batched records. We'll trigger a flush of ALL streams in the Warehouse.`);
|
|
2451
2195
|
return true;
|
|
2452
2196
|
} else {
|
|
@@ -2476,8 +2220,7 @@ var ITarget = class _ITarget {
|
|
|
2476
2220
|
}
|
|
2477
2221
|
};
|
|
2478
2222
|
emitState = (state) => {
|
|
2479
|
-
|
|
2480
|
-
return writeStateFile(configResolver, JSON.stringify(state));
|
|
2223
|
+
return this.stateProvider.writeState(JSON.stringify(state));
|
|
2481
2224
|
};
|
|
2482
2225
|
uploadStreamsToWarehouse = async (streamsToUpload) => {
|
|
2483
2226
|
try {
|
|
@@ -2516,26 +2259,1091 @@ var ITarget = class _ITarget {
|
|
|
2516
2259
|
};
|
|
2517
2260
|
};
|
|
2518
2261
|
|
|
2519
|
-
// src/
|
|
2520
|
-
var
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2262
|
+
// src/sdk/file-processing/types.ts
|
|
2263
|
+
var FileFormat = /* @__PURE__ */ ((FileFormat2) => {
|
|
2264
|
+
FileFormat2["CSV"] = "CSV";
|
|
2265
|
+
FileFormat2["EXCEL"] = "EXCEL";
|
|
2266
|
+
return FileFormat2;
|
|
2267
|
+
})(FileFormat || {});
|
|
2268
|
+
|
|
2269
|
+
// src/sdk/file-processing/schema-utils.ts
|
|
2270
|
+
function fieldTypeToJsonSchema(fieldType) {
|
|
2271
|
+
switch (fieldType) {
|
|
2272
|
+
case "STRING":
|
|
2273
|
+
return { type: ["null", "string"] };
|
|
2274
|
+
case "FLOAT":
|
|
2275
|
+
return { type: ["null", "number"] };
|
|
2276
|
+
case "TIMESTAMP":
|
|
2277
|
+
return { type: ["null", "string"], format: "date-time" };
|
|
2278
|
+
default:
|
|
2279
|
+
return { type: ["null", "string"] };
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
function excelFieldsToJsonSchema(fields) {
|
|
2283
|
+
const properties = {};
|
|
2284
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
2285
|
+
properties[key] = fieldTypeToJsonSchema(field.type);
|
|
2286
|
+
}
|
|
2287
|
+
return { type: "object", properties };
|
|
2288
|
+
}
|
|
2289
|
+
function csvFieldsToJsonSchema(config) {
|
|
2290
|
+
const properties = {};
|
|
2291
|
+
if (Array.isArray(config)) {
|
|
2292
|
+
for (const field of config) {
|
|
2293
|
+
properties[field.key] = fieldTypeToJsonSchema(field.type);
|
|
2294
|
+
}
|
|
2295
|
+
} else {
|
|
2296
|
+
for (const [, field] of Object.entries(config)) {
|
|
2297
|
+
properties[field.key] = fieldTypeToJsonSchema(field.type);
|
|
2526
2298
|
}
|
|
2527
|
-
});
|
|
2528
|
-
returnString = returnString.replace(/^[-\d\s]*/g, "");
|
|
2529
|
-
returnString = returnString.toLowerCase();
|
|
2530
|
-
returnString = returnString.replace("`", "");
|
|
2531
|
-
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2532
|
-
if (returnString.length > 300) {
|
|
2533
|
-
returnString = returnString.substr(0, 300);
|
|
2534
2299
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2300
|
+
return { type: "object", properties };
|
|
2301
|
+
}
|
|
2302
|
+
function extractPrimaryKeysFromCsvConfig(config) {
|
|
2303
|
+
if (Array.isArray(config)) {
|
|
2304
|
+
return config.map((f) => f.key);
|
|
2305
|
+
}
|
|
2306
|
+
return Object.values(config).map((f) => f.key);
|
|
2307
|
+
}
|
|
2308
|
+
function extractPrimaryKeysFromExcelFields(fields) {
|
|
2309
|
+
return Object.entries(fields).filter(([, field]) => {
|
|
2310
|
+
return field.primaryKey === true || field.primaryKey === true;
|
|
2311
|
+
}).map(([key]) => key);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// src/sdk/file-processing/excel/reader.ts
|
|
2315
|
+
var import_xlsx = __toESM(require("xlsx"));
|
|
2316
|
+
var import_path = __toESM(require("path"));
|
|
2317
|
+
var excelColumnToIndex = (columnLetter) => {
|
|
2318
|
+
const letters = columnLetter.toUpperCase();
|
|
2319
|
+
let index = 0;
|
|
2320
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2321
|
+
const charCode = letters.charCodeAt(i);
|
|
2322
|
+
if (charCode < 65 || charCode > 90) {
|
|
2323
|
+
throw new Error(`Invalid column letter: ${columnLetter}`);
|
|
2324
|
+
}
|
|
2325
|
+
index = index * 26 + (charCode - 64);
|
|
2326
|
+
}
|
|
2327
|
+
return index - 1;
|
|
2328
|
+
};
|
|
2329
|
+
var indexToExcelColumn = (columnIndex) => {
|
|
2330
|
+
if (columnIndex < 0) {
|
|
2331
|
+
throw new Error(`Invalid column index: ${columnIndex}. Must be >= 0`);
|
|
2332
|
+
}
|
|
2333
|
+
let result = "";
|
|
2334
|
+
let index = columnIndex;
|
|
2335
|
+
while (true) {
|
|
2336
|
+
result = String.fromCharCode(65 + index % 26) + result;
|
|
2337
|
+
index = Math.floor(index / 26);
|
|
2338
|
+
if (index === 0) break;
|
|
2339
|
+
index--;
|
|
2340
|
+
}
|
|
2341
|
+
return result;
|
|
2342
|
+
};
|
|
2343
|
+
var excelRowToIndex = (rowNumber) => {
|
|
2344
|
+
const rowNum = typeof rowNumber === "string" ? parseInt(rowNumber, 10) : rowNumber;
|
|
2345
|
+
if (isNaN(rowNum) || rowNum < 1) {
|
|
2346
|
+
throw new Error(`Invalid row number: ${rowNumber}. Must be >= 1`);
|
|
2347
|
+
}
|
|
2348
|
+
return rowNum - 1;
|
|
2349
|
+
};
|
|
2350
|
+
var indexToExcelRow = (index) => {
|
|
2351
|
+
if (index < 0) {
|
|
2352
|
+
throw new Error(`Invalid index: ${index}. Must be >= 0`);
|
|
2353
|
+
}
|
|
2354
|
+
return index + 1;
|
|
2355
|
+
};
|
|
2356
|
+
var parseCellReference = (cellRef) => {
|
|
2357
|
+
const match = cellRef.match(/^([A-Z]+)(\d+)$/);
|
|
2358
|
+
if (!match) {
|
|
2359
|
+
throw new Error(`Invalid cell reference: ${cellRef}. Expected format like "A1", "Z24", "AA100"`);
|
|
2360
|
+
}
|
|
2361
|
+
const [, columnLetter, rowNumber] = match;
|
|
2362
|
+
return {
|
|
2363
|
+
columnIndex: excelColumnToIndex(columnLetter),
|
|
2364
|
+
rowIndex: excelRowToIndex(rowNumber)
|
|
2365
|
+
};
|
|
2366
|
+
};
|
|
2367
|
+
var createCellReference = (columnIndex, rowIndex) => {
|
|
2368
|
+
return indexToExcelColumn(columnIndex) + indexToExcelRow(rowIndex);
|
|
2369
|
+
};
|
|
2370
|
+
var extractSingleSheetRows = async (fileName, conf) => {
|
|
2371
|
+
console.log("Extracting single sheet rows from file: %s", fileName);
|
|
2372
|
+
const workbook = import_xlsx.default.readFile(fileName, { dense: true });
|
|
2373
|
+
const sheetKeys = Object.keys(workbook.Sheets);
|
|
2374
|
+
const sheet = conf.sheetName ? workbook.Sheets[conf.sheetName] : workbook.Sheets[sheetKeys[0]];
|
|
2375
|
+
if (!sheet) {
|
|
2376
|
+
throw new Error(`Sheet ${conf.sheetName} not found in workbook`);
|
|
2377
|
+
}
|
|
2378
|
+
const sheetData = sheet["!data"];
|
|
2379
|
+
if (!sheetData) {
|
|
2380
|
+
throw new Error(`No data in ${conf.sheetName || "first"} sheet`);
|
|
2381
|
+
}
|
|
2382
|
+
if (sheetData.length === 0) {
|
|
2383
|
+
throw new Error(`Sheet ${conf.sheetName || "first"} is empty`);
|
|
2384
|
+
}
|
|
2385
|
+
const variables = conf.fileNameVariablesExtractor(fileName, workbook);
|
|
2386
|
+
const data = [];
|
|
2387
|
+
let rowIdx = 0;
|
|
2388
|
+
const minColumnIndex = Object.values(conf.columns).reduce((min, column) => {
|
|
2389
|
+
return column.column ? Math.min(min, excelColumnToIndex(column.column)) : min;
|
|
2390
|
+
}, Number.MAX_SAFE_INTEGER);
|
|
2391
|
+
for (const row of sheetData) {
|
|
2392
|
+
if (rowIdx < conf.numberOfRowsToSkip) {
|
|
2393
|
+
rowIdx++;
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
if (!row) {
|
|
2397
|
+
console.log("Empty row at index: %s, skipped processing.", rowIdx);
|
|
2398
|
+
continue;
|
|
2399
|
+
}
|
|
2400
|
+
if (minColumnIndex !== Number.MAX_SAFE_INTEGER && !row[minColumnIndex]?.v) {
|
|
2401
|
+
console.log("stopping processing because of empty value in the first data column (idx: %s)", minColumnIndex);
|
|
2402
|
+
break;
|
|
2403
|
+
}
|
|
2404
|
+
const rowData = Object.entries(conf.columns).reduce((acc, [key, column]) => {
|
|
2405
|
+
if (column.variableName) {
|
|
2406
|
+
if (!variables) {
|
|
2407
|
+
throw new Error(`No variables extracted from filename, cannot extract derived field ${key}`);
|
|
2408
|
+
}
|
|
2409
|
+
acc[key] = variables[column.variableName] || "";
|
|
2410
|
+
} else {
|
|
2411
|
+
const colIndex = excelColumnToIndex(column.column);
|
|
2412
|
+
acc[key] = row[colIndex]?.v || "";
|
|
2413
|
+
}
|
|
2414
|
+
return acc;
|
|
2415
|
+
}, {});
|
|
2416
|
+
data.push(rowData);
|
|
2417
|
+
rowIdx++;
|
|
2418
|
+
}
|
|
2419
|
+
console.log("Extracted %s rows from sheet %s", data.length, conf.sheetName || "first");
|
|
2420
|
+
return data;
|
|
2421
|
+
};
|
|
2422
|
+
var extractExcelRows = async (localFilePath, conf) => {
|
|
2423
|
+
let data;
|
|
2424
|
+
if (conf.type === "single-sheet-extraction") {
|
|
2425
|
+
data = await extractSingleSheetRows(localFilePath, conf);
|
|
2426
|
+
} else if (conf.type === "processor") {
|
|
2427
|
+
const workbook = import_xlsx.default.readFile(localFilePath, { dense: true });
|
|
2428
|
+
data = await conf.processor(workbook);
|
|
2429
|
+
} else {
|
|
2430
|
+
throw new Error(`Unsupported configuration type: ${conf.type}`);
|
|
2431
|
+
}
|
|
2432
|
+
return data;
|
|
2433
|
+
};
|
|
2434
|
+
var validateExcelConfig = (config) => {
|
|
2435
|
+
if (!config.extension) {
|
|
2436
|
+
throw new Error("Extension is required");
|
|
2437
|
+
}
|
|
2438
|
+
if (!config.tableName) {
|
|
2439
|
+
throw new Error("Table name is required");
|
|
2440
|
+
}
|
|
2441
|
+
if (!config.fileNameValidator || typeof config.fileNameValidator !== "function") {
|
|
2442
|
+
throw new Error("File name validator function is required");
|
|
2443
|
+
}
|
|
2444
|
+
if (!config.fileNameVariablesExtractor || typeof config.fileNameVariablesExtractor !== "function") {
|
|
2445
|
+
throw new Error("File name variables extractor function is required");
|
|
2446
|
+
}
|
|
2447
|
+
if (config.type === "single-sheet-extraction" && (!config.columns || Object.keys(config.columns).length === 0)) {
|
|
2448
|
+
throw new Error("At least one column configuration is required for single-sheet-extraction");
|
|
2449
|
+
}
|
|
2450
|
+
if (config.type === "single-sheet-extraction") {
|
|
2451
|
+
Object.entries(config.columns).forEach(([key, column]) => {
|
|
2452
|
+
if (!column.type) {
|
|
2453
|
+
throw new Error(`Column ${key} must have a type`);
|
|
2454
|
+
}
|
|
2455
|
+
if (column.column) {
|
|
2456
|
+
const colLetter = column.column;
|
|
2457
|
+
if (!/^[A-Z]+$/i.test(colLetter)) {
|
|
2458
|
+
throw new Error(`Invalid column letter format: ${colLetter}`);
|
|
2459
|
+
}
|
|
2460
|
+
} else if (column.variableName) {
|
|
2461
|
+
} else {
|
|
2462
|
+
throw new Error(`Column ${key} must specify either 'column' or 'variableName'`);
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
if (config.numberOfRowsToSkip === void 0 || config.numberOfRowsToSkip < 0) {
|
|
2466
|
+
throw new Error("Number of rows to skip must be specified and non-negative");
|
|
2467
|
+
}
|
|
2468
|
+
} else if (config.type === "processor") {
|
|
2469
|
+
if (!config.processor || typeof config.processor !== "function") {
|
|
2470
|
+
throw new Error("Processor function is required for processor type");
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
var findAllMatchingConfigs = (filename, configs) => {
|
|
2475
|
+
const fileInfo = import_path.default.parse(filename);
|
|
2476
|
+
return configs.filter(
|
|
2477
|
+
(config) => config.fileNameValidator(fileInfo.base) && config.extension === fileInfo.ext.replace(".", "")
|
|
2478
|
+
);
|
|
2479
|
+
};
|
|
2480
|
+
async function* createExcelGenerator(data) {
|
|
2481
|
+
for (const row of data) {
|
|
2482
|
+
yield row;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// src/sdk/file-processing/excel/config-builder.ts
|
|
2487
|
+
var ExcelExtractionConfigBuilder = class _ExcelExtractionConfigBuilder {
|
|
2488
|
+
config = {
|
|
2489
|
+
fileNameValidator: () => true,
|
|
2490
|
+
fileNameVariablesExtractor: () => ({}),
|
|
2491
|
+
replicationMethod: "FULL_TABLE" /* FULL_TABLE */
|
|
2492
|
+
};
|
|
2493
|
+
static create() {
|
|
2494
|
+
return new _ExcelExtractionConfigBuilder();
|
|
2495
|
+
}
|
|
2496
|
+
extension(ext) {
|
|
2497
|
+
this.config.extension = ext;
|
|
2498
|
+
return this;
|
|
2499
|
+
}
|
|
2500
|
+
tableName(name) {
|
|
2501
|
+
this.config.tableName = name;
|
|
2502
|
+
return this;
|
|
2503
|
+
}
|
|
2504
|
+
fileValidator(validator) {
|
|
2505
|
+
this.config.fileNameValidator = validator;
|
|
2506
|
+
return this;
|
|
2507
|
+
}
|
|
2508
|
+
variablesExtractor(extractor) {
|
|
2509
|
+
this.config.fileNameVariablesExtractor = extractor;
|
|
2510
|
+
return this;
|
|
2511
|
+
}
|
|
2512
|
+
replicationMethod(method) {
|
|
2513
|
+
this.config.replicationMethod = method;
|
|
2514
|
+
return this;
|
|
2515
|
+
}
|
|
2516
|
+
columns(cols) {
|
|
2517
|
+
if (this.config.type === "single-sheet-extraction") {
|
|
2518
|
+
this.config.columns = cols;
|
|
2519
|
+
} else {
|
|
2520
|
+
throw new Error("Columns can only be set for single-sheet-extraction");
|
|
2521
|
+
}
|
|
2522
|
+
return this;
|
|
2523
|
+
}
|
|
2524
|
+
singleSheet(sheetName, skipRows = 0) {
|
|
2525
|
+
this.config.type = "single-sheet-extraction";
|
|
2526
|
+
if (sheetName !== void 0) {
|
|
2527
|
+
this.config.sheetName = sheetName;
|
|
2528
|
+
}
|
|
2529
|
+
this.config.numberOfRowsToSkip = skipRows;
|
|
2530
|
+
return this;
|
|
2531
|
+
}
|
|
2532
|
+
processor(processorFn) {
|
|
2533
|
+
this.config.type = "processor";
|
|
2534
|
+
this.config.processor = processorFn;
|
|
2535
|
+
return this;
|
|
2536
|
+
}
|
|
2537
|
+
build() {
|
|
2538
|
+
if (!this.config.type) {
|
|
2539
|
+
throw new Error("Configuration type must be specified (singleSheet or processor)");
|
|
2540
|
+
}
|
|
2541
|
+
if (!this.config.extension) {
|
|
2542
|
+
throw new Error("Extension must be set");
|
|
2543
|
+
}
|
|
2544
|
+
if (!this.config.tableName) {
|
|
2545
|
+
throw new Error("Table name must be set");
|
|
2546
|
+
}
|
|
2547
|
+
if (!this.config.replicationMethod) {
|
|
2548
|
+
throw new Error("Replication method must be set");
|
|
2549
|
+
}
|
|
2550
|
+
if (typeof this.config.fileNameValidator !== "function") {
|
|
2551
|
+
throw new Error("fileNameValidator must be a function");
|
|
2552
|
+
}
|
|
2553
|
+
if (typeof this.config.fileNameVariablesExtractor !== "function") {
|
|
2554
|
+
throw new Error("fileNameVariablesExtractor must be a function");
|
|
2555
|
+
}
|
|
2556
|
+
if (this.config.type === "single-sheet-extraction") {
|
|
2557
|
+
const singleSheetConfig = this.config;
|
|
2558
|
+
if (!singleSheetConfig.columns || Object.keys(singleSheetConfig.columns).length === 0) {
|
|
2559
|
+
throw new Error("Columns must be set with at least one column for single-sheet-extraction");
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
if (this.config.type === "processor") {
|
|
2563
|
+
const processorConfig = this.config;
|
|
2564
|
+
if (typeof processorConfig.processor !== "function") {
|
|
2565
|
+
throw new Error("Processor function must be set for processor configs");
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
return this.config;
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
// src/sdk/file-processing/csv/reader.ts
|
|
2573
|
+
var import_csv_parser = __toESM(require("csv-parser"));
|
|
2574
|
+
var import_fs2 = require("fs");
|
|
2575
|
+
var import_util3 = require("util");
|
|
2576
|
+
var import_stream2 = require("stream");
|
|
2577
|
+
var iconv = __toESM(require("iconv-lite"));
|
|
2578
|
+
var logPrefix = "[csvReader]";
|
|
2579
|
+
var createStripBomTransform = () => {
|
|
2580
|
+
let isFirstChunk = true;
|
|
2581
|
+
return new import_stream2.Transform({
|
|
2582
|
+
transform(chunk2, encoding, callback) {
|
|
2583
|
+
if (isFirstChunk) {
|
|
2584
|
+
isFirstChunk = false;
|
|
2585
|
+
if (chunk2.length >= 3 && chunk2[0] === 239 && chunk2[1] === 187 && chunk2[2] === 191) {
|
|
2586
|
+
chunk2 = chunk2.slice(3);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
callback(null, chunk2);
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
};
|
|
2593
|
+
var getStringHexInfo = (str) => {
|
|
2594
|
+
const bytes = Buffer.from(str, "utf8");
|
|
2595
|
+
const hex = bytes.toString("hex").match(/.{2}/g)?.join(" ") || "";
|
|
2596
|
+
return {
|
|
2597
|
+
original: str,
|
|
2598
|
+
length: str.length,
|
|
2599
|
+
hex,
|
|
2600
|
+
trimmed: str.trim()
|
|
2601
|
+
};
|
|
2602
|
+
};
|
|
2603
|
+
var formatHexComparison = (expected, actual) => {
|
|
2604
|
+
const expectedInfo = getStringHexInfo(expected);
|
|
2605
|
+
const actualInfo = getStringHexInfo(actual);
|
|
2606
|
+
return `
|
|
2607
|
+
Expected: "${expected}" (${expectedInfo.length} chars) [${expectedInfo.hex}]
|
|
2608
|
+
Actual: "${actual}" (${actualInfo.length} chars) [${actualInfo.hex}]`;
|
|
2609
|
+
};
|
|
2610
|
+
async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
2611
|
+
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2612
|
+
const csvOptions = { separator: fileConfig.separator };
|
|
2613
|
+
if (isGeneratorConfigArray) {
|
|
2614
|
+
csvOptions.headers = false;
|
|
2615
|
+
}
|
|
2616
|
+
const csvStream = (0, import_csv_parser.default)(csvOptions);
|
|
2617
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2618
|
+
let initialReadStream;
|
|
2619
|
+
let finalInputStream;
|
|
2620
|
+
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2621
|
+
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2622
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2623
|
+
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2624
|
+
} else {
|
|
2625
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2626
|
+
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2627
|
+
}
|
|
2628
|
+
finalInputStream.pipe(csvStream);
|
|
2629
|
+
try {
|
|
2630
|
+
for await (const row of import_stream2.Readable.from(csvStream)) {
|
|
2631
|
+
if (!isGeneratorConfigArray) {
|
|
2632
|
+
const rowData = Object.keys(fileConfig.fields).reduce((acc, key) => {
|
|
2633
|
+
const keyGenerator = fileConfig.fields[key.trim()];
|
|
2634
|
+
if (!keyGenerator) {
|
|
2635
|
+
return acc;
|
|
2636
|
+
}
|
|
2637
|
+
return {
|
|
2638
|
+
...acc,
|
|
2639
|
+
[keyGenerator.key]: keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[key]) : row[key]
|
|
2640
|
+
};
|
|
2641
|
+
}, fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {});
|
|
2642
|
+
yield rowData;
|
|
2643
|
+
} else {
|
|
2644
|
+
const rowData = Object.keys(row).reduce((acc, key, i) => {
|
|
2645
|
+
const keyGenerator = fileConfig.fields[i];
|
|
2646
|
+
if (!keyGenerator) {
|
|
2647
|
+
return acc;
|
|
2648
|
+
}
|
|
2649
|
+
return {
|
|
2650
|
+
...acc,
|
|
2651
|
+
[keyGenerator.key]: keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i]
|
|
2652
|
+
};
|
|
2653
|
+
}, fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {});
|
|
2654
|
+
yield rowData;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
} finally {
|
|
2658
|
+
initialReadStream.destroy();
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
2662
|
+
return new Promise((resolve, reject) => {
|
|
2663
|
+
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
2664
|
+
console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
|
|
2665
|
+
const info = getStringHexInfo(col);
|
|
2666
|
+
return `"${info.original}" [${info.hex}]`;
|
|
2667
|
+
}));
|
|
2668
|
+
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2669
|
+
const csvOptions2 = { separator: fileConfig.separator };
|
|
2670
|
+
if (isGeneratorConfigArray) {
|
|
2671
|
+
csvOptions2.headers = false;
|
|
2672
|
+
}
|
|
2673
|
+
const csvStream = (0, import_csv_parser.default)(csvOptions2);
|
|
2674
|
+
let initialReadStream;
|
|
2675
|
+
let finalInputStream;
|
|
2676
|
+
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2677
|
+
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2678
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2679
|
+
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2680
|
+
} else {
|
|
2681
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2682
|
+
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2683
|
+
}
|
|
2684
|
+
let isFirstLine = true;
|
|
2685
|
+
finalInputStream.pipe(csvStream).on("data", (row) => {
|
|
2686
|
+
if (isFirstLine) {
|
|
2687
|
+
let headers = [];
|
|
2688
|
+
if (isGeneratorConfigArray) {
|
|
2689
|
+
headers = Object.values(row).filter((r) => !!r);
|
|
2690
|
+
} else {
|
|
2691
|
+
headers = Object.keys(row).filter((r) => !!r);
|
|
2692
|
+
}
|
|
2693
|
+
console.debug(`${logPrefix} CSV Detected headers:`, headers.map((h, idx) => {
|
|
2694
|
+
const info = getStringHexInfo(h);
|
|
2695
|
+
return `[${idx}]: "${info.original}" [${info.hex}]`;
|
|
2696
|
+
}));
|
|
2697
|
+
if (isGeneratorConfigArray) {
|
|
2698
|
+
colsWithParsingConfig.forEach((col, i) => {
|
|
2699
|
+
if (col !== headers[i]) {
|
|
2700
|
+
const hexComparison = formatHexComparison(col, headers[i] || "<undefined>");
|
|
2701
|
+
console.error(`${logPrefix} CSV Header Mismatch:${hexComparison}`);
|
|
2702
|
+
throw new Error((0, import_util3.format)(
|
|
2703
|
+
`CSV header mismatch at position %s: expected '%s' but got '%s' in file %s`,
|
|
2704
|
+
i,
|
|
2705
|
+
col,
|
|
2706
|
+
headers[i] || "<undefined>",
|
|
2707
|
+
path2
|
|
2708
|
+
));
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
} else {
|
|
2712
|
+
colsWithParsingConfig.forEach((col) => {
|
|
2713
|
+
if (!headers.includes(col)) {
|
|
2714
|
+
const colInfo = getStringHexInfo(col);
|
|
2715
|
+
console.error(`CSV Missing column: "${col}" [${colInfo.hex}]`);
|
|
2716
|
+
console.error(`Available headers:`, headers.map((h) => `"${h}" [${getStringHexInfo(h).hex}]`));
|
|
2717
|
+
const trimmedMatch = headers.find((h) => h.trim() === col.trim());
|
|
2718
|
+
if (trimmedMatch) {
|
|
2719
|
+
console.error(`Potential trimmed match: "${trimmedMatch}" [${getStringHexInfo(trimmedMatch).hex}]`);
|
|
2720
|
+
}
|
|
2721
|
+
throw new Error((0, import_util3.format)(
|
|
2722
|
+
`CSV missing expected column '%s' in file: %s`,
|
|
2723
|
+
col,
|
|
2724
|
+
path2
|
|
2725
|
+
));
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
isFirstLine = false;
|
|
2730
|
+
} else {
|
|
2731
|
+
initialReadStream.destroy();
|
|
2732
|
+
}
|
|
2733
|
+
}).on("error", (error) => {
|
|
2734
|
+
reject(new Error(`Error processing CSV file: ${error.message}`));
|
|
2735
|
+
});
|
|
2736
|
+
initialReadStream.on("close", () => {
|
|
2737
|
+
resolve();
|
|
2738
|
+
});
|
|
2739
|
+
});
|
|
2740
|
+
};
|
|
2741
|
+
|
|
2742
|
+
// src/sdk/file-processing/csv/writer.ts
|
|
2743
|
+
var import_csv_writer = require("csv-writer");
|
|
2744
|
+
var import_util4 = require("util");
|
|
2745
|
+
var logPrefix2 = "[csvWriter]";
|
|
2746
|
+
var writeDataToCsv = async (fileName, data) => {
|
|
2747
|
+
try {
|
|
2748
|
+
const firstRow = data[0];
|
|
2749
|
+
if (!firstRow) {
|
|
2750
|
+
console.log(`${logPrefix2} No data to write to CSV file=%s`, fileName);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2754
|
+
path: fileName,
|
|
2755
|
+
header: Object.keys(firstRow).map((col) => {
|
|
2756
|
+
return { id: col, title: col };
|
|
2757
|
+
})
|
|
2758
|
+
});
|
|
2759
|
+
await csvWriter.writeRecords(data);
|
|
2760
|
+
console.log(`${logPrefix2} CSV file=%s written successfully`, fileName);
|
|
2761
|
+
} catch (err) {
|
|
2762
|
+
if (err instanceof Error) {
|
|
2763
|
+
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, fileName, err.message));
|
|
2764
|
+
}
|
|
2765
|
+
throw err;
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
2769
|
+
try {
|
|
2770
|
+
let rowCount = 0;
|
|
2771
|
+
const firstDataRow = (await generator.next()).value;
|
|
2772
|
+
if (!firstDataRow) {
|
|
2773
|
+
console.log(`${logPrefix2} [%s] first data row of csv empty, skipping`, outputFileName);
|
|
2774
|
+
return 0;
|
|
2775
|
+
}
|
|
2776
|
+
console.log(`${logPrefix2} [%s] inferring headers from first data row`, outputFileName);
|
|
2777
|
+
const headers = Object.keys(firstDataRow).map((col) => {
|
|
2778
|
+
return {
|
|
2779
|
+
id: col,
|
|
2780
|
+
title: col
|
|
2781
|
+
};
|
|
2782
|
+
});
|
|
2783
|
+
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2784
|
+
path: outputFileName,
|
|
2785
|
+
header: headers,
|
|
2786
|
+
alwaysQuote: true
|
|
2787
|
+
});
|
|
2788
|
+
console.log(`${logPrefix2} [%s] writing csv`, outputFileName);
|
|
2789
|
+
let batch = [firstDataRow];
|
|
2790
|
+
for await (const data of generator) {
|
|
2791
|
+
batch.push(data);
|
|
2792
|
+
rowCount++;
|
|
2793
|
+
if (batch.length > 1e4) {
|
|
2794
|
+
try {
|
|
2795
|
+
await csvWriter.writeRecords(batch);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
if (err instanceof Error) {
|
|
2798
|
+
throw new Error(
|
|
2799
|
+
(0, import_util4.format)(
|
|
2800
|
+
`error while writing batch to csv batch (first 3 records)=%j, err: %s`,
|
|
2801
|
+
batch.slice(0, 3),
|
|
2802
|
+
err.message
|
|
2803
|
+
)
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
throw err;
|
|
2807
|
+
}
|
|
2808
|
+
batch = [];
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
await csvWriter.writeRecords(batch);
|
|
2812
|
+
console.log(`${logPrefix2} [%s] csv written successfully with %d rows`, outputFileName, rowCount);
|
|
2813
|
+
return rowCount;
|
|
2814
|
+
} catch (err) {
|
|
2815
|
+
if (err instanceof Error) {
|
|
2816
|
+
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, outputFileName, err.message));
|
|
2817
|
+
}
|
|
2818
|
+
throw err;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// src/sdk/file-processing/file-stream.ts
|
|
2823
|
+
var import_xlsx2 = __toESM(require("xlsx"));
|
|
2824
|
+
var FileStream = class extends Stream {
|
|
2825
|
+
localFilePaths;
|
|
2826
|
+
constructor(config, localFilePath, tapState, target) {
|
|
2827
|
+
super(config, tapState, target);
|
|
2828
|
+
this.localFilePaths = Array.isArray(localFilePath) ? localFilePath : [localFilePath];
|
|
2829
|
+
this.streamId = config.streamId;
|
|
2830
|
+
this.primaryKey = config.primaryKeys;
|
|
2831
|
+
this.replicationMethod = config.replicationMethod;
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Build JSON Schema from the file config's field definitions.
|
|
2835
|
+
*/
|
|
2836
|
+
async getSchema() {
|
|
2837
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2838
|
+
const excelConfig = this.config.excel;
|
|
2839
|
+
if (excelConfig.type === "single-sheet-extraction") {
|
|
2840
|
+
const jsonSchema = excelFieldsToJsonSchema(excelConfig.columns);
|
|
2841
|
+
return { jsonSchema };
|
|
2842
|
+
}
|
|
2843
|
+
return void 0;
|
|
2844
|
+
}
|
|
2845
|
+
if (this.config.format === "CSV" /* CSV */) {
|
|
2846
|
+
const jsonSchema = csvFieldsToJsonSchema(this.config.csv.fields);
|
|
2847
|
+
return { jsonSchema };
|
|
2848
|
+
}
|
|
2849
|
+
throw new Error(`FileStream: No valid format config for stream ${this.streamId}`);
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Yield records from all file(s).
|
|
2853
|
+
* When multiple file paths are provided, records are yielded sequentially from each file.
|
|
2854
|
+
*/
|
|
2855
|
+
async *_getRecords() {
|
|
2856
|
+
for (const filePath of this.localFilePaths) {
|
|
2857
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2858
|
+
const data = await extractExcelRows(filePath, this.config.excel);
|
|
2859
|
+
for (const row of data) {
|
|
2860
|
+
yield row;
|
|
2861
|
+
}
|
|
2862
|
+
} else if (this.config.format === "CSV" /* CSV */) {
|
|
2863
|
+
yield* rowGeneratorFromCsv(filePath, this.config.csv);
|
|
2864
|
+
} else {
|
|
2865
|
+
throw new Error(`FileStream: Unsupported format for stream ${this.streamId}`);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
function createExcelStreamConfig(excelConfig, fileName, localFilePath) {
|
|
2871
|
+
if (excelConfig.type === "single-sheet-extraction") {
|
|
2872
|
+
const primaryKeys = extractPrimaryKeysFromExcelFields(excelConfig.columns);
|
|
2873
|
+
let tableName2;
|
|
2874
|
+
if (typeof excelConfig.tableName === "function") {
|
|
2875
|
+
const variables = excelConfig.fileNameVariablesExtractor(fileName);
|
|
2876
|
+
tableName2 = excelConfig.tableName(fileName, void 0, variables);
|
|
2877
|
+
} else {
|
|
2878
|
+
tableName2 = excelConfig.tableName;
|
|
2879
|
+
}
|
|
2880
|
+
return {
|
|
2881
|
+
format: "EXCEL" /* EXCEL */,
|
|
2882
|
+
streamId: tableName2,
|
|
2883
|
+
replicationMethod: excelConfig.replicationMethod,
|
|
2884
|
+
primaryKeys,
|
|
2885
|
+
excel: excelConfig
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
let tableName;
|
|
2889
|
+
if (typeof excelConfig.tableName === "function") {
|
|
2890
|
+
if (!localFilePath) {
|
|
2891
|
+
throw new Error("localFilePath is required for processor configs with dynamic table names");
|
|
2892
|
+
}
|
|
2893
|
+
const workbook = import_xlsx2.default.readFile(localFilePath, { dense: true });
|
|
2894
|
+
const variables = excelConfig.fileNameVariablesExtractor(fileName, workbook);
|
|
2895
|
+
tableName = excelConfig.tableName(fileName, workbook, variables);
|
|
2896
|
+
} else {
|
|
2897
|
+
tableName = excelConfig.tableName;
|
|
2898
|
+
}
|
|
2899
|
+
return {
|
|
2900
|
+
format: "EXCEL" /* EXCEL */,
|
|
2901
|
+
streamId: tableName,
|
|
2902
|
+
replicationMethod: excelConfig.replicationMethod,
|
|
2903
|
+
primaryKeys: [],
|
|
2904
|
+
excel: excelConfig
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
function createCsvStreamConfig(streamId, csvConfig, options) {
|
|
2908
|
+
return {
|
|
2909
|
+
format: "CSV" /* CSV */,
|
|
2910
|
+
streamId,
|
|
2911
|
+
replicationMethod: options?.replicationMethod ?? "FULL_TABLE" /* FULL_TABLE */,
|
|
2912
|
+
primaryKeys: options?.primaryKeys ?? extractPrimaryKeysFromCsvConfig(csvConfig.fields),
|
|
2913
|
+
csv: csvConfig
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
async function processFileStreams(entries, tapState, target) {
|
|
2917
|
+
for (const entry of entries) {
|
|
2918
|
+
const stream = new FileStream(entry.config, entry.filePath, tapState, target);
|
|
2919
|
+
await stream.sync();
|
|
2920
|
+
}
|
|
2921
|
+
await target.complete();
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
// src/sdk/file-processing/csv/config-builder.ts
|
|
2925
|
+
var CsvExtractionConfigBuilder = class _CsvExtractionConfigBuilder {
|
|
2926
|
+
_separator = ",";
|
|
2927
|
+
_encoding;
|
|
2928
|
+
_addSyncedAtColumn;
|
|
2929
|
+
_fields;
|
|
2930
|
+
_fieldsMode;
|
|
2931
|
+
_replicationMethod;
|
|
2932
|
+
_primaryKeys;
|
|
2933
|
+
static create() {
|
|
2934
|
+
return new _CsvExtractionConfigBuilder();
|
|
2935
|
+
}
|
|
2936
|
+
separator(sep) {
|
|
2937
|
+
this._separator = sep;
|
|
2938
|
+
return this;
|
|
2939
|
+
}
|
|
2940
|
+
encoding(enc) {
|
|
2941
|
+
this._encoding = enc;
|
|
2942
|
+
return this;
|
|
2943
|
+
}
|
|
2944
|
+
addSyncedAtColumn(enabled = true) {
|
|
2945
|
+
this._addSyncedAtColumn = enabled;
|
|
2946
|
+
return this;
|
|
2947
|
+
}
|
|
2948
|
+
fieldsFromDict(mapping) {
|
|
2949
|
+
if (this._fieldsMode === "array") {
|
|
2950
|
+
throw new Error("Cannot use fieldsFromDict after fieldsFromArray");
|
|
2951
|
+
}
|
|
2952
|
+
this._fieldsMode = "dict";
|
|
2953
|
+
this._fields = mapping;
|
|
2954
|
+
return this;
|
|
2955
|
+
}
|
|
2956
|
+
fieldsFromArray(fields) {
|
|
2957
|
+
if (this._fieldsMode === "dict") {
|
|
2958
|
+
throw new Error("Cannot use fieldsFromArray after fieldsFromDict");
|
|
2959
|
+
}
|
|
2960
|
+
this._fieldsMode = "array";
|
|
2961
|
+
this._fields = fields;
|
|
2962
|
+
return this;
|
|
2963
|
+
}
|
|
2964
|
+
replicationMethod(method) {
|
|
2965
|
+
this._replicationMethod = method;
|
|
2966
|
+
return this;
|
|
2967
|
+
}
|
|
2968
|
+
primaryKeys(keys) {
|
|
2969
|
+
this._primaryKeys = keys;
|
|
2970
|
+
return this;
|
|
2971
|
+
}
|
|
2972
|
+
build() {
|
|
2973
|
+
if (!this._fields) {
|
|
2974
|
+
throw new Error("Fields must be configured (use fieldsFromDict or fieldsFromArray)");
|
|
2975
|
+
}
|
|
2976
|
+
if (Array.isArray(this._fields) && this._fields.length === 0) {
|
|
2977
|
+
throw new Error("Fields must not be empty");
|
|
2978
|
+
}
|
|
2979
|
+
if (!Array.isArray(this._fields) && Object.keys(this._fields).length === 0) {
|
|
2980
|
+
throw new Error("Fields must not be empty");
|
|
2981
|
+
}
|
|
2982
|
+
const config = {
|
|
2983
|
+
separator: this._separator,
|
|
2984
|
+
fields: this._fields
|
|
2985
|
+
};
|
|
2986
|
+
if (this._encoding !== void 0) {
|
|
2987
|
+
config.encoding = this._encoding;
|
|
2988
|
+
}
|
|
2989
|
+
if (this._addSyncedAtColumn !== void 0) {
|
|
2990
|
+
config.addSyncedAtColumn = this._addSyncedAtColumn;
|
|
2991
|
+
}
|
|
2992
|
+
return config;
|
|
2993
|
+
}
|
|
2994
|
+
buildStreamConfig(streamId) {
|
|
2995
|
+
const csvConfig = this.build();
|
|
2996
|
+
const options = {};
|
|
2997
|
+
if (this._replicationMethod !== void 0) {
|
|
2998
|
+
options.replicationMethod = this._replicationMethod;
|
|
2999
|
+
}
|
|
3000
|
+
if (this._primaryKeys !== void 0) {
|
|
3001
|
+
options.primaryKeys = this._primaryKeys;
|
|
3002
|
+
}
|
|
3003
|
+
return createCsvStreamConfig(streamId, csvConfig, options);
|
|
3004
|
+
}
|
|
3005
|
+
};
|
|
3006
|
+
|
|
3007
|
+
// src/sdk/file-processing/file-tap.ts
|
|
3008
|
+
var FileTap = class _FileTap extends Tap {
|
|
3009
|
+
entries;
|
|
3010
|
+
constructor(target, config, stateProvider, entries) {
|
|
3011
|
+
super(target, config, stateProvider);
|
|
3012
|
+
this.entries = entries;
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Convenience constructor for the common case where all configs share the same file path.
|
|
3016
|
+
*/
|
|
3017
|
+
static fromConfigs(target, config, stateProvider, fileConfigs, sharedFilePath) {
|
|
3018
|
+
const entries = fileConfigs.map((c) => ({
|
|
3019
|
+
config: c,
|
|
3020
|
+
filePath: sharedFilePath
|
|
3021
|
+
}));
|
|
3022
|
+
return new _FileTap(target, config, stateProvider, entries);
|
|
3023
|
+
}
|
|
3024
|
+
async init() {
|
|
3025
|
+
for (const entry of this.entries) {
|
|
3026
|
+
const stream = new FileStream(
|
|
3027
|
+
entry.config,
|
|
3028
|
+
entry.filePath,
|
|
3029
|
+
this.tapState,
|
|
3030
|
+
this.target
|
|
3031
|
+
);
|
|
3032
|
+
this.streams.push(stream);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
// src/sdk/file-processing/file-patterns.ts
|
|
3038
|
+
var VariableExtractors = class {
|
|
3039
|
+
/**
|
|
3040
|
+
* Returns the filename as a variable.
|
|
3041
|
+
*/
|
|
3042
|
+
static filename() {
|
|
3043
|
+
return (filename) => ({
|
|
3044
|
+
fileName: filename
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
/**
|
|
3048
|
+
* Extracts named capture groups from a regex pattern.
|
|
3049
|
+
*/
|
|
3050
|
+
static regex(pattern) {
|
|
3051
|
+
return (filename) => {
|
|
3052
|
+
const match = filename.match(pattern);
|
|
3053
|
+
return match?.groups ?? {};
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Combines multiple extraction functions.
|
|
3058
|
+
*/
|
|
3059
|
+
static combine(...extractors) {
|
|
3060
|
+
return (filename) => {
|
|
3061
|
+
return extractors.reduce((acc, extractor) => {
|
|
3062
|
+
return { ...acc, ...extractor(filename) };
|
|
3063
|
+
}, {});
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
};
|
|
3067
|
+
var FilePatterns = class {
|
|
3068
|
+
/**
|
|
3069
|
+
* Creates a validator for files starting with a specific prefix (case-insensitive).
|
|
3070
|
+
*/
|
|
3071
|
+
static startsWith(prefix) {
|
|
3072
|
+
return (filename) => filename.toLowerCase().startsWith(prefix.toLowerCase());
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Creates a validator for files matching a regex pattern.
|
|
3076
|
+
*/
|
|
3077
|
+
static regex(pattern) {
|
|
3078
|
+
return (filename) => pattern.test(filename);
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Combines multiple validators with AND logic.
|
|
3082
|
+
*/
|
|
3083
|
+
static and(...validators) {
|
|
3084
|
+
return (filename) => validators.every((validator) => validator(filename));
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Combines multiple validators with OR logic.
|
|
3088
|
+
*/
|
|
3089
|
+
static or(...validators) {
|
|
3090
|
+
return (filename) => validators.some((validator) => validator(filename));
|
|
3091
|
+
}
|
|
3092
|
+
};
|
|
3093
|
+
|
|
3094
|
+
// src/services/sftp.ts
|
|
3095
|
+
var import_ssh2_sftp_client = __toESM(require("ssh2-sftp-client"));
|
|
3096
|
+
var import_ssh2_sftp_client2 = __toESM(require("ssh2-sftp-client"));
|
|
3097
|
+
var SftpService = new import_ssh2_sftp_client.default();
|
|
3098
|
+
|
|
3099
|
+
// src/services/cloud-storage.ts
|
|
3100
|
+
var import_storage = require("@google-cloud/storage");
|
|
3101
|
+
var import_util5 = require("util");
|
|
3102
|
+
var pathModule = __toESM(require("path"));
|
|
3103
|
+
var import_fs3 = require("fs");
|
|
3104
|
+
|
|
3105
|
+
// node_modules/uuid/dist/esm-node/rng.js
|
|
3106
|
+
var import_crypto = __toESM(require("crypto"));
|
|
3107
|
+
var rnds8Pool = new Uint8Array(256);
|
|
3108
|
+
var poolPtr = rnds8Pool.length;
|
|
3109
|
+
function rng() {
|
|
3110
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
3111
|
+
import_crypto.default.randomFillSync(rnds8Pool);
|
|
3112
|
+
poolPtr = 0;
|
|
3113
|
+
}
|
|
3114
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// node_modules/uuid/dist/esm-node/regex.js
|
|
3118
|
+
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
3119
|
+
|
|
3120
|
+
// node_modules/uuid/dist/esm-node/validate.js
|
|
3121
|
+
function validate(uuid) {
|
|
3122
|
+
return typeof uuid === "string" && regex_default.test(uuid);
|
|
3123
|
+
}
|
|
3124
|
+
var validate_default = validate;
|
|
3125
|
+
|
|
3126
|
+
// node_modules/uuid/dist/esm-node/stringify.js
|
|
3127
|
+
var byteToHex = [];
|
|
3128
|
+
for (let i = 0; i < 256; ++i) {
|
|
3129
|
+
byteToHex.push((i + 256).toString(16).substr(1));
|
|
3130
|
+
}
|
|
3131
|
+
function stringify3(arr, offset = 0) {
|
|
3132
|
+
const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
3133
|
+
if (!validate_default(uuid)) {
|
|
3134
|
+
throw TypeError("Stringified UUID is invalid");
|
|
3135
|
+
}
|
|
3136
|
+
return uuid;
|
|
3137
|
+
}
|
|
3138
|
+
var stringify_default = stringify3;
|
|
3139
|
+
|
|
3140
|
+
// node_modules/uuid/dist/esm-node/v4.js
|
|
3141
|
+
function v4(options, buf, offset) {
|
|
3142
|
+
options = options || {};
|
|
3143
|
+
const rnds = options.random || (options.rng || rng)();
|
|
3144
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
3145
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
3146
|
+
if (buf) {
|
|
3147
|
+
offset = offset || 0;
|
|
3148
|
+
for (let i = 0; i < 16; ++i) {
|
|
3149
|
+
buf[offset + i] = rnds[i];
|
|
3150
|
+
}
|
|
3151
|
+
return buf;
|
|
3152
|
+
}
|
|
3153
|
+
return stringify_default(rnds);
|
|
3154
|
+
}
|
|
3155
|
+
var v4_default = v4;
|
|
3156
|
+
|
|
3157
|
+
// src/services/cloud-storage.ts
|
|
3158
|
+
var logPrefix3 = "[CloudStorageService]";
|
|
3159
|
+
var tmpDir = "tmp";
|
|
3160
|
+
var CloudStorageService = class {
|
|
3161
|
+
storage;
|
|
3162
|
+
bucket;
|
|
3163
|
+
processedSuffix;
|
|
3164
|
+
supportedExtensions;
|
|
3165
|
+
path;
|
|
3166
|
+
constructor(bucketName, path2, opts) {
|
|
3167
|
+
this.storage = new import_storage.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
3168
|
+
this.bucket = this.storage.bucket(bucketName);
|
|
3169
|
+
this.path = path2;
|
|
3170
|
+
this.processedSuffix = opts?.processedSuffix ?? ".processed";
|
|
3171
|
+
this.supportedExtensions = opts?.supportedExtensions ?? [];
|
|
3172
|
+
}
|
|
3173
|
+
/**
|
|
3174
|
+
* Lists all files in the bucket with an optional prefix.
|
|
3175
|
+
*/
|
|
3176
|
+
async listFiles(prefix) {
|
|
3177
|
+
const effectivePrefix = prefix || this.path;
|
|
3178
|
+
const [files] = await this.bucket.getFiles(
|
|
3179
|
+
effectivePrefix ? { prefix: effectivePrefix } : void 0
|
|
3180
|
+
);
|
|
3181
|
+
return files.map((f) => f.name);
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Returns files that haven't been marked as processed.
|
|
3185
|
+
* Filters by supported extensions if configured.
|
|
3186
|
+
*/
|
|
3187
|
+
async getUnprocessedFiles() {
|
|
3188
|
+
const allFiles = await this.listFiles();
|
|
3189
|
+
const markerFiles = new Set(
|
|
3190
|
+
allFiles.filter((f) => f.endsWith(this.processedSuffix)).map((f) => f.replace(this.processedSuffix, ""))
|
|
3191
|
+
);
|
|
3192
|
+
return allFiles.filter((f) => {
|
|
3193
|
+
if (f.endsWith(this.processedSuffix)) return false;
|
|
3194
|
+
if (f.endsWith("/")) return false;
|
|
3195
|
+
if (this.supportedExtensions.length > 0) {
|
|
3196
|
+
const ext = pathModule.extname(f).toLowerCase();
|
|
3197
|
+
if (!this.supportedExtensions.includes(ext)) return false;
|
|
3198
|
+
}
|
|
3199
|
+
return !markerFiles.has(f);
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Creates a marker file to indicate a file has been processed.
|
|
3204
|
+
*/
|
|
3205
|
+
async createMarkerFile(fileName) {
|
|
3206
|
+
const markerFileName = `${fileName}${this.processedSuffix}`;
|
|
3207
|
+
try {
|
|
3208
|
+
const file = this.bucket.file(markerFileName);
|
|
3209
|
+
await file.save((0, import_util5.format)("Marked file %s as processed", fileName));
|
|
3210
|
+
logger.info(`${logPrefix3} Marker file ${markerFileName} created successfully.`);
|
|
3211
|
+
} catch (err) {
|
|
3212
|
+
if (err instanceof Error) {
|
|
3213
|
+
throw new Error((0, import_util5.format)(`error while writing marker file=%s, err:%s`, markerFileName, err.message));
|
|
3214
|
+
}
|
|
3215
|
+
throw err;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Downloads a file from GCS to a local tmp directory.
|
|
3220
|
+
* Returns the local path of the downloaded file.
|
|
3221
|
+
*/
|
|
3222
|
+
async downloadFile(filePath, fileName) {
|
|
3223
|
+
const file = this.bucket.file(filePath);
|
|
3224
|
+
const tmpDirPath = pathModule.join(process.cwd(), tmpDir);
|
|
3225
|
+
if (!(0, import_fs3.existsSync)(tmpDirPath)) {
|
|
3226
|
+
(0, import_fs3.mkdirSync)(tmpDirPath);
|
|
3227
|
+
}
|
|
3228
|
+
const destFilename = pathModule.join(process.cwd(), tmpDir, fileName);
|
|
3229
|
+
try {
|
|
3230
|
+
if ((0, import_fs3.existsSync)(destFilename)) {
|
|
3231
|
+
(0, import_fs3.unlinkSync)(destFilename);
|
|
3232
|
+
}
|
|
3233
|
+
await file.download({ destination: destFilename });
|
|
3234
|
+
logger.info(`${logPrefix3} Downloaded ${fileName} to ${destFilename}`);
|
|
3235
|
+
return destFilename;
|
|
3236
|
+
} catch (err) {
|
|
3237
|
+
if (err instanceof Error) {
|
|
3238
|
+
throw new Error((0, import_util5.format)(
|
|
3239
|
+
`can't download file=%s from bucket, err:%s`,
|
|
3240
|
+
fileName,
|
|
3241
|
+
err.message
|
|
3242
|
+
));
|
|
3243
|
+
}
|
|
3244
|
+
throw err;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Uploads a local file to GCS.
|
|
3249
|
+
*/
|
|
3250
|
+
async uploadFile(localPath, destPath) {
|
|
3251
|
+
try {
|
|
3252
|
+
logger.info(`${logPrefix3} preparing to upload '%s' to '%s'`, localPath, destPath);
|
|
3253
|
+
const [file] = await this.bucket.upload(localPath, { destination: destPath });
|
|
3254
|
+
logger.info(`${logPrefix3} file %s has been successfully uploaded into %s`, localPath, destPath);
|
|
3255
|
+
return file;
|
|
3256
|
+
} catch (err) {
|
|
3257
|
+
if (err instanceof Error) {
|
|
3258
|
+
throw new Error((0, import_util5.format)(`error while uploading file file=%s into path=%s, err:%s`, localPath, destPath, err.message));
|
|
3259
|
+
}
|
|
3260
|
+
throw err;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Reads a GCS object and returns its contents as a UTF-8 string.
|
|
3265
|
+
*/
|
|
3266
|
+
async readObjectAsString(objectPath) {
|
|
3267
|
+
try {
|
|
3268
|
+
await this.bucket.get({ autoCreate: false });
|
|
3269
|
+
const fileRef = this.bucket.file(objectPath);
|
|
3270
|
+
const [exists] = await fileRef.exists();
|
|
3271
|
+
if (!exists) {
|
|
3272
|
+
throw new Error(`GCS object not found: gs://${this.bucket.name}/${objectPath}`);
|
|
3273
|
+
}
|
|
3274
|
+
const [contents] = await fileRef.download();
|
|
3275
|
+
return contents.toString("utf8");
|
|
3276
|
+
} catch (err) {
|
|
3277
|
+
throw new Error((0, import_util5.format)(`error reading GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Writes a string to a GCS object with JSON content type.
|
|
3282
|
+
*/
|
|
3283
|
+
async writeStringObject(objectPath, contents) {
|
|
3284
|
+
try {
|
|
3285
|
+
await this.bucket.get({ autoCreate: false });
|
|
3286
|
+
const fileRef = this.bucket.file(objectPath);
|
|
3287
|
+
await fileRef.save(contents, { contentType: "application/json" });
|
|
3288
|
+
logger.info(`Uploaded object to gs://${this.bucket.name}/${objectPath}`);
|
|
3289
|
+
} catch (err) {
|
|
3290
|
+
throw new Error((0, import_util5.format)(`error writing GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
3295
|
+
* Returns the GCS File reference.
|
|
3296
|
+
*/
|
|
3297
|
+
async uploadFileWithUniqueName(filePath, prefix, streamId) {
|
|
3298
|
+
try {
|
|
3299
|
+
await this.bucket.get({ autoCreate: false });
|
|
3300
|
+
const destinationFileName = `${prefix}/${streamId}-${v4_default()}.jsonnl`;
|
|
3301
|
+
await this.bucket.upload(filePath, {
|
|
3302
|
+
destination: destinationFileName
|
|
3303
|
+
});
|
|
3304
|
+
logger.info(`Uploaded ${filePath} into ${this.bucket.name}/${destinationFileName} GCS File`);
|
|
3305
|
+
return this.bucket.file(destinationFileName);
|
|
3306
|
+
} catch (err) {
|
|
3307
|
+
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
3308
|
+
|
|
3309
|
+
Error: ${err.message}
|
|
3310
|
+
Stack: ${err.stack}
|
|
3311
|
+
Code: ${err.code}
|
|
3312
|
+
`);
|
|
3313
|
+
throw err;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
|
|
3318
|
+
// src/services/zip.ts
|
|
3319
|
+
var fse = __toESM(require("fs-extra"));
|
|
3320
|
+
var import_decompress = __toESM(require("decompress"));
|
|
3321
|
+
var unzip = async (zipFilePath, extractedPath) => {
|
|
3322
|
+
await fse.ensureDir(extractedPath);
|
|
3323
|
+
await (0, import_decompress.default)(zipFilePath, extractedPath);
|
|
3324
|
+
return extractedPath;
|
|
3325
|
+
};
|
|
3326
|
+
|
|
3327
|
+
// src/targets/bigquery/helpers.ts
|
|
3328
|
+
var safeColumnName = (key) => {
|
|
3329
|
+
let returnString = key;
|
|
3330
|
+
const shouldNotStartWith = ["_TABLE_", "_FILE_", "_PARTITION"];
|
|
3331
|
+
shouldNotStartWith.forEach((snm) => {
|
|
3332
|
+
if (returnString.startsWith(snm)) {
|
|
3333
|
+
returnString = returnString.replace(snm, "");
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
returnString = returnString.replace(/^[-\d\s]*/g, "");
|
|
3337
|
+
returnString = returnString.toLowerCase();
|
|
3338
|
+
returnString = returnString.replace("`", "");
|
|
3339
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3340
|
+
if (returnString.length > 300) {
|
|
3341
|
+
returnString = returnString.substr(0, 300);
|
|
3342
|
+
}
|
|
3343
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
3344
|
+
return returnString.replace(pattern, "_");
|
|
3345
|
+
};
|
|
3346
|
+
var safeTableName = (key) => {
|
|
2539
3347
|
let returnString = key;
|
|
2540
3348
|
returnString = returnString.toLowerCase();
|
|
2541
3349
|
returnString = returnString.replace("`", "");
|
|
@@ -2549,15 +3357,15 @@ var safeTableName = (key) => {
|
|
|
2549
3357
|
|
|
2550
3358
|
// src/targets/bigquery/service/record.ts
|
|
2551
3359
|
var import_decimal = __toESM(require("decimal.js"));
|
|
2552
|
-
var
|
|
3360
|
+
var import_dayjs5 = __toESM(require("dayjs"));
|
|
2553
3361
|
var validateDateRange = (record, schema) => {
|
|
2554
3362
|
Object.keys(schema).forEach((key) => {
|
|
2555
|
-
const { format:
|
|
2556
|
-
if (
|
|
3363
|
+
const { format: format8 } = schema[key];
|
|
3364
|
+
if (format8 === "date-time") {
|
|
2557
3365
|
if (record[key]) {
|
|
2558
|
-
const formattedDate = (0,
|
|
2559
|
-
const isNotTooMuchInTheFuture = (0,
|
|
2560
|
-
const isNotTooMuchInThePast = (0,
|
|
3366
|
+
const formattedDate = (0, import_dayjs5.default)(record[key]).format(defaultDateTimeFormat);
|
|
3367
|
+
const isNotTooMuchInTheFuture = (0, import_dayjs5.default)(record[key]).isBefore((0, import_dayjs5.default)("9999-12-31 23:59:59.999999"));
|
|
3368
|
+
const isNotTooMuchInThePast = (0, import_dayjs5.default)(record[key]).isAfter((0, import_dayjs5.default)("0001-01-01 00:00:00"));
|
|
2561
3369
|
if (formattedDate !== "Invalid date" && isNotTooMuchInTheFuture && isNotTooMuchInThePast) {
|
|
2562
3370
|
record[key] = formattedDate;
|
|
2563
3371
|
} else {
|
|
@@ -2612,7 +3420,7 @@ var convertNumberIntoDecimal = (record, schema) => {
|
|
|
2612
3420
|
};
|
|
2613
3421
|
|
|
2614
3422
|
// src/targets/bigquery/service/dbSync.ts
|
|
2615
|
-
var
|
|
3423
|
+
var import_dayjs6 = __toESM(require("dayjs"));
|
|
2616
3424
|
|
|
2617
3425
|
// src/targets/bigquery/service/bigquery.ts
|
|
2618
3426
|
var import_bigquery = require("@google-cloud/bigquery");
|
|
@@ -2637,96 +3445,6 @@ var BqClientHolder = class {
|
|
|
2637
3445
|
}
|
|
2638
3446
|
};
|
|
2639
3447
|
|
|
2640
|
-
// src/targets/bigquery/service/cloudStorage.ts
|
|
2641
|
-
var import_storage = require("@google-cloud/storage");
|
|
2642
|
-
|
|
2643
|
-
// node_modules/uuid/dist/esm-node/rng.js
|
|
2644
|
-
var import_crypto = __toESM(require("crypto"));
|
|
2645
|
-
var rnds8Pool = new Uint8Array(256);
|
|
2646
|
-
var poolPtr = rnds8Pool.length;
|
|
2647
|
-
function rng() {
|
|
2648
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
2649
|
-
import_crypto.default.randomFillSync(rnds8Pool);
|
|
2650
|
-
poolPtr = 0;
|
|
2651
|
-
}
|
|
2652
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
// node_modules/uuid/dist/esm-node/regex.js
|
|
2656
|
-
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
2657
|
-
|
|
2658
|
-
// node_modules/uuid/dist/esm-node/validate.js
|
|
2659
|
-
function validate(uuid) {
|
|
2660
|
-
return typeof uuid === "string" && regex_default.test(uuid);
|
|
2661
|
-
}
|
|
2662
|
-
var validate_default = validate;
|
|
2663
|
-
|
|
2664
|
-
// node_modules/uuid/dist/esm-node/stringify.js
|
|
2665
|
-
var byteToHex = [];
|
|
2666
|
-
for (let i = 0; i < 256; ++i) {
|
|
2667
|
-
byteToHex.push((i + 256).toString(16).substr(1));
|
|
2668
|
-
}
|
|
2669
|
-
function stringify3(arr, offset = 0) {
|
|
2670
|
-
const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
2671
|
-
if (!validate_default(uuid)) {
|
|
2672
|
-
throw TypeError("Stringified UUID is invalid");
|
|
2673
|
-
}
|
|
2674
|
-
return uuid;
|
|
2675
|
-
}
|
|
2676
|
-
var stringify_default = stringify3;
|
|
2677
|
-
|
|
2678
|
-
// node_modules/uuid/dist/esm-node/v4.js
|
|
2679
|
-
function v4(options, buf, offset) {
|
|
2680
|
-
options = options || {};
|
|
2681
|
-
const rnds = options.random || (options.rng || rng)();
|
|
2682
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
2683
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
2684
|
-
if (buf) {
|
|
2685
|
-
offset = offset || 0;
|
|
2686
|
-
for (let i = 0; i < 16; ++i) {
|
|
2687
|
-
buf[offset + i] = rnds[i];
|
|
2688
|
-
}
|
|
2689
|
-
return buf;
|
|
2690
|
-
}
|
|
2691
|
-
return stringify_default(rnds);
|
|
2692
|
-
}
|
|
2693
|
-
var v4_default = v4;
|
|
2694
|
-
|
|
2695
|
-
// src/targets/bigquery/service/cloudStorage.ts
|
|
2696
|
-
var uploadFileInBucket = async (streamId, gcsBuckerName, filePath) => {
|
|
2697
|
-
try {
|
|
2698
|
-
const retryOptions = { autoRetry: true, maxRetries: 20 };
|
|
2699
|
-
let storage = new import_storage.Storage({ retryOptions });
|
|
2700
|
-
const bucketRef = storage.bucket(gcsBuckerName);
|
|
2701
|
-
await bucketRef.get({ autoCreate: false });
|
|
2702
|
-
const destinationFileName = `${streamId}-${v4_default()}.jsonnl`;
|
|
2703
|
-
await bucketRef.upload(filePath, {
|
|
2704
|
-
destination: destinationFileName
|
|
2705
|
-
});
|
|
2706
|
-
logger.info(`\u{1F5F3} Uploaded ${filePath} into ${gcsBuckerName}/${destinationFileName} GCS File`);
|
|
2707
|
-
return bucketRef.file(destinationFileName);
|
|
2708
|
-
} catch (err) {
|
|
2709
|
-
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
2710
|
-
|
|
2711
|
-
Error: ${err.message}
|
|
2712
|
-
Stack: ${err.stack}
|
|
2713
|
-
Code: ${err.code}
|
|
2714
|
-
`);
|
|
2715
|
-
if (err.code < 500) {
|
|
2716
|
-
await haltAndCatchFire(
|
|
2717
|
-
`unauthorized`,
|
|
2718
|
-
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
2719
|
-
|
|
2720
|
-
The error from Google is: '${err.message}' \u{1F440}
|
|
2721
|
-
|
|
2722
|
-
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
2723
|
-
`Got error from cloud storage lib`
|
|
2724
|
-
);
|
|
2725
|
-
}
|
|
2726
|
-
throw err;
|
|
2727
|
-
}
|
|
2728
|
-
};
|
|
2729
|
-
|
|
2730
3448
|
// src/targets/bigquery/service/dbSync.ts
|
|
2731
3449
|
var import_chunk = __toESM(require("lodash/chunk.js"));
|
|
2732
3450
|
var import_async_mutex2 = require("async-mutex");
|
|
@@ -2767,21 +3485,21 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2767
3485
|
safeColumnName = safeColumnName;
|
|
2768
3486
|
getWarehouseTypeFromJSONSchema = (definition) => {
|
|
2769
3487
|
const type = definition.type;
|
|
2770
|
-
const
|
|
3488
|
+
const format8 = definition.format;
|
|
2771
3489
|
let typeArr;
|
|
2772
3490
|
if (type instanceof Array) {
|
|
2773
3491
|
typeArr = type;
|
|
2774
3492
|
} else {
|
|
2775
3493
|
typeArr = [type];
|
|
2776
3494
|
}
|
|
2777
|
-
if (
|
|
2778
|
-
switch (
|
|
3495
|
+
if (format8) {
|
|
3496
|
+
switch (format8) {
|
|
2779
3497
|
case "date-time":
|
|
2780
3498
|
return "TIMESTAMP";
|
|
2781
3499
|
case "json":
|
|
2782
3500
|
return "STRING";
|
|
2783
3501
|
default:
|
|
2784
|
-
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${
|
|
3502
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format8}`);
|
|
2785
3503
|
}
|
|
2786
3504
|
} else if (typeArr.includes("number")) {
|
|
2787
3505
|
return "NUMERIC";
|
|
@@ -2877,6 +3595,13 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2877
3595
|
...this.getMergeQueries()
|
|
2878
3596
|
];
|
|
2879
3597
|
};
|
|
3598
|
+
getAppendQueries = () => {
|
|
3599
|
+
const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
3600
|
+
const columns = Object.keys(columnUnsafeToSafeMapping).map((unsafeKey) => `\`${columnUnsafeToSafeMapping[unsafeKey]}\``).join(`, `);
|
|
3601
|
+
return [
|
|
3602
|
+
`INSERT INTO ${this.sqlTableId} (${columns}) SELECT ${columns} FROM ${this.sqlStagingTableId};`
|
|
3603
|
+
];
|
|
3604
|
+
};
|
|
2880
3605
|
createDatabaseAndSchemaIfNotExists = async (retryCount) => {
|
|
2881
3606
|
try {
|
|
2882
3607
|
await semaphore2.runExclusive(async () => {
|
|
@@ -2981,7 +3706,7 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2981
3706
|
await this.deleteStagingArea();
|
|
2982
3707
|
}
|
|
2983
3708
|
const schema = this.generateBigquerySchema();
|
|
2984
|
-
const expirationTime = (0,
|
|
3709
|
+
const expirationTime = (0, import_dayjs6.default)().add(1, "day").valueOf().toString();
|
|
2985
3710
|
const createTableOptions = {
|
|
2986
3711
|
schema,
|
|
2987
3712
|
expirationTime
|
|
@@ -3001,10 +3726,11 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
3001
3726
|
};
|
|
3002
3727
|
loadStreamInStagingArea = async (localFilePath) => {
|
|
3003
3728
|
try {
|
|
3004
|
-
const
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3729
|
+
const gcsService = new CloudStorageService(this.config.loading_deck_gcs_bucket_name);
|
|
3730
|
+
const file = await gcsService.uploadFileWithUniqueName(
|
|
3731
|
+
localFilePath,
|
|
3732
|
+
this.config.connector_id,
|
|
3733
|
+
this.streamId
|
|
3008
3734
|
);
|
|
3009
3735
|
const metadata = {
|
|
3010
3736
|
sourceFormat: "NEWLINE_DELIMITED_JSON",
|
|
@@ -3013,6 +3739,17 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
3013
3739
|
await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
|
|
3014
3740
|
} catch (err) {
|
|
3015
3741
|
logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
|
|
3742
|
+
if (err.code < 500) {
|
|
3743
|
+
await haltAndCatchFire(
|
|
3744
|
+
`unauthorized`,
|
|
3745
|
+
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
3746
|
+
|
|
3747
|
+
The error from Google is: '${err.message}' \u{1F440}
|
|
3748
|
+
|
|
3749
|
+
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
3750
|
+
`Got error from cloud storage lib`
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3016
3753
|
throw err;
|
|
3017
3754
|
}
|
|
3018
3755
|
};
|
|
@@ -3115,8 +3852,8 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
3115
3852
|
|
|
3116
3853
|
// src/targets/bigquery/main.ts
|
|
3117
3854
|
var BigQueryTarget = class extends ITarget {
|
|
3118
|
-
constructor(config,
|
|
3119
|
-
super(config,
|
|
3855
|
+
constructor(config, stateProvider) {
|
|
3856
|
+
super(config, stateProvider);
|
|
3120
3857
|
this.renameColumnStore.setSafeColumnNameConverter(this.safeColumnNameConverter);
|
|
3121
3858
|
}
|
|
3122
3859
|
static requiredConfigKeys = [
|
|
@@ -3139,6 +3876,44 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3139
3876
|
validateDateRange = validateDateRange;
|
|
3140
3877
|
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
3141
3878
|
};
|
|
3879
|
+
|
|
3880
|
+
// src/state-providers/gcs/main.ts
|
|
3881
|
+
var import_util6 = require("util");
|
|
3882
|
+
var GCSStateProvider = class {
|
|
3883
|
+
bucketName;
|
|
3884
|
+
connectorId;
|
|
3885
|
+
service;
|
|
3886
|
+
constructor(connectorId, bucketName) {
|
|
3887
|
+
this.connectorId = connectorId;
|
|
3888
|
+
this.bucketName = bucketName;
|
|
3889
|
+
this.service = new CloudStorageService(bucketName);
|
|
3890
|
+
}
|
|
3891
|
+
getObjectPath() {
|
|
3892
|
+
return `${this.connectorId}/state.json`;
|
|
3893
|
+
}
|
|
3894
|
+
async getState() {
|
|
3895
|
+
const objectPath = this.getObjectPath();
|
|
3896
|
+
try {
|
|
3897
|
+
logger.info(`\u{1F4C1} Loading state from gs://${this.bucketName}/${objectPath}`);
|
|
3898
|
+
const raw = await this.service.readObjectAsString(objectPath);
|
|
3899
|
+
const parsed = JSON.parse(raw);
|
|
3900
|
+
return { state: parsed };
|
|
3901
|
+
} catch (err) {
|
|
3902
|
+
logger.warn((0, import_util6.format)(`error loading state from gs://${this.bucketName}/${objectPath}, will start with empty state, err: %s`, err?.message));
|
|
3903
|
+
return { state: void 0 };
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
async writeState(state) {
|
|
3907
|
+
const objectPath = this.getObjectPath();
|
|
3908
|
+
try {
|
|
3909
|
+
logger.info(`\u{1F4DD} Writing state to gs://${this.bucketName}/${objectPath}`);
|
|
3910
|
+
await this.service.writeStringObject(objectPath, state);
|
|
3911
|
+
} catch (err) {
|
|
3912
|
+
logger.error((0, import_util6.format)(`\u{1F4A5} Error while writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
3913
|
+
throw new Error((0, import_util6.format)(`error writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
};
|
|
3142
3917
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3143
3918
|
0 && (module.exports = {
|
|
3144
3919
|
ALL_STREAM_ID_KEYWORD,
|
|
@@ -3146,9 +3921,17 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3146
3921
|
Authenticator,
|
|
3147
3922
|
BATCH_INTERVAL_MS,
|
|
3148
3923
|
BigQueryTarget,
|
|
3924
|
+
CloudStorageService,
|
|
3149
3925
|
CounterMetric,
|
|
3926
|
+
CsvExtractionConfigBuilder,
|
|
3150
3927
|
DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
3151
3928
|
EXECUTION_TIME_METRIC_NAME,
|
|
3929
|
+
ExcelExtractionConfigBuilder,
|
|
3930
|
+
FileFormat,
|
|
3931
|
+
FilePatterns,
|
|
3932
|
+
FileStream,
|
|
3933
|
+
FileTap,
|
|
3934
|
+
GCSStateProvider,
|
|
3152
3935
|
ITarget,
|
|
3153
3936
|
MissingFieldInSchemaError,
|
|
3154
3937
|
MissingSchemaError,
|
|
@@ -3156,22 +3939,37 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3156
3939
|
ROWS_SYNCED_METRIC_NAME,
|
|
3157
3940
|
RecordMessage,
|
|
3158
3941
|
RenameColumnStore,
|
|
3942
|
+
ReplicationMethod,
|
|
3159
3943
|
ReplicationMethodMessage,
|
|
3160
|
-
Resolver,
|
|
3161
3944
|
SchemaMessage,
|
|
3162
3945
|
SchemaValidationError,
|
|
3946
|
+
SftpClient,
|
|
3947
|
+
SftpService,
|
|
3163
3948
|
StateMessage,
|
|
3164
3949
|
StateService,
|
|
3165
3950
|
Stream,
|
|
3166
|
-
StreamMetadata,
|
|
3167
3951
|
StreamWarehouseSyncService,
|
|
3168
3952
|
Tap,
|
|
3953
|
+
VariableExtractors,
|
|
3169
3954
|
_greaterThan,
|
|
3170
3955
|
addWhalyFields,
|
|
3956
|
+
checkCsvHeaderRow,
|
|
3957
|
+
createCellReference,
|
|
3958
|
+
createCsvStreamConfig,
|
|
3959
|
+
createExcelGenerator,
|
|
3960
|
+
createExcelStreamConfig,
|
|
3171
3961
|
createTemporaryFileStream,
|
|
3962
|
+
csvFieldsToJsonSchema,
|
|
3963
|
+
excelColumnToIndex,
|
|
3964
|
+
excelFieldsToJsonSchema,
|
|
3965
|
+
excelRowToIndex,
|
|
3966
|
+
extractExcelRows,
|
|
3967
|
+
extractPrimaryKeysFromCsvConfig,
|
|
3968
|
+
extractPrimaryKeysFromExcelFields,
|
|
3172
3969
|
extractStateForStream,
|
|
3970
|
+
fieldTypeToJsonSchema,
|
|
3173
3971
|
finalizeStateProgressMarkers,
|
|
3174
|
-
|
|
3972
|
+
findAllMatchingConfigs,
|
|
3175
3973
|
flattenSchema,
|
|
3176
3974
|
getAllMetrics,
|
|
3177
3975
|
getAxiosInstance,
|
|
@@ -3184,16 +3982,23 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3184
3982
|
gracefulExit,
|
|
3185
3983
|
haltAndCatchFire,
|
|
3186
3984
|
incrementStreamState,
|
|
3985
|
+
indexToExcelColumn,
|
|
3986
|
+
indexToExcelRow,
|
|
3187
3987
|
loadJson,
|
|
3188
|
-
loadResolver,
|
|
3189
3988
|
loadSchema,
|
|
3190
3989
|
logger,
|
|
3990
|
+
parseCellReference,
|
|
3191
3991
|
postFormDataApiCall,
|
|
3192
3992
|
postJSONApiCall,
|
|
3193
3993
|
postUrlEncodedApiCall,
|
|
3194
3994
|
printMemoryFootprint,
|
|
3995
|
+
processFileStreams,
|
|
3195
3996
|
removeParasiteProperties,
|
|
3997
|
+
rowGeneratorFromCsv,
|
|
3196
3998
|
safePath,
|
|
3197
|
-
|
|
3999
|
+
unzip,
|
|
4000
|
+
validateExcelConfig,
|
|
4001
|
+
writeDataToCsv,
|
|
4002
|
+
writeGeneratorToCSV
|
|
3198
4003
|
});
|
|
3199
4004
|
//# sourceMappingURL=index.js.map
|