@whaly/connector-sdk 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -0
- package/dist/index.d.mts +474 -6
- package/dist/index.d.ts +474 -6
- package/dist/index.js +1181 -162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1146 -161
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -6
package/dist/index.js
CHANGED
|
@@ -35,9 +35,16 @@ __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,
|
|
41
48
|
GCSStateProvider: () => GCSStateProvider,
|
|
42
49
|
ITarget: () => ITarget,
|
|
43
50
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
@@ -50,16 +57,34 @@ __export(index_exports, {
|
|
|
50
57
|
ReplicationMethodMessage: () => ReplicationMethodMessage,
|
|
51
58
|
SchemaMessage: () => SchemaMessage,
|
|
52
59
|
SchemaValidationError: () => SchemaValidationError,
|
|
60
|
+
SftpClient: () => import_ssh2_sftp_client2.default,
|
|
61
|
+
SftpService: () => SftpService,
|
|
53
62
|
StateMessage: () => StateMessage,
|
|
54
63
|
StateService: () => StateService,
|
|
55
64
|
Stream: () => Stream,
|
|
56
65
|
StreamWarehouseSyncService: () => StreamWarehouseSyncService,
|
|
57
66
|
Tap: () => Tap,
|
|
67
|
+
VariableExtractors: () => VariableExtractors,
|
|
58
68
|
_greaterThan: () => _greaterThan,
|
|
59
69
|
addWhalyFields: () => addWhalyFields,
|
|
70
|
+
checkCsvHeaderRow: () => checkCsvHeaderRow,
|
|
71
|
+
countCsvLines: () => countCsvLines,
|
|
72
|
+
createCellReference: () => createCellReference,
|
|
73
|
+
createCsvStreamConfig: () => createCsvStreamConfig,
|
|
74
|
+
createExcelGenerator: () => createExcelGenerator,
|
|
75
|
+
createExcelStreamConfig: () => createExcelStreamConfig,
|
|
60
76
|
createTemporaryFileStream: () => createTemporaryFileStream,
|
|
77
|
+
csvFieldsToJsonSchema: () => csvFieldsToJsonSchema,
|
|
78
|
+
excelColumnToIndex: () => excelColumnToIndex,
|
|
79
|
+
excelFieldsToJsonSchema: () => excelFieldsToJsonSchema,
|
|
80
|
+
excelRowToIndex: () => excelRowToIndex,
|
|
81
|
+
extractExcelRows: () => extractExcelRows,
|
|
82
|
+
extractPrimaryKeysFromCsvConfig: () => extractPrimaryKeysFromCsvConfig,
|
|
83
|
+
extractPrimaryKeysFromExcelFields: () => extractPrimaryKeysFromExcelFields,
|
|
61
84
|
extractStateForStream: () => extractStateForStream,
|
|
85
|
+
fieldTypeToJsonSchema: () => fieldTypeToJsonSchema,
|
|
62
86
|
finalizeStateProgressMarkers: () => finalizeStateProgressMarkers,
|
|
87
|
+
findAllMatchingConfigs: () => findAllMatchingConfigs,
|
|
63
88
|
flattenSchema: () => flattenSchema,
|
|
64
89
|
getAllMetrics: () => getAllMetrics,
|
|
65
90
|
getAxiosInstance: () => getAxiosInstance,
|
|
@@ -72,15 +97,24 @@ __export(index_exports, {
|
|
|
72
97
|
gracefulExit: () => gracefulExit,
|
|
73
98
|
haltAndCatchFire: () => haltAndCatchFire,
|
|
74
99
|
incrementStreamState: () => incrementStreamState,
|
|
100
|
+
indexToExcelColumn: () => indexToExcelColumn,
|
|
101
|
+
indexToExcelRow: () => indexToExcelRow,
|
|
75
102
|
loadJson: () => loadJson,
|
|
76
103
|
loadSchema: () => loadSchema,
|
|
77
104
|
logger: () => logger,
|
|
105
|
+
parseCellReference: () => parseCellReference,
|
|
78
106
|
postFormDataApiCall: () => postFormDataApiCall,
|
|
79
107
|
postJSONApiCall: () => postJSONApiCall,
|
|
80
108
|
postUrlEncodedApiCall: () => postUrlEncodedApiCall,
|
|
81
109
|
printMemoryFootprint: () => printMemoryFootprint,
|
|
110
|
+
processFileStreams: () => processFileStreams,
|
|
82
111
|
removeParasiteProperties: () => removeParasiteProperties,
|
|
83
|
-
|
|
112
|
+
rowGeneratorFromCsv: () => rowGeneratorFromCsv,
|
|
113
|
+
safePath: () => safePath,
|
|
114
|
+
unzip: () => unzip,
|
|
115
|
+
validateExcelConfig: () => validateExcelConfig,
|
|
116
|
+
writeDataToCsv: () => writeDataToCsv,
|
|
117
|
+
writeGeneratorToCSV: () => writeGeneratorToCSV
|
|
84
118
|
});
|
|
85
119
|
module.exports = __toCommonJS(index_exports);
|
|
86
120
|
|
|
@@ -466,6 +500,7 @@ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
|
|
|
466
500
|
var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
|
|
467
501
|
ReplicationMethod2["INCREMENTAL"] = "INCREMENTAL";
|
|
468
502
|
ReplicationMethod2["FULL_TABLE"] = "FULL_TABLE";
|
|
503
|
+
ReplicationMethod2["APPEND"] = "APPEND";
|
|
469
504
|
return ReplicationMethod2;
|
|
470
505
|
})(ReplicationMethod || {});
|
|
471
506
|
|
|
@@ -813,6 +848,8 @@ var Stream = class {
|
|
|
813
848
|
// However, a STATE will still be emitted if the Stream is using an incremental sync type.
|
|
814
849
|
isSilent = false;
|
|
815
850
|
childConcurrency;
|
|
851
|
+
// Optional total row count for percentage progress logging
|
|
852
|
+
totalRows;
|
|
816
853
|
// Metrics configuration
|
|
817
854
|
rowsSyncedMetricsConf;
|
|
818
855
|
executionTimeMetricsConf;
|
|
@@ -1090,6 +1127,10 @@ var Stream = class {
|
|
|
1090
1127
|
metric.increment();
|
|
1091
1128
|
});
|
|
1092
1129
|
rowsSent += 1;
|
|
1130
|
+
if (rowsSent % 1e3 === 0) {
|
|
1131
|
+
const percentStr = this.totalRows ? ` (${Math.round(rowsSent / this.totalRows * 100)}%)` : "";
|
|
1132
|
+
logger.info(`\u23F3 Stream: ${this.streamId} - ${rowsSent} records synced so far...${percentStr}`);
|
|
1133
|
+
}
|
|
1093
1134
|
}
|
|
1094
1135
|
const stateServiceInst = StateService.getInstance();
|
|
1095
1136
|
const state = stateServiceInst.getBookmark(this.streamId);
|
|
@@ -1590,6 +1631,8 @@ var StreamWarehouseSyncService = class {
|
|
|
1590
1631
|
mergeQueries = this.getReplaceQueries();
|
|
1591
1632
|
} else if (replicationMethod === "INCREMENTAL") {
|
|
1592
1633
|
mergeQueries = this.getMergeQueries();
|
|
1634
|
+
} else if (replicationMethod === "APPEND") {
|
|
1635
|
+
mergeQueries = this.getAppendQueries();
|
|
1593
1636
|
} else if (replicationMethod === "LOG_BASED") {
|
|
1594
1637
|
mergeQueries = this.getMergeQueries();
|
|
1595
1638
|
}
|
|
@@ -1760,7 +1803,8 @@ var flattenSchema = (streamId, schema) => {
|
|
|
1760
1803
|
};
|
|
1761
1804
|
|
|
1762
1805
|
// src/sdk/models/target/target.ts
|
|
1763
|
-
var
|
|
1806
|
+
var import_ajv = __toESM(require("ajv"));
|
|
1807
|
+
var import_ajv_formats = __toESM(require("ajv-formats"));
|
|
1764
1808
|
var import_dayjs4 = __toESM(require("dayjs"));
|
|
1765
1809
|
|
|
1766
1810
|
// src/sdk/models/target/streamDbState.ts
|
|
@@ -1771,15 +1815,15 @@ var import_fs_extra = __toESM(require("fs-extra"));
|
|
|
1771
1815
|
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
1772
1816
|
var createTemporaryFileStream = (streamId) => {
|
|
1773
1817
|
import_fs_extra.default.ensureDirSync("./tmp");
|
|
1774
|
-
const
|
|
1775
|
-
const writeStream = import_fs_extra.default.createWriteStream(
|
|
1818
|
+
const path2 = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
1819
|
+
const writeStream = import_fs_extra.default.createWriteStream(path2, { flags: "w" });
|
|
1776
1820
|
return {
|
|
1777
1821
|
stream: writeStream,
|
|
1778
|
-
path
|
|
1822
|
+
path: path2
|
|
1779
1823
|
};
|
|
1780
1824
|
};
|
|
1781
|
-
function safePath(
|
|
1782
|
-
let formattedPath =
|
|
1825
|
+
function safePath(path2) {
|
|
1826
|
+
let formattedPath = path2;
|
|
1783
1827
|
formattedPath = formattedPath.replace(/^The\s+/gi, "");
|
|
1784
1828
|
formattedPath = formattedPath.replace(/\s+(?:\(|\[)?Dis(?:c|k) (?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d)+(?:\)|\])?/gi, "");
|
|
1785
1829
|
formattedPath = formattedPath.replace(/\s+/gi, "_");
|
|
@@ -1838,6 +1882,8 @@ var StreamState = class {
|
|
|
1838
1882
|
// So we need to keep track of whether we are at the first load or not
|
|
1839
1883
|
_hasBeenLoadedYetDuringThisSync;
|
|
1840
1884
|
replicationMethod;
|
|
1885
|
+
// Pre-compiled ajv ValidateFunction — compiled once per schema, reused for every row
|
|
1886
|
+
compiledValidateFn;
|
|
1841
1887
|
constructor(streamId, dbSync, replicationMethod) {
|
|
1842
1888
|
this.streamId = streamId;
|
|
1843
1889
|
this.dbSync = dbSync;
|
|
@@ -1855,6 +1901,12 @@ var StreamState = class {
|
|
|
1855
1901
|
getSchema() {
|
|
1856
1902
|
return this.schema;
|
|
1857
1903
|
}
|
|
1904
|
+
setCompiledValidateFn(fn) {
|
|
1905
|
+
this.compiledValidateFn = fn;
|
|
1906
|
+
}
|
|
1907
|
+
getCompiledValidateFn() {
|
|
1908
|
+
return this.compiledValidateFn;
|
|
1909
|
+
}
|
|
1858
1910
|
getBatchDate() {
|
|
1859
1911
|
return this.batchDate.format(defaultDateTimeFormat);
|
|
1860
1912
|
}
|
|
@@ -1911,8 +1963,8 @@ var ITarget = class _ITarget {
|
|
|
1911
1963
|
flushedState;
|
|
1912
1964
|
renameColumnStore;
|
|
1913
1965
|
streams;
|
|
1914
|
-
// JSON Schema validator
|
|
1915
|
-
|
|
1966
|
+
// JSON Schema validator (ajv — pre-compiles schemas into optimized JS functions)
|
|
1967
|
+
ajv;
|
|
1916
1968
|
constructor(config, stateProvider) {
|
|
1917
1969
|
this.config = config;
|
|
1918
1970
|
this.stateProvider = stateProvider;
|
|
@@ -1920,7 +1972,7 @@ var ITarget = class _ITarget {
|
|
|
1920
1972
|
this.streams = {};
|
|
1921
1973
|
this.batchedState = {};
|
|
1922
1974
|
this.flushedState = {};
|
|
1923
|
-
this.
|
|
1975
|
+
this.ajv = (0, import_ajv_formats.default)(new import_ajv.default({ allErrors: true, strict: false }));
|
|
1924
1976
|
this.syncTime = (0, import_dayjs4.default)();
|
|
1925
1977
|
this.renameColumnStore = new RenameColumnStore();
|
|
1926
1978
|
}
|
|
@@ -1979,12 +2031,14 @@ var ITarget = class _ITarget {
|
|
|
1979
2031
|
if (!stream) {
|
|
1980
2032
|
throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
|
|
1981
2033
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2034
|
+
if (stream.getBatchedRowCount() % 1e4 === 0) {
|
|
2035
|
+
await semaphore.runExclusive(async () => {
|
|
2036
|
+
const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
|
|
2037
|
+
if (shouldUploadIntermediateBatch) {
|
|
2038
|
+
await this.loadAllStreamsInWarehouse({ isFinalLoad: false });
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
1988
2042
|
const record = message.record;
|
|
1989
2043
|
const schema = stream.getSchema();
|
|
1990
2044
|
if (!schema) {
|
|
@@ -1993,13 +2047,13 @@ var ITarget = class _ITarget {
|
|
|
1993
2047
|
}
|
|
1994
2048
|
const recordWithoutParasiteProperties = removeParasiteProperties(record, schema);
|
|
1995
2049
|
const recordWithWhalyFields = addWhalyFields(recordWithoutParasiteProperties, stream.getBatchDate());
|
|
1996
|
-
const
|
|
1997
|
-
if (!
|
|
2050
|
+
const validateFn = stream.getCompiledValidateFn();
|
|
2051
|
+
if (validateFn && !validateFn(recordWithWhalyFields)) {
|
|
1998
2052
|
throw new SchemaValidationError(`Stream: ${streamId} - Record is not valid according to schema.
|
|
1999
|
-
Validation errors: ${JSON.stringify(
|
|
2053
|
+
Validation errors: ${JSON.stringify(validateFn.errors)}
|
|
2000
2054
|
|
|
2001
2055
|
Record: ${JSON.stringify(recordWithoutParasiteProperties)}
|
|
2002
|
-
Schema: ${JSON.stringify(stream.getSchema())}`, streamId,
|
|
2056
|
+
Schema: ${JSON.stringify(stream.getSchema())}`, streamId, validateFn.errors ?? []);
|
|
2003
2057
|
}
|
|
2004
2058
|
const recordWithValidDate = this.validateDateRange(recordWithWhalyFields, schema);
|
|
2005
2059
|
const recordWithDecimal = this.convertNumberIntoDecimal(recordWithValidDate, schema);
|
|
@@ -2088,7 +2142,9 @@ var ITarget = class _ITarget {
|
|
|
2088
2142
|
}
|
|
2089
2143
|
});
|
|
2090
2144
|
streamState.setSchema(schema);
|
|
2091
|
-
|
|
2145
|
+
streamState.setCompiledValidateFn(this.ajv.compile({ type: "object", properties: schema }));
|
|
2146
|
+
const streamReplicationMethod = this.streams[streamId]?.getReplicationMethod();
|
|
2147
|
+
if (message.keyProperties.length === 0 && streamReplicationMethod !== "APPEND" /* APPEND */) {
|
|
2092
2148
|
throw new Error(`StreamId: ${message.stream} - \`key_properties\` field is required and can't be empty in SCHEMA message.
|
|
2093
2149
|
Received SCHEMA message: ${JSON.stringify(message)}`);
|
|
2094
2150
|
}
|
|
@@ -2152,7 +2208,8 @@ var ITarget = class _ITarget {
|
|
|
2152
2208
|
return false;
|
|
2153
2209
|
}
|
|
2154
2210
|
const batchedRecords = stream.getBatchedRowCount();
|
|
2155
|
-
|
|
2211
|
+
const method = stream.getReplicationMethod();
|
|
2212
|
+
if ((method === "INCREMENTAL" || method === "APPEND") && batchedRecords > 1e6) {
|
|
2156
2213
|
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.`);
|
|
2157
2214
|
return true;
|
|
2158
2215
|
} else {
|
|
@@ -2221,6 +2278,1037 @@ var ITarget = class _ITarget {
|
|
|
2221
2278
|
};
|
|
2222
2279
|
};
|
|
2223
2280
|
|
|
2281
|
+
// src/sdk/file-processing/types.ts
|
|
2282
|
+
var FileFormat = /* @__PURE__ */ ((FileFormat2) => {
|
|
2283
|
+
FileFormat2["CSV"] = "CSV";
|
|
2284
|
+
FileFormat2["EXCEL"] = "EXCEL";
|
|
2285
|
+
return FileFormat2;
|
|
2286
|
+
})(FileFormat || {});
|
|
2287
|
+
|
|
2288
|
+
// src/sdk/file-processing/schema-utils.ts
|
|
2289
|
+
function fieldTypeToJsonSchema(fieldType) {
|
|
2290
|
+
switch (fieldType) {
|
|
2291
|
+
case "STRING":
|
|
2292
|
+
return { type: ["null", "string"] };
|
|
2293
|
+
case "FLOAT":
|
|
2294
|
+
return { type: ["null", "number"] };
|
|
2295
|
+
case "TIMESTAMP":
|
|
2296
|
+
return { type: ["null", "string"], format: "date-time" };
|
|
2297
|
+
default:
|
|
2298
|
+
return { type: ["null", "string"] };
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function excelFieldsToJsonSchema(fields) {
|
|
2302
|
+
const properties = {};
|
|
2303
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
2304
|
+
properties[key] = fieldTypeToJsonSchema(field.type);
|
|
2305
|
+
}
|
|
2306
|
+
return { type: "object", properties };
|
|
2307
|
+
}
|
|
2308
|
+
function csvFieldsToJsonSchema(config) {
|
|
2309
|
+
const properties = {};
|
|
2310
|
+
if (Array.isArray(config)) {
|
|
2311
|
+
for (const field of config) {
|
|
2312
|
+
properties[field.key] = fieldTypeToJsonSchema(field.type);
|
|
2313
|
+
}
|
|
2314
|
+
} else {
|
|
2315
|
+
for (const [, field] of Object.entries(config)) {
|
|
2316
|
+
properties[field.key] = fieldTypeToJsonSchema(field.type);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return { type: "object", properties };
|
|
2320
|
+
}
|
|
2321
|
+
function extractPrimaryKeysFromCsvConfig(config) {
|
|
2322
|
+
if (Array.isArray(config)) {
|
|
2323
|
+
return config.map((f) => f.key);
|
|
2324
|
+
}
|
|
2325
|
+
return Object.values(config).map((f) => f.key);
|
|
2326
|
+
}
|
|
2327
|
+
function extractPrimaryKeysFromExcelFields(fields) {
|
|
2328
|
+
return Object.entries(fields).filter(([, field]) => {
|
|
2329
|
+
return field.primaryKey === true || field.primaryKey === true;
|
|
2330
|
+
}).map(([key]) => key);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// src/sdk/file-processing/excel/reader.ts
|
|
2334
|
+
var import_xlsx = __toESM(require("xlsx"));
|
|
2335
|
+
var import_path = __toESM(require("path"));
|
|
2336
|
+
var excelColumnToIndex = (columnLetter) => {
|
|
2337
|
+
const letters = columnLetter.toUpperCase();
|
|
2338
|
+
let index = 0;
|
|
2339
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2340
|
+
const charCode = letters.charCodeAt(i);
|
|
2341
|
+
if (charCode < 65 || charCode > 90) {
|
|
2342
|
+
throw new Error(`Invalid column letter: ${columnLetter}`);
|
|
2343
|
+
}
|
|
2344
|
+
index = index * 26 + (charCode - 64);
|
|
2345
|
+
}
|
|
2346
|
+
return index - 1;
|
|
2347
|
+
};
|
|
2348
|
+
var indexToExcelColumn = (columnIndex) => {
|
|
2349
|
+
if (columnIndex < 0) {
|
|
2350
|
+
throw new Error(`Invalid column index: ${columnIndex}. Must be >= 0`);
|
|
2351
|
+
}
|
|
2352
|
+
let result = "";
|
|
2353
|
+
let index = columnIndex;
|
|
2354
|
+
while (true) {
|
|
2355
|
+
result = String.fromCharCode(65 + index % 26) + result;
|
|
2356
|
+
index = Math.floor(index / 26);
|
|
2357
|
+
if (index === 0) break;
|
|
2358
|
+
index--;
|
|
2359
|
+
}
|
|
2360
|
+
return result;
|
|
2361
|
+
};
|
|
2362
|
+
var excelRowToIndex = (rowNumber) => {
|
|
2363
|
+
const rowNum = typeof rowNumber === "string" ? parseInt(rowNumber, 10) : rowNumber;
|
|
2364
|
+
if (isNaN(rowNum) || rowNum < 1) {
|
|
2365
|
+
throw new Error(`Invalid row number: ${rowNumber}. Must be >= 1`);
|
|
2366
|
+
}
|
|
2367
|
+
return rowNum - 1;
|
|
2368
|
+
};
|
|
2369
|
+
var indexToExcelRow = (index) => {
|
|
2370
|
+
if (index < 0) {
|
|
2371
|
+
throw new Error(`Invalid index: ${index}. Must be >= 0`);
|
|
2372
|
+
}
|
|
2373
|
+
return index + 1;
|
|
2374
|
+
};
|
|
2375
|
+
var parseCellReference = (cellRef) => {
|
|
2376
|
+
const match = cellRef.match(/^([A-Z]+)(\d+)$/);
|
|
2377
|
+
if (!match) {
|
|
2378
|
+
throw new Error(`Invalid cell reference: ${cellRef}. Expected format like "A1", "Z24", "AA100"`);
|
|
2379
|
+
}
|
|
2380
|
+
const [, columnLetter, rowNumber] = match;
|
|
2381
|
+
return {
|
|
2382
|
+
columnIndex: excelColumnToIndex(columnLetter),
|
|
2383
|
+
rowIndex: excelRowToIndex(rowNumber)
|
|
2384
|
+
};
|
|
2385
|
+
};
|
|
2386
|
+
var createCellReference = (columnIndex, rowIndex) => {
|
|
2387
|
+
return indexToExcelColumn(columnIndex) + indexToExcelRow(rowIndex);
|
|
2388
|
+
};
|
|
2389
|
+
var extractSingleSheetRows = async (fileName, conf) => {
|
|
2390
|
+
console.log("Extracting single sheet rows from file: %s", fileName);
|
|
2391
|
+
const workbook = import_xlsx.default.readFile(fileName, { dense: true });
|
|
2392
|
+
const sheetKeys = Object.keys(workbook.Sheets);
|
|
2393
|
+
const sheet = conf.sheetName ? workbook.Sheets[conf.sheetName] : workbook.Sheets[sheetKeys[0]];
|
|
2394
|
+
if (!sheet) {
|
|
2395
|
+
throw new Error(`Sheet ${conf.sheetName} not found in workbook`);
|
|
2396
|
+
}
|
|
2397
|
+
const sheetData = sheet["!data"];
|
|
2398
|
+
if (!sheetData) {
|
|
2399
|
+
throw new Error(`No data in ${conf.sheetName || "first"} sheet`);
|
|
2400
|
+
}
|
|
2401
|
+
if (sheetData.length === 0) {
|
|
2402
|
+
throw new Error(`Sheet ${conf.sheetName || "first"} is empty`);
|
|
2403
|
+
}
|
|
2404
|
+
const variables = conf.fileNameVariablesExtractor(fileName, workbook);
|
|
2405
|
+
const data = [];
|
|
2406
|
+
let rowIdx = 0;
|
|
2407
|
+
const minColumnIndex = Object.values(conf.columns).reduce((min, column) => {
|
|
2408
|
+
return column.column ? Math.min(min, excelColumnToIndex(column.column)) : min;
|
|
2409
|
+
}, Number.MAX_SAFE_INTEGER);
|
|
2410
|
+
for (const row of sheetData) {
|
|
2411
|
+
if (rowIdx < conf.numberOfRowsToSkip) {
|
|
2412
|
+
rowIdx++;
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
if (!row) {
|
|
2416
|
+
console.log("Empty row at index: %s, skipped processing.", rowIdx);
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (minColumnIndex !== Number.MAX_SAFE_INTEGER && !row[minColumnIndex]?.v) {
|
|
2420
|
+
console.log("stopping processing because of empty value in the first data column (idx: %s)", minColumnIndex);
|
|
2421
|
+
break;
|
|
2422
|
+
}
|
|
2423
|
+
const rowData = Object.entries(conf.columns).reduce((acc, [key, column]) => {
|
|
2424
|
+
if (column.variableName) {
|
|
2425
|
+
if (!variables) {
|
|
2426
|
+
throw new Error(`No variables extracted from filename, cannot extract derived field ${key}`);
|
|
2427
|
+
}
|
|
2428
|
+
acc[key] = variables[column.variableName] || "";
|
|
2429
|
+
} else {
|
|
2430
|
+
const colIndex = excelColumnToIndex(column.column);
|
|
2431
|
+
acc[key] = row[colIndex]?.v || "";
|
|
2432
|
+
}
|
|
2433
|
+
return acc;
|
|
2434
|
+
}, {});
|
|
2435
|
+
data.push(rowData);
|
|
2436
|
+
rowIdx++;
|
|
2437
|
+
}
|
|
2438
|
+
console.log("Extracted %s rows from sheet %s", data.length, conf.sheetName || "first");
|
|
2439
|
+
return data;
|
|
2440
|
+
};
|
|
2441
|
+
var extractExcelRows = async (localFilePath, conf) => {
|
|
2442
|
+
let data;
|
|
2443
|
+
if (conf.type === "single-sheet-extraction") {
|
|
2444
|
+
data = await extractSingleSheetRows(localFilePath, conf);
|
|
2445
|
+
} else if (conf.type === "processor") {
|
|
2446
|
+
const workbook = import_xlsx.default.readFile(localFilePath, { dense: true });
|
|
2447
|
+
data = await conf.processor(workbook);
|
|
2448
|
+
} else {
|
|
2449
|
+
throw new Error(`Unsupported configuration type: ${conf.type}`);
|
|
2450
|
+
}
|
|
2451
|
+
return data;
|
|
2452
|
+
};
|
|
2453
|
+
var validateExcelConfig = (config) => {
|
|
2454
|
+
if (!config.extension) {
|
|
2455
|
+
throw new Error("Extension is required");
|
|
2456
|
+
}
|
|
2457
|
+
if (!config.tableName) {
|
|
2458
|
+
throw new Error("Table name is required");
|
|
2459
|
+
}
|
|
2460
|
+
if (!config.fileNameValidator || typeof config.fileNameValidator !== "function") {
|
|
2461
|
+
throw new Error("File name validator function is required");
|
|
2462
|
+
}
|
|
2463
|
+
if (!config.fileNameVariablesExtractor || typeof config.fileNameVariablesExtractor !== "function") {
|
|
2464
|
+
throw new Error("File name variables extractor function is required");
|
|
2465
|
+
}
|
|
2466
|
+
if (config.type === "single-sheet-extraction" && (!config.columns || Object.keys(config.columns).length === 0)) {
|
|
2467
|
+
throw new Error("At least one column configuration is required for single-sheet-extraction");
|
|
2468
|
+
}
|
|
2469
|
+
if (config.type === "single-sheet-extraction") {
|
|
2470
|
+
Object.entries(config.columns).forEach(([key, column]) => {
|
|
2471
|
+
if (!column.type) {
|
|
2472
|
+
throw new Error(`Column ${key} must have a type`);
|
|
2473
|
+
}
|
|
2474
|
+
if (column.column) {
|
|
2475
|
+
const colLetter = column.column;
|
|
2476
|
+
if (!/^[A-Z]+$/i.test(colLetter)) {
|
|
2477
|
+
throw new Error(`Invalid column letter format: ${colLetter}`);
|
|
2478
|
+
}
|
|
2479
|
+
} else if (column.variableName) {
|
|
2480
|
+
} else {
|
|
2481
|
+
throw new Error(`Column ${key} must specify either 'column' or 'variableName'`);
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
if (config.numberOfRowsToSkip === void 0 || config.numberOfRowsToSkip < 0) {
|
|
2485
|
+
throw new Error("Number of rows to skip must be specified and non-negative");
|
|
2486
|
+
}
|
|
2487
|
+
} else if (config.type === "processor") {
|
|
2488
|
+
if (!config.processor || typeof config.processor !== "function") {
|
|
2489
|
+
throw new Error("Processor function is required for processor type");
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
};
|
|
2493
|
+
var findAllMatchingConfigs = (filename, configs) => {
|
|
2494
|
+
const fileInfo = import_path.default.parse(filename);
|
|
2495
|
+
return configs.filter(
|
|
2496
|
+
(config) => config.fileNameValidator(fileInfo.base) && config.extension === fileInfo.ext.replace(".", "")
|
|
2497
|
+
);
|
|
2498
|
+
};
|
|
2499
|
+
async function* createExcelGenerator(data) {
|
|
2500
|
+
for (const row of data) {
|
|
2501
|
+
yield row;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/sdk/file-processing/excel/config-builder.ts
|
|
2506
|
+
var ExcelExtractionConfigBuilder = class _ExcelExtractionConfigBuilder {
|
|
2507
|
+
config = {
|
|
2508
|
+
fileNameValidator: () => true,
|
|
2509
|
+
fileNameVariablesExtractor: () => ({}),
|
|
2510
|
+
replicationMethod: "FULL_TABLE" /* FULL_TABLE */
|
|
2511
|
+
};
|
|
2512
|
+
static create() {
|
|
2513
|
+
return new _ExcelExtractionConfigBuilder();
|
|
2514
|
+
}
|
|
2515
|
+
extension(ext) {
|
|
2516
|
+
this.config.extension = ext;
|
|
2517
|
+
return this;
|
|
2518
|
+
}
|
|
2519
|
+
tableName(name) {
|
|
2520
|
+
this.config.tableName = name;
|
|
2521
|
+
return this;
|
|
2522
|
+
}
|
|
2523
|
+
fileValidator(validator) {
|
|
2524
|
+
this.config.fileNameValidator = validator;
|
|
2525
|
+
return this;
|
|
2526
|
+
}
|
|
2527
|
+
variablesExtractor(extractor) {
|
|
2528
|
+
this.config.fileNameVariablesExtractor = extractor;
|
|
2529
|
+
return this;
|
|
2530
|
+
}
|
|
2531
|
+
replicationMethod(method) {
|
|
2532
|
+
this.config.replicationMethod = method;
|
|
2533
|
+
return this;
|
|
2534
|
+
}
|
|
2535
|
+
columns(cols) {
|
|
2536
|
+
if (this.config.type === "single-sheet-extraction") {
|
|
2537
|
+
this.config.columns = cols;
|
|
2538
|
+
} else {
|
|
2539
|
+
throw new Error("Columns can only be set for single-sheet-extraction");
|
|
2540
|
+
}
|
|
2541
|
+
return this;
|
|
2542
|
+
}
|
|
2543
|
+
singleSheet(sheetName, skipRows = 0) {
|
|
2544
|
+
this.config.type = "single-sheet-extraction";
|
|
2545
|
+
if (sheetName !== void 0) {
|
|
2546
|
+
this.config.sheetName = sheetName;
|
|
2547
|
+
}
|
|
2548
|
+
this.config.numberOfRowsToSkip = skipRows;
|
|
2549
|
+
return this;
|
|
2550
|
+
}
|
|
2551
|
+
processor(processorFn) {
|
|
2552
|
+
this.config.type = "processor";
|
|
2553
|
+
this.config.processor = processorFn;
|
|
2554
|
+
return this;
|
|
2555
|
+
}
|
|
2556
|
+
build() {
|
|
2557
|
+
if (!this.config.type) {
|
|
2558
|
+
throw new Error("Configuration type must be specified (singleSheet or processor)");
|
|
2559
|
+
}
|
|
2560
|
+
if (!this.config.extension) {
|
|
2561
|
+
throw new Error("Extension must be set");
|
|
2562
|
+
}
|
|
2563
|
+
if (!this.config.tableName) {
|
|
2564
|
+
throw new Error("Table name must be set");
|
|
2565
|
+
}
|
|
2566
|
+
if (!this.config.replicationMethod) {
|
|
2567
|
+
throw new Error("Replication method must be set");
|
|
2568
|
+
}
|
|
2569
|
+
if (typeof this.config.fileNameValidator !== "function") {
|
|
2570
|
+
throw new Error("fileNameValidator must be a function");
|
|
2571
|
+
}
|
|
2572
|
+
if (typeof this.config.fileNameVariablesExtractor !== "function") {
|
|
2573
|
+
throw new Error("fileNameVariablesExtractor must be a function");
|
|
2574
|
+
}
|
|
2575
|
+
if (this.config.type === "single-sheet-extraction") {
|
|
2576
|
+
const singleSheetConfig = this.config;
|
|
2577
|
+
if (!singleSheetConfig.columns || Object.keys(singleSheetConfig.columns).length === 0) {
|
|
2578
|
+
throw new Error("Columns must be set with at least one column for single-sheet-extraction");
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
if (this.config.type === "processor") {
|
|
2582
|
+
const processorConfig = this.config;
|
|
2583
|
+
if (typeof processorConfig.processor !== "function") {
|
|
2584
|
+
throw new Error("Processor function must be set for processor configs");
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
return this.config;
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2591
|
+
// src/sdk/file-processing/csv/reader.ts
|
|
2592
|
+
var import_csv_parser = __toESM(require("csv-parser"));
|
|
2593
|
+
var import_fs2 = require("fs");
|
|
2594
|
+
var import_util3 = require("util");
|
|
2595
|
+
var import_stream2 = require("stream");
|
|
2596
|
+
var iconv = __toESM(require("iconv-lite"));
|
|
2597
|
+
var logPrefix = "[csvReader]";
|
|
2598
|
+
var createStripBomTransform = () => {
|
|
2599
|
+
let isFirstChunk = true;
|
|
2600
|
+
return new import_stream2.Transform({
|
|
2601
|
+
transform(chunk2, encoding, callback) {
|
|
2602
|
+
if (isFirstChunk) {
|
|
2603
|
+
isFirstChunk = false;
|
|
2604
|
+
if (chunk2.length >= 3 && chunk2[0] === 239 && chunk2[1] === 187 && chunk2[2] === 191) {
|
|
2605
|
+
chunk2 = chunk2.slice(3);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
callback(null, chunk2);
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
};
|
|
2612
|
+
var getStringHexInfo = (str) => {
|
|
2613
|
+
const bytes = Buffer.from(str, "utf8");
|
|
2614
|
+
const hex = bytes.toString("hex").match(/.{2}/g)?.join(" ") || "";
|
|
2615
|
+
return {
|
|
2616
|
+
original: str,
|
|
2617
|
+
length: str.length,
|
|
2618
|
+
hex,
|
|
2619
|
+
trimmed: str.trim()
|
|
2620
|
+
};
|
|
2621
|
+
};
|
|
2622
|
+
var formatHexComparison = (expected, actual) => {
|
|
2623
|
+
const expectedInfo = getStringHexInfo(expected);
|
|
2624
|
+
const actualInfo = getStringHexInfo(actual);
|
|
2625
|
+
return `
|
|
2626
|
+
Expected: "${expected}" (${expectedInfo.length} chars) [${expectedInfo.hex}]
|
|
2627
|
+
Actual: "${actual}" (${actualInfo.length} chars) [${actualInfo.hex}]`;
|
|
2628
|
+
};
|
|
2629
|
+
async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
2630
|
+
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2631
|
+
const configKeys = isGeneratorConfigArray ? [] : Object.keys(fileConfig.fields);
|
|
2632
|
+
const csvOptions = { separator: fileConfig.separator };
|
|
2633
|
+
if (isGeneratorConfigArray) {
|
|
2634
|
+
csvOptions.headers = false;
|
|
2635
|
+
}
|
|
2636
|
+
const csvStream = (0, import_csv_parser.default)(csvOptions);
|
|
2637
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2638
|
+
let initialReadStream;
|
|
2639
|
+
let finalInputStream;
|
|
2640
|
+
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2641
|
+
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2642
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2643
|
+
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2644
|
+
} else {
|
|
2645
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2646
|
+
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2647
|
+
}
|
|
2648
|
+
finalInputStream.pipe(csvStream);
|
|
2649
|
+
try {
|
|
2650
|
+
for await (const row of import_stream2.Readable.from(csvStream)) {
|
|
2651
|
+
if (!isGeneratorConfigArray) {
|
|
2652
|
+
const rowData = fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {};
|
|
2653
|
+
for (const key of configKeys) {
|
|
2654
|
+
const keyGenerator = fileConfig.fields[key.trim()];
|
|
2655
|
+
if (!keyGenerator) continue;
|
|
2656
|
+
rowData[keyGenerator.key] = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[key]) : row[key];
|
|
2657
|
+
}
|
|
2658
|
+
yield rowData;
|
|
2659
|
+
} else {
|
|
2660
|
+
const rowKeys = Object.keys(row);
|
|
2661
|
+
const rowData = fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {};
|
|
2662
|
+
for (let i = 0; i < rowKeys.length; i++) {
|
|
2663
|
+
const keyGenerator = fileConfig.fields[i];
|
|
2664
|
+
if (!keyGenerator) continue;
|
|
2665
|
+
rowData[keyGenerator.key] = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i];
|
|
2666
|
+
}
|
|
2667
|
+
yield rowData;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
} finally {
|
|
2671
|
+
initialReadStream.destroy();
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
async function countCsvLines(filePath) {
|
|
2675
|
+
return new Promise((resolve, reject) => {
|
|
2676
|
+
let newlines = 0;
|
|
2677
|
+
(0, import_fs2.createReadStream)(filePath).on("data", (chunk2) => {
|
|
2678
|
+
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
|
|
2679
|
+
for (let i = 0; i < buf.length; i++) {
|
|
2680
|
+
if (buf[i] === 10) newlines++;
|
|
2681
|
+
}
|
|
2682
|
+
}).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
2686
|
+
return new Promise((resolve, reject) => {
|
|
2687
|
+
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
2688
|
+
console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
|
|
2689
|
+
const info = getStringHexInfo(col);
|
|
2690
|
+
return `"${info.original}" [${info.hex}]`;
|
|
2691
|
+
}));
|
|
2692
|
+
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2693
|
+
const csvOptions2 = { separator: fileConfig.separator };
|
|
2694
|
+
if (isGeneratorConfigArray) {
|
|
2695
|
+
csvOptions2.headers = false;
|
|
2696
|
+
}
|
|
2697
|
+
const csvStream = (0, import_csv_parser.default)(csvOptions2);
|
|
2698
|
+
let initialReadStream;
|
|
2699
|
+
let finalInputStream;
|
|
2700
|
+
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2701
|
+
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2702
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2703
|
+
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2704
|
+
} else {
|
|
2705
|
+
initialReadStream = (0, import_fs2.createReadStream)(path2);
|
|
2706
|
+
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2707
|
+
}
|
|
2708
|
+
let isFirstLine = true;
|
|
2709
|
+
finalInputStream.pipe(csvStream).on("data", (row) => {
|
|
2710
|
+
if (isFirstLine) {
|
|
2711
|
+
let headers = [];
|
|
2712
|
+
if (isGeneratorConfigArray) {
|
|
2713
|
+
headers = Object.values(row).filter((r) => !!r);
|
|
2714
|
+
} else {
|
|
2715
|
+
headers = Object.keys(row).filter((r) => !!r);
|
|
2716
|
+
}
|
|
2717
|
+
console.debug(`${logPrefix} CSV Detected headers:`, headers.map((h, idx) => {
|
|
2718
|
+
const info = getStringHexInfo(h);
|
|
2719
|
+
return `[${idx}]: "${info.original}" [${info.hex}]`;
|
|
2720
|
+
}));
|
|
2721
|
+
if (isGeneratorConfigArray) {
|
|
2722
|
+
colsWithParsingConfig.forEach((col, i) => {
|
|
2723
|
+
if (col !== headers[i]) {
|
|
2724
|
+
const hexComparison = formatHexComparison(col, headers[i] || "<undefined>");
|
|
2725
|
+
console.error(`${logPrefix} CSV Header Mismatch:${hexComparison}`);
|
|
2726
|
+
throw new Error((0, import_util3.format)(
|
|
2727
|
+
`CSV header mismatch at position %s: expected '%s' but got '%s' in file %s`,
|
|
2728
|
+
i,
|
|
2729
|
+
col,
|
|
2730
|
+
headers[i] || "<undefined>",
|
|
2731
|
+
path2
|
|
2732
|
+
));
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
} else {
|
|
2736
|
+
colsWithParsingConfig.forEach((col) => {
|
|
2737
|
+
if (!headers.includes(col)) {
|
|
2738
|
+
const colInfo = getStringHexInfo(col);
|
|
2739
|
+
console.error(`CSV Missing column: "${col}" [${colInfo.hex}]`);
|
|
2740
|
+
console.error(`Available headers:`, headers.map((h) => `"${h}" [${getStringHexInfo(h).hex}]`));
|
|
2741
|
+
const trimmedMatch = headers.find((h) => h.trim() === col.trim());
|
|
2742
|
+
if (trimmedMatch) {
|
|
2743
|
+
console.error(`Potential trimmed match: "${trimmedMatch}" [${getStringHexInfo(trimmedMatch).hex}]`);
|
|
2744
|
+
}
|
|
2745
|
+
throw new Error((0, import_util3.format)(
|
|
2746
|
+
`CSV missing expected column '%s' in file: %s`,
|
|
2747
|
+
col,
|
|
2748
|
+
path2
|
|
2749
|
+
));
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
isFirstLine = false;
|
|
2754
|
+
} else {
|
|
2755
|
+
initialReadStream.destroy();
|
|
2756
|
+
}
|
|
2757
|
+
}).on("error", (error) => {
|
|
2758
|
+
reject(new Error(`Error processing CSV file: ${error.message}`));
|
|
2759
|
+
});
|
|
2760
|
+
initialReadStream.on("close", () => {
|
|
2761
|
+
resolve();
|
|
2762
|
+
});
|
|
2763
|
+
});
|
|
2764
|
+
};
|
|
2765
|
+
|
|
2766
|
+
// src/sdk/file-processing/csv/writer.ts
|
|
2767
|
+
var import_csv_writer = require("csv-writer");
|
|
2768
|
+
var import_util4 = require("util");
|
|
2769
|
+
var logPrefix2 = "[csvWriter]";
|
|
2770
|
+
var writeDataToCsv = async (fileName, data) => {
|
|
2771
|
+
try {
|
|
2772
|
+
const firstRow = data[0];
|
|
2773
|
+
if (!firstRow) {
|
|
2774
|
+
console.log(`${logPrefix2} No data to write to CSV file=%s`, fileName);
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2778
|
+
path: fileName,
|
|
2779
|
+
header: Object.keys(firstRow).map((col) => {
|
|
2780
|
+
return { id: col, title: col };
|
|
2781
|
+
})
|
|
2782
|
+
});
|
|
2783
|
+
await csvWriter.writeRecords(data);
|
|
2784
|
+
console.log(`${logPrefix2} CSV file=%s written successfully`, fileName);
|
|
2785
|
+
} catch (err) {
|
|
2786
|
+
if (err instanceof Error) {
|
|
2787
|
+
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, fileName, err.message));
|
|
2788
|
+
}
|
|
2789
|
+
throw err;
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
2793
|
+
try {
|
|
2794
|
+
let rowCount = 0;
|
|
2795
|
+
const firstDataRow = (await generator.next()).value;
|
|
2796
|
+
if (!firstDataRow) {
|
|
2797
|
+
console.log(`${logPrefix2} [%s] first data row of csv empty, skipping`, outputFileName);
|
|
2798
|
+
return 0;
|
|
2799
|
+
}
|
|
2800
|
+
console.log(`${logPrefix2} [%s] inferring headers from first data row`, outputFileName);
|
|
2801
|
+
const headers = Object.keys(firstDataRow).map((col) => {
|
|
2802
|
+
return {
|
|
2803
|
+
id: col,
|
|
2804
|
+
title: col
|
|
2805
|
+
};
|
|
2806
|
+
});
|
|
2807
|
+
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2808
|
+
path: outputFileName,
|
|
2809
|
+
header: headers,
|
|
2810
|
+
alwaysQuote: true
|
|
2811
|
+
});
|
|
2812
|
+
console.log(`${logPrefix2} [%s] writing csv`, outputFileName);
|
|
2813
|
+
let batch = [firstDataRow];
|
|
2814
|
+
for await (const data of generator) {
|
|
2815
|
+
batch.push(data);
|
|
2816
|
+
rowCount++;
|
|
2817
|
+
if (batch.length > 1e4) {
|
|
2818
|
+
try {
|
|
2819
|
+
await csvWriter.writeRecords(batch);
|
|
2820
|
+
} catch (err) {
|
|
2821
|
+
if (err instanceof Error) {
|
|
2822
|
+
throw new Error(
|
|
2823
|
+
(0, import_util4.format)(
|
|
2824
|
+
`error while writing batch to csv batch (first 3 records)=%j, err: %s`,
|
|
2825
|
+
batch.slice(0, 3),
|
|
2826
|
+
err.message
|
|
2827
|
+
)
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
throw err;
|
|
2831
|
+
}
|
|
2832
|
+
batch = [];
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
await csvWriter.writeRecords(batch);
|
|
2836
|
+
console.log(`${logPrefix2} [%s] csv written successfully with %d rows`, outputFileName, rowCount);
|
|
2837
|
+
return rowCount;
|
|
2838
|
+
} catch (err) {
|
|
2839
|
+
if (err instanceof Error) {
|
|
2840
|
+
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, outputFileName, err.message));
|
|
2841
|
+
}
|
|
2842
|
+
throw err;
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
|
|
2846
|
+
// src/sdk/file-processing/file-stream.ts
|
|
2847
|
+
var import_xlsx2 = __toESM(require("xlsx"));
|
|
2848
|
+
var FileStream = class extends Stream {
|
|
2849
|
+
localFilePaths;
|
|
2850
|
+
constructor(config, localFilePath, tapState, target) {
|
|
2851
|
+
super(config, tapState, target);
|
|
2852
|
+
this.localFilePaths = Array.isArray(localFilePath) ? localFilePath : [localFilePath];
|
|
2853
|
+
this.streamId = config.streamId;
|
|
2854
|
+
this.primaryKey = config.primaryKeys;
|
|
2855
|
+
this.replicationMethod = config.replicationMethod;
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Build JSON Schema from the file config's field definitions.
|
|
2859
|
+
*/
|
|
2860
|
+
async getSchema() {
|
|
2861
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2862
|
+
const excelConfig = this.config.excel;
|
|
2863
|
+
if (excelConfig.type === "single-sheet-extraction") {
|
|
2864
|
+
const jsonSchema = excelFieldsToJsonSchema(excelConfig.columns);
|
|
2865
|
+
return { jsonSchema };
|
|
2866
|
+
}
|
|
2867
|
+
return void 0;
|
|
2868
|
+
}
|
|
2869
|
+
if (this.config.format === "CSV" /* CSV */) {
|
|
2870
|
+
const jsonSchema = csvFieldsToJsonSchema(this.config.csv.fields);
|
|
2871
|
+
return { jsonSchema };
|
|
2872
|
+
}
|
|
2873
|
+
throw new Error(`FileStream: No valid format config for stream ${this.streamId}`);
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Yield records from all file(s).
|
|
2877
|
+
* When multiple file paths are provided, records are yielded sequentially from each file.
|
|
2878
|
+
* Pre-counts total rows across all files to enable percentage progress logging.
|
|
2879
|
+
*/
|
|
2880
|
+
async *_getRecords() {
|
|
2881
|
+
let total = 0;
|
|
2882
|
+
for (const filePath of this.localFilePaths) {
|
|
2883
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2884
|
+
const data = await extractExcelRows(filePath, this.config.excel);
|
|
2885
|
+
total += data.length;
|
|
2886
|
+
} else if (this.config.format === "CSV" /* CSV */) {
|
|
2887
|
+
total += await countCsvLines(filePath);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
this.totalRows = total;
|
|
2891
|
+
for (const filePath of this.localFilePaths) {
|
|
2892
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2893
|
+
const data = await extractExcelRows(filePath, this.config.excel);
|
|
2894
|
+
for (const row of data) {
|
|
2895
|
+
yield row;
|
|
2896
|
+
}
|
|
2897
|
+
} else if (this.config.format === "CSV" /* CSV */) {
|
|
2898
|
+
yield* rowGeneratorFromCsv(filePath, this.config.csv);
|
|
2899
|
+
} else {
|
|
2900
|
+
throw new Error(`FileStream: Unsupported format for stream ${this.streamId}`);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
};
|
|
2905
|
+
function createExcelStreamConfig(excelConfig, fileName, localFilePath) {
|
|
2906
|
+
if (excelConfig.type === "single-sheet-extraction") {
|
|
2907
|
+
const primaryKeys = extractPrimaryKeysFromExcelFields(excelConfig.columns);
|
|
2908
|
+
let tableName2;
|
|
2909
|
+
if (typeof excelConfig.tableName === "function") {
|
|
2910
|
+
const variables = excelConfig.fileNameVariablesExtractor(fileName);
|
|
2911
|
+
tableName2 = excelConfig.tableName(fileName, void 0, variables);
|
|
2912
|
+
} else {
|
|
2913
|
+
tableName2 = excelConfig.tableName;
|
|
2914
|
+
}
|
|
2915
|
+
return {
|
|
2916
|
+
format: "EXCEL" /* EXCEL */,
|
|
2917
|
+
streamId: tableName2,
|
|
2918
|
+
replicationMethod: excelConfig.replicationMethod,
|
|
2919
|
+
primaryKeys,
|
|
2920
|
+
excel: excelConfig
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
let tableName;
|
|
2924
|
+
if (typeof excelConfig.tableName === "function") {
|
|
2925
|
+
if (!localFilePath) {
|
|
2926
|
+
throw new Error("localFilePath is required for processor configs with dynamic table names");
|
|
2927
|
+
}
|
|
2928
|
+
const workbook = import_xlsx2.default.readFile(localFilePath, { dense: true });
|
|
2929
|
+
const variables = excelConfig.fileNameVariablesExtractor(fileName, workbook);
|
|
2930
|
+
tableName = excelConfig.tableName(fileName, workbook, variables);
|
|
2931
|
+
} else {
|
|
2932
|
+
tableName = excelConfig.tableName;
|
|
2933
|
+
}
|
|
2934
|
+
return {
|
|
2935
|
+
format: "EXCEL" /* EXCEL */,
|
|
2936
|
+
streamId: tableName,
|
|
2937
|
+
replicationMethod: excelConfig.replicationMethod,
|
|
2938
|
+
primaryKeys: [],
|
|
2939
|
+
excel: excelConfig
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function createCsvStreamConfig(streamId, csvConfig, options) {
|
|
2943
|
+
return {
|
|
2944
|
+
format: "CSV" /* CSV */,
|
|
2945
|
+
streamId,
|
|
2946
|
+
replicationMethod: options?.replicationMethod ?? "FULL_TABLE" /* FULL_TABLE */,
|
|
2947
|
+
primaryKeys: options?.primaryKeys ?? extractPrimaryKeysFromCsvConfig(csvConfig.fields),
|
|
2948
|
+
csv: csvConfig
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
async function processFileStreams(entries, tapState, target) {
|
|
2952
|
+
for (const entry of entries) {
|
|
2953
|
+
const stream = new FileStream(entry.config, entry.filePath, tapState, target);
|
|
2954
|
+
await stream.sync();
|
|
2955
|
+
}
|
|
2956
|
+
await target.complete();
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// src/sdk/file-processing/csv/config-builder.ts
|
|
2960
|
+
var CsvExtractionConfigBuilder = class _CsvExtractionConfigBuilder {
|
|
2961
|
+
_separator = ",";
|
|
2962
|
+
_encoding;
|
|
2963
|
+
_addSyncedAtColumn;
|
|
2964
|
+
_fields;
|
|
2965
|
+
_fieldsMode;
|
|
2966
|
+
_replicationMethod;
|
|
2967
|
+
_primaryKeys;
|
|
2968
|
+
static create() {
|
|
2969
|
+
return new _CsvExtractionConfigBuilder();
|
|
2970
|
+
}
|
|
2971
|
+
separator(sep) {
|
|
2972
|
+
this._separator = sep;
|
|
2973
|
+
return this;
|
|
2974
|
+
}
|
|
2975
|
+
encoding(enc) {
|
|
2976
|
+
this._encoding = enc;
|
|
2977
|
+
return this;
|
|
2978
|
+
}
|
|
2979
|
+
addSyncedAtColumn(enabled = true) {
|
|
2980
|
+
this._addSyncedAtColumn = enabled;
|
|
2981
|
+
return this;
|
|
2982
|
+
}
|
|
2983
|
+
fieldsFromDict(mapping) {
|
|
2984
|
+
if (this._fieldsMode === "array") {
|
|
2985
|
+
throw new Error("Cannot use fieldsFromDict after fieldsFromArray");
|
|
2986
|
+
}
|
|
2987
|
+
this._fieldsMode = "dict";
|
|
2988
|
+
this._fields = mapping;
|
|
2989
|
+
return this;
|
|
2990
|
+
}
|
|
2991
|
+
fieldsFromArray(fields) {
|
|
2992
|
+
if (this._fieldsMode === "dict") {
|
|
2993
|
+
throw new Error("Cannot use fieldsFromArray after fieldsFromDict");
|
|
2994
|
+
}
|
|
2995
|
+
this._fieldsMode = "array";
|
|
2996
|
+
this._fields = fields;
|
|
2997
|
+
return this;
|
|
2998
|
+
}
|
|
2999
|
+
replicationMethod(method) {
|
|
3000
|
+
this._replicationMethod = method;
|
|
3001
|
+
return this;
|
|
3002
|
+
}
|
|
3003
|
+
primaryKeys(keys) {
|
|
3004
|
+
this._primaryKeys = keys;
|
|
3005
|
+
return this;
|
|
3006
|
+
}
|
|
3007
|
+
build() {
|
|
3008
|
+
if (!this._fields) {
|
|
3009
|
+
throw new Error("Fields must be configured (use fieldsFromDict or fieldsFromArray)");
|
|
3010
|
+
}
|
|
3011
|
+
if (Array.isArray(this._fields) && this._fields.length === 0) {
|
|
3012
|
+
throw new Error("Fields must not be empty");
|
|
3013
|
+
}
|
|
3014
|
+
if (!Array.isArray(this._fields) && Object.keys(this._fields).length === 0) {
|
|
3015
|
+
throw new Error("Fields must not be empty");
|
|
3016
|
+
}
|
|
3017
|
+
const config = {
|
|
3018
|
+
separator: this._separator,
|
|
3019
|
+
fields: this._fields
|
|
3020
|
+
};
|
|
3021
|
+
if (this._encoding !== void 0) {
|
|
3022
|
+
config.encoding = this._encoding;
|
|
3023
|
+
}
|
|
3024
|
+
if (this._addSyncedAtColumn !== void 0) {
|
|
3025
|
+
config.addSyncedAtColumn = this._addSyncedAtColumn;
|
|
3026
|
+
}
|
|
3027
|
+
return config;
|
|
3028
|
+
}
|
|
3029
|
+
buildStreamConfig(streamId) {
|
|
3030
|
+
const csvConfig = this.build();
|
|
3031
|
+
const options = {};
|
|
3032
|
+
if (this._replicationMethod !== void 0) {
|
|
3033
|
+
options.replicationMethod = this._replicationMethod;
|
|
3034
|
+
}
|
|
3035
|
+
if (this._primaryKeys !== void 0) {
|
|
3036
|
+
options.primaryKeys = this._primaryKeys;
|
|
3037
|
+
}
|
|
3038
|
+
return createCsvStreamConfig(streamId, csvConfig, options);
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
|
|
3042
|
+
// src/sdk/file-processing/file-tap.ts
|
|
3043
|
+
var FileTap = class _FileTap extends Tap {
|
|
3044
|
+
entries;
|
|
3045
|
+
constructor(target, config, stateProvider, entries) {
|
|
3046
|
+
super(target, config, stateProvider);
|
|
3047
|
+
this.entries = entries;
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Convenience constructor for the common case where all configs share the same file path.
|
|
3051
|
+
*/
|
|
3052
|
+
static fromConfigs(target, config, stateProvider, fileConfigs, sharedFilePath) {
|
|
3053
|
+
const entries = fileConfigs.map((c) => ({
|
|
3054
|
+
config: c,
|
|
3055
|
+
filePath: sharedFilePath
|
|
3056
|
+
}));
|
|
3057
|
+
return new _FileTap(target, config, stateProvider, entries);
|
|
3058
|
+
}
|
|
3059
|
+
async init() {
|
|
3060
|
+
for (const entry of this.entries) {
|
|
3061
|
+
const stream = new FileStream(
|
|
3062
|
+
entry.config,
|
|
3063
|
+
entry.filePath,
|
|
3064
|
+
this.tapState,
|
|
3065
|
+
this.target
|
|
3066
|
+
);
|
|
3067
|
+
this.streams.push(stream);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
|
|
3072
|
+
// src/sdk/file-processing/file-patterns.ts
|
|
3073
|
+
var VariableExtractors = class {
|
|
3074
|
+
/**
|
|
3075
|
+
* Returns the filename as a variable.
|
|
3076
|
+
*/
|
|
3077
|
+
static filename() {
|
|
3078
|
+
return (filename) => ({
|
|
3079
|
+
fileName: filename
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* Extracts named capture groups from a regex pattern.
|
|
3084
|
+
*/
|
|
3085
|
+
static regex(pattern) {
|
|
3086
|
+
return (filename) => {
|
|
3087
|
+
const match = filename.match(pattern);
|
|
3088
|
+
return match?.groups ?? {};
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Combines multiple extraction functions.
|
|
3093
|
+
*/
|
|
3094
|
+
static combine(...extractors) {
|
|
3095
|
+
return (filename) => {
|
|
3096
|
+
return extractors.reduce((acc, extractor) => {
|
|
3097
|
+
return { ...acc, ...extractor(filename) };
|
|
3098
|
+
}, {});
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
var FilePatterns = class {
|
|
3103
|
+
/**
|
|
3104
|
+
* Creates a validator for files starting with a specific prefix (case-insensitive).
|
|
3105
|
+
*/
|
|
3106
|
+
static startsWith(prefix) {
|
|
3107
|
+
return (filename) => filename.toLowerCase().startsWith(prefix.toLowerCase());
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Creates a validator for files matching a regex pattern.
|
|
3111
|
+
*/
|
|
3112
|
+
static regex(pattern) {
|
|
3113
|
+
return (filename) => pattern.test(filename);
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Combines multiple validators with AND logic.
|
|
3117
|
+
*/
|
|
3118
|
+
static and(...validators) {
|
|
3119
|
+
return (filename) => validators.every((validator) => validator(filename));
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Combines multiple validators with OR logic.
|
|
3123
|
+
*/
|
|
3124
|
+
static or(...validators) {
|
|
3125
|
+
return (filename) => validators.some((validator) => validator(filename));
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
|
|
3129
|
+
// src/services/sftp.ts
|
|
3130
|
+
var import_ssh2_sftp_client = __toESM(require("ssh2-sftp-client"));
|
|
3131
|
+
var import_ssh2_sftp_client2 = __toESM(require("ssh2-sftp-client"));
|
|
3132
|
+
var SftpService = new import_ssh2_sftp_client.default();
|
|
3133
|
+
|
|
3134
|
+
// src/services/cloud-storage.ts
|
|
3135
|
+
var import_storage = require("@google-cloud/storage");
|
|
3136
|
+
var import_util5 = require("util");
|
|
3137
|
+
var pathModule = __toESM(require("path"));
|
|
3138
|
+
var import_fs3 = require("fs");
|
|
3139
|
+
var import_crypto = require("crypto");
|
|
3140
|
+
var logPrefix3 = "[CloudStorageService]";
|
|
3141
|
+
var tmpDir = "tmp";
|
|
3142
|
+
var CloudStorageService = class {
|
|
3143
|
+
storage;
|
|
3144
|
+
bucket;
|
|
3145
|
+
processedSuffix;
|
|
3146
|
+
supportedExtensions;
|
|
3147
|
+
path;
|
|
3148
|
+
constructor(bucketName, path2, opts) {
|
|
3149
|
+
this.storage = new import_storage.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
3150
|
+
this.bucket = this.storage.bucket(bucketName);
|
|
3151
|
+
this.path = path2;
|
|
3152
|
+
this.processedSuffix = opts?.processedSuffix ?? ".processed";
|
|
3153
|
+
this.supportedExtensions = opts?.supportedExtensions ?? [];
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Lists all files in the bucket with an optional prefix.
|
|
3157
|
+
*/
|
|
3158
|
+
async listFiles(prefix) {
|
|
3159
|
+
const effectivePrefix = prefix || this.path;
|
|
3160
|
+
const [files] = await this.bucket.getFiles(
|
|
3161
|
+
effectivePrefix ? { prefix: effectivePrefix } : void 0
|
|
3162
|
+
);
|
|
3163
|
+
return files.map((f) => f.name);
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3166
|
+
* Returns files that haven't been marked as processed.
|
|
3167
|
+
* Filters by supported extensions if configured.
|
|
3168
|
+
*/
|
|
3169
|
+
async getUnprocessedFiles() {
|
|
3170
|
+
const allFiles = await this.listFiles();
|
|
3171
|
+
const markerFiles = new Set(
|
|
3172
|
+
allFiles.filter((f) => f.endsWith(this.processedSuffix)).map((f) => f.replace(this.processedSuffix, ""))
|
|
3173
|
+
);
|
|
3174
|
+
return allFiles.filter((f) => {
|
|
3175
|
+
if (f.endsWith(this.processedSuffix)) return false;
|
|
3176
|
+
if (f.endsWith("/")) return false;
|
|
3177
|
+
if (this.supportedExtensions.length > 0) {
|
|
3178
|
+
const ext = pathModule.extname(f).toLowerCase();
|
|
3179
|
+
if (!this.supportedExtensions.includes(ext)) return false;
|
|
3180
|
+
}
|
|
3181
|
+
return !markerFiles.has(f);
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Creates a marker file to indicate a file has been processed.
|
|
3186
|
+
*/
|
|
3187
|
+
async createMarkerFile(fileName) {
|
|
3188
|
+
const markerFileName = `${fileName}${this.processedSuffix}`;
|
|
3189
|
+
try {
|
|
3190
|
+
const file = this.bucket.file(markerFileName);
|
|
3191
|
+
await file.save((0, import_util5.format)("Marked file %s as processed", fileName));
|
|
3192
|
+
logger.info(`${logPrefix3} Marker file ${markerFileName} created successfully.`);
|
|
3193
|
+
} catch (err) {
|
|
3194
|
+
if (err instanceof Error) {
|
|
3195
|
+
throw new Error((0, import_util5.format)(`error while writing marker file=%s, err:%s`, markerFileName, err.message));
|
|
3196
|
+
}
|
|
3197
|
+
throw err;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Downloads a file from GCS to a local tmp directory.
|
|
3202
|
+
* Returns the local path of the downloaded file.
|
|
3203
|
+
*/
|
|
3204
|
+
async downloadFile(filePath, fileName) {
|
|
3205
|
+
const file = this.bucket.file(filePath);
|
|
3206
|
+
const tmpDirPath = pathModule.join(process.cwd(), tmpDir);
|
|
3207
|
+
if (!(0, import_fs3.existsSync)(tmpDirPath)) {
|
|
3208
|
+
(0, import_fs3.mkdirSync)(tmpDirPath);
|
|
3209
|
+
}
|
|
3210
|
+
const destFilename = pathModule.join(process.cwd(), tmpDir, fileName);
|
|
3211
|
+
try {
|
|
3212
|
+
if ((0, import_fs3.existsSync)(destFilename)) {
|
|
3213
|
+
(0, import_fs3.unlinkSync)(destFilename);
|
|
3214
|
+
}
|
|
3215
|
+
await file.download({ destination: destFilename });
|
|
3216
|
+
logger.info(`${logPrefix3} Downloaded ${fileName} to ${destFilename}`);
|
|
3217
|
+
return destFilename;
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
if (err instanceof Error) {
|
|
3220
|
+
throw new Error((0, import_util5.format)(
|
|
3221
|
+
`can't download file=%s from bucket, err:%s`,
|
|
3222
|
+
fileName,
|
|
3223
|
+
err.message
|
|
3224
|
+
));
|
|
3225
|
+
}
|
|
3226
|
+
throw err;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
/**
|
|
3230
|
+
* Uploads a local file to GCS.
|
|
3231
|
+
*/
|
|
3232
|
+
async uploadFile(localPath, destPath) {
|
|
3233
|
+
try {
|
|
3234
|
+
logger.info(`${logPrefix3} preparing to upload '%s' to '%s'`, localPath, destPath);
|
|
3235
|
+
const [file] = await this.bucket.upload(localPath, { destination: destPath });
|
|
3236
|
+
logger.info(`${logPrefix3} file %s has been successfully uploaded into %s`, localPath, destPath);
|
|
3237
|
+
return file;
|
|
3238
|
+
} catch (err) {
|
|
3239
|
+
if (err instanceof Error) {
|
|
3240
|
+
throw new Error((0, import_util5.format)(`error while uploading file file=%s into path=%s, err:%s`, localPath, destPath, err.message));
|
|
3241
|
+
}
|
|
3242
|
+
throw err;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Reads a GCS object and returns its contents as a UTF-8 string.
|
|
3247
|
+
*/
|
|
3248
|
+
async readObjectAsString(objectPath) {
|
|
3249
|
+
try {
|
|
3250
|
+
await this.bucket.get({ autoCreate: false });
|
|
3251
|
+
const fileRef = this.bucket.file(objectPath);
|
|
3252
|
+
const [exists] = await fileRef.exists();
|
|
3253
|
+
if (!exists) {
|
|
3254
|
+
throw new Error(`GCS object not found: gs://${this.bucket.name}/${objectPath}`);
|
|
3255
|
+
}
|
|
3256
|
+
const [contents] = await fileRef.download();
|
|
3257
|
+
return contents.toString("utf8");
|
|
3258
|
+
} catch (err) {
|
|
3259
|
+
throw new Error((0, import_util5.format)(`error reading GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Writes a string to a GCS object with JSON content type.
|
|
3264
|
+
*/
|
|
3265
|
+
async writeStringObject(objectPath, contents) {
|
|
3266
|
+
try {
|
|
3267
|
+
await this.bucket.get({ autoCreate: false });
|
|
3268
|
+
const fileRef = this.bucket.file(objectPath);
|
|
3269
|
+
await fileRef.save(contents, { contentType: "application/json" });
|
|
3270
|
+
logger.info(`Uploaded object to gs://${this.bucket.name}/${objectPath}`);
|
|
3271
|
+
} catch (err) {
|
|
3272
|
+
throw new Error((0, import_util5.format)(`error writing GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
3277
|
+
* Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
|
|
3278
|
+
* or `<prefix>/default/` otherwise.
|
|
3279
|
+
* Returns the GCS File reference.
|
|
3280
|
+
*/
|
|
3281
|
+
async uploadFileWithUniqueName(filePath, prefix, streamId) {
|
|
3282
|
+
try {
|
|
3283
|
+
await this.bucket.get({ autoCreate: false });
|
|
3284
|
+
const runFolder = process.env.RUN_ID ?? "default";
|
|
3285
|
+
const destinationFileName = `${prefix}/${runFolder}/${streamId}-${(0, import_crypto.randomUUID)()}.jsonnl`;
|
|
3286
|
+
await this.bucket.upload(filePath, {
|
|
3287
|
+
destination: destinationFileName
|
|
3288
|
+
});
|
|
3289
|
+
logger.info(`Uploaded ${filePath} into ${this.bucket.name}/${destinationFileName} GCS File`);
|
|
3290
|
+
return this.bucket.file(destinationFileName);
|
|
3291
|
+
} catch (err) {
|
|
3292
|
+
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
3293
|
+
|
|
3294
|
+
Error: ${err.message}
|
|
3295
|
+
Stack: ${err.stack}
|
|
3296
|
+
Code: ${err.code}
|
|
3297
|
+
`);
|
|
3298
|
+
throw err;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3303
|
+
// src/services/zip.ts
|
|
3304
|
+
var fse = __toESM(require("fs-extra"));
|
|
3305
|
+
var import_decompress = __toESM(require("decompress"));
|
|
3306
|
+
var unzip = async (zipFilePath, extractedPath) => {
|
|
3307
|
+
await fse.ensureDir(extractedPath);
|
|
3308
|
+
await (0, import_decompress.default)(zipFilePath, extractedPath);
|
|
3309
|
+
return extractedPath;
|
|
3310
|
+
};
|
|
3311
|
+
|
|
2224
3312
|
// src/targets/bigquery/helpers.ts
|
|
2225
3313
|
var safeColumnName = (key) => {
|
|
2226
3314
|
let returnString = key;
|
|
@@ -2257,8 +3345,8 @@ var import_decimal = __toESM(require("decimal.js"));
|
|
|
2257
3345
|
var import_dayjs5 = __toESM(require("dayjs"));
|
|
2258
3346
|
var validateDateRange = (record, schema) => {
|
|
2259
3347
|
Object.keys(schema).forEach((key) => {
|
|
2260
|
-
const { format:
|
|
2261
|
-
if (
|
|
3348
|
+
const { format: format8 } = schema[key];
|
|
3349
|
+
if (format8 === "date-time") {
|
|
2262
3350
|
if (record[key]) {
|
|
2263
3351
|
const formattedDate = (0, import_dayjs5.default)(record[key]).format(defaultDateTimeFormat);
|
|
2264
3352
|
const isNotTooMuchInTheFuture = (0, import_dayjs5.default)(record[key]).isBefore((0, import_dayjs5.default)("9999-12-31 23:59:59.999999"));
|
|
@@ -2342,96 +3430,6 @@ var BqClientHolder = class {
|
|
|
2342
3430
|
}
|
|
2343
3431
|
};
|
|
2344
3432
|
|
|
2345
|
-
// src/targets/bigquery/service/cloudStorage.ts
|
|
2346
|
-
var import_storage = require("@google-cloud/storage");
|
|
2347
|
-
|
|
2348
|
-
// node_modules/uuid/dist/esm-node/rng.js
|
|
2349
|
-
var import_crypto = __toESM(require("crypto"));
|
|
2350
|
-
var rnds8Pool = new Uint8Array(256);
|
|
2351
|
-
var poolPtr = rnds8Pool.length;
|
|
2352
|
-
function rng() {
|
|
2353
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
2354
|
-
import_crypto.default.randomFillSync(rnds8Pool);
|
|
2355
|
-
poolPtr = 0;
|
|
2356
|
-
}
|
|
2357
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
// node_modules/uuid/dist/esm-node/regex.js
|
|
2361
|
-
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;
|
|
2362
|
-
|
|
2363
|
-
// node_modules/uuid/dist/esm-node/validate.js
|
|
2364
|
-
function validate(uuid) {
|
|
2365
|
-
return typeof uuid === "string" && regex_default.test(uuid);
|
|
2366
|
-
}
|
|
2367
|
-
var validate_default = validate;
|
|
2368
|
-
|
|
2369
|
-
// node_modules/uuid/dist/esm-node/stringify.js
|
|
2370
|
-
var byteToHex = [];
|
|
2371
|
-
for (let i = 0; i < 256; ++i) {
|
|
2372
|
-
byteToHex.push((i + 256).toString(16).substr(1));
|
|
2373
|
-
}
|
|
2374
|
-
function stringify3(arr, offset = 0) {
|
|
2375
|
-
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();
|
|
2376
|
-
if (!validate_default(uuid)) {
|
|
2377
|
-
throw TypeError("Stringified UUID is invalid");
|
|
2378
|
-
}
|
|
2379
|
-
return uuid;
|
|
2380
|
-
}
|
|
2381
|
-
var stringify_default = stringify3;
|
|
2382
|
-
|
|
2383
|
-
// node_modules/uuid/dist/esm-node/v4.js
|
|
2384
|
-
function v4(options, buf, offset) {
|
|
2385
|
-
options = options || {};
|
|
2386
|
-
const rnds = options.random || (options.rng || rng)();
|
|
2387
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
2388
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
2389
|
-
if (buf) {
|
|
2390
|
-
offset = offset || 0;
|
|
2391
|
-
for (let i = 0; i < 16; ++i) {
|
|
2392
|
-
buf[offset + i] = rnds[i];
|
|
2393
|
-
}
|
|
2394
|
-
return buf;
|
|
2395
|
-
}
|
|
2396
|
-
return stringify_default(rnds);
|
|
2397
|
-
}
|
|
2398
|
-
var v4_default = v4;
|
|
2399
|
-
|
|
2400
|
-
// src/targets/bigquery/service/cloudStorage.ts
|
|
2401
|
-
var uploadFileInBucket = async (streamId, gcsBuckerName, connectorId, filePath) => {
|
|
2402
|
-
try {
|
|
2403
|
-
const retryOptions = { autoRetry: true, maxRetries: 20 };
|
|
2404
|
-
let storage = new import_storage.Storage({ retryOptions });
|
|
2405
|
-
const bucketRef = storage.bucket(gcsBuckerName);
|
|
2406
|
-
await bucketRef.get({ autoCreate: false });
|
|
2407
|
-
const destinationFileName = `${connectorId}/${streamId}-${v4_default()}.jsonnl`;
|
|
2408
|
-
await bucketRef.upload(filePath, {
|
|
2409
|
-
destination: destinationFileName
|
|
2410
|
-
});
|
|
2411
|
-
logger.info(`\u{1F5F3} Uploaded ${filePath} into ${gcsBuckerName}/${destinationFileName} GCS File`);
|
|
2412
|
-
return bucketRef.file(destinationFileName);
|
|
2413
|
-
} catch (err) {
|
|
2414
|
-
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
2415
|
-
|
|
2416
|
-
Error: ${err.message}
|
|
2417
|
-
Stack: ${err.stack}
|
|
2418
|
-
Code: ${err.code}
|
|
2419
|
-
`);
|
|
2420
|
-
if (err.code < 500) {
|
|
2421
|
-
await haltAndCatchFire(
|
|
2422
|
-
`unauthorized`,
|
|
2423
|
-
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
2424
|
-
|
|
2425
|
-
The error from Google is: '${err.message}' \u{1F440}
|
|
2426
|
-
|
|
2427
|
-
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
2428
|
-
`Got error from cloud storage lib`
|
|
2429
|
-
);
|
|
2430
|
-
}
|
|
2431
|
-
throw err;
|
|
2432
|
-
}
|
|
2433
|
-
};
|
|
2434
|
-
|
|
2435
3433
|
// src/targets/bigquery/service/dbSync.ts
|
|
2436
3434
|
var import_chunk = __toESM(require("lodash/chunk.js"));
|
|
2437
3435
|
var import_async_mutex2 = require("async-mutex");
|
|
@@ -2472,21 +3470,21 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2472
3470
|
safeColumnName = safeColumnName;
|
|
2473
3471
|
getWarehouseTypeFromJSONSchema = (definition) => {
|
|
2474
3472
|
const type = definition.type;
|
|
2475
|
-
const
|
|
3473
|
+
const format8 = definition.format;
|
|
2476
3474
|
let typeArr;
|
|
2477
3475
|
if (type instanceof Array) {
|
|
2478
3476
|
typeArr = type;
|
|
2479
3477
|
} else {
|
|
2480
3478
|
typeArr = [type];
|
|
2481
3479
|
}
|
|
2482
|
-
if (
|
|
2483
|
-
switch (
|
|
3480
|
+
if (format8) {
|
|
3481
|
+
switch (format8) {
|
|
2484
3482
|
case "date-time":
|
|
2485
3483
|
return "TIMESTAMP";
|
|
2486
3484
|
case "json":
|
|
2487
3485
|
return "STRING";
|
|
2488
3486
|
default:
|
|
2489
|
-
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${
|
|
3487
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format8}`);
|
|
2490
3488
|
}
|
|
2491
3489
|
} else if (typeArr.includes("number")) {
|
|
2492
3490
|
return "NUMERIC";
|
|
@@ -2582,6 +3580,13 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2582
3580
|
...this.getMergeQueries()
|
|
2583
3581
|
];
|
|
2584
3582
|
};
|
|
3583
|
+
getAppendQueries = () => {
|
|
3584
|
+
const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
3585
|
+
const columns = Object.keys(columnUnsafeToSafeMapping).map((unsafeKey) => `\`${columnUnsafeToSafeMapping[unsafeKey]}\``).join(`, `);
|
|
3586
|
+
return [
|
|
3587
|
+
`INSERT INTO ${this.sqlTableId} (${columns}) SELECT ${columns} FROM ${this.sqlStagingTableId};`
|
|
3588
|
+
];
|
|
3589
|
+
};
|
|
2585
3590
|
createDatabaseAndSchemaIfNotExists = async (retryCount) => {
|
|
2586
3591
|
try {
|
|
2587
3592
|
await semaphore2.runExclusive(async () => {
|
|
@@ -2706,11 +3711,11 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2706
3711
|
};
|
|
2707
3712
|
loadStreamInStagingArea = async (localFilePath) => {
|
|
2708
3713
|
try {
|
|
2709
|
-
const
|
|
2710
|
-
|
|
2711
|
-
|
|
3714
|
+
const gcsService = new CloudStorageService(this.config.loading_deck_gcs_bucket_name);
|
|
3715
|
+
const file = await gcsService.uploadFileWithUniqueName(
|
|
3716
|
+
localFilePath,
|
|
2712
3717
|
this.config.connector_id,
|
|
2713
|
-
|
|
3718
|
+
this.streamId
|
|
2714
3719
|
);
|
|
2715
3720
|
const metadata = {
|
|
2716
3721
|
sourceFormat: "NEWLINE_DELIMITED_JSON",
|
|
@@ -2719,6 +3724,17 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
2719
3724
|
await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
|
|
2720
3725
|
} catch (err) {
|
|
2721
3726
|
logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
|
|
3727
|
+
if (err.code < 500) {
|
|
3728
|
+
await haltAndCatchFire(
|
|
3729
|
+
`unauthorized`,
|
|
3730
|
+
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
3731
|
+
|
|
3732
|
+
The error from Google is: '${err.message}' \u{1F440}
|
|
3733
|
+
|
|
3734
|
+
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
3735
|
+
`Got error from cloud storage lib`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
2722
3738
|
throw err;
|
|
2723
3739
|
}
|
|
2724
3740
|
};
|
|
@@ -2846,47 +3862,16 @@ var BigQueryTarget = class extends ITarget {
|
|
|
2846
3862
|
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
2847
3863
|
};
|
|
2848
3864
|
|
|
2849
|
-
// src/sdk/service/gcs.ts
|
|
2850
|
-
var import_storage2 = require("@google-cloud/storage");
|
|
2851
|
-
var import_util3 = require("util");
|
|
2852
|
-
var getStorage = () => new import_storage2.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
2853
|
-
var readObjectAsString = async (bucketName, objectPath) => {
|
|
2854
|
-
try {
|
|
2855
|
-
const storage = getStorage();
|
|
2856
|
-
const bucketRef = storage.bucket(bucketName);
|
|
2857
|
-
await bucketRef.get({ autoCreate: false });
|
|
2858
|
-
const fileRef = bucketRef.file(objectPath);
|
|
2859
|
-
const [exists] = await fileRef.exists();
|
|
2860
|
-
if (!exists) {
|
|
2861
|
-
throw new Error(`GCS object not found: gs://${bucketName}/${objectPath}`);
|
|
2862
|
-
}
|
|
2863
|
-
const [contents] = await fileRef.download();
|
|
2864
|
-
return contents.toString("utf8");
|
|
2865
|
-
} catch (err) {
|
|
2866
|
-
throw new Error((0, import_util3.format)(`error reading GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2867
|
-
}
|
|
2868
|
-
};
|
|
2869
|
-
var writeStringObject = async (bucketName, objectPath, contents) => {
|
|
2870
|
-
try {
|
|
2871
|
-
const storage = getStorage();
|
|
2872
|
-
const bucketRef = storage.bucket(bucketName);
|
|
2873
|
-
await bucketRef.get({ autoCreate: false });
|
|
2874
|
-
const fileRef = bucketRef.file(objectPath);
|
|
2875
|
-
await fileRef.save(contents, { contentType: "application/json" });
|
|
2876
|
-
logger.info(`\u{1F5F3} Uploaded object to gs://${bucketName}/${objectPath}`);
|
|
2877
|
-
} catch (err) {
|
|
2878
|
-
throw new Error((0, import_util3.format)(`error writing GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2879
|
-
}
|
|
2880
|
-
};
|
|
2881
|
-
|
|
2882
3865
|
// src/state-providers/gcs/main.ts
|
|
2883
|
-
var
|
|
3866
|
+
var import_util6 = require("util");
|
|
2884
3867
|
var GCSStateProvider = class {
|
|
2885
3868
|
bucketName;
|
|
2886
3869
|
connectorId;
|
|
3870
|
+
service;
|
|
2887
3871
|
constructor(connectorId, bucketName) {
|
|
2888
3872
|
this.connectorId = connectorId;
|
|
2889
3873
|
this.bucketName = bucketName;
|
|
3874
|
+
this.service = new CloudStorageService(bucketName);
|
|
2890
3875
|
}
|
|
2891
3876
|
getObjectPath() {
|
|
2892
3877
|
return `${this.connectorId}/state.json`;
|
|
@@ -2895,11 +3880,11 @@ var GCSStateProvider = class {
|
|
|
2895
3880
|
const objectPath = this.getObjectPath();
|
|
2896
3881
|
try {
|
|
2897
3882
|
logger.info(`\u{1F4C1} Loading state from gs://${this.bucketName}/${objectPath}`);
|
|
2898
|
-
const raw = await readObjectAsString(
|
|
3883
|
+
const raw = await this.service.readObjectAsString(objectPath);
|
|
2899
3884
|
const parsed = JSON.parse(raw);
|
|
2900
3885
|
return { state: parsed };
|
|
2901
3886
|
} catch (err) {
|
|
2902
|
-
logger.warn((0,
|
|
3887
|
+
logger.warn((0, import_util6.format)(`error loading state from gs://${this.bucketName}/${objectPath}, will start with empty state, err: %s`, err?.message));
|
|
2903
3888
|
return { state: void 0 };
|
|
2904
3889
|
}
|
|
2905
3890
|
}
|
|
@@ -2907,10 +3892,10 @@ var GCSStateProvider = class {
|
|
|
2907
3892
|
const objectPath = this.getObjectPath();
|
|
2908
3893
|
try {
|
|
2909
3894
|
logger.info(`\u{1F4DD} Writing state to gs://${this.bucketName}/${objectPath}`);
|
|
2910
|
-
await writeStringObject(
|
|
3895
|
+
await this.service.writeStringObject(objectPath, state);
|
|
2911
3896
|
} catch (err) {
|
|
2912
|
-
logger.error((0,
|
|
2913
|
-
throw new Error((0,
|
|
3897
|
+
logger.error((0, import_util6.format)(`\u{1F4A5} Error while writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
3898
|
+
throw new Error((0, import_util6.format)(`error writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2914
3899
|
}
|
|
2915
3900
|
}
|
|
2916
3901
|
};
|
|
@@ -2921,9 +3906,16 @@ var GCSStateProvider = class {
|
|
|
2921
3906
|
Authenticator,
|
|
2922
3907
|
BATCH_INTERVAL_MS,
|
|
2923
3908
|
BigQueryTarget,
|
|
3909
|
+
CloudStorageService,
|
|
2924
3910
|
CounterMetric,
|
|
3911
|
+
CsvExtractionConfigBuilder,
|
|
2925
3912
|
DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
2926
3913
|
EXECUTION_TIME_METRIC_NAME,
|
|
3914
|
+
ExcelExtractionConfigBuilder,
|
|
3915
|
+
FileFormat,
|
|
3916
|
+
FilePatterns,
|
|
3917
|
+
FileStream,
|
|
3918
|
+
FileTap,
|
|
2927
3919
|
GCSStateProvider,
|
|
2928
3920
|
ITarget,
|
|
2929
3921
|
MissingFieldInSchemaError,
|
|
@@ -2936,16 +3928,34 @@ var GCSStateProvider = class {
|
|
|
2936
3928
|
ReplicationMethodMessage,
|
|
2937
3929
|
SchemaMessage,
|
|
2938
3930
|
SchemaValidationError,
|
|
3931
|
+
SftpClient,
|
|
3932
|
+
SftpService,
|
|
2939
3933
|
StateMessage,
|
|
2940
3934
|
StateService,
|
|
2941
3935
|
Stream,
|
|
2942
3936
|
StreamWarehouseSyncService,
|
|
2943
3937
|
Tap,
|
|
3938
|
+
VariableExtractors,
|
|
2944
3939
|
_greaterThan,
|
|
2945
3940
|
addWhalyFields,
|
|
3941
|
+
checkCsvHeaderRow,
|
|
3942
|
+
countCsvLines,
|
|
3943
|
+
createCellReference,
|
|
3944
|
+
createCsvStreamConfig,
|
|
3945
|
+
createExcelGenerator,
|
|
3946
|
+
createExcelStreamConfig,
|
|
2946
3947
|
createTemporaryFileStream,
|
|
3948
|
+
csvFieldsToJsonSchema,
|
|
3949
|
+
excelColumnToIndex,
|
|
3950
|
+
excelFieldsToJsonSchema,
|
|
3951
|
+
excelRowToIndex,
|
|
3952
|
+
extractExcelRows,
|
|
3953
|
+
extractPrimaryKeysFromCsvConfig,
|
|
3954
|
+
extractPrimaryKeysFromExcelFields,
|
|
2947
3955
|
extractStateForStream,
|
|
3956
|
+
fieldTypeToJsonSchema,
|
|
2948
3957
|
finalizeStateProgressMarkers,
|
|
3958
|
+
findAllMatchingConfigs,
|
|
2949
3959
|
flattenSchema,
|
|
2950
3960
|
getAllMetrics,
|
|
2951
3961
|
getAxiosInstance,
|
|
@@ -2958,14 +3968,23 @@ var GCSStateProvider = class {
|
|
|
2958
3968
|
gracefulExit,
|
|
2959
3969
|
haltAndCatchFire,
|
|
2960
3970
|
incrementStreamState,
|
|
3971
|
+
indexToExcelColumn,
|
|
3972
|
+
indexToExcelRow,
|
|
2961
3973
|
loadJson,
|
|
2962
3974
|
loadSchema,
|
|
2963
3975
|
logger,
|
|
3976
|
+
parseCellReference,
|
|
2964
3977
|
postFormDataApiCall,
|
|
2965
3978
|
postJSONApiCall,
|
|
2966
3979
|
postUrlEncodedApiCall,
|
|
2967
3980
|
printMemoryFootprint,
|
|
3981
|
+
processFileStreams,
|
|
2968
3982
|
removeParasiteProperties,
|
|
2969
|
-
|
|
3983
|
+
rowGeneratorFromCsv,
|
|
3984
|
+
safePath,
|
|
3985
|
+
unzip,
|
|
3986
|
+
validateExcelConfig,
|
|
3987
|
+
writeDataToCsv,
|
|
3988
|
+
writeGeneratorToCSV
|
|
2970
3989
|
});
|
|
2971
3990
|
//# sourceMappingURL=index.js.map
|