@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/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
- safePath: () => safePath
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 import_jsonschema = require("jsonschema");
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 path = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
1775
- const writeStream = import_fs_extra.default.createWriteStream(path, { flags: "w" });
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(path) {
1782
- let formattedPath = path;
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
- validator;
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.validator = new import_jsonschema.Validator();
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
- await semaphore.runExclusive(async () => {
1983
- const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
1984
- if (shouldUploadIntermediateBatch) {
1985
- await this.loadAllStreamsInWarehouse({ isFinalLoad: false });
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 validationResult = this.validator.validate(recordWithWhalyFields, schema);
1997
- if (!validationResult.valid) {
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(validationResult.errors)}
2053
+ Validation errors: ${JSON.stringify(validateFn.errors)}
2000
2054
 
2001
2055
  Record: ${JSON.stringify(recordWithoutParasiteProperties)}
2002
- Schema: ${JSON.stringify(stream.getSchema())}`, streamId, validationResult.errors);
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
- if (message.keyProperties.length === 0) {
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
- if (stream.getReplicationMethod() === "INCREMENTAL" && batchedRecords > 1e6) {
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: format6 } = schema[key];
2261
- if (format6 === "date-time") {
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 format6 = definition.format;
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 (format6) {
2483
- switch (format6) {
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: ${format6}`);
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 file = await uploadFileInBucket(
2710
- this.streamId,
2711
- this.config.loading_deck_gcs_bucket_name,
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
- localFilePath
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 import_util4 = require("util");
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(this.bucketName, objectPath);
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, import_util4.format)(`error loading state from gs://${this.bucketName}/${objectPath}, will start with empty state, err: %s`, err?.message));
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(this.bucketName, objectPath, state);
3895
+ await this.service.writeStringObject(objectPath, state);
2911
3896
  } catch (err) {
2912
- logger.error((0, import_util4.format)(`\u{1F4A5} Error while writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
2913
- throw new Error((0, import_util4.format)(`error writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
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
- safePath
3983
+ rowGeneratorFromCsv,
3984
+ safePath,
3985
+ unzip,
3986
+ validateExcelConfig,
3987
+ writeDataToCsv,
3988
+ writeGeneratorToCSV
2970
3989
  });
2971
3990
  //# sourceMappingURL=index.js.map