@whaly/connector-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -25,10 +25,10 @@ var readAndParseJSONFile = (path) => {
25
25
  };
26
26
 
27
27
  // src/resolvers/local-file/hook/schemaHook.ts
28
- import { appendFile } from "fs-extra";
28
+ import fsExtra from "fs-extra";
29
29
  var LocalSchemaHook = class {
30
30
  async writeSchema(input) {
31
- await appendFile("./schema.tmp", JSON.stringify(input) + "\n");
31
+ await fsExtra.appendFile("./schema.tmp", JSON.stringify(input) + "\n");
32
32
  }
33
33
  };
34
34
 
@@ -89,15 +89,9 @@ var CounterMetric = class {
89
89
 
90
90
  // src/resolvers/local-file/resolver.ts
91
91
  var { combine, prettyPrint, timestamp, errors, splat } = winston.format;
92
- var configPath = "./config.json";
93
- var targetConfigPath = "./target-config.json";
94
- var catalogPath = "./catalog.json";
95
92
  var statePath = "./state.json";
96
93
  var metricsPath = "./metrics.json";
97
94
  var LocalFilesResolver = class extends Resolver {
98
- startVPNIfNeeded() {
99
- return Promise.resolve();
100
- }
101
95
  checkIfCanSync() {
102
96
  return Promise.resolve(true);
103
97
  }
@@ -122,49 +116,15 @@ var LocalFilesResolver = class extends Resolver {
122
116
  constructor() {
123
117
  super();
124
118
  }
125
- getConfig(command) {
119
+ getState() {
126
120
  logger.info(`\u{1F4C1} Using the LOCAL resolver`);
127
- const config = readAndParseJSONFile(configPath);
128
- const targetConfig = readAndParseJSONFile(targetConfigPath);
129
- const destination = process.env.SHORE__DESTINATION;
130
- if (!destination) {
131
- throw new Error(`\u274C Env variable \`SHORE__DESTINATION\` is not set.
132
- Did you forget to configure it?
133
-
134
- Possible values are: \`GOOGLE_BIGQUERY\`, \`SNOWFLAKE\``);
135
- }
136
- if (command === `READ`) {
137
- if (destination === `GOOGLE_BIGQUERY`) {
138
- const googleCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
139
- if (!googleCredentials) {
140
- throw new Error(`Env variable \`GOOGLE_APPLICATION_CREDENTIALS\` is not set.
141
- Did you forget to configure it?
142
-
143
- This variable should link to a file containing the proper Google Service Account credentials.
144
- `);
145
- }
146
- }
147
- const catalog = readAndParseJSONFile(catalogPath);
148
- const state = readAndParseJSONFile(statePath);
149
- return Promise.resolve({
150
- command,
151
- destination,
152
- config,
153
- targetConfig,
154
- catalog,
155
- state
156
- });
157
- } else {
158
- return Promise.resolve({
159
- command,
160
- destination,
161
- config,
162
- targetConfig
163
- });
164
- }
121
+ const state = readAndParseJSONFile(statePath);
122
+ return Promise.resolve({
123
+ state
124
+ });
165
125
  }
166
126
  writeCatalog(catalog) {
167
- return writeFile(catalogPath, JSON.stringify(catalog));
127
+ return Promise.resolve();
168
128
  }
169
129
  writeState(state) {
170
130
  return writeFile(statePath, state);
@@ -195,9 +155,7 @@ var LocalFilesResolver = class extends Resolver {
195
155
  }
196
156
  updateSourceValue(optionKey, optionValue) {
197
157
  logger.info(`Updating source value ${optionKey} with value ${optionValue}`, { private: true });
198
- const config = readAndParseJSONFile(configPath);
199
- config[optionKey] = optionValue;
200
- return writeFile(configPath, JSON.stringify(config));
158
+ return Promise.resolve();
201
159
  }
202
160
  getLogFormat() {
203
161
  return combine(
@@ -210,29 +168,8 @@ var LocalFilesResolver = class extends Resolver {
210
168
  };
211
169
 
212
170
  // src/sdk/service/resolver.ts
213
- function initResolver(resolverType) {
214
- if (resolverType === "LOCAL") {
215
- return new LocalFilesResolver();
216
- } else {
217
- throw new Error(`\u274C Unsupported configResolver: ${resolverType}`);
218
- }
219
- }
220
171
  var loadResolver = () => {
221
- const configResolver = process.env.SHORE__CONFIG_RESOLVER;
222
- if (!configResolver) {
223
- throw new Error(`Env variable \`SHORE__CONFIG_RESOLVER\` is not set.
224
- Did you forget to configure it?
225
-
226
- Possible values are \`LOCAL\`, \`WHALY\`.
227
- Please read the documentation to get the full description of the behavior of each.
228
- `);
229
- }
230
- if (configResolver === "WHALY" || configResolver === "LOCAL") {
231
- return initResolver(configResolver);
232
- } else {
233
- throw new Error(`Resolver: ${configResolver} is not supported.
234
- Did you properly configure \`SHORE__CONFIG_RESOLVER\`?`);
235
- }
172
+ return new LocalFilesResolver();
236
173
  };
237
174
 
238
175
  // src/sdk/service/logger.ts
@@ -282,30 +219,6 @@ var writeStateFile = (resolver, state) => {
282
219
  logger.info(`\u{1F4DD} Writing the state file.`);
283
220
  return resolver.writeState(state);
284
221
  };
285
- var writeCatalogFile = (resolver, catalog) => {
286
- logger.info(`\u{1F4DD} Writing the catalog`);
287
- return resolver.writeCatalog(catalog);
288
- };
289
- var loadShoreConfig = (resolver) => {
290
- const command = process.env.SHORE__COMMAND;
291
- if (!command) {
292
- throw new Error(`Env variable \`SHORE__COMMAND\` is not set.
293
- Did you forget to configure it?
294
-
295
- Possible values are: \`DISCOVER\`, \`READ\``);
296
- }
297
- if (command === `DISCOVER`) {
298
- logger.info(`\u{1F50E} Mode: discovery
299
- Shore will detect the available streams in the source and their configuration.`);
300
- } else if (command === `READ`) {
301
- logger.info(`\u{1F501} Mode: Synchronizing data
302
- Shore will extract and push data from the source to the destination.`);
303
- } else {
304
- throw new Error(`Command: \`${command}\` is not supported.
305
- Did you properly configured env variable \`SHORE__COMMAND\`?`);
306
- }
307
- return resolver.getConfig(command);
308
- };
309
222
  function readFile(fileName, filePath) {
310
223
  try {
311
224
  return readFileSync(filePath);
@@ -331,13 +244,6 @@ var loadSchema = (streamId) => {
331
244
  const schema = loadJson(`schemas/${streamId}.json`);
332
245
  return schema;
333
246
  };
334
- var checkRequiredConfigKeys = (args, requiredConfigKeys) => {
335
- requiredConfigKeys.forEach((configKey) => {
336
- if (!args[configKey]) {
337
- throw new Error(`Config is missing required key: ${configKey}`);
338
- }
339
- });
340
- };
341
247
 
342
248
  // src/sdk/service/network.ts
343
249
  import axios from "axios";
@@ -615,6 +521,102 @@ var findRelatedRelationships = (streamId, allRels) => {
615
521
  }, { left: [], right: [] });
616
522
  };
617
523
 
524
+ // src/sdk/models/messages.ts
525
+ var RecordMessage = class {
526
+ type = "RECORD";
527
+ stream;
528
+ record;
529
+ constructor(record, stream) {
530
+ this.record = record;
531
+ this.stream = stream;
532
+ }
533
+ toString() {
534
+ const result = {
535
+ type: "RECORD",
536
+ record: this.record,
537
+ stream: this.stream
538
+ };
539
+ return JSON.stringify(result);
540
+ }
541
+ };
542
+ var StateMessage = class {
543
+ type = "STATE";
544
+ value;
545
+ constructor(value) {
546
+ this.value = value;
547
+ }
548
+ toString() {
549
+ const result = {
550
+ type: "STATE",
551
+ value: this.value
552
+ };
553
+ return JSON.stringify(result);
554
+ }
555
+ };
556
+ var ReplicationMethodMessage = class {
557
+ type = "REPLICATION_METHOD";
558
+ stream;
559
+ replication;
560
+ constructor(stream, version) {
561
+ this.stream = stream;
562
+ this.replication = version;
563
+ }
564
+ toString() {
565
+ const result = {
566
+ type: "REPLICATION_METHOD",
567
+ replication: this.replication,
568
+ stream: this.stream
569
+ };
570
+ return JSON.stringify(result);
571
+ }
572
+ };
573
+ var SchemaMessage = class {
574
+ type = "SCHEMA";
575
+ stream;
576
+ schema;
577
+ keyProperties;
578
+ bookmarkProperties;
579
+ displayLabel;
580
+ description;
581
+ propertiesMetadata;
582
+ relationships;
583
+ constructor(opts) {
584
+ const {
585
+ stream,
586
+ schema,
587
+ keyProperties,
588
+ bookmarkProperties,
589
+ displayLabel,
590
+ description,
591
+ relationships,
592
+ propertiesMetadata
593
+ } = opts;
594
+ this.stream = stream;
595
+ this.schema = schema;
596
+ this.keyProperties = keyProperties;
597
+ this.bookmarkProperties = bookmarkProperties;
598
+ this.displayLabel = displayLabel;
599
+ this.description = description;
600
+ this.relationships = relationships;
601
+ this.propertiesMetadata = propertiesMetadata;
602
+ }
603
+ toString() {
604
+ const result = {
605
+ type: "SCHEMA",
606
+ stream: this.stream,
607
+ schema: this.schema,
608
+ key_properties: this.keyProperties,
609
+ label: this.displayLabel,
610
+ description: this.description,
611
+ relationships: this.relationships
612
+ };
613
+ if (this.bookmarkProperties) {
614
+ result["bookmark_properties"] = this.bookmarkProperties;
615
+ }
616
+ return JSON.stringify(result);
617
+ }
618
+ };
619
+
618
620
  // src/sdk/models/metadata.ts
619
621
  var MetadataMap = class extends Map {
620
622
  areEquals(array1, array2) {
@@ -735,155 +737,6 @@ var StreamMetadata = class {
735
737
  }
736
738
  };
737
739
 
738
- // src/sdk/models/catalog.ts
739
- import * as _ from "lodash";
740
- var Catalog = class _Catalog {
741
- streams;
742
- static fromFile(fileName) {
743
- const catalogFile = loadJson(fileName);
744
- const catalog = new _Catalog();
745
- catalog.streams = catalogFile.streams;
746
- return catalog;
747
- }
748
- static fromCatalogFile(catalogFile) {
749
- const catalog = new _Catalog();
750
- catalog.streams = catalogFile.streams;
751
- return catalog;
752
- }
753
- getSelectedStreams() {
754
- if (!this.streams) {
755
- return [];
756
- }
757
- const directSelectedStreams = this.streams.filter((stream) => {
758
- const metadata = new StreamMetadata();
759
- metadata.fromCatalogStreamMetadata(stream.metadata);
760
- return metadata.isStreamSelected();
761
- });
762
- logger.debug(`Directly selected streams: ${directSelectedStreams.map((stream) => stream.stream).join(",")}`);
763
- const getIndirectSelectedStreams = (stream, ancestorsIds) => {
764
- const currAncestorsIds = ancestorsIds || [];
765
- const rootBreadcrumbMetadata = stream.metadata.find((metadata) => metadata.breadcrumb.length === 0);
766
- const parentStreamId = rootBreadcrumbMetadata?.metadata?.["whaly-parent-stream"];
767
- if (parentStreamId) {
768
- const parentStream = this.streams?.find((stream2) => stream2.stream === parentStreamId);
769
- if (parentStream) {
770
- currAncestorsIds.push(parentStreamId);
771
- return getIndirectSelectedStreams(parentStream, currAncestorsIds);
772
- }
773
- }
774
- return currAncestorsIds;
775
- };
776
- const indirectSelectedStreamIds = _.uniq(
777
- directSelectedStreams.flatMap((stream) => {
778
- return getIndirectSelectedStreams(stream);
779
- })
780
- );
781
- logger.debug(`Indirectly selected streams: ${indirectSelectedStreamIds.join(",")}`);
782
- const indirectSelectedStreams = indirectSelectedStreamIds.map((streamId) => {
783
- if (!directSelectedStreams.find((stream) => stream.stream === streamId)) {
784
- return this.streams?.find((stream) => stream.stream === streamId);
785
- }
786
- }).filter((stream) => !!stream);
787
- return directSelectedStreams.concat(indirectSelectedStreams);
788
- }
789
- };
790
-
791
- // src/sdk/models/messages.ts
792
- var RecordMessage = class {
793
- type = "RECORD";
794
- stream;
795
- record;
796
- constructor(record, stream) {
797
- this.record = record;
798
- this.stream = stream;
799
- }
800
- toString() {
801
- const result = {
802
- type: "RECORD",
803
- record: this.record,
804
- stream: this.stream
805
- };
806
- return JSON.stringify(result);
807
- }
808
- };
809
- var StateMessage = class {
810
- type = "STATE";
811
- value;
812
- constructor(value) {
813
- this.value = value;
814
- }
815
- toString() {
816
- const result = {
817
- type: "STATE",
818
- value: this.value
819
- };
820
- return JSON.stringify(result);
821
- }
822
- };
823
- var ReplicationMethodMessage = class {
824
- type = "REPLICATION_METHOD";
825
- stream;
826
- replication;
827
- constructor(stream, version) {
828
- this.stream = stream;
829
- this.replication = version;
830
- }
831
- toString() {
832
- const result = {
833
- type: "REPLICATION_METHOD",
834
- replication: this.replication,
835
- stream: this.stream
836
- };
837
- return JSON.stringify(result);
838
- }
839
- };
840
- var SchemaMessage = class {
841
- type = "SCHEMA";
842
- stream;
843
- schema;
844
- keyProperties;
845
- bookmarkProperties;
846
- displayLabel;
847
- description;
848
- propertiesMetadata;
849
- relationships;
850
- constructor(opts) {
851
- const {
852
- stream,
853
- schema,
854
- keyProperties,
855
- bookmarkProperties,
856
- displayLabel,
857
- description,
858
- relationships,
859
- propertiesMetadata
860
- } = opts;
861
- this.stream = stream;
862
- this.schema = schema;
863
- this.keyProperties = keyProperties;
864
- this.bookmarkProperties = bookmarkProperties;
865
- this.displayLabel = displayLabel;
866
- this.description = description;
867
- this.relationships = relationships;
868
- this.propertiesMetadata = propertiesMetadata;
869
- }
870
- toString() {
871
- const result = {
872
- type: "SCHEMA",
873
- stream: this.stream,
874
- schema: this.schema,
875
- key_properties: this.keyProperties,
876
- label: this.displayLabel,
877
- description: this.description,
878
- relationships: this.relationships
879
- };
880
- if (this.bookmarkProperties) {
881
- result["bookmark_properties"] = this.bookmarkProperties;
882
- }
883
- return JSON.stringify(result);
884
- }
885
- };
886
-
887
740
  // src/sdk/models/state.ts
888
741
  import moment from "moment";
889
742
 
@@ -1064,186 +917,46 @@ var isDatetimeType = (typeDef) => {
1064
917
  };
1065
918
 
1066
919
  // src/sdk/models/tap/stream.ts
1067
- import * as _2 from "lodash";
1068
- import { Promise as BPromise2 } from "bluebird";
920
+ import cloneDeep from "lodash/cloneDeep.js";
921
+ import Bluebird2 from "bluebird";
1069
922
 
1070
923
  // src/sdk/models/tap/tap.ts
1071
- import { Promise as BPromise } from "bluebird";
1072
- import { DepGraph } from "dependency-graph";
924
+ import Bluebird from "bluebird";
1073
925
  var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
1074
926
  var Tap = class {
1075
- _target;
1076
- _config;
1077
- _streams;
1078
- _concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
927
+ target;
928
+ config;
929
+ streams;
930
+ concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
1079
931
  constructor(target, config) {
1080
- this._streams = [];
1081
- this._target = target;
1082
- this._config = config;
1083
- }
1084
- discover = async (existingCatalog) => {
1085
- logger.debug("Loading schemas");
1086
- const streamDepGraph = new DepGraph();
1087
- const buildDependencyGraph = (streams2, parentId) => {
1088
- streams2.forEach((s) => {
1089
- streamDepGraph.addNode(s.streamId, s);
1090
- if (parentId) {
1091
- streamDepGraph.addDependency(s.streamId, parentId);
1092
- }
1093
- if (s.children) {
1094
- return buildDependencyGraph(s.children, s.streamId);
1095
- }
1096
- });
1097
- };
1098
- buildDependencyGraph(this._streams);
1099
- const discoveredStreamCatalogEntries = await BPromise.map(
1100
- streamDepGraph.overallOrder(),
1101
- async (streamId) => {
1102
- logger.info(`\u2728 Loading schema for ${streamId}`);
1103
- const stream = streamDepGraph.getNodeData(streamId);
1104
- const schema = await stream.getSchema();
1105
- const parentStreamIds = streamDepGraph.directDependenciesOf(streamId);
1106
- if (parentStreamIds.length > 1) {
1107
- throw new Error(`StreamId=${streamId} directly depends on more than 1 stream, e.g. ${parentStreamIds.join(",")} which is not supported!`);
1108
- }
1109
- const parentStreamId = parentStreamIds?.[0];
1110
- const { metadata } = await this.generateMetadataFromStream(stream, parentStreamId);
1111
- return {
1112
- schema,
1113
- metadata,
1114
- stream: stream.streamId,
1115
- tap_stream_id: stream.streamId
1116
- };
1117
- },
1118
- { concurrency: this._concurrency }
1119
- );
1120
- const existingCatalogStreamIds = (existingCatalog?.streams || []).map((entry) => {
1121
- return entry.stream;
1122
- });
1123
- const newlyDiscoveredStreamCatalogEntries = discoveredStreamCatalogEntries.filter((entry) => {
1124
- if (existingCatalogStreamIds.includes(entry.stream)) {
1125
- return false;
1126
- } else {
1127
- return true;
1128
- }
1129
- });
1130
- logger.debug(`Newly discovered streams: %j`, newlyDiscoveredStreamCatalogEntries);
1131
- const allDiscoveredStreamIds = discoveredStreamCatalogEntries.map((entry) => {
1132
- return entry.stream;
1133
- });
1134
- const existingCatalogMinusDeletedStreams = (existingCatalog?.streams || []).filter((entry) => {
1135
- if (!allDiscoveredStreamIds.includes(entry.stream)) {
1136
- logger.debug(`Stream %s was deleted, removing it from the catalog`, entry.stream);
1137
- return false;
1138
- } else {
1139
- return true;
1140
- }
1141
- });
1142
- const updatedExistingCatalog = existingCatalogMinusDeletedStreams.map((existingCatalogEntry) => {
1143
- const matchingDiscoveredCatalogEntry = discoveredStreamCatalogEntries.find((entry) => entry.stream === existingCatalogEntry.stream);
1144
- if (matchingDiscoveredCatalogEntry) {
1145
- const existingRootMetadata = existingCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
1146
- const discoveredRootMetadata = matchingDiscoveredCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
1147
- if (existingRootMetadata) {
1148
- const parentStreamVal = discoveredRootMetadata?.metadata["whaly-parent-stream"];
1149
- if (parentStreamVal !== void 0) {
1150
- existingRootMetadata.metadata["whaly-parent-stream"] = parentStreamVal;
1151
- }
1152
- const forcedReplicationMethodVal = discoveredRootMetadata?.metadata["forced-replication-method"];
1153
- if (forcedReplicationMethodVal !== void 0) {
1154
- existingRootMetadata.metadata["forced-replication-method"] = forcedReplicationMethodVal;
1155
- }
1156
- const forcedReplicationKeyVal = discoveredRootMetadata?.metadata["forced-replication-key"];
1157
- if (forcedReplicationKeyVal !== void 0) {
1158
- existingRootMetadata.metadata["forced-replication-key"] = forcedReplicationKeyVal;
1159
- }
1160
- const validReplicationKeysVal = discoveredRootMetadata?.metadata["valid-replication-keys"];
1161
- if (validReplicationKeysVal !== void 0) {
1162
- existingRootMetadata.metadata["valid-replication-keys"] = validReplicationKeysVal;
1163
- }
1164
- const rowCountVal = discoveredRootMetadata?.metadata["row-count"];
1165
- if (rowCountVal !== void 0) {
1166
- existingRootMetadata.metadata["row-count"] = rowCountVal;
1167
- }
1168
- }
1169
- }
1170
- return existingCatalogEntry;
1171
- });
1172
- const streams = updatedExistingCatalog.concat(newlyDiscoveredStreamCatalogEntries);
1173
- return { streams };
1174
- };
1175
- sync = async (catalog) => {
1176
- await this.initiateSync(catalog);
1177
- logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
1178
- await this._target.complete();
1179
- return Promise.resolve();
1180
- };
1181
- async generateMetadataFromStream(stream, parentStreamId) {
1182
- const metadata = new StreamMetadata();
1183
- metadata.write([], "table-key-properties", stream.primaryKey);
1184
- const defaultReplicationConfig = stream.defaultReplicationConfig();
1185
- metadata.write([], "replication-method", defaultReplicationConfig.replicationMethod);
1186
- const validReplicationkeys = await stream.getValidReplicationKeys();
1187
- metadata.write([], "valid-replication-keys", validReplicationkeys);
1188
- if (defaultReplicationConfig.isForced) {
1189
- metadata.write([], "forced-replication-method", defaultReplicationConfig.replicationMethod);
1190
- if (stream.forcedReplicationKey) {
1191
- metadata.write([], "forced-replication-key", stream.forcedReplicationKey);
1192
- }
1193
- }
1194
- const rowCount = await stream.getRowCount();
1195
- if (rowCount !== void 0) {
1196
- metadata.write([], "row-count", rowCount);
1197
- }
1198
- if (stream.isSilent === true) {
1199
- metadata.write([], "whaly-is-hidden", stream.isSilent);
1200
- }
1201
- if (stream.isSilent === false) {
1202
- metadata.write([], "selected-by-default", stream.selectedByDefault);
1203
- }
1204
- if (stream.displayLabel) {
1205
- metadata.write([], "whaly-display-label", stream.displayLabel);
1206
- }
1207
- if (stream.description) {
1208
- metadata.write([], "whaly-description", stream.description);
1209
- }
1210
- if (parentStreamId !== void 0) {
1211
- metadata.write([], "whaly-parent-stream", parentStreamId);
1212
- }
1213
- return { metadata: metadata.toList() };
932
+ this.streams = [];
933
+ this.target = target;
934
+ this.config = config;
1214
935
  }
1215
- async initiateSync(catalog) {
936
+ sync = async (options) => {
1216
937
  logger.info(`\u{1F680} Start syncing`);
1217
938
  const state = StateService.getInstance().get();
1218
939
  logger.info(`\u{1F4CD} Received state: %j`, state);
1219
- logger.debug(`\u{1F4DA} Received catalog: %j`, catalog);
1220
- const selectedStreams = catalog.getSelectedStreams();
1221
- if (selectedStreams.length === 0) {
1222
- logger.info(`\u{1F631} There is no stream to sync.
1223
-
1224
- Did you update the \`catalog.json\` to add \`"selected": true\` in the table metadata associated with the root breadcrumb (e.g. \`[]\`)`);
1225
- return;
1226
- }
1227
- const selectedStreamIds = selectedStreams.map((stream) => stream.stream);
1228
- logger.info(`\u{1F973} Syncing selected streams: %s`, selectedStreamIds.join(","));
1229
- await BPromise.map(
1230
- this._streams,
1231
- (stream) => {
1232
- if (selectedStreamIds.includes(stream.streamId)) {
1233
- const selectedStream = selectedStreams.find(((selectedStream2) => selectedStream2.stream === stream.streamId));
1234
- const rootMetadataEntry = selectedStream?.metadata.find((streamMdt) => streamMdt.breadcrumb.length === 0);
1235
- if (!rootMetadataEntry) {
1236
- throw new Error(`Can't find a catalog root metadata entry for StreamId=${stream.streamId}`);
1237
- }
1238
- stream.setRootMetadataEntry(rootMetadataEntry.metadata);
1239
- return stream.sync();
1240
- }
1241
- },
1242
- {
1243
- concurrency: this._concurrency
1244
- }
1245
- );
1246
- }
940
+ const envInclude = process.env.TAP_STREAMS ? process.env.TAP_STREAMS.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
941
+ const include = options?.include && options.include.length > 0 ? options.include : envInclude;
942
+ const exclude = new Set((options?.exclude || []).map((s) => s.trim()));
943
+ let streamsToRun = this.streams.filter((s) => !s.isSilent);
944
+ if (include && include.length > 0) {
945
+ const includeSet = new Set(include);
946
+ streamsToRun = streamsToRun.filter((s) => includeSet.has(s.streamId));
947
+ }
948
+ if (exclude.size > 0) {
949
+ streamsToRun = streamsToRun.filter((s) => !exclude.has(s.streamId));
950
+ }
951
+ const selectedStreamIds = streamsToRun.map((s) => s.streamId);
952
+ logger.info(`\u{1F973} Syncing streams: %s`, selectedStreamIds.join(","));
953
+ await Bluebird.map(streamsToRun, (stream) => {
954
+ return stream.sync();
955
+ }, { concurrency: this.concurrency });
956
+ logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
957
+ await this.target.complete();
958
+ return Promise.resolve();
959
+ };
1247
960
  // Used to clean up any ressource before exit
1248
961
  end() {
1249
962
  return Promise.resolve();
@@ -1252,15 +965,11 @@ var Tap = class {
1252
965
 
1253
966
  // src/sdk/models/tap/stream.ts
1254
967
  import { format as format2 } from "util";
1255
- var StreamV2 = class {
968
+ var Stream = class {
1256
969
  // Runtime values, can't be overriden
1257
- _config;
1258
- _tapName = "default";
1259
- _tapState;
1260
- _target;
1261
- // Contains the root metadata entry of the catalog stream configuration
1262
- // contains some end user schema configuration
1263
- rootMetadataEntry;
970
+ config;
971
+ tapState;
972
+ target;
1264
973
  // Compile time values, can be overriden
1265
974
  // Replication method that can be forced in the stream implemenation. Will prevail over any user/catalog choice
1266
975
  forcedReplicationMethod = void 0;
@@ -1286,11 +995,10 @@ var StreamV2 = class {
1286
995
  // Metrics configuration
1287
996
  rowsSyncedMetricsConf;
1288
997
  executionTimeMetricsConf;
1289
- constructor(tapName, config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
1290
- this._tapName = tapName;
1291
- this._config = config;
1292
- this._tapState = _2.cloneDeep(state);
1293
- this._target = target;
998
+ constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
999
+ this.config = config;
1000
+ this.tapState = cloneDeep(state);
1001
+ this.target = target;
1294
1002
  this.childConcurrency = childConcurrency;
1295
1003
  this.rowsSyncedMetricsConf = {
1296
1004
  isEnabled: () => !this.isSilent,
@@ -1301,9 +1009,7 @@ var StreamV2 = class {
1301
1009
  getStreamIds: () => [this.streamId]
1302
1010
  };
1303
1011
  }
1304
- setRootMetadataEntry = (rootMetadataEntry) => {
1305
- this.rootMetadataEntry = rootMetadataEntry;
1306
- };
1012
+ // No root metadata: replication config is derived from stream defaults only
1307
1013
  /**
1308
1014
  * Sync this stream
1309
1015
  * Called only for streams with no parents, aka. "root tap streams"
@@ -1334,7 +1040,7 @@ var StreamV2 = class {
1334
1040
  `\u{1F6BD} Flushing ${replicationMethod} sync of stream '${this.streamId}' + all of its children...`
1335
1041
  );
1336
1042
  await this._flush();
1337
- await BPromise2.map(this.children, (child) => {
1043
+ await Bluebird2.map(this.children, (child) => {
1338
1044
  return child.flush();
1339
1045
  }, { concurrency: 3 });
1340
1046
  };
@@ -1344,7 +1050,7 @@ var StreamV2 = class {
1344
1050
  */
1345
1051
  _writeStateMessage = () => {
1346
1052
  const message = new StateMessage(StateService.getInstance().get());
1347
- return this._target.state(message);
1053
+ return this.target.state(message);
1348
1054
  };
1349
1055
  /**
1350
1056
  * Write out a ACTIVATE_VERSION message.
@@ -1352,10 +1058,10 @@ var StreamV2 = class {
1352
1058
  _writeReplicationMethodMessage = async () => {
1353
1059
  const method = this.configuredReplicationConfig().replicationMethod;
1354
1060
  const message = new ReplicationMethodMessage(this.streamId, method);
1355
- await BPromise2.map(this.children, async (c) => {
1061
+ await Bluebird2.map(this.children, async (c) => {
1356
1062
  return c._writeReplicationMethodMessage();
1357
1063
  }, { concurrency: 3 });
1358
- await this._target.replicationMethod(message);
1064
+ await this.target.replicationMethod(message);
1359
1065
  };
1360
1066
  /**
1361
1067
  * Write out a SCHEMA message with the stream schema.
@@ -1376,11 +1082,11 @@ var StreamV2 = class {
1376
1082
  ...this.description ? { description: this.description } : {},
1377
1083
  ...schema.propertiesMetadata ? { propertiesMetadata: schema.propertiesMetadata } : {}
1378
1084
  });
1379
- await this._target.schema(message);
1085
+ await this.target.schema(message);
1380
1086
  }
1381
1087
  }
1382
1088
  if (skipChildrenSchema !== true) {
1383
- await BPromise2.map(this.children, async (child) => {
1089
+ await Bluebird2.map(this.children, async (child) => {
1384
1090
  await child._writeSchemaMessage();
1385
1091
  }, { concurrency: 3 });
1386
1092
  }
@@ -1455,8 +1161,8 @@ var StreamV2 = class {
1455
1161
  **/
1456
1162
  getStartingTimestamp() {
1457
1163
  const streamIdToReadFrom = this.useStateFromStreamId ? this.useStateFromStreamId : this.streamId;
1458
- const state = extractStateForStream(this._tapState, streamIdToReadFrom);
1459
- const startDate = this._config.start_date;
1164
+ const state = extractStateForStream(this.tapState, streamIdToReadFrom);
1165
+ const startDate = this.config.start_date;
1460
1166
  const replicationConfig = this.configuredReplicationConfig();
1461
1167
  logger.debug(`${this.streamId}: state.replicationKeyValue: ${state.replicationKeyValue},
1462
1168
  state.replicationKey: ${state.replicationKey},
@@ -1515,16 +1221,10 @@ var StreamV2 = class {
1515
1221
  * Return the configured replication method that will be used for the sync.
1516
1222
  */
1517
1223
  configuredReplicationConfig = () => {
1518
- const defaultReplicationMethod = this.defaultReplicationConfig();
1519
- if (defaultReplicationMethod.isForced) {
1520
- return {
1521
- replicationMethod: defaultReplicationMethod.replicationMethod,
1522
- replicationKey: defaultReplicationMethod.replicationKey
1523
- };
1524
- }
1224
+ const defaults = this.defaultReplicationConfig();
1525
1225
  return {
1526
- replicationMethod: this.rootMetadataEntry?.["replication-method"] || defaultReplicationMethod.replicationMethod,
1527
- replicationKey: this.rootMetadataEntry?.["replication-key"] || defaultReplicationMethod.replicationKey
1226
+ replicationMethod: defaults.replicationMethod,
1227
+ replicationKey: defaults.replicationKey
1528
1228
  };
1529
1229
  };
1530
1230
  // Private sync methods:
@@ -1554,9 +1254,9 @@ var StreamV2 = class {
1554
1254
  this.streamId
1555
1255
  );
1556
1256
  if (!this.isSilent) {
1557
- await this._target.record(recordMessage);
1257
+ await this.target.record(recordMessage);
1558
1258
  }
1559
- await BPromise2.map(
1259
+ await Bluebird2.map(
1560
1260
  this.children,
1561
1261
  async (child) => {
1562
1262
  await child.asyncInit(row);
@@ -1600,7 +1300,7 @@ var StreamV2 = class {
1600
1300
  getSchema() {
1601
1301
  logger.debug(`stream: ${this.streamId} - getSchema() is called`);
1602
1302
  if (this.schemaPath) {
1603
- const schema = loadJson(`schemas/${this._tapName}/${this.schemaPath}`);
1303
+ const schema = loadJson(`schemas/${this.schemaPath}`);
1604
1304
  return Promise.resolve({ jsonSchema: schema });
1605
1305
  } else {
1606
1306
  if (this.isSilent) {
@@ -1637,7 +1337,7 @@ var StreamV2 = class {
1637
1337
  };
1638
1338
 
1639
1339
  // src/sdk/helpers/qs-logger.ts
1640
- import * as _3 from "lodash";
1340
+ import isPlainObject from "lodash/isPlainObject.js";
1641
1341
  var MAX_QS_KEYS = 10;
1642
1342
  var MAX_QS_STRING_LENGTH = 200;
1643
1343
  var MAX_QS_ARRAY_ITEMS = 5;
@@ -1658,7 +1358,7 @@ function truncateQueryParams(value, depth = 0) {
1658
1358
  }
1659
1359
  return items;
1660
1360
  }
1661
- if (_3.isPlainObject(value)) {
1361
+ if (isPlainObject(value)) {
1662
1362
  const keys = Object.keys(value);
1663
1363
  const result = {};
1664
1364
  for (const key of keys.slice(0, MAX_QS_KEYS)) {
@@ -1681,11 +1381,12 @@ function getTruncatedParamsForLog(params) {
1681
1381
  }
1682
1382
 
1683
1383
  // src/sdk/models/tap/restStream.ts
1684
- import * as _4 from "lodash";
1685
- var RESTStreamV2 = class extends StreamV2 {
1384
+ import cloneDeep2 from "lodash/cloneDeep.js";
1385
+ import isEqual from "lodash/isEqual.js";
1386
+ var RESTStream = class extends Stream {
1686
1387
  axiosInstance;
1687
- constructor(tapName, config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
1688
- super(tapName, config, state, target, childConcurrency);
1388
+ constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
1389
+ super(config, state, target, childConcurrency);
1689
1390
  this.apiCallsMetricsConf = {
1690
1391
  isEnabled: () => !this.isSilent,
1691
1392
  getStreamIds: () => [this.streamId]
@@ -1708,7 +1409,7 @@ var RESTStreamV2 = class extends StreamV2 {
1708
1409
  const authenticator = this.authenticator;
1709
1410
  const params = {
1710
1411
  ...this.getNextUrlParams(nextPageToken, parent),
1711
- ...await authenticator?.getAuthQS(this._config)
1412
+ ...await authenticator?.getAuthQS(this.config)
1712
1413
  };
1713
1414
  const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
1714
1415
  let headers = this.getCustomHTTPHeaders(parent);
@@ -1716,7 +1417,7 @@ var RESTStreamV2 = class extends StreamV2 {
1716
1417
  headers = {};
1717
1418
  }
1718
1419
  if (authenticator) {
1719
- const authHeaders = await authenticator.getAuthHeaders(this._config);
1420
+ const authHeaders = await authenticator.getAuthHeaders(this.config);
1720
1421
  headers = {
1721
1422
  ...headers,
1722
1423
  ...authHeaders
@@ -1799,9 +1500,9 @@ var RESTStreamV2 = class extends StreamV2 {
1799
1500
  for await (const row of rows) {
1800
1501
  yield row;
1801
1502
  }
1802
- const previousToken = _4.cloneDeep(nextPageToken);
1503
+ const previousToken = cloneDeep2(nextPageToken);
1803
1504
  nextPageToken = this.getNextPageToken(resp, previousToken);
1804
- if (nextPageToken && _4.isEqual(nextPageToken, previousToken)) {
1505
+ if (nextPageToken && isEqual(nextPageToken, previousToken)) {
1805
1506
  throw new Error(
1806
1507
  `${this.streamId} - Loop detected in pagination. "
1807
1508
  Pagination token: \`${JSON.stringify(nextPageToken)}\` is identical to prior token \`${JSON.stringify(previousToken)}\`.`
@@ -2002,20 +1703,20 @@ var StreamWarehouseSyncService = class {
2002
1703
  const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
2003
1704
  const columnsMappingStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
2004
1705
  const columnsToAdd = Object.keys(columnsMappingStore).filter((unsafeColumnKey) => {
2005
- const safeColumnName = columnsMappingStore[unsafeColumnKey];
2006
- if (!safeColumnName) {
1706
+ const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
1707
+ if (!safeColumnName2) {
2007
1708
  return false;
2008
1709
  }
2009
- return !columnNamesFromWarehouse.includes(safeColumnName);
1710
+ return !columnNamesFromWarehouse.includes(safeColumnName2);
2010
1711
  }).map((unsafeColumnKey) => {
2011
- const safeColumnName = columnsMappingStore[unsafeColumnKey];
1712
+ const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
2012
1713
  const definition = this.flattenedSchema[unsafeColumnKey];
2013
- if (!safeColumnName || !definition) {
1714
+ if (!safeColumnName2 || !definition) {
2014
1715
  return void 0;
2015
1716
  }
2016
1717
  return {
2017
1718
  unsafeName: unsafeColumnKey,
2018
- name: safeColumnName,
1719
+ name: safeColumnName2,
2019
1720
  definition
2020
1721
  };
2021
1722
  }).filter((item) => !!item);
@@ -2237,7 +1938,7 @@ import moment3 from "moment";
2237
1938
 
2238
1939
  // src/sdk/models/target/temporaryFile.ts
2239
1940
  import fs3 from "fs-extra";
2240
- import { uniqueId } from "lodash";
1941
+ import uniqueId from "lodash/uniqueId.js";
2241
1942
  var createTemporaryFileStream = (streamId) => {
2242
1943
  fs3.ensureDirSync("./tmp");
2243
1944
  const path = `./tmp/${safePath(streamId)}-${uniqueId()}.tmp`;
@@ -2365,7 +2066,7 @@ var StreamState = class {
2365
2066
 
2366
2067
  // src/sdk/models/target/target.ts
2367
2068
  import { Semaphore } from "async-mutex";
2368
- import { Promise as BPromise3 } from "bluebird";
2069
+ import Bluebird3 from "bluebird";
2369
2070
  var semaphore = new Semaphore(1);
2370
2071
  var ITarget = class _ITarget {
2371
2072
  config;
@@ -2610,7 +2311,7 @@ var ITarget = class _ITarget {
2610
2311
  })
2611
2312
  }
2612
2313
  };
2613
- await BPromise3.map(this.schemaHooks, async (hook) => {
2314
+ await Bluebird3.map(this.schemaHooks, async (hook) => {
2614
2315
  const input = {
2615
2316
  databaseName,
2616
2317
  schemaName,
@@ -2691,7 +2392,7 @@ var ITarget = class _ITarget {
2691
2392
  };
2692
2393
  uploadStreamsToWarehouse = async (streamsToUpload) => {
2693
2394
  try {
2694
- await BPromise3.map(streamsToUpload, async (streamId) => {
2395
+ await Bluebird3.map(streamsToUpload, async (streamId) => {
2695
2396
  logger.info(`\u{1F4E4} Upload stream: ${streamId} to Warehouse`);
2696
2397
  const streamState = this.streams[streamId];
2697
2398
  if (streamState) {
@@ -2725,19 +2426,643 @@ var ITarget = class _ITarget {
2725
2426
  }
2726
2427
  };
2727
2428
  };
2429
+
2430
+ // src/targets/bigquery/helpers.ts
2431
+ var safeColumnName = (key) => {
2432
+ let returnString = key;
2433
+ const shouldNotStartWith = ["_TABLE_", "_FILE_", "_PARTITION"];
2434
+ shouldNotStartWith.forEach((snm) => {
2435
+ if (returnString.startsWith(snm)) {
2436
+ returnString = returnString.replace(snm, "");
2437
+ }
2438
+ });
2439
+ returnString = returnString.replace(/^[-\d\s]*/g, "");
2440
+ returnString = returnString.toLowerCase();
2441
+ returnString = returnString.replace("`", "");
2442
+ returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2443
+ if (returnString.length > 300) {
2444
+ returnString = returnString.substr(0, 300);
2445
+ }
2446
+ const pattern = /[^a-zA-Z0-9_]/gm;
2447
+ return returnString.replace(pattern, "_");
2448
+ };
2449
+ var safeTableName = (key) => {
2450
+ let returnString = key;
2451
+ returnString = returnString.toLowerCase();
2452
+ returnString = returnString.replace("`", "");
2453
+ returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2454
+ if (returnString.length > 1024) {
2455
+ returnString = returnString.substr(0, 1024);
2456
+ }
2457
+ const pattern = /[^a-zA-Z0-9_]/gm;
2458
+ return returnString.replace(pattern, "_");
2459
+ };
2460
+
2461
+ // src/targets/bigquery/service/record.ts
2462
+ import Decimal from "decimal.js";
2463
+ import moment5 from "moment";
2464
+ var validateDateRange = (record, schema) => {
2465
+ Object.keys(schema).forEach((key) => {
2466
+ const { format: format3 } = schema[key];
2467
+ if (format3 === "date-time") {
2468
+ if (record[key]) {
2469
+ const formattedDate = moment5(record[key]).format(defaultDateTimeFormat);
2470
+ const isNotTooMuchInTheFuture = moment5(record[key]).isBefore(moment5("9999-12-31 23:59:59.999999"));
2471
+ const isNotTooMuchInThePast = moment5(record[key]).isAfter(moment5("0001-01-01 00:00:00"));
2472
+ if (formattedDate !== "Invalid date" && isNotTooMuchInTheFuture && isNotTooMuchInThePast) {
2473
+ record[key] = formattedDate;
2474
+ } else {
2475
+ record[key] = void 0;
2476
+ logger.info("Skipping date for key %s for being invalid", key);
2477
+ }
2478
+ }
2479
+ }
2480
+ });
2481
+ return record;
2482
+ };
2483
+ var maxBQNumericValue = new Decimal("9.9999999999999999999999999999999999999E+28");
2484
+ var convertNumberIntoDecimal = (record, schema) => {
2485
+ Object.keys(record).forEach((key) => {
2486
+ if (schema[key]) {
2487
+ const { type, items } = schema[key];
2488
+ const types = type instanceof Array ? type : [type];
2489
+ if (types.includes("number")) {
2490
+ if (typeof record[key] === "number" || record[key] instanceof Decimal) {
2491
+ const decimal = new Decimal(record[key]).toDecimalPlaces(9);
2492
+ if (decimal.greaterThanOrEqualTo(maxBQNumericValue)) {
2493
+ record[key] = null;
2494
+ } else {
2495
+ record[key] = decimal;
2496
+ }
2497
+ } else {
2498
+ record[key] = null;
2499
+ }
2500
+ } else if (types.includes("array") && items?.type.includes("number")) {
2501
+ if (Array.isArray(record[key])) {
2502
+ const rawArr = record[key];
2503
+ const newArr = rawArr.map((item) => {
2504
+ if (typeof item === "number" || item instanceof Decimal) {
2505
+ const parsed = new Decimal(item).toDecimalPlaces(9);
2506
+ if (parsed.greaterThanOrEqualTo(maxBQNumericValue)) {
2507
+ return void 0;
2508
+ } else {
2509
+ return parsed;
2510
+ }
2511
+ } else {
2512
+ return void 0;
2513
+ }
2514
+ }).filter((item) => !!item);
2515
+ record[key] = newArr;
2516
+ } else {
2517
+ record[key] = null;
2518
+ }
2519
+ }
2520
+ }
2521
+ });
2522
+ return record;
2523
+ };
2524
+
2525
+ // src/targets/bigquery/service/dbSync.ts
2526
+ import moment6 from "moment";
2527
+
2528
+ // src/targets/bigquery/service/bigquery.ts
2529
+ import { BigQuery } from "@google-cloud/bigquery";
2530
+ var BqClientHolder = class {
2531
+ static instance;
2532
+ client;
2533
+ constructor(config) {
2534
+ const retryOptions = {
2535
+ autoRetry: true,
2536
+ maxRetries: 3
2537
+ };
2538
+ this.client = new BigQuery({
2539
+ projectId: config.database.trim(),
2540
+ ...retryOptions
2541
+ });
2542
+ }
2543
+ static client(config) {
2544
+ if (!this.instance) {
2545
+ this.instance = new this(config);
2546
+ }
2547
+ return this.instance.client;
2548
+ }
2549
+ };
2550
+
2551
+ // src/targets/bigquery/service/cloudStorage.ts
2552
+ import { Storage } from "@google-cloud/storage";
2553
+
2554
+ // node_modules/uuid/dist/esm-node/rng.js
2555
+ import crypto from "crypto";
2556
+ var rnds8Pool = new Uint8Array(256);
2557
+ var poolPtr = rnds8Pool.length;
2558
+ function rng() {
2559
+ if (poolPtr > rnds8Pool.length - 16) {
2560
+ crypto.randomFillSync(rnds8Pool);
2561
+ poolPtr = 0;
2562
+ }
2563
+ return rnds8Pool.slice(poolPtr, poolPtr += 16);
2564
+ }
2565
+
2566
+ // node_modules/uuid/dist/esm-node/regex.js
2567
+ 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;
2568
+
2569
+ // node_modules/uuid/dist/esm-node/validate.js
2570
+ function validate(uuid) {
2571
+ return typeof uuid === "string" && regex_default.test(uuid);
2572
+ }
2573
+ var validate_default = validate;
2574
+
2575
+ // node_modules/uuid/dist/esm-node/stringify.js
2576
+ var byteToHex = [];
2577
+ for (let i = 0; i < 256; ++i) {
2578
+ byteToHex.push((i + 256).toString(16).substr(1));
2579
+ }
2580
+ function stringify3(arr, offset = 0) {
2581
+ 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();
2582
+ if (!validate_default(uuid)) {
2583
+ throw TypeError("Stringified UUID is invalid");
2584
+ }
2585
+ return uuid;
2586
+ }
2587
+ var stringify_default = stringify3;
2588
+
2589
+ // node_modules/uuid/dist/esm-node/v4.js
2590
+ function v4(options, buf, offset) {
2591
+ options = options || {};
2592
+ const rnds = options.random || (options.rng || rng)();
2593
+ rnds[6] = rnds[6] & 15 | 64;
2594
+ rnds[8] = rnds[8] & 63 | 128;
2595
+ if (buf) {
2596
+ offset = offset || 0;
2597
+ for (let i = 0; i < 16; ++i) {
2598
+ buf[offset + i] = rnds[i];
2599
+ }
2600
+ return buf;
2601
+ }
2602
+ return stringify_default(rnds);
2603
+ }
2604
+ var v4_default = v4;
2605
+
2606
+ // src/targets/bigquery/service/cloudStorage.ts
2607
+ var uploadFileInBucket = async (streamId, gcsBuckerName, filePath) => {
2608
+ try {
2609
+ const retryOptions = { autoRetry: true, maxRetries: 20 };
2610
+ let storage = new Storage({ retryOptions });
2611
+ const bucketRef = storage.bucket(gcsBuckerName);
2612
+ await bucketRef.get({ autoCreate: false });
2613
+ const destinationFileName = `${streamId}-${v4_default()}.jsonnl`;
2614
+ await bucketRef.upload(filePath, {
2615
+ destination: destinationFileName
2616
+ });
2617
+ logger.info(`\u{1F5F3} Uploaded ${filePath} into ${gcsBuckerName}/${destinationFileName} GCS File`);
2618
+ return bucketRef.file(destinationFileName);
2619
+ } catch (err) {
2620
+ logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
2621
+
2622
+ Error: ${err.message}
2623
+ Stack: ${err.stack}
2624
+ Code: ${err.code}
2625
+ `);
2626
+ if (err.code < 500) {
2627
+ await haltAndCatchFire(
2628
+ `unauthorized`,
2629
+ `We couldn't connect to your Cloud Storage bucket \u{1F614}
2630
+
2631
+ The error from Google is: '${err.message}' \u{1F440}
2632
+
2633
+ Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
2634
+ `Got error from cloud storage lib`
2635
+ );
2636
+ }
2637
+ throw err;
2638
+ }
2639
+ };
2640
+
2641
+ // src/targets/bigquery/service/dbSync.ts
2642
+ import chunk from "lodash/chunk.js";
2643
+ import { Semaphore as Semaphore2 } from "async-mutex";
2644
+ var semaphore2 = new Semaphore2(1);
2645
+ var BigQueryDBSync = class extends StreamWarehouseSyncService {
2646
+ config;
2647
+ bqClient;
2648
+ bqDataset;
2649
+ bqTable;
2650
+ bqStagingTable;
2651
+ tableName;
2652
+ stagingTableName;
2653
+ sqlTableId;
2654
+ sqlStagingTableId;
2655
+ constructor(config, streamId, database, schema, table, renameColumnStore) {
2656
+ super(
2657
+ streamId,
2658
+ database,
2659
+ schema,
2660
+ table,
2661
+ renameColumnStore
2662
+ );
2663
+ this.config = config;
2664
+ this.bqClient = BqClientHolder.client(config);
2665
+ this.bqDataset = this.bqClient.dataset(schema, { location: config.location || "EU" });
2666
+ this.bqTable = this.bqDataset.table(table, { location: config.location || "EU" });
2667
+ this.tableName = table;
2668
+ this.stagingTableName = `${table}_temp`;
2669
+ this.bqStagingTable = this.bqDataset.table(this.stagingTableName, { location: config.location || "EU" });
2670
+ this.sqlTableId = `${schema}.${table}`;
2671
+ this.sqlStagingTableId = `${this.sqlTableId}_temp`;
2672
+ }
2673
+ // Implementation of abstract classes
2674
+ getSerializedRecord = (record) => {
2675
+ return JSON.stringify(record) + `
2676
+ `;
2677
+ };
2678
+ safeColumnName = safeColumnName;
2679
+ getWarehouseTypeFromJSONSchema = (definition) => {
2680
+ const type = definition.type;
2681
+ const format3 = definition.format;
2682
+ let typeArr;
2683
+ if (type instanceof Array) {
2684
+ typeArr = type;
2685
+ } else {
2686
+ typeArr = [type];
2687
+ }
2688
+ if (format3) {
2689
+ switch (format3) {
2690
+ case "date-time":
2691
+ return "TIMESTAMP";
2692
+ case "json":
2693
+ return "STRING";
2694
+ default:
2695
+ throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format3}`);
2696
+ }
2697
+ } else if (typeArr.includes("number")) {
2698
+ return "NUMERIC";
2699
+ } else if (typeArr.includes("integer") && type.includes("string")) {
2700
+ return "NUMERIC";
2701
+ } else if (typeArr.includes("integer")) {
2702
+ return "INTEGER";
2703
+ } else if (typeArr.includes("boolean")) {
2704
+ return "BOOLEAN";
2705
+ } else if (typeArr.includes("string")) {
2706
+ return "STRING";
2707
+ } else if (typeArr.includes("array")) {
2708
+ const items = definition.items;
2709
+ if (items) {
2710
+ return this.getWarehouseTypeFromJSONSchema(items);
2711
+ }
2712
+ throw new Error(`StreamId: ${this.streamId} - Unsupported array schema field definition: ${JSON.stringify(definition)}`);
2713
+ } else {
2714
+ throw new Error(`StreamId: ${this.streamId} - Unsupported schema field definition: ${JSON.stringify(definition)}`);
2715
+ }
2716
+ };
2717
+ getMergeQueries = () => {
2718
+ const primaryKeyCondition = this.primaryKeys.map((pk) => {
2719
+ return `\`sourced\`.\`${pk}\` = \`destination\`.\`${pk}\``;
2720
+ }).join(` AND `);
2721
+ const primaryKeys = this.primaryKeys.map((pk) => `\`${pk}\``).join(` ,`);
2722
+ const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
2723
+ const columns = Object.keys(columnUnsafeToSafeMapping).map((column) => {
2724
+ return {
2725
+ isUnsafe: true,
2726
+ colName: column
2727
+ };
2728
+ });
2729
+ const columnsChunks = chunk(columns, 300).map((chunk2) => {
2730
+ this.primaryKeys.forEach((pk) => {
2731
+ if (!chunk2.map((c) => columnUnsafeToSafeMapping[c.colName]).includes(pk)) {
2732
+ chunk2.push({
2733
+ isUnsafe: false,
2734
+ colName: pk
2735
+ });
2736
+ }
2737
+ });
2738
+ return chunk2;
2739
+ });
2740
+ return columnsChunks.map((chunk2) => {
2741
+ const setValues = chunk2.map((column) => {
2742
+ if (column.isUnsafe) {
2743
+ return `\`destination\`.\`${columnUnsafeToSafeMapping[column.colName]}\` = \`sourced\`.\`${columnUnsafeToSafeMapping[column.colName]}\``;
2744
+ } else {
2745
+ return `\`destination\`.\`${column.colName}\` = \`sourced\`.\`${column.colName}\``;
2746
+ }
2747
+ }).join(`, `);
2748
+ const renamedCols = chunk2.map((column) => {
2749
+ if (column.isUnsafe) {
2750
+ return `\`${columnUnsafeToSafeMapping[column.colName]}\``;
2751
+ } else {
2752
+ return `\`${column.colName}\``;
2753
+ }
2754
+ }).join(`, `);
2755
+ const query = `
2756
+ MERGE ${this.sqlTableId} destination
2757
+ USING (
2758
+ WITH all_staging_rows AS (
2759
+ SELECT *
2760
+ FROM ${this.sqlStagingTableId}
2761
+ ),
2762
+ numbered_rows AS (
2763
+ SELECT *,
2764
+ ROW_NUMBER() OVER (PARTITION BY ${primaryKeys}) as _wly_row_nb,
2765
+ COUNT(1) OVER (PARTITION BY ${primaryKeys}) AS _wly_partition_size,
2766
+ FROM all_staging_rows
2767
+ ),
2768
+ staging_deduped AS (
2769
+ SELECT * EXCEPT(_wly_partition_size, _wly_row_nb)
2770
+ FROM numbered_rows
2771
+ WHERE _wly_partition_size = _wly_row_nb
2772
+ )
2773
+ SELECT *
2774
+ FROM staging_deduped
2775
+ ) AS sourced
2776
+ ON ${primaryKeyCondition}
2777
+ WHEN MATCHED THEN
2778
+ UPDATE SET ${setValues}
2779
+ WHEN NOT MATCHED THEN
2780
+ INSERT (${renamedCols}) VALUES (${renamedCols});
2781
+ `;
2782
+ return query;
2783
+ });
2784
+ };
2785
+ getReplaceQueries = () => {
2786
+ return [
2787
+ `TRUNCATE TABLE ${this.sqlTableId};`,
2788
+ ...this.getMergeQueries()
2789
+ ];
2790
+ };
2791
+ createDatabaseAndSchemaIfNotExists = async (retryCount) => {
2792
+ try {
2793
+ await semaphore2.runExclusive(async () => {
2794
+ const [exists] = await this.bqDataset.exists();
2795
+ if (!exists) {
2796
+ logger.info(`\u{1F423} Creating dataset: ${this.bqDataset.id} on BigQuery`);
2797
+ await this.bqDataset.create();
2798
+ } else {
2799
+ const [metadata] = await this.bqDataset.getMetadata();
2800
+ const alreadyExistinglocation = metadata.location;
2801
+ if (this.config.location && this.config.location !== alreadyExistinglocation) {
2802
+ await haltAndCatchFire(
2803
+ `unknown`,
2804
+ `A BigQuery dataset called '${this.bqDataset.id}' already exists in location: '${alreadyExistinglocation}' \u{1F605}
2805
+
2806
+ This connector is configured to load data into the location '${this.config.location}' which is not possible as 2 datasets sharing the same name can't exist in different location on Google Cloud \u{1F63F}
2807
+
2808
+ To fix this issue, could you either: a. Delete the already existing dataset? b. Change the configured destination schema name to avoid the name conflict? \u{1F64F}
2809
+
2810
+ Please relaunch the connector once it's done to fix the issue \u{1F917}
2811
+ `,
2812
+ `
2813
+ We have a conflict between an already existing dataset in location ${alreadyExistinglocation} and the configured location: ${this.config.location}
2814
+ `
2815
+ );
2816
+ throw Error();
2817
+ }
2818
+ logger.info(`\u{1F44B} Dataset: ${this.bqDataset.id} already exists on BigQuery, reusing it.`);
2819
+ }
2820
+ });
2821
+ } catch (err) {
2822
+ if (err.code === 400) {
2823
+ await haltAndCatchFire(
2824
+ `unauthorized`,
2825
+ `We can't connect to your BigQuery account \u{1F614}
2826
+
2827
+ Here is the message from BigQuery: ${err.message}
2828
+
2829
+ Can you check that your Warehouse credentials and Source target schema are properly configured and try to sync again the source? \u{1F64F}
2830
+ `,
2831
+ `Got error when trying to get Dataset from BigQuery: ${err.message} - ${err.code}`
2832
+ );
2833
+ return;
2834
+ }
2835
+ if (retryCount && retryCount > this.maxRetryCount) {
2836
+ logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. ${err.message} - ${err.code}`);
2837
+ gracefulExit(1);
2838
+ } else {
2839
+ logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
2840
+ await new Promise((resolve, reject) => setTimeout(resolve, 1e3 * retryCount));
2841
+ await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
2842
+ }
2843
+ }
2844
+ return Promise.resolve();
2845
+ };
2846
+ createTable = async () => {
2847
+ try {
2848
+ const tableName = this.tableName;
2849
+ const schema = this.generateBigquerySchema();
2850
+ logger.info(`\u{1F423} Creating table: ${tableName} on BigQuery`);
2851
+ const createTableOptions = {
2852
+ schema
2853
+ };
2854
+ await this.bqDataset.createTable(
2855
+ tableName,
2856
+ createTableOptions
2857
+ );
2858
+ return;
2859
+ } catch (err) {
2860
+ throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
2861
+
2862
+ Error: ${err.message}
2863
+ Stack: ${err.stack}`);
2864
+ }
2865
+ };
2866
+ addColumns = async (tableFields) => {
2867
+ if (tableFields.length > 0) {
2868
+ logger.info(`\u{1F195} Stream: ${this.streamId} - Adding following columns in BigQuery schema: ${JSON.stringify(tableFields)}`);
2869
+ const [metadata] = await this.bqTable.getMetadata();
2870
+ const bgTableFields = tableFields.map((tblField) => {
2871
+ return this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(tblField.name, tblField.definition);
2872
+ }).filter((fld) => {
2873
+ return !!fld;
2874
+ });
2875
+ const schema = metadata.schema;
2876
+ const new_schema = {
2877
+ ...schema,
2878
+ fields: schema.fields.concat(bgTableFields)
2879
+ };
2880
+ metadata.schema = new_schema;
2881
+ await this.bqTable.setMetadata(metadata);
2882
+ return;
2883
+ }
2884
+ };
2885
+ createStagingArea = async () => {
2886
+ try {
2887
+ const tableName = this.stagingTableName;
2888
+ const [exists] = await this.bqDataset.table(tableName).exists();
2889
+ if (exists) {
2890
+ logger.info(`\u{1F474} Temporary table ${tableName} already exists, it should be a relic form the past.
2891
+ Dropping it.`);
2892
+ await this.deleteStagingArea();
2893
+ }
2894
+ const schema = this.generateBigquerySchema();
2895
+ const expirationTime = moment6().add("1", "day").valueOf().toString();
2896
+ const createTableOptions = {
2897
+ schema,
2898
+ expirationTime
2899
+ };
2900
+ logger.info(`\u{1F423} Creating staging table: ${tableName} on BigQuery`);
2901
+ await this.bqDataset.createTable(
2902
+ tableName,
2903
+ createTableOptions
2904
+ );
2905
+ return;
2906
+ } catch (err) {
2907
+ throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
2908
+
2909
+ Error: ${err.message}
2910
+ Stack: ${err.stack}`);
2911
+ }
2912
+ };
2913
+ loadStreamInStagingArea = async (localFilePath) => {
2914
+ try {
2915
+ const file = await uploadFileInBucket(
2916
+ this.streamId,
2917
+ this.config.loading_deck_gcs_bucket_name,
2918
+ localFilePath
2919
+ );
2920
+ const metadata = {
2921
+ sourceFormat: "NEWLINE_DELIMITED_JSON",
2922
+ writeDisposition: "WRITE_TRUNCATE"
2923
+ };
2924
+ await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
2925
+ } catch (err) {
2926
+ logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
2927
+ throw err;
2928
+ }
2929
+ };
2930
+ deleteStagingArea = () => {
2931
+ const query = `DROP TABLE IF EXISTS ${this.sqlStagingTableId};`;
2932
+ return this.runQueriesWithRetry([query]);
2933
+ };
2934
+ getTablesInSchema = async () => {
2935
+ const [bqTables] = await this.bqDataset.getTables();
2936
+ const foundTables = bqTables.filter((tbl) => {
2937
+ return !!tbl.id;
2938
+ }).map((bqTable) => {
2939
+ return {
2940
+ tableName: bqTable.id
2941
+ };
2942
+ });
2943
+ return foundTables;
2944
+ };
2945
+ getTableColumnsFromWarehouse = async () => {
2946
+ const [table] = await this.bqTable.getMetadata();
2947
+ return table.schema.fields;
2948
+ };
2949
+ runQueries = async (queries) => {
2950
+ const sqlQuery = `
2951
+ BEGIN
2952
+ ${queries.join(`
2953
+ `)}
2954
+ END;
2955
+ `;
2956
+ await this.bqClient.query(sqlQuery);
2957
+ };
2958
+ // Specific methods
2959
+ /**
2960
+ *
2961
+ * Generate a TableField definition object that can be passed to BigQuery APIs to define a column on a table
2962
+ *
2963
+ * @param colName
2964
+ * @param schemaFieldDefinition
2965
+ * @returns
2966
+ */
2967
+ getBigqueryTableFieldFromInternalSchemaFieldDefinition = (colName, schemaFieldDefinition) => {
2968
+ const name = colName;
2969
+ if (!schemaFieldDefinition) {
2970
+ throw new Error(`StreamId: ${this.streamId} - Schema is unknown for colName=${colName}, so we can't infer the proper BigQuery definition for this column.`);
2971
+ }
2972
+ const type = schemaFieldDefinition.type;
2973
+ let typeArr;
2974
+ if (type instanceof Array) {
2975
+ typeArr = type;
2976
+ } else {
2977
+ typeArr = [type];
2978
+ }
2979
+ if (typeArr.includes("array")) {
2980
+ return {
2981
+ name,
2982
+ mode: "REPEATED",
2983
+ type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
2984
+ };
2985
+ } else {
2986
+ let nullableMode = "NULLABLE";
2987
+ return {
2988
+ name,
2989
+ mode: nullableMode,
2990
+ type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
2991
+ };
2992
+ }
2993
+ };
2994
+ generateBigquerySchema = () => {
2995
+ const unsafeToSafeMappingColumnsFromColumnStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
2996
+ if (!unsafeToSafeMappingColumnsFromColumnStore) {
2997
+ throw new Error(`StreamId: ${this.streamId} - There is no unsafe->safe column mapping`);
2998
+ }
2999
+ return Object.keys(unsafeToSafeMappingColumnsFromColumnStore).reduce((acc, unsafeKey) => {
3000
+ const renamedColName = this.renamedColumnStore.getColumnTranslation(this.streamId, unsafeKey);
3001
+ const bigqueryTableField = this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(renamedColName, this.flattenedSchema[unsafeKey]);
3002
+ if (bigqueryTableField) {
3003
+ acc.push(bigqueryTableField);
3004
+ }
3005
+ return acc;
3006
+ }, []);
3007
+ };
3008
+ loadGCSFileInTable = async (tableName, file, metadata) => {
3009
+ try {
3010
+ const [job] = await this.bqStagingTable.load(file, metadata);
3011
+ logger.info(`\u{1F69A} Loaded GCS file \`${file.id}\` in BigQuery table: ${tableName} with Job: \`${job.id}\``);
3012
+ logger.debug(`Upload Stats: ${JSON.stringify(job.statistics)}`);
3013
+ const errors2 = job.status?.errors;
3014
+ if (errors2 && errors2.length > 0) {
3015
+ throw errors2;
3016
+ }
3017
+ } catch (err) {
3018
+ logger.error(err);
3019
+ throw new Error(`StreamId: ${this.streamId} - Issue when loading file in BigQuery table | fileName: ${file.name}, tableName: ${tableName}
3020
+
3021
+ Error: ${err.message}
3022
+ Stack: ${err.stack}`);
3023
+ }
3024
+ };
3025
+ };
3026
+
3027
+ // src/targets/bigquery/main.ts
3028
+ var BigQueryTarget = class extends ITarget {
3029
+ constructor(config, resolver) {
3030
+ super(config, resolver);
3031
+ this.renameColumnStore.setSafeColumnNameConverter(this.safeColumnNameConverter);
3032
+ }
3033
+ static requiredConfigKeys = [
3034
+ "schema",
3035
+ "project_id",
3036
+ "loading_deck_gcs_bucket_name"
3037
+ ];
3038
+ newDBSyncInstance = (streamId, database, schema, table) => {
3039
+ return new BigQueryDBSync(
3040
+ this.config,
3041
+ streamId,
3042
+ database,
3043
+ schema,
3044
+ table,
3045
+ this.renameColumnStore
3046
+ );
3047
+ };
3048
+ genTableName = safeTableName;
3049
+ safeColumnNameConverter = safeColumnName;
3050
+ validateDateRange = validateDateRange;
3051
+ convertNumberIntoDecimal = convertNumberIntoDecimal;
3052
+ };
2728
3053
  export {
2729
3054
  ALL_STREAM_ID_KEYWORD,
2730
3055
  API_CALLS_METRIC_NAME,
2731
3056
  Authenticator,
2732
3057
  BATCH_INTERVAL_MS,
2733
- Catalog,
3058
+ BigQueryTarget,
2734
3059
  CounterMetric,
2735
3060
  DEFAULT_MAX_CONCURRENT_STREAMS,
2736
3061
  EXECUTION_TIME_METRIC_NAME,
2737
3062
  ITarget,
2738
3063
  MissingFieldInSchemaError,
2739
3064
  MissingSchemaError,
2740
- RESTStreamV2,
3065
+ RESTStream,
2741
3066
  ROWS_SYNCED_METRIC_NAME,
2742
3067
  RecordMessage,
2743
3068
  RenameColumnStore,
@@ -2747,13 +3072,12 @@ export {
2747
3072
  SchemaValidationError,
2748
3073
  StateMessage,
2749
3074
  StateService,
3075
+ Stream,
2750
3076
  StreamMetadata,
2751
- StreamV2,
2752
3077
  StreamWarehouseSyncService,
2753
3078
  Tap,
2754
3079
  _greaterThan,
2755
3080
  addWhalyFields,
2756
- checkRequiredConfigKeys,
2757
3081
  createTemporaryFileStream,
2758
3082
  extractStateForStream,
2759
3083
  finalizeStateProgressMarkers,
@@ -2771,8 +3095,8 @@ export {
2771
3095
  haltAndCatchFire,
2772
3096
  incrementStreamState,
2773
3097
  loadJson,
3098
+ loadResolver,
2774
3099
  loadSchema,
2775
- loadShoreConfig,
2776
3100
  logger,
2777
3101
  postFormDataApiCall,
2778
3102
  postJSONApiCall,
@@ -2780,7 +3104,6 @@ export {
2780
3104
  printMemoryFootprint,
2781
3105
  removeParasiteProperties,
2782
3106
  safePath,
2783
- writeCatalogFile,
2784
3107
  writeStateFile
2785
3108
  };
2786
3109
  //# sourceMappingURL=index.mjs.map