@whaly/connector-sdk 0.1.1 → 0.3.0

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