@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.d.mts +227 -186
- package/dist/index.d.ts +227 -186
- package/dist/index.js +814 -493
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +809 -486
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -34,14 +34,14 @@ __export(index_exports, {
|
|
|
34
34
|
API_CALLS_METRIC_NAME: () => API_CALLS_METRIC_NAME,
|
|
35
35
|
Authenticator: () => Authenticator,
|
|
36
36
|
BATCH_INTERVAL_MS: () => BATCH_INTERVAL_MS,
|
|
37
|
-
|
|
37
|
+
BigQueryTarget: () => BigQueryTarget,
|
|
38
38
|
CounterMetric: () => CounterMetric,
|
|
39
39
|
DEFAULT_MAX_CONCURRENT_STREAMS: () => DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
40
40
|
EXECUTION_TIME_METRIC_NAME: () => EXECUTION_TIME_METRIC_NAME,
|
|
41
41
|
ITarget: () => ITarget,
|
|
42
42
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
43
43
|
MissingSchemaError: () => MissingSchemaError,
|
|
44
|
-
|
|
44
|
+
RESTStream: () => RESTStream,
|
|
45
45
|
ROWS_SYNCED_METRIC_NAME: () => ROWS_SYNCED_METRIC_NAME,
|
|
46
46
|
RecordMessage: () => RecordMessage,
|
|
47
47
|
RenameColumnStore: () => RenameColumnStore,
|
|
@@ -51,13 +51,12 @@ __export(index_exports, {
|
|
|
51
51
|
SchemaValidationError: () => SchemaValidationError,
|
|
52
52
|
StateMessage: () => StateMessage,
|
|
53
53
|
StateService: () => StateService,
|
|
54
|
+
Stream: () => Stream,
|
|
54
55
|
StreamMetadata: () => StreamMetadata,
|
|
55
|
-
StreamV2: () => StreamV2,
|
|
56
56
|
StreamWarehouseSyncService: () => StreamWarehouseSyncService,
|
|
57
57
|
Tap: () => Tap,
|
|
58
58
|
_greaterThan: () => _greaterThan,
|
|
59
59
|
addWhalyFields: () => addWhalyFields,
|
|
60
|
-
checkRequiredConfigKeys: () => checkRequiredConfigKeys,
|
|
61
60
|
createTemporaryFileStream: () => createTemporaryFileStream,
|
|
62
61
|
extractStateForStream: () => extractStateForStream,
|
|
63
62
|
finalizeStateProgressMarkers: () => finalizeStateProgressMarkers,
|
|
@@ -75,8 +74,8 @@ __export(index_exports, {
|
|
|
75
74
|
haltAndCatchFire: () => haltAndCatchFire,
|
|
76
75
|
incrementStreamState: () => incrementStreamState,
|
|
77
76
|
loadJson: () => loadJson,
|
|
77
|
+
loadResolver: () => loadResolver,
|
|
78
78
|
loadSchema: () => loadSchema,
|
|
79
|
-
loadShoreConfig: () => loadShoreConfig,
|
|
80
79
|
logger: () => logger,
|
|
81
80
|
postFormDataApiCall: () => postFormDataApiCall,
|
|
82
81
|
postJSONApiCall: () => postJSONApiCall,
|
|
@@ -84,7 +83,6 @@ __export(index_exports, {
|
|
|
84
83
|
printMemoryFootprint: () => printMemoryFootprint,
|
|
85
84
|
removeParasiteProperties: () => removeParasiteProperties,
|
|
86
85
|
safePath: () => safePath,
|
|
87
|
-
writeCatalogFile: () => writeCatalogFile,
|
|
88
86
|
writeStateFile: () => writeStateFile
|
|
89
87
|
});
|
|
90
88
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -116,10 +114,10 @@ var readAndParseJSONFile = (path) => {
|
|
|
116
114
|
};
|
|
117
115
|
|
|
118
116
|
// src/resolvers/local-file/hook/schemaHook.ts
|
|
119
|
-
var import_fs_extra = require("fs-extra");
|
|
117
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
120
118
|
var LocalSchemaHook = class {
|
|
121
119
|
async writeSchema(input) {
|
|
122
|
-
await
|
|
120
|
+
await import_fs_extra.default.appendFile("./schema.tmp", JSON.stringify(input) + "\n");
|
|
123
121
|
}
|
|
124
122
|
};
|
|
125
123
|
|
|
@@ -180,15 +178,9 @@ var CounterMetric = class {
|
|
|
180
178
|
|
|
181
179
|
// src/resolvers/local-file/resolver.ts
|
|
182
180
|
var { combine, prettyPrint, timestamp, errors, splat } = import_winston.default.format;
|
|
183
|
-
var configPath = "./config.json";
|
|
184
|
-
var targetConfigPath = "./target-config.json";
|
|
185
|
-
var catalogPath = "./catalog.json";
|
|
186
181
|
var statePath = "./state.json";
|
|
187
182
|
var metricsPath = "./metrics.json";
|
|
188
183
|
var LocalFilesResolver = class extends Resolver {
|
|
189
|
-
startVPNIfNeeded() {
|
|
190
|
-
return Promise.resolve();
|
|
191
|
-
}
|
|
192
184
|
checkIfCanSync() {
|
|
193
185
|
return Promise.resolve(true);
|
|
194
186
|
}
|
|
@@ -213,49 +205,15 @@ var LocalFilesResolver = class extends Resolver {
|
|
|
213
205
|
constructor() {
|
|
214
206
|
super();
|
|
215
207
|
}
|
|
216
|
-
|
|
208
|
+
getState() {
|
|
217
209
|
logger.info(`\u{1F4C1} Using the LOCAL resolver`);
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
throw new Error(`\u274C Env variable \`SHORE__DESTINATION\` is not set.
|
|
223
|
-
Did you forget to configure it?
|
|
224
|
-
|
|
225
|
-
Possible values are: \`GOOGLE_BIGQUERY\`, \`SNOWFLAKE\``);
|
|
226
|
-
}
|
|
227
|
-
if (command === `READ`) {
|
|
228
|
-
if (destination === `GOOGLE_BIGQUERY`) {
|
|
229
|
-
const googleCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
230
|
-
if (!googleCredentials) {
|
|
231
|
-
throw new Error(`Env variable \`GOOGLE_APPLICATION_CREDENTIALS\` is not set.
|
|
232
|
-
Did you forget to configure it?
|
|
233
|
-
|
|
234
|
-
This variable should link to a file containing the proper Google Service Account credentials.
|
|
235
|
-
`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const catalog = readAndParseJSONFile(catalogPath);
|
|
239
|
-
const state = readAndParseJSONFile(statePath);
|
|
240
|
-
return Promise.resolve({
|
|
241
|
-
command,
|
|
242
|
-
destination,
|
|
243
|
-
config,
|
|
244
|
-
targetConfig,
|
|
245
|
-
catalog,
|
|
246
|
-
state
|
|
247
|
-
});
|
|
248
|
-
} else {
|
|
249
|
-
return Promise.resolve({
|
|
250
|
-
command,
|
|
251
|
-
destination,
|
|
252
|
-
config,
|
|
253
|
-
targetConfig
|
|
254
|
-
});
|
|
255
|
-
}
|
|
210
|
+
const state = readAndParseJSONFile(statePath);
|
|
211
|
+
return Promise.resolve({
|
|
212
|
+
state
|
|
213
|
+
});
|
|
256
214
|
}
|
|
257
215
|
writeCatalog(catalog) {
|
|
258
|
-
return
|
|
216
|
+
return Promise.resolve();
|
|
259
217
|
}
|
|
260
218
|
writeState(state) {
|
|
261
219
|
return writeFile(statePath, state);
|
|
@@ -286,9 +244,7 @@ var LocalFilesResolver = class extends Resolver {
|
|
|
286
244
|
}
|
|
287
245
|
updateSourceValue(optionKey, optionValue) {
|
|
288
246
|
logger.info(`Updating source value ${optionKey} with value ${optionValue}`, { private: true });
|
|
289
|
-
|
|
290
|
-
config[optionKey] = optionValue;
|
|
291
|
-
return writeFile(configPath, JSON.stringify(config));
|
|
247
|
+
return Promise.resolve();
|
|
292
248
|
}
|
|
293
249
|
getLogFormat() {
|
|
294
250
|
return combine(
|
|
@@ -301,29 +257,8 @@ var LocalFilesResolver = class extends Resolver {
|
|
|
301
257
|
};
|
|
302
258
|
|
|
303
259
|
// src/sdk/service/resolver.ts
|
|
304
|
-
function initResolver(resolverType) {
|
|
305
|
-
if (resolverType === "LOCAL") {
|
|
306
|
-
return new LocalFilesResolver();
|
|
307
|
-
} else {
|
|
308
|
-
throw new Error(`\u274C Unsupported configResolver: ${resolverType}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
260
|
var loadResolver = () => {
|
|
312
|
-
|
|
313
|
-
if (!configResolver) {
|
|
314
|
-
throw new Error(`Env variable \`SHORE__CONFIG_RESOLVER\` is not set.
|
|
315
|
-
Did you forget to configure it?
|
|
316
|
-
|
|
317
|
-
Possible values are \`LOCAL\`, \`WHALY\`.
|
|
318
|
-
Please read the documentation to get the full description of the behavior of each.
|
|
319
|
-
`);
|
|
320
|
-
}
|
|
321
|
-
if (configResolver === "WHALY" || configResolver === "LOCAL") {
|
|
322
|
-
return initResolver(configResolver);
|
|
323
|
-
} else {
|
|
324
|
-
throw new Error(`Resolver: ${configResolver} is not supported.
|
|
325
|
-
Did you properly configure \`SHORE__CONFIG_RESOLVER\`?`);
|
|
326
|
-
}
|
|
261
|
+
return new LocalFilesResolver();
|
|
327
262
|
};
|
|
328
263
|
|
|
329
264
|
// src/sdk/service/logger.ts
|
|
@@ -373,30 +308,6 @@ var writeStateFile = (resolver, state) => {
|
|
|
373
308
|
logger.info(`\u{1F4DD} Writing the state file.`);
|
|
374
309
|
return resolver.writeState(state);
|
|
375
310
|
};
|
|
376
|
-
var writeCatalogFile = (resolver, catalog) => {
|
|
377
|
-
logger.info(`\u{1F4DD} Writing the catalog`);
|
|
378
|
-
return resolver.writeCatalog(catalog);
|
|
379
|
-
};
|
|
380
|
-
var loadShoreConfig = (resolver) => {
|
|
381
|
-
const command = process.env.SHORE__COMMAND;
|
|
382
|
-
if (!command) {
|
|
383
|
-
throw new Error(`Env variable \`SHORE__COMMAND\` is not set.
|
|
384
|
-
Did you forget to configure it?
|
|
385
|
-
|
|
386
|
-
Possible values are: \`DISCOVER\`, \`READ\``);
|
|
387
|
-
}
|
|
388
|
-
if (command === `DISCOVER`) {
|
|
389
|
-
logger.info(`\u{1F50E} Mode: discovery
|
|
390
|
-
Shore will detect the available streams in the source and their configuration.`);
|
|
391
|
-
} else if (command === `READ`) {
|
|
392
|
-
logger.info(`\u{1F501} Mode: Synchronizing data
|
|
393
|
-
Shore will extract and push data from the source to the destination.`);
|
|
394
|
-
} else {
|
|
395
|
-
throw new Error(`Command: \`${command}\` is not supported.
|
|
396
|
-
Did you properly configured env variable \`SHORE__COMMAND\`?`);
|
|
397
|
-
}
|
|
398
|
-
return resolver.getConfig(command);
|
|
399
|
-
};
|
|
400
311
|
function readFile(fileName, filePath) {
|
|
401
312
|
try {
|
|
402
313
|
return (0, import_fs2.readFileSync)(filePath);
|
|
@@ -422,13 +333,6 @@ var loadSchema = (streamId) => {
|
|
|
422
333
|
const schema = loadJson(`schemas/${streamId}.json`);
|
|
423
334
|
return schema;
|
|
424
335
|
};
|
|
425
|
-
var checkRequiredConfigKeys = (args, requiredConfigKeys) => {
|
|
426
|
-
requiredConfigKeys.forEach((configKey) => {
|
|
427
|
-
if (!args[configKey]) {
|
|
428
|
-
throw new Error(`Config is missing required key: ${configKey}`);
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
336
|
|
|
433
337
|
// src/sdk/service/network.ts
|
|
434
338
|
var import_axios = __toESM(require("axios"));
|
|
@@ -706,6 +610,102 @@ var findRelatedRelationships = (streamId, allRels) => {
|
|
|
706
610
|
}, { left: [], right: [] });
|
|
707
611
|
};
|
|
708
612
|
|
|
613
|
+
// src/sdk/models/messages.ts
|
|
614
|
+
var RecordMessage = class {
|
|
615
|
+
type = "RECORD";
|
|
616
|
+
stream;
|
|
617
|
+
record;
|
|
618
|
+
constructor(record, stream) {
|
|
619
|
+
this.record = record;
|
|
620
|
+
this.stream = stream;
|
|
621
|
+
}
|
|
622
|
+
toString() {
|
|
623
|
+
const result = {
|
|
624
|
+
type: "RECORD",
|
|
625
|
+
record: this.record,
|
|
626
|
+
stream: this.stream
|
|
627
|
+
};
|
|
628
|
+
return JSON.stringify(result);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var StateMessage = class {
|
|
632
|
+
type = "STATE";
|
|
633
|
+
value;
|
|
634
|
+
constructor(value) {
|
|
635
|
+
this.value = value;
|
|
636
|
+
}
|
|
637
|
+
toString() {
|
|
638
|
+
const result = {
|
|
639
|
+
type: "STATE",
|
|
640
|
+
value: this.value
|
|
641
|
+
};
|
|
642
|
+
return JSON.stringify(result);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
var ReplicationMethodMessage = class {
|
|
646
|
+
type = "REPLICATION_METHOD";
|
|
647
|
+
stream;
|
|
648
|
+
replication;
|
|
649
|
+
constructor(stream, version) {
|
|
650
|
+
this.stream = stream;
|
|
651
|
+
this.replication = version;
|
|
652
|
+
}
|
|
653
|
+
toString() {
|
|
654
|
+
const result = {
|
|
655
|
+
type: "REPLICATION_METHOD",
|
|
656
|
+
replication: this.replication,
|
|
657
|
+
stream: this.stream
|
|
658
|
+
};
|
|
659
|
+
return JSON.stringify(result);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
var SchemaMessage = class {
|
|
663
|
+
type = "SCHEMA";
|
|
664
|
+
stream;
|
|
665
|
+
schema;
|
|
666
|
+
keyProperties;
|
|
667
|
+
bookmarkProperties;
|
|
668
|
+
displayLabel;
|
|
669
|
+
description;
|
|
670
|
+
propertiesMetadata;
|
|
671
|
+
relationships;
|
|
672
|
+
constructor(opts) {
|
|
673
|
+
const {
|
|
674
|
+
stream,
|
|
675
|
+
schema,
|
|
676
|
+
keyProperties,
|
|
677
|
+
bookmarkProperties,
|
|
678
|
+
displayLabel,
|
|
679
|
+
description,
|
|
680
|
+
relationships,
|
|
681
|
+
propertiesMetadata
|
|
682
|
+
} = opts;
|
|
683
|
+
this.stream = stream;
|
|
684
|
+
this.schema = schema;
|
|
685
|
+
this.keyProperties = keyProperties;
|
|
686
|
+
this.bookmarkProperties = bookmarkProperties;
|
|
687
|
+
this.displayLabel = displayLabel;
|
|
688
|
+
this.description = description;
|
|
689
|
+
this.relationships = relationships;
|
|
690
|
+
this.propertiesMetadata = propertiesMetadata;
|
|
691
|
+
}
|
|
692
|
+
toString() {
|
|
693
|
+
const result = {
|
|
694
|
+
type: "SCHEMA",
|
|
695
|
+
stream: this.stream,
|
|
696
|
+
schema: this.schema,
|
|
697
|
+
key_properties: this.keyProperties,
|
|
698
|
+
label: this.displayLabel,
|
|
699
|
+
description: this.description,
|
|
700
|
+
relationships: this.relationships
|
|
701
|
+
};
|
|
702
|
+
if (this.bookmarkProperties) {
|
|
703
|
+
result["bookmark_properties"] = this.bookmarkProperties;
|
|
704
|
+
}
|
|
705
|
+
return JSON.stringify(result);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
709
|
// src/sdk/models/metadata.ts
|
|
710
710
|
var MetadataMap = class extends Map {
|
|
711
711
|
areEquals(array1, array2) {
|
|
@@ -826,155 +826,6 @@ var StreamMetadata = class {
|
|
|
826
826
|
}
|
|
827
827
|
};
|
|
828
828
|
|
|
829
|
-
// src/sdk/models/catalog.ts
|
|
830
|
-
var _ = __toESM(require("lodash"));
|
|
831
|
-
var Catalog = class _Catalog {
|
|
832
|
-
streams;
|
|
833
|
-
static fromFile(fileName) {
|
|
834
|
-
const catalogFile = loadJson(fileName);
|
|
835
|
-
const catalog = new _Catalog();
|
|
836
|
-
catalog.streams = catalogFile.streams;
|
|
837
|
-
return catalog;
|
|
838
|
-
}
|
|
839
|
-
static fromCatalogFile(catalogFile) {
|
|
840
|
-
const catalog = new _Catalog();
|
|
841
|
-
catalog.streams = catalogFile.streams;
|
|
842
|
-
return catalog;
|
|
843
|
-
}
|
|
844
|
-
getSelectedStreams() {
|
|
845
|
-
if (!this.streams) {
|
|
846
|
-
return [];
|
|
847
|
-
}
|
|
848
|
-
const directSelectedStreams = this.streams.filter((stream) => {
|
|
849
|
-
const metadata = new StreamMetadata();
|
|
850
|
-
metadata.fromCatalogStreamMetadata(stream.metadata);
|
|
851
|
-
return metadata.isStreamSelected();
|
|
852
|
-
});
|
|
853
|
-
logger.debug(`Directly selected streams: ${directSelectedStreams.map((stream) => stream.stream).join(",")}`);
|
|
854
|
-
const getIndirectSelectedStreams = (stream, ancestorsIds) => {
|
|
855
|
-
const currAncestorsIds = ancestorsIds || [];
|
|
856
|
-
const rootBreadcrumbMetadata = stream.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
857
|
-
const parentStreamId = rootBreadcrumbMetadata?.metadata?.["whaly-parent-stream"];
|
|
858
|
-
if (parentStreamId) {
|
|
859
|
-
const parentStream = this.streams?.find((stream2) => stream2.stream === parentStreamId);
|
|
860
|
-
if (parentStream) {
|
|
861
|
-
currAncestorsIds.push(parentStreamId);
|
|
862
|
-
return getIndirectSelectedStreams(parentStream, currAncestorsIds);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
return currAncestorsIds;
|
|
866
|
-
};
|
|
867
|
-
const indirectSelectedStreamIds = _.uniq(
|
|
868
|
-
directSelectedStreams.flatMap((stream) => {
|
|
869
|
-
return getIndirectSelectedStreams(stream);
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
logger.debug(`Indirectly selected streams: ${indirectSelectedStreamIds.join(",")}`);
|
|
873
|
-
const indirectSelectedStreams = indirectSelectedStreamIds.map((streamId) => {
|
|
874
|
-
if (!directSelectedStreams.find((stream) => stream.stream === streamId)) {
|
|
875
|
-
return this.streams?.find((stream) => stream.stream === streamId);
|
|
876
|
-
}
|
|
877
|
-
}).filter((stream) => !!stream);
|
|
878
|
-
return directSelectedStreams.concat(indirectSelectedStreams);
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
// src/sdk/models/messages.ts
|
|
883
|
-
var RecordMessage = class {
|
|
884
|
-
type = "RECORD";
|
|
885
|
-
stream;
|
|
886
|
-
record;
|
|
887
|
-
constructor(record, stream) {
|
|
888
|
-
this.record = record;
|
|
889
|
-
this.stream = stream;
|
|
890
|
-
}
|
|
891
|
-
toString() {
|
|
892
|
-
const result = {
|
|
893
|
-
type: "RECORD",
|
|
894
|
-
record: this.record,
|
|
895
|
-
stream: this.stream
|
|
896
|
-
};
|
|
897
|
-
return JSON.stringify(result);
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
var StateMessage = class {
|
|
901
|
-
type = "STATE";
|
|
902
|
-
value;
|
|
903
|
-
constructor(value) {
|
|
904
|
-
this.value = value;
|
|
905
|
-
}
|
|
906
|
-
toString() {
|
|
907
|
-
const result = {
|
|
908
|
-
type: "STATE",
|
|
909
|
-
value: this.value
|
|
910
|
-
};
|
|
911
|
-
return JSON.stringify(result);
|
|
912
|
-
}
|
|
913
|
-
};
|
|
914
|
-
var ReplicationMethodMessage = class {
|
|
915
|
-
type = "REPLICATION_METHOD";
|
|
916
|
-
stream;
|
|
917
|
-
replication;
|
|
918
|
-
constructor(stream, version) {
|
|
919
|
-
this.stream = stream;
|
|
920
|
-
this.replication = version;
|
|
921
|
-
}
|
|
922
|
-
toString() {
|
|
923
|
-
const result = {
|
|
924
|
-
type: "REPLICATION_METHOD",
|
|
925
|
-
replication: this.replication,
|
|
926
|
-
stream: this.stream
|
|
927
|
-
};
|
|
928
|
-
return JSON.stringify(result);
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
var SchemaMessage = class {
|
|
932
|
-
type = "SCHEMA";
|
|
933
|
-
stream;
|
|
934
|
-
schema;
|
|
935
|
-
keyProperties;
|
|
936
|
-
bookmarkProperties;
|
|
937
|
-
displayLabel;
|
|
938
|
-
description;
|
|
939
|
-
propertiesMetadata;
|
|
940
|
-
relationships;
|
|
941
|
-
constructor(opts) {
|
|
942
|
-
const {
|
|
943
|
-
stream,
|
|
944
|
-
schema,
|
|
945
|
-
keyProperties,
|
|
946
|
-
bookmarkProperties,
|
|
947
|
-
displayLabel,
|
|
948
|
-
description,
|
|
949
|
-
relationships,
|
|
950
|
-
propertiesMetadata
|
|
951
|
-
} = opts;
|
|
952
|
-
this.stream = stream;
|
|
953
|
-
this.schema = schema;
|
|
954
|
-
this.keyProperties = keyProperties;
|
|
955
|
-
this.bookmarkProperties = bookmarkProperties;
|
|
956
|
-
this.displayLabel = displayLabel;
|
|
957
|
-
this.description = description;
|
|
958
|
-
this.relationships = relationships;
|
|
959
|
-
this.propertiesMetadata = propertiesMetadata;
|
|
960
|
-
}
|
|
961
|
-
toString() {
|
|
962
|
-
const result = {
|
|
963
|
-
type: "SCHEMA",
|
|
964
|
-
stream: this.stream,
|
|
965
|
-
schema: this.schema,
|
|
966
|
-
key_properties: this.keyProperties,
|
|
967
|
-
label: this.displayLabel,
|
|
968
|
-
description: this.description,
|
|
969
|
-
relationships: this.relationships
|
|
970
|
-
};
|
|
971
|
-
if (this.bookmarkProperties) {
|
|
972
|
-
result["bookmark_properties"] = this.bookmarkProperties;
|
|
973
|
-
}
|
|
974
|
-
return JSON.stringify(result);
|
|
975
|
-
}
|
|
976
|
-
};
|
|
977
|
-
|
|
978
829
|
// src/sdk/models/state.ts
|
|
979
830
|
var import_moment = __toESM(require("moment"));
|
|
980
831
|
|
|
@@ -1155,186 +1006,46 @@ var isDatetimeType = (typeDef) => {
|
|
|
1155
1006
|
};
|
|
1156
1007
|
|
|
1157
1008
|
// src/sdk/models/tap/stream.ts
|
|
1158
|
-
var
|
|
1159
|
-
var import_bluebird2 = require("bluebird");
|
|
1009
|
+
var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"));
|
|
1010
|
+
var import_bluebird2 = __toESM(require("bluebird"));
|
|
1160
1011
|
|
|
1161
1012
|
// src/sdk/models/tap/tap.ts
|
|
1162
|
-
var import_bluebird = require("bluebird");
|
|
1163
|
-
var import_dependency_graph = require("dependency-graph");
|
|
1013
|
+
var import_bluebird = __toESM(require("bluebird"));
|
|
1164
1014
|
var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
|
|
1165
1015
|
var Tap = class {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1016
|
+
target;
|
|
1017
|
+
config;
|
|
1018
|
+
streams;
|
|
1019
|
+
concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
|
|
1170
1020
|
constructor(target, config) {
|
|
1171
|
-
this.
|
|
1172
|
-
this.
|
|
1173
|
-
this.
|
|
1174
|
-
}
|
|
1175
|
-
discover = async (existingCatalog) => {
|
|
1176
|
-
logger.debug("Loading schemas");
|
|
1177
|
-
const streamDepGraph = new import_dependency_graph.DepGraph();
|
|
1178
|
-
const buildDependencyGraph = (streams2, parentId) => {
|
|
1179
|
-
streams2.forEach((s) => {
|
|
1180
|
-
streamDepGraph.addNode(s.streamId, s);
|
|
1181
|
-
if (parentId) {
|
|
1182
|
-
streamDepGraph.addDependency(s.streamId, parentId);
|
|
1183
|
-
}
|
|
1184
|
-
if (s.children) {
|
|
1185
|
-
return buildDependencyGraph(s.children, s.streamId);
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
};
|
|
1189
|
-
buildDependencyGraph(this._streams);
|
|
1190
|
-
const discoveredStreamCatalogEntries = await import_bluebird.Promise.map(
|
|
1191
|
-
streamDepGraph.overallOrder(),
|
|
1192
|
-
async (streamId) => {
|
|
1193
|
-
logger.info(`\u2728 Loading schema for ${streamId}`);
|
|
1194
|
-
const stream = streamDepGraph.getNodeData(streamId);
|
|
1195
|
-
const schema = await stream.getSchema();
|
|
1196
|
-
const parentStreamIds = streamDepGraph.directDependenciesOf(streamId);
|
|
1197
|
-
if (parentStreamIds.length > 1) {
|
|
1198
|
-
throw new Error(`StreamId=${streamId} directly depends on more than 1 stream, e.g. ${parentStreamIds.join(",")} which is not supported!`);
|
|
1199
|
-
}
|
|
1200
|
-
const parentStreamId = parentStreamIds?.[0];
|
|
1201
|
-
const { metadata } = await this.generateMetadataFromStream(stream, parentStreamId);
|
|
1202
|
-
return {
|
|
1203
|
-
schema,
|
|
1204
|
-
metadata,
|
|
1205
|
-
stream: stream.streamId,
|
|
1206
|
-
tap_stream_id: stream.streamId
|
|
1207
|
-
};
|
|
1208
|
-
},
|
|
1209
|
-
{ concurrency: this._concurrency }
|
|
1210
|
-
);
|
|
1211
|
-
const existingCatalogStreamIds = (existingCatalog?.streams || []).map((entry) => {
|
|
1212
|
-
return entry.stream;
|
|
1213
|
-
});
|
|
1214
|
-
const newlyDiscoveredStreamCatalogEntries = discoveredStreamCatalogEntries.filter((entry) => {
|
|
1215
|
-
if (existingCatalogStreamIds.includes(entry.stream)) {
|
|
1216
|
-
return false;
|
|
1217
|
-
} else {
|
|
1218
|
-
return true;
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
logger.debug(`Newly discovered streams: %j`, newlyDiscoveredStreamCatalogEntries);
|
|
1222
|
-
const allDiscoveredStreamIds = discoveredStreamCatalogEntries.map((entry) => {
|
|
1223
|
-
return entry.stream;
|
|
1224
|
-
});
|
|
1225
|
-
const existingCatalogMinusDeletedStreams = (existingCatalog?.streams || []).filter((entry) => {
|
|
1226
|
-
if (!allDiscoveredStreamIds.includes(entry.stream)) {
|
|
1227
|
-
logger.debug(`Stream %s was deleted, removing it from the catalog`, entry.stream);
|
|
1228
|
-
return false;
|
|
1229
|
-
} else {
|
|
1230
|
-
return true;
|
|
1231
|
-
}
|
|
1232
|
-
});
|
|
1233
|
-
const updatedExistingCatalog = existingCatalogMinusDeletedStreams.map((existingCatalogEntry) => {
|
|
1234
|
-
const matchingDiscoveredCatalogEntry = discoveredStreamCatalogEntries.find((entry) => entry.stream === existingCatalogEntry.stream);
|
|
1235
|
-
if (matchingDiscoveredCatalogEntry) {
|
|
1236
|
-
const existingRootMetadata = existingCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
1237
|
-
const discoveredRootMetadata = matchingDiscoveredCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
1238
|
-
if (existingRootMetadata) {
|
|
1239
|
-
const parentStreamVal = discoveredRootMetadata?.metadata["whaly-parent-stream"];
|
|
1240
|
-
if (parentStreamVal !== void 0) {
|
|
1241
|
-
existingRootMetadata.metadata["whaly-parent-stream"] = parentStreamVal;
|
|
1242
|
-
}
|
|
1243
|
-
const forcedReplicationMethodVal = discoveredRootMetadata?.metadata["forced-replication-method"];
|
|
1244
|
-
if (forcedReplicationMethodVal !== void 0) {
|
|
1245
|
-
existingRootMetadata.metadata["forced-replication-method"] = forcedReplicationMethodVal;
|
|
1246
|
-
}
|
|
1247
|
-
const forcedReplicationKeyVal = discoveredRootMetadata?.metadata["forced-replication-key"];
|
|
1248
|
-
if (forcedReplicationKeyVal !== void 0) {
|
|
1249
|
-
existingRootMetadata.metadata["forced-replication-key"] = forcedReplicationKeyVal;
|
|
1250
|
-
}
|
|
1251
|
-
const validReplicationKeysVal = discoveredRootMetadata?.metadata["valid-replication-keys"];
|
|
1252
|
-
if (validReplicationKeysVal !== void 0) {
|
|
1253
|
-
existingRootMetadata.metadata["valid-replication-keys"] = validReplicationKeysVal;
|
|
1254
|
-
}
|
|
1255
|
-
const rowCountVal = discoveredRootMetadata?.metadata["row-count"];
|
|
1256
|
-
if (rowCountVal !== void 0) {
|
|
1257
|
-
existingRootMetadata.metadata["row-count"] = rowCountVal;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
return existingCatalogEntry;
|
|
1262
|
-
});
|
|
1263
|
-
const streams = updatedExistingCatalog.concat(newlyDiscoveredStreamCatalogEntries);
|
|
1264
|
-
return { streams };
|
|
1265
|
-
};
|
|
1266
|
-
sync = async (catalog) => {
|
|
1267
|
-
await this.initiateSync(catalog);
|
|
1268
|
-
logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
|
|
1269
|
-
await this._target.complete();
|
|
1270
|
-
return Promise.resolve();
|
|
1271
|
-
};
|
|
1272
|
-
async generateMetadataFromStream(stream, parentStreamId) {
|
|
1273
|
-
const metadata = new StreamMetadata();
|
|
1274
|
-
metadata.write([], "table-key-properties", stream.primaryKey);
|
|
1275
|
-
const defaultReplicationConfig = stream.defaultReplicationConfig();
|
|
1276
|
-
metadata.write([], "replication-method", defaultReplicationConfig.replicationMethod);
|
|
1277
|
-
const validReplicationkeys = await stream.getValidReplicationKeys();
|
|
1278
|
-
metadata.write([], "valid-replication-keys", validReplicationkeys);
|
|
1279
|
-
if (defaultReplicationConfig.isForced) {
|
|
1280
|
-
metadata.write([], "forced-replication-method", defaultReplicationConfig.replicationMethod);
|
|
1281
|
-
if (stream.forcedReplicationKey) {
|
|
1282
|
-
metadata.write([], "forced-replication-key", stream.forcedReplicationKey);
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
const rowCount = await stream.getRowCount();
|
|
1286
|
-
if (rowCount !== void 0) {
|
|
1287
|
-
metadata.write([], "row-count", rowCount);
|
|
1288
|
-
}
|
|
1289
|
-
if (stream.isSilent === true) {
|
|
1290
|
-
metadata.write([], "whaly-is-hidden", stream.isSilent);
|
|
1291
|
-
}
|
|
1292
|
-
if (stream.isSilent === false) {
|
|
1293
|
-
metadata.write([], "selected-by-default", stream.selectedByDefault);
|
|
1294
|
-
}
|
|
1295
|
-
if (stream.displayLabel) {
|
|
1296
|
-
metadata.write([], "whaly-display-label", stream.displayLabel);
|
|
1297
|
-
}
|
|
1298
|
-
if (stream.description) {
|
|
1299
|
-
metadata.write([], "whaly-description", stream.description);
|
|
1300
|
-
}
|
|
1301
|
-
if (parentStreamId !== void 0) {
|
|
1302
|
-
metadata.write([], "whaly-parent-stream", parentStreamId);
|
|
1303
|
-
}
|
|
1304
|
-
return { metadata: metadata.toList() };
|
|
1021
|
+
this.streams = [];
|
|
1022
|
+
this.target = target;
|
|
1023
|
+
this.config = config;
|
|
1305
1024
|
}
|
|
1306
|
-
async
|
|
1025
|
+
sync = async (options) => {
|
|
1307
1026
|
logger.info(`\u{1F680} Start syncing`);
|
|
1308
1027
|
const state = StateService.getInstance().get();
|
|
1309
1028
|
logger.info(`\u{1F4CD} Received state: %j`, state);
|
|
1310
|
-
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
return stream.sync();
|
|
1331
|
-
}
|
|
1332
|
-
},
|
|
1333
|
-
{
|
|
1334
|
-
concurrency: this._concurrency
|
|
1335
|
-
}
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
1029
|
+
const envInclude = process.env.TAP_STREAMS ? process.env.TAP_STREAMS.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
1030
|
+
const include = options?.include && options.include.length > 0 ? options.include : envInclude;
|
|
1031
|
+
const exclude = new Set((options?.exclude || []).map((s) => s.trim()));
|
|
1032
|
+
let streamsToRun = this.streams.filter((s) => !s.isSilent);
|
|
1033
|
+
if (include && include.length > 0) {
|
|
1034
|
+
const includeSet = new Set(include);
|
|
1035
|
+
streamsToRun = streamsToRun.filter((s) => includeSet.has(s.streamId));
|
|
1036
|
+
}
|
|
1037
|
+
if (exclude.size > 0) {
|
|
1038
|
+
streamsToRun = streamsToRun.filter((s) => !exclude.has(s.streamId));
|
|
1039
|
+
}
|
|
1040
|
+
const selectedStreamIds = streamsToRun.map((s) => s.streamId);
|
|
1041
|
+
logger.info(`\u{1F973} Syncing streams: %s`, selectedStreamIds.join(","));
|
|
1042
|
+
await import_bluebird.default.map(streamsToRun, (stream) => {
|
|
1043
|
+
return stream.sync();
|
|
1044
|
+
}, { concurrency: this.concurrency });
|
|
1045
|
+
logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
|
|
1046
|
+
await this.target.complete();
|
|
1047
|
+
return Promise.resolve();
|
|
1048
|
+
};
|
|
1338
1049
|
// Used to clean up any ressource before exit
|
|
1339
1050
|
end() {
|
|
1340
1051
|
return Promise.resolve();
|
|
@@ -1343,15 +1054,11 @@ var Tap = class {
|
|
|
1343
1054
|
|
|
1344
1055
|
// src/sdk/models/tap/stream.ts
|
|
1345
1056
|
var import_util2 = require("util");
|
|
1346
|
-
var
|
|
1057
|
+
var Stream = class {
|
|
1347
1058
|
// Runtime values, can't be overriden
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
_target;
|
|
1352
|
-
// Contains the root metadata entry of the catalog stream configuration
|
|
1353
|
-
// contains some end user schema configuration
|
|
1354
|
-
rootMetadataEntry;
|
|
1059
|
+
config;
|
|
1060
|
+
tapState;
|
|
1061
|
+
target;
|
|
1355
1062
|
// Compile time values, can be overriden
|
|
1356
1063
|
// Replication method that can be forced in the stream implemenation. Will prevail over any user/catalog choice
|
|
1357
1064
|
forcedReplicationMethod = void 0;
|
|
@@ -1377,11 +1084,10 @@ var StreamV2 = class {
|
|
|
1377
1084
|
// Metrics configuration
|
|
1378
1085
|
rowsSyncedMetricsConf;
|
|
1379
1086
|
executionTimeMetricsConf;
|
|
1380
|
-
constructor(
|
|
1381
|
-
this.
|
|
1382
|
-
this.
|
|
1383
|
-
this.
|
|
1384
|
-
this._target = target;
|
|
1087
|
+
constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
1088
|
+
this.config = config;
|
|
1089
|
+
this.tapState = (0, import_cloneDeep.default)(state);
|
|
1090
|
+
this.target = target;
|
|
1385
1091
|
this.childConcurrency = childConcurrency;
|
|
1386
1092
|
this.rowsSyncedMetricsConf = {
|
|
1387
1093
|
isEnabled: () => !this.isSilent,
|
|
@@ -1392,9 +1098,7 @@ var StreamV2 = class {
|
|
|
1392
1098
|
getStreamIds: () => [this.streamId]
|
|
1393
1099
|
};
|
|
1394
1100
|
}
|
|
1395
|
-
|
|
1396
|
-
this.rootMetadataEntry = rootMetadataEntry;
|
|
1397
|
-
};
|
|
1101
|
+
// No root metadata: replication config is derived from stream defaults only
|
|
1398
1102
|
/**
|
|
1399
1103
|
* Sync this stream
|
|
1400
1104
|
* Called only for streams with no parents, aka. "root tap streams"
|
|
@@ -1425,7 +1129,7 @@ var StreamV2 = class {
|
|
|
1425
1129
|
`\u{1F6BD} Flushing ${replicationMethod} sync of stream '${this.streamId}' + all of its children...`
|
|
1426
1130
|
);
|
|
1427
1131
|
await this._flush();
|
|
1428
|
-
await import_bluebird2.
|
|
1132
|
+
await import_bluebird2.default.map(this.children, (child) => {
|
|
1429
1133
|
return child.flush();
|
|
1430
1134
|
}, { concurrency: 3 });
|
|
1431
1135
|
};
|
|
@@ -1435,7 +1139,7 @@ var StreamV2 = class {
|
|
|
1435
1139
|
*/
|
|
1436
1140
|
_writeStateMessage = () => {
|
|
1437
1141
|
const message = new StateMessage(StateService.getInstance().get());
|
|
1438
|
-
return this.
|
|
1142
|
+
return this.target.state(message);
|
|
1439
1143
|
};
|
|
1440
1144
|
/**
|
|
1441
1145
|
* Write out a ACTIVATE_VERSION message.
|
|
@@ -1443,10 +1147,10 @@ var StreamV2 = class {
|
|
|
1443
1147
|
_writeReplicationMethodMessage = async () => {
|
|
1444
1148
|
const method = this.configuredReplicationConfig().replicationMethod;
|
|
1445
1149
|
const message = new ReplicationMethodMessage(this.streamId, method);
|
|
1446
|
-
await import_bluebird2.
|
|
1150
|
+
await import_bluebird2.default.map(this.children, async (c) => {
|
|
1447
1151
|
return c._writeReplicationMethodMessage();
|
|
1448
1152
|
}, { concurrency: 3 });
|
|
1449
|
-
await this.
|
|
1153
|
+
await this.target.replicationMethod(message);
|
|
1450
1154
|
};
|
|
1451
1155
|
/**
|
|
1452
1156
|
* Write out a SCHEMA message with the stream schema.
|
|
@@ -1467,11 +1171,11 @@ var StreamV2 = class {
|
|
|
1467
1171
|
...this.description ? { description: this.description } : {},
|
|
1468
1172
|
...schema.propertiesMetadata ? { propertiesMetadata: schema.propertiesMetadata } : {}
|
|
1469
1173
|
});
|
|
1470
|
-
await this.
|
|
1174
|
+
await this.target.schema(message);
|
|
1471
1175
|
}
|
|
1472
1176
|
}
|
|
1473
1177
|
if (skipChildrenSchema !== true) {
|
|
1474
|
-
await import_bluebird2.
|
|
1178
|
+
await import_bluebird2.default.map(this.children, async (child) => {
|
|
1475
1179
|
await child._writeSchemaMessage();
|
|
1476
1180
|
}, { concurrency: 3 });
|
|
1477
1181
|
}
|
|
@@ -1546,8 +1250,8 @@ var StreamV2 = class {
|
|
|
1546
1250
|
**/
|
|
1547
1251
|
getStartingTimestamp() {
|
|
1548
1252
|
const streamIdToReadFrom = this.useStateFromStreamId ? this.useStateFromStreamId : this.streamId;
|
|
1549
|
-
const state = extractStateForStream(this.
|
|
1550
|
-
const startDate = this.
|
|
1253
|
+
const state = extractStateForStream(this.tapState, streamIdToReadFrom);
|
|
1254
|
+
const startDate = this.config.start_date;
|
|
1551
1255
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1552
1256
|
logger.debug(`${this.streamId}: state.replicationKeyValue: ${state.replicationKeyValue},
|
|
1553
1257
|
state.replicationKey: ${state.replicationKey},
|
|
@@ -1606,16 +1310,10 @@ var StreamV2 = class {
|
|
|
1606
1310
|
* Return the configured replication method that will be used for the sync.
|
|
1607
1311
|
*/
|
|
1608
1312
|
configuredReplicationConfig = () => {
|
|
1609
|
-
const
|
|
1610
|
-
if (defaultReplicationMethod.isForced) {
|
|
1611
|
-
return {
|
|
1612
|
-
replicationMethod: defaultReplicationMethod.replicationMethod,
|
|
1613
|
-
replicationKey: defaultReplicationMethod.replicationKey
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1313
|
+
const defaults = this.defaultReplicationConfig();
|
|
1616
1314
|
return {
|
|
1617
|
-
replicationMethod:
|
|
1618
|
-
replicationKey:
|
|
1315
|
+
replicationMethod: defaults.replicationMethod,
|
|
1316
|
+
replicationKey: defaults.replicationKey
|
|
1619
1317
|
};
|
|
1620
1318
|
};
|
|
1621
1319
|
// Private sync methods:
|
|
@@ -1645,9 +1343,9 @@ var StreamV2 = class {
|
|
|
1645
1343
|
this.streamId
|
|
1646
1344
|
);
|
|
1647
1345
|
if (!this.isSilent) {
|
|
1648
|
-
await this.
|
|
1346
|
+
await this.target.record(recordMessage);
|
|
1649
1347
|
}
|
|
1650
|
-
await import_bluebird2.
|
|
1348
|
+
await import_bluebird2.default.map(
|
|
1651
1349
|
this.children,
|
|
1652
1350
|
async (child) => {
|
|
1653
1351
|
await child.asyncInit(row);
|
|
@@ -1691,7 +1389,7 @@ var StreamV2 = class {
|
|
|
1691
1389
|
getSchema() {
|
|
1692
1390
|
logger.debug(`stream: ${this.streamId} - getSchema() is called`);
|
|
1693
1391
|
if (this.schemaPath) {
|
|
1694
|
-
const schema = loadJson(`schemas/${this.
|
|
1392
|
+
const schema = loadJson(`schemas/${this.schemaPath}`);
|
|
1695
1393
|
return Promise.resolve({ jsonSchema: schema });
|
|
1696
1394
|
} else {
|
|
1697
1395
|
if (this.isSilent) {
|
|
@@ -1728,7 +1426,7 @@ var StreamV2 = class {
|
|
|
1728
1426
|
};
|
|
1729
1427
|
|
|
1730
1428
|
// src/sdk/helpers/qs-logger.ts
|
|
1731
|
-
var
|
|
1429
|
+
var import_isPlainObject = __toESM(require("lodash/isPlainObject.js"));
|
|
1732
1430
|
var MAX_QS_KEYS = 10;
|
|
1733
1431
|
var MAX_QS_STRING_LENGTH = 200;
|
|
1734
1432
|
var MAX_QS_ARRAY_ITEMS = 5;
|
|
@@ -1749,7 +1447,7 @@ function truncateQueryParams(value, depth = 0) {
|
|
|
1749
1447
|
}
|
|
1750
1448
|
return items;
|
|
1751
1449
|
}
|
|
1752
|
-
if (
|
|
1450
|
+
if ((0, import_isPlainObject.default)(value)) {
|
|
1753
1451
|
const keys = Object.keys(value);
|
|
1754
1452
|
const result = {};
|
|
1755
1453
|
for (const key of keys.slice(0, MAX_QS_KEYS)) {
|
|
@@ -1772,11 +1470,12 @@ function getTruncatedParamsForLog(params) {
|
|
|
1772
1470
|
}
|
|
1773
1471
|
|
|
1774
1472
|
// src/sdk/models/tap/restStream.ts
|
|
1775
|
-
var
|
|
1776
|
-
var
|
|
1473
|
+
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"));
|
|
1474
|
+
var import_isEqual = __toESM(require("lodash/isEqual.js"));
|
|
1475
|
+
var RESTStream = class extends Stream {
|
|
1777
1476
|
axiosInstance;
|
|
1778
|
-
constructor(
|
|
1779
|
-
super(
|
|
1477
|
+
constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
1478
|
+
super(config, state, target, childConcurrency);
|
|
1780
1479
|
this.apiCallsMetricsConf = {
|
|
1781
1480
|
isEnabled: () => !this.isSilent,
|
|
1782
1481
|
getStreamIds: () => [this.streamId]
|
|
@@ -1799,7 +1498,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1799
1498
|
const authenticator = this.authenticator;
|
|
1800
1499
|
const params = {
|
|
1801
1500
|
...this.getNextUrlParams(nextPageToken, parent),
|
|
1802
|
-
...await authenticator?.getAuthQS(this.
|
|
1501
|
+
...await authenticator?.getAuthQS(this.config)
|
|
1803
1502
|
};
|
|
1804
1503
|
const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
|
|
1805
1504
|
let headers = this.getCustomHTTPHeaders(parent);
|
|
@@ -1807,7 +1506,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1807
1506
|
headers = {};
|
|
1808
1507
|
}
|
|
1809
1508
|
if (authenticator) {
|
|
1810
|
-
const authHeaders = await authenticator.getAuthHeaders(this.
|
|
1509
|
+
const authHeaders = await authenticator.getAuthHeaders(this.config);
|
|
1811
1510
|
headers = {
|
|
1812
1511
|
...headers,
|
|
1813
1512
|
...authHeaders
|
|
@@ -1890,9 +1589,9 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1890
1589
|
for await (const row of rows) {
|
|
1891
1590
|
yield row;
|
|
1892
1591
|
}
|
|
1893
|
-
const previousToken =
|
|
1592
|
+
const previousToken = (0, import_cloneDeep2.default)(nextPageToken);
|
|
1894
1593
|
nextPageToken = this.getNextPageToken(resp, previousToken);
|
|
1895
|
-
if (nextPageToken &&
|
|
1594
|
+
if (nextPageToken && (0, import_isEqual.default)(nextPageToken, previousToken)) {
|
|
1896
1595
|
throw new Error(
|
|
1897
1596
|
`${this.streamId} - Loop detected in pagination. "
|
|
1898
1597
|
Pagination token: \`${JSON.stringify(nextPageToken)}\` is identical to prior token \`${JSON.stringify(previousToken)}\`.`
|
|
@@ -2093,20 +1792,20 @@ var StreamWarehouseSyncService = class {
|
|
|
2093
1792
|
const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
|
|
2094
1793
|
const columnsMappingStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2095
1794
|
const columnsToAdd = Object.keys(columnsMappingStore).filter((unsafeColumnKey) => {
|
|
2096
|
-
const
|
|
2097
|
-
if (!
|
|
1795
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
1796
|
+
if (!safeColumnName2) {
|
|
2098
1797
|
return false;
|
|
2099
1798
|
}
|
|
2100
|
-
return !columnNamesFromWarehouse.includes(
|
|
1799
|
+
return !columnNamesFromWarehouse.includes(safeColumnName2);
|
|
2101
1800
|
}).map((unsafeColumnKey) => {
|
|
2102
|
-
const
|
|
1801
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
2103
1802
|
const definition = this.flattenedSchema[unsafeColumnKey];
|
|
2104
|
-
if (!
|
|
1803
|
+
if (!safeColumnName2 || !definition) {
|
|
2105
1804
|
return void 0;
|
|
2106
1805
|
}
|
|
2107
1806
|
return {
|
|
2108
1807
|
unsafeName: unsafeColumnKey,
|
|
2109
|
-
name:
|
|
1808
|
+
name: safeColumnName2,
|
|
2110
1809
|
definition
|
|
2111
1810
|
};
|
|
2112
1811
|
}).filter((item) => !!item);
|
|
@@ -2328,10 +2027,10 @@ var import_moment3 = __toESM(require("moment"));
|
|
|
2328
2027
|
|
|
2329
2028
|
// src/sdk/models/target/temporaryFile.ts
|
|
2330
2029
|
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
2331
|
-
var
|
|
2030
|
+
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
2332
2031
|
var createTemporaryFileStream = (streamId) => {
|
|
2333
2032
|
import_fs_extra2.default.ensureDirSync("./tmp");
|
|
2334
|
-
const path = `./tmp/${safePath(streamId)}-${(0,
|
|
2033
|
+
const path = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
2335
2034
|
const writeStream = import_fs_extra2.default.createWriteStream(path, { flags: "w" });
|
|
2336
2035
|
return {
|
|
2337
2036
|
stream: writeStream,
|
|
@@ -2456,7 +2155,7 @@ var StreamState = class {
|
|
|
2456
2155
|
|
|
2457
2156
|
// src/sdk/models/target/target.ts
|
|
2458
2157
|
var import_async_mutex = require("async-mutex");
|
|
2459
|
-
var import_bluebird3 = require("bluebird");
|
|
2158
|
+
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2460
2159
|
var semaphore = new import_async_mutex.Semaphore(1);
|
|
2461
2160
|
var ITarget = class _ITarget {
|
|
2462
2161
|
config;
|
|
@@ -2701,7 +2400,7 @@ var ITarget = class _ITarget {
|
|
|
2701
2400
|
})
|
|
2702
2401
|
}
|
|
2703
2402
|
};
|
|
2704
|
-
await import_bluebird3.
|
|
2403
|
+
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2705
2404
|
const input = {
|
|
2706
2405
|
databaseName,
|
|
2707
2406
|
schemaName,
|
|
@@ -2782,7 +2481,7 @@ var ITarget = class _ITarget {
|
|
|
2782
2481
|
};
|
|
2783
2482
|
uploadStreamsToWarehouse = async (streamsToUpload) => {
|
|
2784
2483
|
try {
|
|
2785
|
-
await import_bluebird3.
|
|
2484
|
+
await import_bluebird3.default.map(streamsToUpload, async (streamId) => {
|
|
2786
2485
|
logger.info(`\u{1F4E4} Upload stream: ${streamId} to Warehouse`);
|
|
2787
2486
|
const streamState = this.streams[streamId];
|
|
2788
2487
|
if (streamState) {
|
|
@@ -2816,20 +2515,644 @@ var ITarget = class _ITarget {
|
|
|
2816
2515
|
}
|
|
2817
2516
|
};
|
|
2818
2517
|
};
|
|
2518
|
+
|
|
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, "");
|
|
2526
|
+
}
|
|
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
|
+
}
|
|
2535
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2536
|
+
return returnString.replace(pattern, "_");
|
|
2537
|
+
};
|
|
2538
|
+
var safeTableName = (key) => {
|
|
2539
|
+
let returnString = key;
|
|
2540
|
+
returnString = returnString.toLowerCase();
|
|
2541
|
+
returnString = returnString.replace("`", "");
|
|
2542
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2543
|
+
if (returnString.length > 1024) {
|
|
2544
|
+
returnString = returnString.substr(0, 1024);
|
|
2545
|
+
}
|
|
2546
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2547
|
+
return returnString.replace(pattern, "_");
|
|
2548
|
+
};
|
|
2549
|
+
|
|
2550
|
+
// src/targets/bigquery/service/record.ts
|
|
2551
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
2552
|
+
var import_moment5 = __toESM(require("moment"));
|
|
2553
|
+
var validateDateRange = (record, schema) => {
|
|
2554
|
+
Object.keys(schema).forEach((key) => {
|
|
2555
|
+
const { format: format3 } = schema[key];
|
|
2556
|
+
if (format3 === "date-time") {
|
|
2557
|
+
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"));
|
|
2561
|
+
if (formattedDate !== "Invalid date" && isNotTooMuchInTheFuture && isNotTooMuchInThePast) {
|
|
2562
|
+
record[key] = formattedDate;
|
|
2563
|
+
} else {
|
|
2564
|
+
record[key] = void 0;
|
|
2565
|
+
logger.info("Skipping date for key %s for being invalid", key);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
return record;
|
|
2571
|
+
};
|
|
2572
|
+
var maxBQNumericValue = new import_decimal.default("9.9999999999999999999999999999999999999E+28");
|
|
2573
|
+
var convertNumberIntoDecimal = (record, schema) => {
|
|
2574
|
+
Object.keys(record).forEach((key) => {
|
|
2575
|
+
if (schema[key]) {
|
|
2576
|
+
const { type, items } = schema[key];
|
|
2577
|
+
const types = type instanceof Array ? type : [type];
|
|
2578
|
+
if (types.includes("number")) {
|
|
2579
|
+
if (typeof record[key] === "number" || record[key] instanceof import_decimal.default) {
|
|
2580
|
+
const decimal = new import_decimal.default(record[key]).toDecimalPlaces(9);
|
|
2581
|
+
if (decimal.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2582
|
+
record[key] = null;
|
|
2583
|
+
} else {
|
|
2584
|
+
record[key] = decimal;
|
|
2585
|
+
}
|
|
2586
|
+
} else {
|
|
2587
|
+
record[key] = null;
|
|
2588
|
+
}
|
|
2589
|
+
} else if (types.includes("array") && items?.type.includes("number")) {
|
|
2590
|
+
if (Array.isArray(record[key])) {
|
|
2591
|
+
const rawArr = record[key];
|
|
2592
|
+
const newArr = rawArr.map((item) => {
|
|
2593
|
+
if (typeof item === "number" || item instanceof import_decimal.default) {
|
|
2594
|
+
const parsed = new import_decimal.default(item).toDecimalPlaces(9);
|
|
2595
|
+
if (parsed.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2596
|
+
return void 0;
|
|
2597
|
+
} else {
|
|
2598
|
+
return parsed;
|
|
2599
|
+
}
|
|
2600
|
+
} else {
|
|
2601
|
+
return void 0;
|
|
2602
|
+
}
|
|
2603
|
+
}).filter((item) => !!item);
|
|
2604
|
+
record[key] = newArr;
|
|
2605
|
+
} else {
|
|
2606
|
+
record[key] = null;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
return record;
|
|
2612
|
+
};
|
|
2613
|
+
|
|
2614
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2615
|
+
var import_moment6 = __toESM(require("moment"));
|
|
2616
|
+
|
|
2617
|
+
// src/targets/bigquery/service/bigquery.ts
|
|
2618
|
+
var import_bigquery = require("@google-cloud/bigquery");
|
|
2619
|
+
var BqClientHolder = class {
|
|
2620
|
+
static instance;
|
|
2621
|
+
client;
|
|
2622
|
+
constructor(config) {
|
|
2623
|
+
const retryOptions = {
|
|
2624
|
+
autoRetry: true,
|
|
2625
|
+
maxRetries: 3
|
|
2626
|
+
};
|
|
2627
|
+
this.client = new import_bigquery.BigQuery({
|
|
2628
|
+
projectId: config.database.trim(),
|
|
2629
|
+
...retryOptions
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
static client(config) {
|
|
2633
|
+
if (!this.instance) {
|
|
2634
|
+
this.instance = new this(config);
|
|
2635
|
+
}
|
|
2636
|
+
return this.instance.client;
|
|
2637
|
+
}
|
|
2638
|
+
};
|
|
2639
|
+
|
|
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
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2731
|
+
var import_chunk = __toESM(require("lodash/chunk.js"));
|
|
2732
|
+
var import_async_mutex2 = require("async-mutex");
|
|
2733
|
+
var semaphore2 = new import_async_mutex2.Semaphore(1);
|
|
2734
|
+
var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
2735
|
+
config;
|
|
2736
|
+
bqClient;
|
|
2737
|
+
bqDataset;
|
|
2738
|
+
bqTable;
|
|
2739
|
+
bqStagingTable;
|
|
2740
|
+
tableName;
|
|
2741
|
+
stagingTableName;
|
|
2742
|
+
sqlTableId;
|
|
2743
|
+
sqlStagingTableId;
|
|
2744
|
+
constructor(config, streamId, database, schema, table, renameColumnStore) {
|
|
2745
|
+
super(
|
|
2746
|
+
streamId,
|
|
2747
|
+
database,
|
|
2748
|
+
schema,
|
|
2749
|
+
table,
|
|
2750
|
+
renameColumnStore
|
|
2751
|
+
);
|
|
2752
|
+
this.config = config;
|
|
2753
|
+
this.bqClient = BqClientHolder.client(config);
|
|
2754
|
+
this.bqDataset = this.bqClient.dataset(schema, { location: config.location || "EU" });
|
|
2755
|
+
this.bqTable = this.bqDataset.table(table, { location: config.location || "EU" });
|
|
2756
|
+
this.tableName = table;
|
|
2757
|
+
this.stagingTableName = `${table}_temp`;
|
|
2758
|
+
this.bqStagingTable = this.bqDataset.table(this.stagingTableName, { location: config.location || "EU" });
|
|
2759
|
+
this.sqlTableId = `${schema}.${table}`;
|
|
2760
|
+
this.sqlStagingTableId = `${this.sqlTableId}_temp`;
|
|
2761
|
+
}
|
|
2762
|
+
// Implementation of abstract classes
|
|
2763
|
+
getSerializedRecord = (record) => {
|
|
2764
|
+
return JSON.stringify(record) + `
|
|
2765
|
+
`;
|
|
2766
|
+
};
|
|
2767
|
+
safeColumnName = safeColumnName;
|
|
2768
|
+
getWarehouseTypeFromJSONSchema = (definition) => {
|
|
2769
|
+
const type = definition.type;
|
|
2770
|
+
const format3 = definition.format;
|
|
2771
|
+
let typeArr;
|
|
2772
|
+
if (type instanceof Array) {
|
|
2773
|
+
typeArr = type;
|
|
2774
|
+
} else {
|
|
2775
|
+
typeArr = [type];
|
|
2776
|
+
}
|
|
2777
|
+
if (format3) {
|
|
2778
|
+
switch (format3) {
|
|
2779
|
+
case "date-time":
|
|
2780
|
+
return "TIMESTAMP";
|
|
2781
|
+
case "json":
|
|
2782
|
+
return "STRING";
|
|
2783
|
+
default:
|
|
2784
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format3}`);
|
|
2785
|
+
}
|
|
2786
|
+
} else if (typeArr.includes("number")) {
|
|
2787
|
+
return "NUMERIC";
|
|
2788
|
+
} else if (typeArr.includes("integer") && type.includes("string")) {
|
|
2789
|
+
return "NUMERIC";
|
|
2790
|
+
} else if (typeArr.includes("integer")) {
|
|
2791
|
+
return "INTEGER";
|
|
2792
|
+
} else if (typeArr.includes("boolean")) {
|
|
2793
|
+
return "BOOLEAN";
|
|
2794
|
+
} else if (typeArr.includes("string")) {
|
|
2795
|
+
return "STRING";
|
|
2796
|
+
} else if (typeArr.includes("array")) {
|
|
2797
|
+
const items = definition.items;
|
|
2798
|
+
if (items) {
|
|
2799
|
+
return this.getWarehouseTypeFromJSONSchema(items);
|
|
2800
|
+
}
|
|
2801
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported array schema field definition: ${JSON.stringify(definition)}`);
|
|
2802
|
+
} else {
|
|
2803
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported schema field definition: ${JSON.stringify(definition)}`);
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2806
|
+
getMergeQueries = () => {
|
|
2807
|
+
const primaryKeyCondition = this.primaryKeys.map((pk) => {
|
|
2808
|
+
return `\`sourced\`.\`${pk}\` = \`destination\`.\`${pk}\``;
|
|
2809
|
+
}).join(` AND `);
|
|
2810
|
+
const primaryKeys = this.primaryKeys.map((pk) => `\`${pk}\``).join(` ,`);
|
|
2811
|
+
const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2812
|
+
const columns = Object.keys(columnUnsafeToSafeMapping).map((column) => {
|
|
2813
|
+
return {
|
|
2814
|
+
isUnsafe: true,
|
|
2815
|
+
colName: column
|
|
2816
|
+
};
|
|
2817
|
+
});
|
|
2818
|
+
const columnsChunks = (0, import_chunk.default)(columns, 300).map((chunk2) => {
|
|
2819
|
+
this.primaryKeys.forEach((pk) => {
|
|
2820
|
+
if (!chunk2.map((c) => columnUnsafeToSafeMapping[c.colName]).includes(pk)) {
|
|
2821
|
+
chunk2.push({
|
|
2822
|
+
isUnsafe: false,
|
|
2823
|
+
colName: pk
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
return chunk2;
|
|
2828
|
+
});
|
|
2829
|
+
return columnsChunks.map((chunk2) => {
|
|
2830
|
+
const setValues = chunk2.map((column) => {
|
|
2831
|
+
if (column.isUnsafe) {
|
|
2832
|
+
return `\`destination\`.\`${columnUnsafeToSafeMapping[column.colName]}\` = \`sourced\`.\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2833
|
+
} else {
|
|
2834
|
+
return `\`destination\`.\`${column.colName}\` = \`sourced\`.\`${column.colName}\``;
|
|
2835
|
+
}
|
|
2836
|
+
}).join(`, `);
|
|
2837
|
+
const renamedCols = chunk2.map((column) => {
|
|
2838
|
+
if (column.isUnsafe) {
|
|
2839
|
+
return `\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2840
|
+
} else {
|
|
2841
|
+
return `\`${column.colName}\``;
|
|
2842
|
+
}
|
|
2843
|
+
}).join(`, `);
|
|
2844
|
+
const query = `
|
|
2845
|
+
MERGE ${this.sqlTableId} destination
|
|
2846
|
+
USING (
|
|
2847
|
+
WITH all_staging_rows AS (
|
|
2848
|
+
SELECT *
|
|
2849
|
+
FROM ${this.sqlStagingTableId}
|
|
2850
|
+
),
|
|
2851
|
+
numbered_rows AS (
|
|
2852
|
+
SELECT *,
|
|
2853
|
+
ROW_NUMBER() OVER (PARTITION BY ${primaryKeys}) as _wly_row_nb,
|
|
2854
|
+
COUNT(1) OVER (PARTITION BY ${primaryKeys}) AS _wly_partition_size,
|
|
2855
|
+
FROM all_staging_rows
|
|
2856
|
+
),
|
|
2857
|
+
staging_deduped AS (
|
|
2858
|
+
SELECT * EXCEPT(_wly_partition_size, _wly_row_nb)
|
|
2859
|
+
FROM numbered_rows
|
|
2860
|
+
WHERE _wly_partition_size = _wly_row_nb
|
|
2861
|
+
)
|
|
2862
|
+
SELECT *
|
|
2863
|
+
FROM staging_deduped
|
|
2864
|
+
) AS sourced
|
|
2865
|
+
ON ${primaryKeyCondition}
|
|
2866
|
+
WHEN MATCHED THEN
|
|
2867
|
+
UPDATE SET ${setValues}
|
|
2868
|
+
WHEN NOT MATCHED THEN
|
|
2869
|
+
INSERT (${renamedCols}) VALUES (${renamedCols});
|
|
2870
|
+
`;
|
|
2871
|
+
return query;
|
|
2872
|
+
});
|
|
2873
|
+
};
|
|
2874
|
+
getReplaceQueries = () => {
|
|
2875
|
+
return [
|
|
2876
|
+
`TRUNCATE TABLE ${this.sqlTableId};`,
|
|
2877
|
+
...this.getMergeQueries()
|
|
2878
|
+
];
|
|
2879
|
+
};
|
|
2880
|
+
createDatabaseAndSchemaIfNotExists = async (retryCount) => {
|
|
2881
|
+
try {
|
|
2882
|
+
await semaphore2.runExclusive(async () => {
|
|
2883
|
+
const [exists] = await this.bqDataset.exists();
|
|
2884
|
+
if (!exists) {
|
|
2885
|
+
logger.info(`\u{1F423} Creating dataset: ${this.bqDataset.id} on BigQuery`);
|
|
2886
|
+
await this.bqDataset.create();
|
|
2887
|
+
} else {
|
|
2888
|
+
const [metadata] = await this.bqDataset.getMetadata();
|
|
2889
|
+
const alreadyExistinglocation = metadata.location;
|
|
2890
|
+
if (this.config.location && this.config.location !== alreadyExistinglocation) {
|
|
2891
|
+
await haltAndCatchFire(
|
|
2892
|
+
`unknown`,
|
|
2893
|
+
`A BigQuery dataset called '${this.bqDataset.id}' already exists in location: '${alreadyExistinglocation}' \u{1F605}
|
|
2894
|
+
|
|
2895
|
+
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}
|
|
2896
|
+
|
|
2897
|
+
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}
|
|
2898
|
+
|
|
2899
|
+
Please relaunch the connector once it's done to fix the issue \u{1F917}
|
|
2900
|
+
`,
|
|
2901
|
+
`
|
|
2902
|
+
We have a conflict between an already existing dataset in location ${alreadyExistinglocation} and the configured location: ${this.config.location}
|
|
2903
|
+
`
|
|
2904
|
+
);
|
|
2905
|
+
throw Error();
|
|
2906
|
+
}
|
|
2907
|
+
logger.info(`\u{1F44B} Dataset: ${this.bqDataset.id} already exists on BigQuery, reusing it.`);
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
} catch (err) {
|
|
2911
|
+
if (err.code === 400) {
|
|
2912
|
+
await haltAndCatchFire(
|
|
2913
|
+
`unauthorized`,
|
|
2914
|
+
`We can't connect to your BigQuery account \u{1F614}
|
|
2915
|
+
|
|
2916
|
+
Here is the message from BigQuery: ${err.message}
|
|
2917
|
+
|
|
2918
|
+
Can you check that your Warehouse credentials and Source target schema are properly configured and try to sync again the source? \u{1F64F}
|
|
2919
|
+
`,
|
|
2920
|
+
`Got error when trying to get Dataset from BigQuery: ${err.message} - ${err.code}`
|
|
2921
|
+
);
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (retryCount && retryCount > this.maxRetryCount) {
|
|
2925
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. ${err.message} - ${err.code}`);
|
|
2926
|
+
gracefulExit(1);
|
|
2927
|
+
} else {
|
|
2928
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
|
|
2929
|
+
await new Promise((resolve, reject) => setTimeout(resolve, 1e3 * retryCount));
|
|
2930
|
+
await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return Promise.resolve();
|
|
2934
|
+
};
|
|
2935
|
+
createTable = async () => {
|
|
2936
|
+
try {
|
|
2937
|
+
const tableName = this.tableName;
|
|
2938
|
+
const schema = this.generateBigquerySchema();
|
|
2939
|
+
logger.info(`\u{1F423} Creating table: ${tableName} on BigQuery`);
|
|
2940
|
+
const createTableOptions = {
|
|
2941
|
+
schema
|
|
2942
|
+
};
|
|
2943
|
+
await this.bqDataset.createTable(
|
|
2944
|
+
tableName,
|
|
2945
|
+
createTableOptions
|
|
2946
|
+
);
|
|
2947
|
+
return;
|
|
2948
|
+
} catch (err) {
|
|
2949
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2950
|
+
|
|
2951
|
+
Error: ${err.message}
|
|
2952
|
+
Stack: ${err.stack}`);
|
|
2953
|
+
}
|
|
2954
|
+
};
|
|
2955
|
+
addColumns = async (tableFields) => {
|
|
2956
|
+
if (tableFields.length > 0) {
|
|
2957
|
+
logger.info(`\u{1F195} Stream: ${this.streamId} - Adding following columns in BigQuery schema: ${JSON.stringify(tableFields)}`);
|
|
2958
|
+
const [metadata] = await this.bqTable.getMetadata();
|
|
2959
|
+
const bgTableFields = tableFields.map((tblField) => {
|
|
2960
|
+
return this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(tblField.name, tblField.definition);
|
|
2961
|
+
}).filter((fld) => {
|
|
2962
|
+
return !!fld;
|
|
2963
|
+
});
|
|
2964
|
+
const schema = metadata.schema;
|
|
2965
|
+
const new_schema = {
|
|
2966
|
+
...schema,
|
|
2967
|
+
fields: schema.fields.concat(bgTableFields)
|
|
2968
|
+
};
|
|
2969
|
+
metadata.schema = new_schema;
|
|
2970
|
+
await this.bqTable.setMetadata(metadata);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
};
|
|
2974
|
+
createStagingArea = async () => {
|
|
2975
|
+
try {
|
|
2976
|
+
const tableName = this.stagingTableName;
|
|
2977
|
+
const [exists] = await this.bqDataset.table(tableName).exists();
|
|
2978
|
+
if (exists) {
|
|
2979
|
+
logger.info(`\u{1F474} Temporary table ${tableName} already exists, it should be a relic form the past.
|
|
2980
|
+
Dropping it.`);
|
|
2981
|
+
await this.deleteStagingArea();
|
|
2982
|
+
}
|
|
2983
|
+
const schema = this.generateBigquerySchema();
|
|
2984
|
+
const expirationTime = (0, import_moment6.default)().add("1", "day").valueOf().toString();
|
|
2985
|
+
const createTableOptions = {
|
|
2986
|
+
schema,
|
|
2987
|
+
expirationTime
|
|
2988
|
+
};
|
|
2989
|
+
logger.info(`\u{1F423} Creating staging table: ${tableName} on BigQuery`);
|
|
2990
|
+
await this.bqDataset.createTable(
|
|
2991
|
+
tableName,
|
|
2992
|
+
createTableOptions
|
|
2993
|
+
);
|
|
2994
|
+
return;
|
|
2995
|
+
} catch (err) {
|
|
2996
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2997
|
+
|
|
2998
|
+
Error: ${err.message}
|
|
2999
|
+
Stack: ${err.stack}`);
|
|
3000
|
+
}
|
|
3001
|
+
};
|
|
3002
|
+
loadStreamInStagingArea = async (localFilePath) => {
|
|
3003
|
+
try {
|
|
3004
|
+
const file = await uploadFileInBucket(
|
|
3005
|
+
this.streamId,
|
|
3006
|
+
this.config.loading_deck_gcs_bucket_name,
|
|
3007
|
+
localFilePath
|
|
3008
|
+
);
|
|
3009
|
+
const metadata = {
|
|
3010
|
+
sourceFormat: "NEWLINE_DELIMITED_JSON",
|
|
3011
|
+
writeDisposition: "WRITE_TRUNCATE"
|
|
3012
|
+
};
|
|
3013
|
+
await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
|
|
3014
|
+
} catch (err) {
|
|
3015
|
+
logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
|
|
3016
|
+
throw err;
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
deleteStagingArea = () => {
|
|
3020
|
+
const query = `DROP TABLE IF EXISTS ${this.sqlStagingTableId};`;
|
|
3021
|
+
return this.runQueriesWithRetry([query]);
|
|
3022
|
+
};
|
|
3023
|
+
getTablesInSchema = async () => {
|
|
3024
|
+
const [bqTables] = await this.bqDataset.getTables();
|
|
3025
|
+
const foundTables = bqTables.filter((tbl) => {
|
|
3026
|
+
return !!tbl.id;
|
|
3027
|
+
}).map((bqTable) => {
|
|
3028
|
+
return {
|
|
3029
|
+
tableName: bqTable.id
|
|
3030
|
+
};
|
|
3031
|
+
});
|
|
3032
|
+
return foundTables;
|
|
3033
|
+
};
|
|
3034
|
+
getTableColumnsFromWarehouse = async () => {
|
|
3035
|
+
const [table] = await this.bqTable.getMetadata();
|
|
3036
|
+
return table.schema.fields;
|
|
3037
|
+
};
|
|
3038
|
+
runQueries = async (queries) => {
|
|
3039
|
+
const sqlQuery = `
|
|
3040
|
+
BEGIN
|
|
3041
|
+
${queries.join(`
|
|
3042
|
+
`)}
|
|
3043
|
+
END;
|
|
3044
|
+
`;
|
|
3045
|
+
await this.bqClient.query(sqlQuery);
|
|
3046
|
+
};
|
|
3047
|
+
// Specific methods
|
|
3048
|
+
/**
|
|
3049
|
+
*
|
|
3050
|
+
* Generate a TableField definition object that can be passed to BigQuery APIs to define a column on a table
|
|
3051
|
+
*
|
|
3052
|
+
* @param colName
|
|
3053
|
+
* @param schemaFieldDefinition
|
|
3054
|
+
* @returns
|
|
3055
|
+
*/
|
|
3056
|
+
getBigqueryTableFieldFromInternalSchemaFieldDefinition = (colName, schemaFieldDefinition) => {
|
|
3057
|
+
const name = colName;
|
|
3058
|
+
if (!schemaFieldDefinition) {
|
|
3059
|
+
throw new Error(`StreamId: ${this.streamId} - Schema is unknown for colName=${colName}, so we can't infer the proper BigQuery definition for this column.`);
|
|
3060
|
+
}
|
|
3061
|
+
const type = schemaFieldDefinition.type;
|
|
3062
|
+
let typeArr;
|
|
3063
|
+
if (type instanceof Array) {
|
|
3064
|
+
typeArr = type;
|
|
3065
|
+
} else {
|
|
3066
|
+
typeArr = [type];
|
|
3067
|
+
}
|
|
3068
|
+
if (typeArr.includes("array")) {
|
|
3069
|
+
return {
|
|
3070
|
+
name,
|
|
3071
|
+
mode: "REPEATED",
|
|
3072
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
3073
|
+
};
|
|
3074
|
+
} else {
|
|
3075
|
+
let nullableMode = "NULLABLE";
|
|
3076
|
+
return {
|
|
3077
|
+
name,
|
|
3078
|
+
mode: nullableMode,
|
|
3079
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
generateBigquerySchema = () => {
|
|
3084
|
+
const unsafeToSafeMappingColumnsFromColumnStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
3085
|
+
if (!unsafeToSafeMappingColumnsFromColumnStore) {
|
|
3086
|
+
throw new Error(`StreamId: ${this.streamId} - There is no unsafe->safe column mapping`);
|
|
3087
|
+
}
|
|
3088
|
+
return Object.keys(unsafeToSafeMappingColumnsFromColumnStore).reduce((acc, unsafeKey) => {
|
|
3089
|
+
const renamedColName = this.renamedColumnStore.getColumnTranslation(this.streamId, unsafeKey);
|
|
3090
|
+
const bigqueryTableField = this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(renamedColName, this.flattenedSchema[unsafeKey]);
|
|
3091
|
+
if (bigqueryTableField) {
|
|
3092
|
+
acc.push(bigqueryTableField);
|
|
3093
|
+
}
|
|
3094
|
+
return acc;
|
|
3095
|
+
}, []);
|
|
3096
|
+
};
|
|
3097
|
+
loadGCSFileInTable = async (tableName, file, metadata) => {
|
|
3098
|
+
try {
|
|
3099
|
+
const [job] = await this.bqStagingTable.load(file, metadata);
|
|
3100
|
+
logger.info(`\u{1F69A} Loaded GCS file \`${file.id}\` in BigQuery table: ${tableName} with Job: \`${job.id}\``);
|
|
3101
|
+
logger.debug(`Upload Stats: ${JSON.stringify(job.statistics)}`);
|
|
3102
|
+
const errors2 = job.status?.errors;
|
|
3103
|
+
if (errors2 && errors2.length > 0) {
|
|
3104
|
+
throw errors2;
|
|
3105
|
+
}
|
|
3106
|
+
} catch (err) {
|
|
3107
|
+
logger.error(err);
|
|
3108
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when loading file in BigQuery table | fileName: ${file.name}, tableName: ${tableName}
|
|
3109
|
+
|
|
3110
|
+
Error: ${err.message}
|
|
3111
|
+
Stack: ${err.stack}`);
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/targets/bigquery/main.ts
|
|
3117
|
+
var BigQueryTarget = class extends ITarget {
|
|
3118
|
+
constructor(config, resolver) {
|
|
3119
|
+
super(config, resolver);
|
|
3120
|
+
this.renameColumnStore.setSafeColumnNameConverter(this.safeColumnNameConverter);
|
|
3121
|
+
}
|
|
3122
|
+
static requiredConfigKeys = [
|
|
3123
|
+
"schema",
|
|
3124
|
+
"project_id",
|
|
3125
|
+
"loading_deck_gcs_bucket_name"
|
|
3126
|
+
];
|
|
3127
|
+
newDBSyncInstance = (streamId, database, schema, table) => {
|
|
3128
|
+
return new BigQueryDBSync(
|
|
3129
|
+
this.config,
|
|
3130
|
+
streamId,
|
|
3131
|
+
database,
|
|
3132
|
+
schema,
|
|
3133
|
+
table,
|
|
3134
|
+
this.renameColumnStore
|
|
3135
|
+
);
|
|
3136
|
+
};
|
|
3137
|
+
genTableName = safeTableName;
|
|
3138
|
+
safeColumnNameConverter = safeColumnName;
|
|
3139
|
+
validateDateRange = validateDateRange;
|
|
3140
|
+
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
3141
|
+
};
|
|
2819
3142
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2820
3143
|
0 && (module.exports = {
|
|
2821
3144
|
ALL_STREAM_ID_KEYWORD,
|
|
2822
3145
|
API_CALLS_METRIC_NAME,
|
|
2823
3146
|
Authenticator,
|
|
2824
3147
|
BATCH_INTERVAL_MS,
|
|
2825
|
-
|
|
3148
|
+
BigQueryTarget,
|
|
2826
3149
|
CounterMetric,
|
|
2827
3150
|
DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
2828
3151
|
EXECUTION_TIME_METRIC_NAME,
|
|
2829
3152
|
ITarget,
|
|
2830
3153
|
MissingFieldInSchemaError,
|
|
2831
3154
|
MissingSchemaError,
|
|
2832
|
-
|
|
3155
|
+
RESTStream,
|
|
2833
3156
|
ROWS_SYNCED_METRIC_NAME,
|
|
2834
3157
|
RecordMessage,
|
|
2835
3158
|
RenameColumnStore,
|
|
@@ -2839,13 +3162,12 @@ var ITarget = class _ITarget {
|
|
|
2839
3162
|
SchemaValidationError,
|
|
2840
3163
|
StateMessage,
|
|
2841
3164
|
StateService,
|
|
3165
|
+
Stream,
|
|
2842
3166
|
StreamMetadata,
|
|
2843
|
-
StreamV2,
|
|
2844
3167
|
StreamWarehouseSyncService,
|
|
2845
3168
|
Tap,
|
|
2846
3169
|
_greaterThan,
|
|
2847
3170
|
addWhalyFields,
|
|
2848
|
-
checkRequiredConfigKeys,
|
|
2849
3171
|
createTemporaryFileStream,
|
|
2850
3172
|
extractStateForStream,
|
|
2851
3173
|
finalizeStateProgressMarkers,
|
|
@@ -2863,8 +3185,8 @@ var ITarget = class _ITarget {
|
|
|
2863
3185
|
haltAndCatchFire,
|
|
2864
3186
|
incrementStreamState,
|
|
2865
3187
|
loadJson,
|
|
3188
|
+
loadResolver,
|
|
2866
3189
|
loadSchema,
|
|
2867
|
-
loadShoreConfig,
|
|
2868
3190
|
logger,
|
|
2869
3191
|
postFormDataApiCall,
|
|
2870
3192
|
postJSONApiCall,
|
|
@@ -2872,7 +3194,6 @@ var ITarget = class _ITarget {
|
|
|
2872
3194
|
printMemoryFootprint,
|
|
2873
3195
|
removeParasiteProperties,
|
|
2874
3196
|
safePath,
|
|
2875
|
-
writeCatalogFile,
|
|
2876
3197
|
writeStateFile
|
|
2877
3198
|
});
|
|
2878
3199
|
//# sourceMappingURL=index.js.map
|