@whaly/connector-sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +192 -239
- package/dist/index.d.ts +192 -239
- package/dist/index.js +927 -834
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +922 -824
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
package/dist/index.mjs
CHANGED
|
@@ -2,243 +2,10 @@
|
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
|
|
4
4
|
// src/sdk/service/logger.ts
|
|
5
|
-
import * as
|
|
6
|
-
|
|
7
|
-
// src/sdk/models/resolver.ts
|
|
8
|
-
var Resolver = class {
|
|
9
|
-
constructor() {
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// src/resolvers/local-file/services/files.ts
|
|
14
|
-
import fs from "fs";
|
|
15
|
-
var writeFile = (path, content) => {
|
|
16
|
-
return Promise.resolve(fs.writeFileSync(path, content));
|
|
17
|
-
};
|
|
18
|
-
var readAndParseJSONFile = (path) => {
|
|
19
|
-
if (!fs.existsSync(path)) {
|
|
20
|
-
throw new Error(`File \`${path}\` wasn't found at: ${process.cwd()}`);
|
|
21
|
-
}
|
|
22
|
-
const raw = fs.readFileSync(path);
|
|
23
|
-
const parsed = JSON.parse(raw.toString());
|
|
24
|
-
return parsed;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// src/resolvers/local-file/hook/schemaHook.ts
|
|
28
|
-
import { appendFile } from "fs-extra";
|
|
29
|
-
var LocalSchemaHook = class {
|
|
30
|
-
async writeSchema(input) {
|
|
31
|
-
await appendFile("./schema.tmp", JSON.stringify(input) + "\n");
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// src/resolvers/local-file/resolver.ts
|
|
36
|
-
import winston from "winston";
|
|
37
|
-
|
|
38
|
-
// src/sdk/service/metric.ts
|
|
39
|
-
var metricStore = {};
|
|
40
|
-
var ALL_STREAM_ID_KEYWORD = "all";
|
|
41
|
-
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
42
|
-
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
43
|
-
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
44
|
-
var getCounterMetrics = (name, streamIds) => {
|
|
45
|
-
return streamIds.map((streamId) => {
|
|
46
|
-
return getCounterMetric(name, streamId);
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
50
|
-
if (!metricStore[streamId]) {
|
|
51
|
-
metricStore[streamId] = {};
|
|
52
|
-
}
|
|
53
|
-
const streamMetricStore = metricStore[streamId];
|
|
54
|
-
if (!streamMetricStore[name]) {
|
|
55
|
-
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
56
|
-
}
|
|
57
|
-
return streamMetricStore[name];
|
|
58
|
-
};
|
|
59
|
-
var getAllMetrics = () => {
|
|
60
|
-
return Object.keys(metricStore).flatMap((streamId) => {
|
|
61
|
-
const streamMetricStore = metricStore[streamId];
|
|
62
|
-
if (!streamMetricStore) {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
var CounterMetric = class {
|
|
69
|
-
value = 0;
|
|
70
|
-
name;
|
|
71
|
-
streamId;
|
|
72
|
-
constructor(name, streamId) {
|
|
73
|
-
this.name = name;
|
|
74
|
-
this.streamId = streamId;
|
|
75
|
-
}
|
|
76
|
-
increment(inc = 1) {
|
|
77
|
-
this.value = this.value + inc;
|
|
78
|
-
}
|
|
79
|
-
getStreamId() {
|
|
80
|
-
return this.streamId;
|
|
81
|
-
}
|
|
82
|
-
getName() {
|
|
83
|
-
return this.name;
|
|
84
|
-
}
|
|
85
|
-
getValue() {
|
|
86
|
-
return this.value;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// src/resolvers/local-file/resolver.ts
|
|
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
|
-
var statePath = "./state.json";
|
|
96
|
-
var metricsPath = "./metrics.json";
|
|
97
|
-
var LocalFilesResolver = class extends Resolver {
|
|
98
|
-
startVPNIfNeeded() {
|
|
99
|
-
return Promise.resolve();
|
|
100
|
-
}
|
|
101
|
-
checkIfCanSync() {
|
|
102
|
-
return Promise.resolve(true);
|
|
103
|
-
}
|
|
104
|
-
markSyncStarted() {
|
|
105
|
-
return Promise.resolve();
|
|
106
|
-
}
|
|
107
|
-
onError(errorType, errorText, errorDebugText) {
|
|
108
|
-
const path = `./error.json`;
|
|
109
|
-
return writeFile(path, JSON.stringify({
|
|
110
|
-
errorType,
|
|
111
|
-
errorText,
|
|
112
|
-
errorDebugText
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
getSchemaHooks() {
|
|
116
|
-
const wlyHook = new LocalSchemaHook();
|
|
117
|
-
return [wlyHook];
|
|
118
|
-
}
|
|
119
|
-
writeSchema() {
|
|
120
|
-
return Promise.resolve();
|
|
121
|
-
}
|
|
122
|
-
constructor() {
|
|
123
|
-
super();
|
|
124
|
-
}
|
|
125
|
-
getConfig(command) {
|
|
126
|
-
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
|
-
}
|
|
165
|
-
}
|
|
166
|
-
writeCatalog(catalog) {
|
|
167
|
-
return writeFile(catalogPath, JSON.stringify(catalog));
|
|
168
|
-
}
|
|
169
|
-
writeState(state) {
|
|
170
|
-
return writeFile(statePath, state);
|
|
171
|
-
}
|
|
172
|
-
markSyncFailed() {
|
|
173
|
-
logger.info(`\u{1F622} Sync has failed.`);
|
|
174
|
-
return Promise.resolve();
|
|
175
|
-
}
|
|
176
|
-
markSyncComplete() {
|
|
177
|
-
logger.info(`\u{1F389} Sync is complete.`);
|
|
178
|
-
return Promise.resolve();
|
|
179
|
-
}
|
|
180
|
-
markDiscoveryComplete() {
|
|
181
|
-
logger.info(`\u{1F389} Discovery is complete.`);
|
|
182
|
-
return Promise.resolve();
|
|
183
|
-
}
|
|
184
|
-
async flushMetrics() {
|
|
185
|
-
const allMetrics = getAllMetrics();
|
|
186
|
-
const metricsOuput = allMetrics.map((metric) => {
|
|
187
|
-
return {
|
|
188
|
-
streamId: metric.getStreamId(),
|
|
189
|
-
name: metric.getName(),
|
|
190
|
-
value: metric.getValue()
|
|
191
|
-
};
|
|
192
|
-
}).map((obj) => JSON.stringify(obj)).join(`
|
|
193
|
-
`);
|
|
194
|
-
return writeFile(metricsPath, metricsOuput);
|
|
195
|
-
}
|
|
196
|
-
updateSourceValue(optionKey, optionValue) {
|
|
197
|
-
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));
|
|
201
|
-
}
|
|
202
|
-
getLogFormat() {
|
|
203
|
-
return combine(
|
|
204
|
-
timestamp(),
|
|
205
|
-
errors({ stack: true }),
|
|
206
|
-
splat(),
|
|
207
|
-
prettyPrint()
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// 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
|
-
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
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// src/sdk/service/logger.ts
|
|
5
|
+
import * as winston from "winston";
|
|
239
6
|
var BATCH_INTERVAL_MS = 100;
|
|
240
7
|
var transports2 = {
|
|
241
|
-
console: new
|
|
8
|
+
console: new winston.transports.Console({
|
|
242
9
|
stderrLevels: [],
|
|
243
10
|
level: "info"
|
|
244
11
|
})
|
|
@@ -248,8 +15,14 @@ var getTransports = () => {
|
|
|
248
15
|
transports2.console
|
|
249
16
|
];
|
|
250
17
|
};
|
|
251
|
-
var
|
|
252
|
-
|
|
18
|
+
var { combine, prettyPrint, timestamp, errors, splat } = winston.format;
|
|
19
|
+
var winstonLogger = winston.createLogger({
|
|
20
|
+
format: combine(
|
|
21
|
+
timestamp(),
|
|
22
|
+
errors({ stack: true }),
|
|
23
|
+
splat(),
|
|
24
|
+
prettyPrint()
|
|
25
|
+
),
|
|
253
26
|
transports: getTransports()
|
|
254
27
|
});
|
|
255
28
|
var isWinstonOpen = true;
|
|
@@ -278,34 +51,6 @@ var logger = {
|
|
|
278
51
|
};
|
|
279
52
|
|
|
280
53
|
// src/sdk/utils.ts
|
|
281
|
-
var writeStateFile = (resolver, state) => {
|
|
282
|
-
logger.info(`\u{1F4DD} Writing the state file.`);
|
|
283
|
-
return resolver.writeState(state);
|
|
284
|
-
};
|
|
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
54
|
function readFile(fileName, filePath) {
|
|
310
55
|
try {
|
|
311
56
|
return readFileSync(filePath);
|
|
@@ -331,18 +76,11 @@ var loadSchema = (streamId) => {
|
|
|
331
76
|
const schema = loadJson(`schemas/${streamId}.json`);
|
|
332
77
|
return schema;
|
|
333
78
|
};
|
|
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
79
|
|
|
342
80
|
// src/sdk/service/network.ts
|
|
343
81
|
import axios from "axios";
|
|
344
82
|
import { URLSearchParams } from "url";
|
|
345
|
-
import * as
|
|
83
|
+
import * as fs from "fs";
|
|
346
84
|
import * as qs from "qs";
|
|
347
85
|
import axiosRetry, { isNetworkOrIdempotentRequestError } from "axios-retry";
|
|
348
86
|
var shouldRetry = (error) => {
|
|
@@ -455,7 +193,7 @@ var getJSONApiCall = async (endpoint, config, privateLogging) => {
|
|
|
455
193
|
};
|
|
456
194
|
var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) => {
|
|
457
195
|
const instance = getAxiosInstance(config.retryCount);
|
|
458
|
-
const writer =
|
|
196
|
+
const writer = fs.createWriteStream(output);
|
|
459
197
|
const paramsSerializer = (params) => {
|
|
460
198
|
return qs.stringify(params, { arrayFormat: "repeat" });
|
|
461
199
|
};
|
|
@@ -549,6 +287,58 @@ var postUrlEncodedApiCall = async (endpoint, config, payload) => {
|
|
|
549
287
|
}
|
|
550
288
|
};
|
|
551
289
|
|
|
290
|
+
// src/sdk/service/metric.ts
|
|
291
|
+
var metricStore = {};
|
|
292
|
+
var ALL_STREAM_ID_KEYWORD = "all";
|
|
293
|
+
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
294
|
+
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
295
|
+
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
296
|
+
var getCounterMetrics = (name, streamIds) => {
|
|
297
|
+
return streamIds.map((streamId) => {
|
|
298
|
+
return getCounterMetric(name, streamId);
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
302
|
+
if (!metricStore[streamId]) {
|
|
303
|
+
metricStore[streamId] = {};
|
|
304
|
+
}
|
|
305
|
+
const streamMetricStore = metricStore[streamId];
|
|
306
|
+
if (!streamMetricStore[name]) {
|
|
307
|
+
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
308
|
+
}
|
|
309
|
+
return streamMetricStore[name];
|
|
310
|
+
};
|
|
311
|
+
var getAllMetrics = () => {
|
|
312
|
+
return Object.keys(metricStore).flatMap((streamId) => {
|
|
313
|
+
const streamMetricStore = metricStore[streamId];
|
|
314
|
+
if (!streamMetricStore) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
var CounterMetric = class {
|
|
321
|
+
value = 0;
|
|
322
|
+
name;
|
|
323
|
+
streamId;
|
|
324
|
+
constructor(name, streamId) {
|
|
325
|
+
this.name = name;
|
|
326
|
+
this.streamId = streamId;
|
|
327
|
+
}
|
|
328
|
+
increment(inc = 1) {
|
|
329
|
+
this.value = this.value + inc;
|
|
330
|
+
}
|
|
331
|
+
getStreamId() {
|
|
332
|
+
return this.streamId;
|
|
333
|
+
}
|
|
334
|
+
getName() {
|
|
335
|
+
return this.name;
|
|
336
|
+
}
|
|
337
|
+
getValue() {
|
|
338
|
+
return this.value;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
552
342
|
// src/sdk/service/memory.ts
|
|
553
343
|
var formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
|
|
554
344
|
var printMemoryFootprint = (prefix) => {
|
|
@@ -564,9 +354,6 @@ var printMemoryFootprint = (prefix) => {
|
|
|
564
354
|
|
|
565
355
|
// src/sdk/service/exit.ts
|
|
566
356
|
async function gracefulExit(exitCode) {
|
|
567
|
-
if (exitCode !== 0) {
|
|
568
|
-
await loadResolver().markSyncFailed();
|
|
569
|
-
}
|
|
570
357
|
const loggerFinish = new Promise((resolve, reject) => {
|
|
571
358
|
logger.closeWinston(resolve);
|
|
572
359
|
});
|
|
@@ -586,207 +373,15 @@ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
|
|
|
586
373
|
errorText,
|
|
587
374
|
errorDebugText
|
|
588
375
|
);
|
|
589
|
-
const resolver = loadResolver();
|
|
590
|
-
await resolver.onError(errorType, errorText, errorDebugText);
|
|
591
376
|
await gracefulExit(0);
|
|
592
377
|
};
|
|
593
378
|
|
|
594
|
-
// src/sdk/
|
|
595
|
-
var
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
to: rel.to
|
|
601
|
-
};
|
|
602
|
-
if (rel.left === streamId) {
|
|
603
|
-
acc.right.push({
|
|
604
|
-
streamId: rel.right,
|
|
605
|
-
...base
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
if (rel.right === streamId) {
|
|
609
|
-
acc.left.push({
|
|
610
|
-
streamId: rel.left,
|
|
611
|
-
...base
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
return acc;
|
|
615
|
-
}, { left: [], right: [] });
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
// src/sdk/models/metadata.ts
|
|
619
|
-
var MetadataMap = class extends Map {
|
|
620
|
-
areEquals(array1, array2) {
|
|
621
|
-
return array1.length === array2.length && array1.every(function(value, index) {
|
|
622
|
-
return value === array2[index];
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
findExistingEntry(key) {
|
|
626
|
-
return Array.from(super.entries()).find((entry) => {
|
|
627
|
-
if (this.areEquals(entry[0], key)) {
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
get(key) {
|
|
633
|
-
const existingEntry = this.findExistingEntry(key);
|
|
634
|
-
return existingEntry?.[1];
|
|
635
|
-
}
|
|
636
|
-
set(key, value) {
|
|
637
|
-
const existingKey = this.findExistingEntry(key);
|
|
638
|
-
if (existingKey) {
|
|
639
|
-
return super.set(existingKey?.[0], value);
|
|
640
|
-
} else {
|
|
641
|
-
return super.set(key, value);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
var StreamMetadata = class {
|
|
646
|
-
metadataByBreadcrumb;
|
|
647
|
-
constructor() {
|
|
648
|
-
this.metadataByBreadcrumb = new MetadataMap();
|
|
649
|
-
}
|
|
650
|
-
fromCatalogStreamMetadata(metadataArr) {
|
|
651
|
-
metadataArr.forEach((metadata) => {
|
|
652
|
-
this.metadataByBreadcrumb.set(metadata.breadcrumb, metadata.metadata);
|
|
653
|
-
});
|
|
654
|
-
return this;
|
|
655
|
-
}
|
|
656
|
-
toString() {
|
|
657
|
-
return this.metadataByBreadcrumb;
|
|
658
|
-
}
|
|
659
|
-
get(breadcrumb) {
|
|
660
|
-
return this.metadataByBreadcrumb.get(breadcrumb);
|
|
661
|
-
}
|
|
662
|
-
write(breadcrumb, key, value) {
|
|
663
|
-
if (value === void 0) {
|
|
664
|
-
throw new Error(`The value you're trying to write on the metadata at breadcrumb: \`${breadcrumb}\` and key: \`${key}\` is undefined.
|
|
665
|
-
|
|
666
|
-
Writing an undefined value is not a valid operation. Is there something wrong with your value?`);
|
|
667
|
-
}
|
|
668
|
-
if (!this.metadataByBreadcrumb.get(breadcrumb)) {
|
|
669
|
-
this.metadataByBreadcrumb.set(breadcrumb, {});
|
|
670
|
-
}
|
|
671
|
-
const previousValue = this.metadataByBreadcrumb.get(breadcrumb);
|
|
672
|
-
const newValue = {};
|
|
673
|
-
newValue[key] = value;
|
|
674
|
-
this.metadataByBreadcrumb.set(breadcrumb, { ...previousValue, ...newValue });
|
|
675
|
-
return this;
|
|
676
|
-
}
|
|
677
|
-
getRootBreadcrumbMetadata() {
|
|
678
|
-
const rootBreadcrumbMetadata = this.metadataByBreadcrumb.get([]);
|
|
679
|
-
if (!rootBreadcrumbMetadata) {
|
|
680
|
-
throw new Error(`No metadata was attached to the root breadcrumb \`[\`] while we need one to store metadata at the table level.
|
|
681
|
-
|
|
682
|
-
Is your catalog properly generated?`);
|
|
683
|
-
}
|
|
684
|
-
return rootBreadcrumbMetadata;
|
|
685
|
-
}
|
|
686
|
-
isStreamSelected() {
|
|
687
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
688
|
-
if (rootBreadcrumbMetadata.selected === false) {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
if (rootBreadcrumbMetadata.selected === true || rootBreadcrumbMetadata["selected-by-default"] === true) {
|
|
692
|
-
return true;
|
|
693
|
-
}
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
getKeyProperties() {
|
|
697
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
698
|
-
if (!rootBreadcrumbMetadata["table-key-properties"]) {
|
|
699
|
-
return [];
|
|
700
|
-
}
|
|
701
|
-
return rootBreadcrumbMetadata["table-key-properties"];
|
|
702
|
-
}
|
|
703
|
-
getReplicationKey() {
|
|
704
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
705
|
-
if (!rootBreadcrumbMetadata["replication-key"]) {
|
|
706
|
-
return void 0;
|
|
707
|
-
}
|
|
708
|
-
return rootBreadcrumbMetadata["replication-key"];
|
|
709
|
-
}
|
|
710
|
-
getReplicationMethod() {
|
|
711
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
712
|
-
if (!rootBreadcrumbMetadata["replication-method"]) {
|
|
713
|
-
return void 0;
|
|
714
|
-
}
|
|
715
|
-
return rootBreadcrumbMetadata["replication-method"];
|
|
716
|
-
}
|
|
717
|
-
getForcedReplicationMethod() {
|
|
718
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
719
|
-
if (!rootBreadcrumbMetadata["forced-replication-method"]) {
|
|
720
|
-
return void 0;
|
|
721
|
-
}
|
|
722
|
-
return rootBreadcrumbMetadata["forced-replication-method"];
|
|
723
|
-
}
|
|
724
|
-
toList() {
|
|
725
|
-
return Array.from(this.metadataByBreadcrumb.keys()).map((breadcrumb) => {
|
|
726
|
-
const metadata = this.metadataByBreadcrumb.get(breadcrumb);
|
|
727
|
-
if (!metadata) {
|
|
728
|
-
throw new Error(`There was no metadata entry for breadcrumb: \`${breadcrumb}\` in metadata map: ${this.metadataByBreadcrumb.toString()}`);
|
|
729
|
-
}
|
|
730
|
-
return {
|
|
731
|
-
breadcrumb,
|
|
732
|
-
metadata
|
|
733
|
-
};
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
|
|
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
|
-
};
|
|
379
|
+
// src/sdk/models/replication.ts
|
|
380
|
+
var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
|
|
381
|
+
ReplicationMethod2["INCREMENTAL"] = "INCREMENTAL";
|
|
382
|
+
ReplicationMethod2["FULL_TABLE"] = "FULL_TABLE";
|
|
383
|
+
return ReplicationMethod2;
|
|
384
|
+
})(ReplicationMethod || {});
|
|
790
385
|
|
|
791
386
|
// src/sdk/models/messages.ts
|
|
792
387
|
var RecordMessage = class {
|
|
@@ -846,7 +441,6 @@ var SchemaMessage = class {
|
|
|
846
441
|
displayLabel;
|
|
847
442
|
description;
|
|
848
443
|
propertiesMetadata;
|
|
849
|
-
relationships;
|
|
850
444
|
constructor(opts) {
|
|
851
445
|
const {
|
|
852
446
|
stream,
|
|
@@ -855,7 +449,6 @@ var SchemaMessage = class {
|
|
|
855
449
|
bookmarkProperties,
|
|
856
450
|
displayLabel,
|
|
857
451
|
description,
|
|
858
|
-
relationships,
|
|
859
452
|
propertiesMetadata
|
|
860
453
|
} = opts;
|
|
861
454
|
this.stream = stream;
|
|
@@ -864,7 +457,6 @@ var SchemaMessage = class {
|
|
|
864
457
|
this.bookmarkProperties = bookmarkProperties;
|
|
865
458
|
this.displayLabel = displayLabel;
|
|
866
459
|
this.description = description;
|
|
867
|
-
this.relationships = relationships;
|
|
868
460
|
this.propertiesMetadata = propertiesMetadata;
|
|
869
461
|
}
|
|
870
462
|
toString() {
|
|
@@ -874,8 +466,7 @@ var SchemaMessage = class {
|
|
|
874
466
|
schema: this.schema,
|
|
875
467
|
key_properties: this.keyProperties,
|
|
876
468
|
label: this.displayLabel,
|
|
877
|
-
description: this.description
|
|
878
|
-
relationships: this.relationships
|
|
469
|
+
description: this.description
|
|
879
470
|
};
|
|
880
471
|
if (this.bookmarkProperties) {
|
|
881
472
|
result["bookmark_properties"] = this.bookmarkProperties;
|
|
@@ -885,27 +476,27 @@ var SchemaMessage = class {
|
|
|
885
476
|
};
|
|
886
477
|
|
|
887
478
|
// src/sdk/models/state.ts
|
|
888
|
-
import
|
|
479
|
+
import dayjs from "dayjs";
|
|
889
480
|
|
|
890
481
|
// src/sdk/constants/date.ts
|
|
891
482
|
var defaultDateTimeFormat = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ";
|
|
892
483
|
|
|
893
484
|
// src/sdk/models/state.ts
|
|
894
|
-
import { format } from "util";
|
|
485
|
+
import { format as format2 } from "util";
|
|
895
486
|
var PROGRESS_MARKERS = "progressMarkers";
|
|
896
487
|
var PROGRESS_MARKER_NOTE = "Note";
|
|
897
488
|
var SIGNPOST_MARKER = "replicationKeySignpost";
|
|
898
489
|
var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
899
490
|
let newRkValue = latestRecord[replicationKey];
|
|
900
491
|
if (!newRkValue) {
|
|
901
|
-
throw new Error(
|
|
492
|
+
throw new Error(format2(`No value for replicationKey: ${replicationKey} in record with keys: %j`, Object.keys(latestRecord)));
|
|
902
493
|
}
|
|
903
|
-
let newRkValueMoment =
|
|
494
|
+
let newRkValueMoment = dayjs(newRkValue);
|
|
904
495
|
let prevRkValue;
|
|
905
496
|
let prevRkValueMoment;
|
|
906
497
|
if (isSorted) {
|
|
907
498
|
prevRkValue = state["replicationKeyValue"];
|
|
908
|
-
prevRkValueMoment =
|
|
499
|
+
prevRkValueMoment = dayjs(prevRkValue);
|
|
909
500
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
910
501
|
throw new Error(
|
|
911
502
|
`Unsorted data detected in stream. Latest value '${newRkValue}' is
|
|
@@ -920,24 +511,24 @@ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
|
920
511
|
state[PROGRESS_MARKERS] = marker;
|
|
921
512
|
}
|
|
922
513
|
prevRkValue = state[PROGRESS_MARKERS]["replicationKeyValue"];
|
|
923
|
-
prevRkValueMoment =
|
|
514
|
+
prevRkValueMoment = dayjs(prevRkValue || state["replicationKeyValue"]);
|
|
924
515
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
925
516
|
newRkValueMoment = prevRkValueMoment;
|
|
926
517
|
newRkValue = prevRkValue;
|
|
927
518
|
}
|
|
928
519
|
}
|
|
929
520
|
const signpostMarker = state[SIGNPOST_MARKER];
|
|
930
|
-
const replicationKeySignpostMoment =
|
|
521
|
+
const replicationKeySignpostMoment = dayjs(signpostMarker);
|
|
931
522
|
if (replicationKeySignpostMoment && replicationKeySignpostMoment.isBefore(newRkValueMoment)) {
|
|
932
523
|
newRkValueMoment = replicationKeySignpostMoment;
|
|
933
524
|
newRkValue = replicationKeySignpostMoment.format(defaultDateTimeFormat);
|
|
934
525
|
}
|
|
935
526
|
if (isSorted === false) {
|
|
936
527
|
state[PROGRESS_MARKERS]["replicationKey"] = replicationKey;
|
|
937
|
-
state[PROGRESS_MARKERS]["replicationKeyValue"] =
|
|
528
|
+
state[PROGRESS_MARKERS]["replicationKeyValue"] = dayjs(newRkValueMoment).format(defaultDateTimeFormat);
|
|
938
529
|
} else {
|
|
939
530
|
state["replicationKey"] = replicationKey;
|
|
940
|
-
state["replicationKeyValue"] =
|
|
531
|
+
state["replicationKeyValue"] = dayjs(newRkValueMoment).format(defaultDateTimeFormat);
|
|
941
532
|
}
|
|
942
533
|
return state;
|
|
943
534
|
};
|
|
@@ -959,7 +550,7 @@ var finalizeStateProgressMarkers = (state) => {
|
|
|
959
550
|
return state;
|
|
960
551
|
};
|
|
961
552
|
var _greaterThan = (aValue, bValue) => {
|
|
962
|
-
return
|
|
553
|
+
return dayjs(aValue).isAfter(dayjs(bValue));
|
|
963
554
|
};
|
|
964
555
|
var extractStateForStream = (state, streamId) => {
|
|
965
556
|
if (!state.bookmarks) {
|
|
@@ -982,26 +573,16 @@ var StateService = class {
|
|
|
982
573
|
}
|
|
983
574
|
return this.instance;
|
|
984
575
|
}
|
|
985
|
-
|
|
986
|
-
setBookmark(streamId, ts) {
|
|
987
|
-
this.bookmarks[streamId] = ts.format(defaultDateTimeFormat);
|
|
988
|
-
}
|
|
989
|
-
setBookmarkV2(streamId, streamState) {
|
|
576
|
+
setBookmark(streamId, streamState) {
|
|
990
577
|
this.bookmarks[streamId] = streamState;
|
|
991
578
|
}
|
|
992
|
-
|
|
579
|
+
setBookmarkSignpost(streamId, signpostValue) {
|
|
993
580
|
if (!this.bookmarks[streamId]) {
|
|
994
581
|
this.bookmarks[streamId] = {};
|
|
995
582
|
}
|
|
996
583
|
this.bookmarks[streamId][SIGNPOST_MARKER] = signpostValue;
|
|
997
584
|
}
|
|
998
585
|
getBookmark(streamId) {
|
|
999
|
-
if (!this.bookmarks[streamId]) {
|
|
1000
|
-
return moment(0);
|
|
1001
|
-
}
|
|
1002
|
-
return moment(this.bookmarks[streamId]);
|
|
1003
|
-
}
|
|
1004
|
-
getBookmarkV2(streamId) {
|
|
1005
586
|
if (this.bookmarks[streamId]) {
|
|
1006
587
|
return this.bookmarks[streamId];
|
|
1007
588
|
} else {
|
|
@@ -1041,7 +622,7 @@ import axios2 from "axios";
|
|
|
1041
622
|
import * as qs2 from "qs";
|
|
1042
623
|
|
|
1043
624
|
// src/sdk/models/tap/stream.ts
|
|
1044
|
-
import
|
|
625
|
+
import dayjs2 from "dayjs";
|
|
1045
626
|
|
|
1046
627
|
// src/sdk/helpers/typing.ts
|
|
1047
628
|
var isDatetimeType = (typeDef) => {
|
|
@@ -1064,186 +645,56 @@ var isDatetimeType = (typeDef) => {
|
|
|
1064
645
|
};
|
|
1065
646
|
|
|
1066
647
|
// src/sdk/models/tap/stream.ts
|
|
1067
|
-
import
|
|
1068
|
-
import
|
|
648
|
+
import cloneDeep2 from "lodash/cloneDeep.js";
|
|
649
|
+
import Bluebird2 from "bluebird";
|
|
1069
650
|
|
|
1070
651
|
// src/sdk/models/tap/tap.ts
|
|
1071
|
-
import
|
|
1072
|
-
import
|
|
652
|
+
import cloneDeep from "lodash/cloneDeep.js";
|
|
653
|
+
import Bluebird from "bluebird";
|
|
1073
654
|
var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
|
|
1074
655
|
var Tap = class {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
this.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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() };
|
|
656
|
+
target;
|
|
657
|
+
config;
|
|
658
|
+
streams;
|
|
659
|
+
concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
|
|
660
|
+
stateProvider;
|
|
661
|
+
tapState;
|
|
662
|
+
constructor(target, config, stateProvider) {
|
|
663
|
+
this.streams = [];
|
|
664
|
+
this.target = target;
|
|
665
|
+
this.config = config;
|
|
666
|
+
this.stateProvider = stateProvider;
|
|
667
|
+
this.tapState = { bookmarks: {} };
|
|
1214
668
|
}
|
|
1215
|
-
async
|
|
669
|
+
sync = async (options) => {
|
|
670
|
+
const initialState = await this.stateProvider.getState();
|
|
671
|
+
if (initialState.state && initialState.state.bookmarks) {
|
|
672
|
+
this.tapState = cloneDeep(initialState.state);
|
|
673
|
+
}
|
|
674
|
+
await this.init();
|
|
1216
675
|
logger.info(`\u{1F680} Start syncing`);
|
|
1217
676
|
const state = StateService.getInstance().get();
|
|
1218
677
|
logger.info(`\u{1F4CD} Received state: %j`, state);
|
|
1219
|
-
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
return stream.sync();
|
|
1240
|
-
}
|
|
1241
|
-
},
|
|
1242
|
-
{
|
|
1243
|
-
concurrency: this._concurrency
|
|
1244
|
-
}
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
678
|
+
const envInclude = process.env.TAP_STREAMS ? process.env.TAP_STREAMS.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
679
|
+
const include = options?.include && options.include.length > 0 ? options.include : envInclude;
|
|
680
|
+
const exclude = new Set((options?.exclude || []).map((s) => s.trim()));
|
|
681
|
+
let streamsToRun = this.streams.filter((s) => !s.isSilent);
|
|
682
|
+
if (include && include.length > 0) {
|
|
683
|
+
const includeSet = new Set(include);
|
|
684
|
+
streamsToRun = streamsToRun.filter((s) => includeSet.has(s.streamId));
|
|
685
|
+
}
|
|
686
|
+
if (exclude.size > 0) {
|
|
687
|
+
streamsToRun = streamsToRun.filter((s) => !exclude.has(s.streamId));
|
|
688
|
+
}
|
|
689
|
+
const selectedStreamIds = streamsToRun.map((s) => s.streamId);
|
|
690
|
+
logger.info(`\u{1F973} Syncing streams: %s`, selectedStreamIds.join(","));
|
|
691
|
+
await Bluebird.map(streamsToRun, (stream) => {
|
|
692
|
+
return stream.sync();
|
|
693
|
+
}, { concurrency: this.concurrency });
|
|
694
|
+
logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
|
|
695
|
+
await this.target.complete();
|
|
696
|
+
return Promise.resolve();
|
|
697
|
+
};
|
|
1247
698
|
// Used to clean up any ressource before exit
|
|
1248
699
|
end() {
|
|
1249
700
|
return Promise.resolve();
|
|
@@ -1251,19 +702,13 @@ var Tap = class {
|
|
|
1251
702
|
};
|
|
1252
703
|
|
|
1253
704
|
// src/sdk/models/tap/stream.ts
|
|
1254
|
-
import { format as
|
|
1255
|
-
var
|
|
705
|
+
import { format as format3 } from "util";
|
|
706
|
+
var Stream = class {
|
|
1256
707
|
// Runtime values, can't be overriden
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
// Contains the root metadata entry of the catalog stream configuration
|
|
1262
|
-
// contains some end user schema configuration
|
|
1263
|
-
rootMetadataEntry;
|
|
1264
|
-
// Compile time values, can be overriden
|
|
1265
|
-
// Replication method that can be forced in the stream implemenation. Will prevail over any user/catalog choice
|
|
1266
|
-
forcedReplicationMethod = void 0;
|
|
708
|
+
config;
|
|
709
|
+
tapState;
|
|
710
|
+
target;
|
|
711
|
+
replicationMethod = void 0;
|
|
1267
712
|
selectedByDefault = true;
|
|
1268
713
|
STATE_MSG_FREQUENCY = 10;
|
|
1269
714
|
streamId = "default";
|
|
@@ -1271,10 +716,9 @@ var StreamV2 = class {
|
|
|
1271
716
|
displayLabel = void 0;
|
|
1272
717
|
description = void 0;
|
|
1273
718
|
primaryKey = [];
|
|
1274
|
-
|
|
719
|
+
replicationKey = void 0;
|
|
1275
720
|
// When set, use the state from another stream for this stream
|
|
1276
721
|
useStateFromStreamId = void 0;
|
|
1277
|
-
relationships = [];
|
|
1278
722
|
children = [];
|
|
1279
723
|
// If true, it means that the records in this stream are sorted by replicationKey.
|
|
1280
724
|
// If false, then the records are coming in an unsorted manner.
|
|
@@ -1286,11 +730,10 @@ var StreamV2 = class {
|
|
|
1286
730
|
// Metrics configuration
|
|
1287
731
|
rowsSyncedMetricsConf;
|
|
1288
732
|
executionTimeMetricsConf;
|
|
1289
|
-
constructor(
|
|
1290
|
-
this.
|
|
1291
|
-
this.
|
|
1292
|
-
this.
|
|
1293
|
-
this._target = target;
|
|
733
|
+
constructor(config, tapState, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
734
|
+
this.config = config;
|
|
735
|
+
this.tapState = cloneDeep2(tapState);
|
|
736
|
+
this.target = target;
|
|
1294
737
|
this.childConcurrency = childConcurrency;
|
|
1295
738
|
this.rowsSyncedMetricsConf = {
|
|
1296
739
|
isEnabled: () => !this.isSilent,
|
|
@@ -1301,9 +744,7 @@ var StreamV2 = class {
|
|
|
1301
744
|
getStreamIds: () => [this.streamId]
|
|
1302
745
|
};
|
|
1303
746
|
}
|
|
1304
|
-
|
|
1305
|
-
this.rootMetadataEntry = rootMetadataEntry;
|
|
1306
|
-
};
|
|
747
|
+
// No root metadata: replication config is derived from stream defaults only
|
|
1307
748
|
/**
|
|
1308
749
|
* Sync this stream
|
|
1309
750
|
* Called only for streams with no parents, aka. "root tap streams"
|
|
@@ -1323,7 +764,7 @@ var StreamV2 = class {
|
|
|
1323
764
|
return this.flush();
|
|
1324
765
|
} catch (err) {
|
|
1325
766
|
if (err instanceof Error) {
|
|
1326
|
-
throw new Error(
|
|
767
|
+
throw new Error(format3(`got an error while syncing streamId=%s, err: %s`, this.streamId, err.message));
|
|
1327
768
|
}
|
|
1328
769
|
throw err;
|
|
1329
770
|
}
|
|
@@ -1334,7 +775,7 @@ var StreamV2 = class {
|
|
|
1334
775
|
`\u{1F6BD} Flushing ${replicationMethod} sync of stream '${this.streamId}' + all of its children...`
|
|
1335
776
|
);
|
|
1336
777
|
await this._flush();
|
|
1337
|
-
await
|
|
778
|
+
await Bluebird2.map(this.children, (child) => {
|
|
1338
779
|
return child.flush();
|
|
1339
780
|
}, { concurrency: 3 });
|
|
1340
781
|
};
|
|
@@ -1344,7 +785,7 @@ var StreamV2 = class {
|
|
|
1344
785
|
*/
|
|
1345
786
|
_writeStateMessage = () => {
|
|
1346
787
|
const message = new StateMessage(StateService.getInstance().get());
|
|
1347
|
-
return this.
|
|
788
|
+
return this.target.state(message);
|
|
1348
789
|
};
|
|
1349
790
|
/**
|
|
1350
791
|
* Write out a ACTIVATE_VERSION message.
|
|
@@ -1352,10 +793,10 @@ var StreamV2 = class {
|
|
|
1352
793
|
_writeReplicationMethodMessage = async () => {
|
|
1353
794
|
const method = this.configuredReplicationConfig().replicationMethod;
|
|
1354
795
|
const message = new ReplicationMethodMessage(this.streamId, method);
|
|
1355
|
-
await
|
|
796
|
+
await Bluebird2.map(this.children, async (c) => {
|
|
1356
797
|
return c._writeReplicationMethodMessage();
|
|
1357
798
|
}, { concurrency: 3 });
|
|
1358
|
-
await this.
|
|
799
|
+
await this.target.replicationMethod(message);
|
|
1359
800
|
};
|
|
1360
801
|
/**
|
|
1361
802
|
* Write out a SCHEMA message with the stream schema.
|
|
@@ -1364,23 +805,21 @@ var StreamV2 = class {
|
|
|
1364
805
|
if (!this.isSilent) {
|
|
1365
806
|
const schema = await this.getSchema();
|
|
1366
807
|
if (schema !== void 0) {
|
|
1367
|
-
const relatedRels = findRelatedRelationships(this.streamId, this.relationships);
|
|
1368
808
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1369
809
|
const message = new SchemaMessage({
|
|
1370
810
|
keyProperties: this.primaryKey,
|
|
1371
811
|
stream: this.streamId,
|
|
1372
812
|
schema: schema.jsonSchema,
|
|
1373
|
-
relationships: relatedRels,
|
|
1374
813
|
...replicationConfig.replicationKey ? { bookmarkProperties: [replicationConfig.replicationKey] } : {},
|
|
1375
814
|
...this.displayLabel ? { displayLabel: this.displayLabel } : {},
|
|
1376
815
|
...this.description ? { description: this.description } : {},
|
|
1377
816
|
...schema.propertiesMetadata ? { propertiesMetadata: schema.propertiesMetadata } : {}
|
|
1378
817
|
});
|
|
1379
|
-
await this.
|
|
818
|
+
await this.target.schema(message);
|
|
1380
819
|
}
|
|
1381
820
|
}
|
|
1382
821
|
if (skipChildrenSchema !== true) {
|
|
1383
|
-
await
|
|
822
|
+
await Bluebird2.map(this.children, async (child) => {
|
|
1384
823
|
await child._writeSchemaMessage();
|
|
1385
824
|
}, { concurrency: 3 });
|
|
1386
825
|
}
|
|
@@ -1390,7 +829,7 @@ var StreamV2 = class {
|
|
|
1390
829
|
const signpostMoment = await this.getReplicationKeySignpost();
|
|
1391
830
|
const signpostValue = signpostMoment?.format(defaultDateTimeFormat);
|
|
1392
831
|
if (signpostValue) {
|
|
1393
|
-
StateService.getInstance().
|
|
832
|
+
StateService.getInstance().setBookmarkSignpost(this.streamId, signpostValue);
|
|
1394
833
|
}
|
|
1395
834
|
};
|
|
1396
835
|
/**
|
|
@@ -1406,7 +845,7 @@ var StreamV2 = class {
|
|
|
1406
845
|
if (this.useStateFromStreamId) {
|
|
1407
846
|
return;
|
|
1408
847
|
}
|
|
1409
|
-
if (this.
|
|
848
|
+
if (this.replicationMethod && !this.replicationKey) {
|
|
1410
849
|
return;
|
|
1411
850
|
}
|
|
1412
851
|
if (!replicationConfig.replicationKey) {
|
|
@@ -1416,14 +855,14 @@ var StreamV2 = class {
|
|
|
1416
855
|
);
|
|
1417
856
|
}
|
|
1418
857
|
const stateServiceInst = StateService.getInstance();
|
|
1419
|
-
const state = stateServiceInst.
|
|
858
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1420
859
|
const newState = incrementStreamState(
|
|
1421
860
|
state,
|
|
1422
861
|
replicationConfig.replicationKey,
|
|
1423
862
|
latestRecord,
|
|
1424
863
|
this.isSorted
|
|
1425
864
|
);
|
|
1426
|
-
StateService.getInstance().
|
|
865
|
+
StateService.getInstance().setBookmark(this.streamId, newState);
|
|
1427
866
|
}
|
|
1428
867
|
}
|
|
1429
868
|
};
|
|
@@ -1455,27 +894,27 @@ var StreamV2 = class {
|
|
|
1455
894
|
**/
|
|
1456
895
|
getStartingTimestamp() {
|
|
1457
896
|
const streamIdToReadFrom = this.useStateFromStreamId ? this.useStateFromStreamId : this.streamId;
|
|
1458
|
-
const state = extractStateForStream(this.
|
|
1459
|
-
const startDate = this.
|
|
897
|
+
const state = extractStateForStream(this.tapState, streamIdToReadFrom);
|
|
898
|
+
const startDate = this.config.start_date;
|
|
1460
899
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1461
900
|
logger.debug(`${this.streamId}: state.replicationKeyValue: ${state.replicationKeyValue},
|
|
1462
901
|
state.replicationKey: ${state.replicationKey},
|
|
1463
902
|
this.replicationKey: ${replicationConfig.replicationKey}`);
|
|
1464
903
|
if (state.replicationKeyValue) {
|
|
1465
904
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning replication key value: ${state.replicationKeyValue}`);
|
|
1466
|
-
return
|
|
905
|
+
return dayjs2(state.replicationKeyValue);
|
|
1467
906
|
} else if (startDate) {
|
|
1468
907
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning config start date: ${startDate}`);
|
|
1469
|
-
return
|
|
908
|
+
return dayjs2(startDate);
|
|
1470
909
|
} else {
|
|
1471
910
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning EPOCH (1970)`);
|
|
1472
|
-
return
|
|
911
|
+
return dayjs2(0);
|
|
1473
912
|
}
|
|
1474
913
|
}
|
|
1475
914
|
/**
|
|
1476
915
|
* Return the max allowable bookmark value for this stream's replication key.
|
|
1477
916
|
*
|
|
1478
|
-
* For timestamp-based replication keys, this defaults to `
|
|
917
|
+
* For timestamp-based replication keys, this defaults to `dayjs()`. For
|
|
1479
918
|
* non-timestamp replication keys, default to `undefined`.
|
|
1480
919
|
*
|
|
1481
920
|
* Override this value to prevent bookmarks from being advanced in cases where we
|
|
@@ -1483,7 +922,7 @@ var StreamV2 = class {
|
|
|
1483
922
|
*/
|
|
1484
923
|
getReplicationKeySignpost = async () => {
|
|
1485
924
|
if (await this.isTimestampReplicationKey()) {
|
|
1486
|
-
return
|
|
925
|
+
return dayjs2();
|
|
1487
926
|
}
|
|
1488
927
|
return void 0;
|
|
1489
928
|
};
|
|
@@ -1491,22 +930,22 @@ var StreamV2 = class {
|
|
|
1491
930
|
* Return the default replication method for the stream that will be used to write the catalog.
|
|
1492
931
|
*/
|
|
1493
932
|
defaultReplicationConfig = () => {
|
|
1494
|
-
if (this.
|
|
933
|
+
if (this.replicationMethod) {
|
|
1495
934
|
return {
|
|
1496
|
-
replicationMethod: this.
|
|
1497
|
-
replicationKey: this.
|
|
935
|
+
replicationMethod: this.replicationMethod,
|
|
936
|
+
replicationKey: this.replicationKey,
|
|
1498
937
|
isForced: true
|
|
1499
938
|
};
|
|
1500
939
|
}
|
|
1501
|
-
if (this.
|
|
940
|
+
if (this.replicationKey || this.useStateFromStreamId) {
|
|
1502
941
|
return {
|
|
1503
|
-
replicationMethod: "INCREMENTAL"
|
|
1504
|
-
replicationKey: this.
|
|
942
|
+
replicationMethod: "INCREMENTAL" /* INCREMENTAL */,
|
|
943
|
+
replicationKey: this.replicationKey,
|
|
1505
944
|
isForced: true
|
|
1506
945
|
};
|
|
1507
946
|
}
|
|
1508
947
|
return {
|
|
1509
|
-
replicationMethod: "FULL_TABLE"
|
|
948
|
+
replicationMethod: "FULL_TABLE" /* FULL_TABLE */,
|
|
1510
949
|
replicationKey: void 0,
|
|
1511
950
|
isForced: false
|
|
1512
951
|
};
|
|
@@ -1515,16 +954,10 @@ var StreamV2 = class {
|
|
|
1515
954
|
* Return the configured replication method that will be used for the sync.
|
|
1516
955
|
*/
|
|
1517
956
|
configuredReplicationConfig = () => {
|
|
1518
|
-
const
|
|
1519
|
-
if (defaultReplicationMethod.isForced) {
|
|
1520
|
-
return {
|
|
1521
|
-
replicationMethod: defaultReplicationMethod.replicationMethod,
|
|
1522
|
-
replicationKey: defaultReplicationMethod.replicationKey
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
957
|
+
const defaults = this.defaultReplicationConfig();
|
|
1525
958
|
return {
|
|
1526
|
-
replicationMethod:
|
|
1527
|
-
replicationKey:
|
|
959
|
+
replicationMethod: defaults.replicationMethod,
|
|
960
|
+
replicationKey: defaults.replicationKey
|
|
1528
961
|
};
|
|
1529
962
|
};
|
|
1530
963
|
// Private sync methods:
|
|
@@ -1554,9 +987,9 @@ var StreamV2 = class {
|
|
|
1554
987
|
this.streamId
|
|
1555
988
|
);
|
|
1556
989
|
if (!this.isSilent) {
|
|
1557
|
-
await this.
|
|
990
|
+
await this.target.record(recordMessage);
|
|
1558
991
|
}
|
|
1559
|
-
await
|
|
992
|
+
await Bluebird2.map(
|
|
1560
993
|
this.children,
|
|
1561
994
|
async (child) => {
|
|
1562
995
|
await child.asyncInit(row);
|
|
@@ -1573,9 +1006,9 @@ var StreamV2 = class {
|
|
|
1573
1006
|
rowsSent += 1;
|
|
1574
1007
|
}
|
|
1575
1008
|
const stateServiceInst = StateService.getInstance();
|
|
1576
|
-
const state = stateServiceInst.
|
|
1009
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1577
1010
|
const newState = finalizeStateProgressMarkers(state);
|
|
1578
|
-
stateServiceInst.
|
|
1011
|
+
stateServiceInst.setBookmark(this.streamId, newState);
|
|
1579
1012
|
if (!parent) {
|
|
1580
1013
|
logger.info(`\u2705 Completed sync for stream: ${this.streamId} (${rowsSent} records)`);
|
|
1581
1014
|
} else {
|
|
@@ -1600,7 +1033,7 @@ var StreamV2 = class {
|
|
|
1600
1033
|
getSchema() {
|
|
1601
1034
|
logger.debug(`stream: ${this.streamId} - getSchema() is called`);
|
|
1602
1035
|
if (this.schemaPath) {
|
|
1603
|
-
const schema = loadJson(`schemas/${this.
|
|
1036
|
+
const schema = loadJson(`schemas/${this.schemaPath}`);
|
|
1604
1037
|
return Promise.resolve({ jsonSchema: schema });
|
|
1605
1038
|
} else {
|
|
1606
1039
|
if (this.isSilent) {
|
|
@@ -1637,7 +1070,7 @@ var StreamV2 = class {
|
|
|
1637
1070
|
};
|
|
1638
1071
|
|
|
1639
1072
|
// src/sdk/helpers/qs-logger.ts
|
|
1640
|
-
import
|
|
1073
|
+
import isPlainObject from "lodash/isPlainObject.js";
|
|
1641
1074
|
var MAX_QS_KEYS = 10;
|
|
1642
1075
|
var MAX_QS_STRING_LENGTH = 200;
|
|
1643
1076
|
var MAX_QS_ARRAY_ITEMS = 5;
|
|
@@ -1658,7 +1091,7 @@ function truncateQueryParams(value, depth = 0) {
|
|
|
1658
1091
|
}
|
|
1659
1092
|
return items;
|
|
1660
1093
|
}
|
|
1661
|
-
if (
|
|
1094
|
+
if (isPlainObject(value)) {
|
|
1662
1095
|
const keys = Object.keys(value);
|
|
1663
1096
|
const result = {};
|
|
1664
1097
|
for (const key of keys.slice(0, MAX_QS_KEYS)) {
|
|
@@ -1681,11 +1114,12 @@ function getTruncatedParamsForLog(params) {
|
|
|
1681
1114
|
}
|
|
1682
1115
|
|
|
1683
1116
|
// src/sdk/models/tap/restStream.ts
|
|
1684
|
-
import
|
|
1685
|
-
|
|
1117
|
+
import cloneDeep3 from "lodash/cloneDeep.js";
|
|
1118
|
+
import isEqual from "lodash/isEqual.js";
|
|
1119
|
+
var RESTStream = class extends Stream {
|
|
1686
1120
|
axiosInstance;
|
|
1687
|
-
constructor(
|
|
1688
|
-
super(
|
|
1121
|
+
constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
1122
|
+
super(config, state, target, childConcurrency);
|
|
1689
1123
|
this.apiCallsMetricsConf = {
|
|
1690
1124
|
isEnabled: () => !this.isSilent,
|
|
1691
1125
|
getStreamIds: () => [this.streamId]
|
|
@@ -1701,14 +1135,25 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1701
1135
|
*/
|
|
1702
1136
|
_prepareRequest = async (nextPageToken, parent) => {
|
|
1703
1137
|
const method = this.httpMethod;
|
|
1704
|
-
const url = this.
|
|
1138
|
+
const url = this.getNextUrl(nextPageToken, parent);
|
|
1705
1139
|
if (!url) {
|
|
1706
1140
|
throw new Error(`StreamId: ${this.streamId} - URL is undefined. Did your properly define the URL for this stream?`);
|
|
1707
1141
|
}
|
|
1708
1142
|
const authenticator = this.authenticator;
|
|
1143
|
+
let finalUrl = url;
|
|
1144
|
+
let urlQueryParams = {};
|
|
1145
|
+
const queryIndex = url.indexOf("?");
|
|
1146
|
+
if (queryIndex !== -1) {
|
|
1147
|
+
finalUrl = url.substring(0, queryIndex);
|
|
1148
|
+
const rawQueryString = url.substring(queryIndex + 1);
|
|
1149
|
+
urlQueryParams = qs2.parse(rawQueryString);
|
|
1150
|
+
}
|
|
1709
1151
|
const params = {
|
|
1710
1152
|
...this.getNextUrlParams(nextPageToken, parent),
|
|
1711
|
-
|
|
1153
|
+
// Query params embedded in URL should override computed pagination params
|
|
1154
|
+
// but should not override auth params which are appended last
|
|
1155
|
+
...urlQueryParams,
|
|
1156
|
+
...await authenticator?.getAuthQS(this.config)
|
|
1712
1157
|
};
|
|
1713
1158
|
const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
|
|
1714
1159
|
let headers = this.getCustomHTTPHeaders(parent);
|
|
@@ -1716,7 +1161,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1716
1161
|
headers = {};
|
|
1717
1162
|
}
|
|
1718
1163
|
if (authenticator) {
|
|
1719
|
-
const authHeaders = await authenticator.getAuthHeaders(this.
|
|
1164
|
+
const authHeaders = await authenticator.getAuthHeaders(this.config);
|
|
1720
1165
|
headers = {
|
|
1721
1166
|
...headers,
|
|
1722
1167
|
...authHeaders
|
|
@@ -1726,7 +1171,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1726
1171
|
return qs2.stringify(params2, { arrayFormat: "repeat" });
|
|
1727
1172
|
};
|
|
1728
1173
|
const request = {
|
|
1729
|
-
url,
|
|
1174
|
+
url: finalUrl,
|
|
1730
1175
|
method,
|
|
1731
1176
|
data: requestBody,
|
|
1732
1177
|
params,
|
|
@@ -1799,9 +1244,9 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1799
1244
|
for await (const row of rows) {
|
|
1800
1245
|
yield row;
|
|
1801
1246
|
}
|
|
1802
|
-
const previousToken =
|
|
1247
|
+
const previousToken = cloneDeep3(nextPageToken);
|
|
1803
1248
|
nextPageToken = this.getNextPageToken(resp, previousToken);
|
|
1804
|
-
if (nextPageToken &&
|
|
1249
|
+
if (nextPageToken && isEqual(nextPageToken, previousToken)) {
|
|
1805
1250
|
throw new Error(
|
|
1806
1251
|
`${this.streamId} - Loop detected in pagination. "
|
|
1807
1252
|
Pagination token: \`${JSON.stringify(nextPageToken)}\` is identical to prior token \`${JSON.stringify(previousToken)}\`.`
|
|
@@ -1878,7 +1323,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1878
1323
|
* TODO: Make URL + Path templatisable and replace those with Extractor (or Tap) config
|
|
1879
1324
|
* @returns
|
|
1880
1325
|
*/
|
|
1881
|
-
|
|
1326
|
+
getNextUrl(previousToken, parent) {
|
|
1882
1327
|
const urlPattern = [this.baseUrl, this.path].join("");
|
|
1883
1328
|
return urlPattern;
|
|
1884
1329
|
}
|
|
@@ -2002,20 +1447,20 @@ var StreamWarehouseSyncService = class {
|
|
|
2002
1447
|
const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
|
|
2003
1448
|
const columnsMappingStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2004
1449
|
const columnsToAdd = Object.keys(columnsMappingStore).filter((unsafeColumnKey) => {
|
|
2005
|
-
const
|
|
2006
|
-
if (!
|
|
1450
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
1451
|
+
if (!safeColumnName2) {
|
|
2007
1452
|
return false;
|
|
2008
1453
|
}
|
|
2009
|
-
return !columnNamesFromWarehouse.includes(
|
|
1454
|
+
return !columnNamesFromWarehouse.includes(safeColumnName2);
|
|
2010
1455
|
}).map((unsafeColumnKey) => {
|
|
2011
|
-
const
|
|
1456
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
2012
1457
|
const definition = this.flattenedSchema[unsafeColumnKey];
|
|
2013
|
-
if (!
|
|
1458
|
+
if (!safeColumnName2 || !definition) {
|
|
2014
1459
|
return void 0;
|
|
2015
1460
|
}
|
|
2016
1461
|
return {
|
|
2017
1462
|
unsafeName: unsafeColumnKey,
|
|
2018
|
-
name:
|
|
1463
|
+
name: safeColumnName2,
|
|
2019
1464
|
definition
|
|
2020
1465
|
};
|
|
2021
1466
|
}).filter((item) => !!item);
|
|
@@ -2230,18 +1675,18 @@ var flattenSchema = (streamId, schema) => {
|
|
|
2230
1675
|
|
|
2231
1676
|
// src/sdk/models/target/target.ts
|
|
2232
1677
|
import { Validator } from "jsonschema";
|
|
2233
|
-
import
|
|
1678
|
+
import dayjs4 from "dayjs";
|
|
2234
1679
|
|
|
2235
1680
|
// src/sdk/models/target/streamDbState.ts
|
|
2236
|
-
import
|
|
1681
|
+
import dayjs3 from "dayjs";
|
|
2237
1682
|
|
|
2238
1683
|
// src/sdk/models/target/temporaryFile.ts
|
|
2239
|
-
import
|
|
2240
|
-
import
|
|
1684
|
+
import fs2 from "fs-extra";
|
|
1685
|
+
import uniqueId from "lodash/uniqueId.js";
|
|
2241
1686
|
var createTemporaryFileStream = (streamId) => {
|
|
2242
|
-
|
|
1687
|
+
fs2.ensureDirSync("./tmp");
|
|
2243
1688
|
const path = `./tmp/${safePath(streamId)}-${uniqueId()}.tmp`;
|
|
2244
|
-
const writeStream =
|
|
1689
|
+
const writeStream = fs2.createWriteStream(path, { flags: "w" });
|
|
2245
1690
|
return {
|
|
2246
1691
|
stream: writeStream,
|
|
2247
1692
|
path
|
|
@@ -2289,7 +1734,7 @@ function replaceSymbols(str) {
|
|
|
2289
1734
|
}
|
|
2290
1735
|
|
|
2291
1736
|
// src/sdk/models/target/streamDbState.ts
|
|
2292
|
-
import * as
|
|
1737
|
+
import * as fs3 from "fs";
|
|
2293
1738
|
var StreamState = class {
|
|
2294
1739
|
streamId;
|
|
2295
1740
|
schema;
|
|
@@ -2314,7 +1759,7 @@ var StreamState = class {
|
|
|
2314
1759
|
this.fileToLoad = createTemporaryFileStream(streamId);
|
|
2315
1760
|
this.syncedRowCount = 0;
|
|
2316
1761
|
this.keyProperties = [];
|
|
2317
|
-
this.batchDate =
|
|
1762
|
+
this.batchDate = dayjs3();
|
|
2318
1763
|
this.replicationMethod = replicationMethod;
|
|
2319
1764
|
this._hasBeenLoadedYetDuringThisSync = false;
|
|
2320
1765
|
}
|
|
@@ -2339,7 +1784,7 @@ var StreamState = class {
|
|
|
2339
1784
|
resetFileToLoad() {
|
|
2340
1785
|
this.fileToLoad.stream.close();
|
|
2341
1786
|
const oldPath = this.fileToLoad.path;
|
|
2342
|
-
|
|
1787
|
+
fs3.unlinkSync(oldPath);
|
|
2343
1788
|
this.fileToLoad = createTemporaryFileStream(this.streamId);
|
|
2344
1789
|
this.batchedRowCount = 0;
|
|
2345
1790
|
}
|
|
@@ -2365,13 +1810,13 @@ var StreamState = class {
|
|
|
2365
1810
|
|
|
2366
1811
|
// src/sdk/models/target/target.ts
|
|
2367
1812
|
import { Semaphore } from "async-mutex";
|
|
2368
|
-
import
|
|
1813
|
+
import Bluebird3 from "bluebird";
|
|
2369
1814
|
var semaphore = new Semaphore(1);
|
|
2370
1815
|
var ITarget = class _ITarget {
|
|
2371
1816
|
config;
|
|
2372
1817
|
schemaHooks;
|
|
2373
|
-
resolver;
|
|
2374
1818
|
syncTime;
|
|
1819
|
+
stateProvider;
|
|
2375
1820
|
// Latest state message received from the Tap
|
|
2376
1821
|
// Not yet flushed as we didn't upload the stream data since receiving it
|
|
2377
1822
|
batchedState;
|
|
@@ -2382,15 +1827,15 @@ var ITarget = class _ITarget {
|
|
|
2382
1827
|
streams;
|
|
2383
1828
|
// JSON Schema validator
|
|
2384
1829
|
validator;
|
|
2385
|
-
constructor(config,
|
|
1830
|
+
constructor(config, stateProvider) {
|
|
2386
1831
|
this.config = config;
|
|
2387
|
-
this.
|
|
2388
|
-
this.schemaHooks =
|
|
1832
|
+
this.stateProvider = stateProvider;
|
|
1833
|
+
this.schemaHooks = [];
|
|
2389
1834
|
this.streams = {};
|
|
2390
1835
|
this.batchedState = {};
|
|
2391
1836
|
this.flushedState = {};
|
|
2392
1837
|
this.validator = new Validator();
|
|
2393
|
-
this.syncTime =
|
|
1838
|
+
this.syncTime = dayjs4();
|
|
2394
1839
|
this.renameColumnStore = new RenameColumnStore();
|
|
2395
1840
|
}
|
|
2396
1841
|
///////////////////////////////////////////
|
|
@@ -2499,7 +1944,7 @@ var ITarget = class _ITarget {
|
|
|
2499
1944
|
this.streams[streamId] = new StreamState(
|
|
2500
1945
|
streamId,
|
|
2501
1946
|
dbSyncInstance,
|
|
2502
|
-
replicationMethod || "FULL_TABLE"
|
|
1947
|
+
replicationMethod || "FULL_TABLE" /* FULL_TABLE */
|
|
2503
1948
|
);
|
|
2504
1949
|
}
|
|
2505
1950
|
replicationMethod = (message) => {
|
|
@@ -2576,41 +2021,9 @@ var ITarget = class _ITarget {
|
|
|
2576
2021
|
return translation;
|
|
2577
2022
|
}
|
|
2578
2023
|
return k;
|
|
2579
|
-
})
|
|
2580
|
-
relationships: {
|
|
2581
|
-
left: message.relationships.left.flatMap((l) => {
|
|
2582
|
-
if (this.renameColumnStore.isReady(l.streamId) && this.renameColumnStore.isReady(message.stream)) {
|
|
2583
|
-
const from = this.renameColumnStore.getColumnTranslation(l.streamId, l.from);
|
|
2584
|
-
const to = this.renameColumnStore.getColumnTranslation(message.stream, l.to);
|
|
2585
|
-
if (from && to) {
|
|
2586
|
-
return [{
|
|
2587
|
-
type: l.type,
|
|
2588
|
-
streamId: l.streamId,
|
|
2589
|
-
from,
|
|
2590
|
-
to
|
|
2591
|
-
}];
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
return [];
|
|
2595
|
-
}),
|
|
2596
|
-
right: message.relationships.right.flatMap((r) => {
|
|
2597
|
-
if (this.renameColumnStore.isReady(message.stream) && this.renameColumnStore.isReady(r.streamId)) {
|
|
2598
|
-
const from = this.renameColumnStore.getColumnTranslation(message.stream, r.from);
|
|
2599
|
-
const to = this.renameColumnStore.getColumnTranslation(r.streamId, r.to);
|
|
2600
|
-
if (from && to) {
|
|
2601
|
-
return [{
|
|
2602
|
-
type: r.type,
|
|
2603
|
-
streamId: r.streamId,
|
|
2604
|
-
from,
|
|
2605
|
-
to
|
|
2606
|
-
}];
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
return [];
|
|
2610
|
-
})
|
|
2611
|
-
}
|
|
2024
|
+
})
|
|
2612
2025
|
};
|
|
2613
|
-
await
|
|
2026
|
+
await Bluebird3.map(this.schemaHooks, async (hook) => {
|
|
2614
2027
|
const input = {
|
|
2615
2028
|
databaseName,
|
|
2616
2029
|
schemaName,
|
|
@@ -2641,9 +2054,6 @@ var ITarget = class _ITarget {
|
|
|
2641
2054
|
try {
|
|
2642
2055
|
logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
|
|
2643
2056
|
await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
|
|
2644
|
-
const configResolver = loadResolver();
|
|
2645
|
-
await configResolver.markSyncComplete();
|
|
2646
|
-
await configResolver.flushMetrics();
|
|
2647
2057
|
return Promise.resolve();
|
|
2648
2058
|
} catch (err) {
|
|
2649
2059
|
logger.error(`Error while handling complete event.`);
|
|
@@ -2686,12 +2096,11 @@ var ITarget = class _ITarget {
|
|
|
2686
2096
|
}
|
|
2687
2097
|
};
|
|
2688
2098
|
emitState = (state) => {
|
|
2689
|
-
|
|
2690
|
-
return writeStateFile(configResolver, JSON.stringify(state));
|
|
2099
|
+
return this.stateProvider.writeState(JSON.stringify(state));
|
|
2691
2100
|
};
|
|
2692
2101
|
uploadStreamsToWarehouse = async (streamsToUpload) => {
|
|
2693
2102
|
try {
|
|
2694
|
-
await
|
|
2103
|
+
await Bluebird3.map(streamsToUpload, async (streamId) => {
|
|
2695
2104
|
logger.info(`\u{1F4E4} Upload stream: ${streamId} to Warehouse`);
|
|
2696
2105
|
const streamState = this.streams[streamId];
|
|
2697
2106
|
if (streamState) {
|
|
@@ -2725,39 +2134,731 @@ var ITarget = class _ITarget {
|
|
|
2725
2134
|
}
|
|
2726
2135
|
};
|
|
2727
2136
|
};
|
|
2137
|
+
|
|
2138
|
+
// src/targets/bigquery/helpers.ts
|
|
2139
|
+
var safeColumnName = (key) => {
|
|
2140
|
+
let returnString = key;
|
|
2141
|
+
const shouldNotStartWith = ["_TABLE_", "_FILE_", "_PARTITION"];
|
|
2142
|
+
shouldNotStartWith.forEach((snm) => {
|
|
2143
|
+
if (returnString.startsWith(snm)) {
|
|
2144
|
+
returnString = returnString.replace(snm, "");
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
returnString = returnString.replace(/^[-\d\s]*/g, "");
|
|
2148
|
+
returnString = returnString.toLowerCase();
|
|
2149
|
+
returnString = returnString.replace("`", "");
|
|
2150
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2151
|
+
if (returnString.length > 300) {
|
|
2152
|
+
returnString = returnString.substr(0, 300);
|
|
2153
|
+
}
|
|
2154
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2155
|
+
return returnString.replace(pattern, "_");
|
|
2156
|
+
};
|
|
2157
|
+
var safeTableName = (key) => {
|
|
2158
|
+
let returnString = key;
|
|
2159
|
+
returnString = returnString.toLowerCase();
|
|
2160
|
+
returnString = returnString.replace("`", "");
|
|
2161
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2162
|
+
if (returnString.length > 1024) {
|
|
2163
|
+
returnString = returnString.substr(0, 1024);
|
|
2164
|
+
}
|
|
2165
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2166
|
+
return returnString.replace(pattern, "_");
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
// src/targets/bigquery/service/record.ts
|
|
2170
|
+
import Decimal from "decimal.js";
|
|
2171
|
+
import dayjs5 from "dayjs";
|
|
2172
|
+
var validateDateRange = (record, schema) => {
|
|
2173
|
+
Object.keys(schema).forEach((key) => {
|
|
2174
|
+
const { format: format6 } = schema[key];
|
|
2175
|
+
if (format6 === "date-time") {
|
|
2176
|
+
if (record[key]) {
|
|
2177
|
+
const formattedDate = dayjs5(record[key]).format(defaultDateTimeFormat);
|
|
2178
|
+
const isNotTooMuchInTheFuture = dayjs5(record[key]).isBefore(dayjs5("9999-12-31 23:59:59.999999"));
|
|
2179
|
+
const isNotTooMuchInThePast = dayjs5(record[key]).isAfter(dayjs5("0001-01-01 00:00:00"));
|
|
2180
|
+
if (formattedDate !== "Invalid date" && isNotTooMuchInTheFuture && isNotTooMuchInThePast) {
|
|
2181
|
+
record[key] = formattedDate;
|
|
2182
|
+
} else {
|
|
2183
|
+
record[key] = void 0;
|
|
2184
|
+
logger.info("Skipping date for key %s for being invalid", key);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
return record;
|
|
2190
|
+
};
|
|
2191
|
+
var maxBQNumericValue = new Decimal("9.9999999999999999999999999999999999999E+28");
|
|
2192
|
+
var convertNumberIntoDecimal = (record, schema) => {
|
|
2193
|
+
Object.keys(record).forEach((key) => {
|
|
2194
|
+
if (schema[key]) {
|
|
2195
|
+
const { type, items } = schema[key];
|
|
2196
|
+
const types = type instanceof Array ? type : [type];
|
|
2197
|
+
if (types.includes("number")) {
|
|
2198
|
+
if (typeof record[key] === "number" || record[key] instanceof Decimal) {
|
|
2199
|
+
const decimal = new Decimal(record[key]).toDecimalPlaces(9);
|
|
2200
|
+
if (decimal.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2201
|
+
record[key] = null;
|
|
2202
|
+
} else {
|
|
2203
|
+
record[key] = decimal;
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
record[key] = null;
|
|
2207
|
+
}
|
|
2208
|
+
} else if (types.includes("array") && items?.type.includes("number")) {
|
|
2209
|
+
if (Array.isArray(record[key])) {
|
|
2210
|
+
const rawArr = record[key];
|
|
2211
|
+
const newArr = rawArr.map((item) => {
|
|
2212
|
+
if (typeof item === "number" || item instanceof Decimal) {
|
|
2213
|
+
const parsed = new Decimal(item).toDecimalPlaces(9);
|
|
2214
|
+
if (parsed.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2215
|
+
return void 0;
|
|
2216
|
+
} else {
|
|
2217
|
+
return parsed;
|
|
2218
|
+
}
|
|
2219
|
+
} else {
|
|
2220
|
+
return void 0;
|
|
2221
|
+
}
|
|
2222
|
+
}).filter((item) => !!item);
|
|
2223
|
+
record[key] = newArr;
|
|
2224
|
+
} else {
|
|
2225
|
+
record[key] = null;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
return record;
|
|
2231
|
+
};
|
|
2232
|
+
|
|
2233
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2234
|
+
import dayjs6 from "dayjs";
|
|
2235
|
+
|
|
2236
|
+
// src/targets/bigquery/service/bigquery.ts
|
|
2237
|
+
import { BigQuery } from "@google-cloud/bigquery";
|
|
2238
|
+
var BqClientHolder = class {
|
|
2239
|
+
static instance;
|
|
2240
|
+
client;
|
|
2241
|
+
constructor(config) {
|
|
2242
|
+
const retryOptions = {
|
|
2243
|
+
autoRetry: true,
|
|
2244
|
+
maxRetries: 3
|
|
2245
|
+
};
|
|
2246
|
+
this.client = new BigQuery({
|
|
2247
|
+
projectId: config.database.trim(),
|
|
2248
|
+
...retryOptions
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
static client(config) {
|
|
2252
|
+
if (!this.instance) {
|
|
2253
|
+
this.instance = new this(config);
|
|
2254
|
+
}
|
|
2255
|
+
return this.instance.client;
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
// src/targets/bigquery/service/cloudStorage.ts
|
|
2260
|
+
import { Storage } from "@google-cloud/storage";
|
|
2261
|
+
|
|
2262
|
+
// node_modules/uuid/dist/esm-node/rng.js
|
|
2263
|
+
import crypto from "crypto";
|
|
2264
|
+
var rnds8Pool = new Uint8Array(256);
|
|
2265
|
+
var poolPtr = rnds8Pool.length;
|
|
2266
|
+
function rng() {
|
|
2267
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
2268
|
+
crypto.randomFillSync(rnds8Pool);
|
|
2269
|
+
poolPtr = 0;
|
|
2270
|
+
}
|
|
2271
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// node_modules/uuid/dist/esm-node/regex.js
|
|
2275
|
+
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;
|
|
2276
|
+
|
|
2277
|
+
// node_modules/uuid/dist/esm-node/validate.js
|
|
2278
|
+
function validate(uuid) {
|
|
2279
|
+
return typeof uuid === "string" && regex_default.test(uuid);
|
|
2280
|
+
}
|
|
2281
|
+
var validate_default = validate;
|
|
2282
|
+
|
|
2283
|
+
// node_modules/uuid/dist/esm-node/stringify.js
|
|
2284
|
+
var byteToHex = [];
|
|
2285
|
+
for (let i = 0; i < 256; ++i) {
|
|
2286
|
+
byteToHex.push((i + 256).toString(16).substr(1));
|
|
2287
|
+
}
|
|
2288
|
+
function stringify3(arr, offset = 0) {
|
|
2289
|
+
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();
|
|
2290
|
+
if (!validate_default(uuid)) {
|
|
2291
|
+
throw TypeError("Stringified UUID is invalid");
|
|
2292
|
+
}
|
|
2293
|
+
return uuid;
|
|
2294
|
+
}
|
|
2295
|
+
var stringify_default = stringify3;
|
|
2296
|
+
|
|
2297
|
+
// node_modules/uuid/dist/esm-node/v4.js
|
|
2298
|
+
function v4(options, buf, offset) {
|
|
2299
|
+
options = options || {};
|
|
2300
|
+
const rnds = options.random || (options.rng || rng)();
|
|
2301
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
2302
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
2303
|
+
if (buf) {
|
|
2304
|
+
offset = offset || 0;
|
|
2305
|
+
for (let i = 0; i < 16; ++i) {
|
|
2306
|
+
buf[offset + i] = rnds[i];
|
|
2307
|
+
}
|
|
2308
|
+
return buf;
|
|
2309
|
+
}
|
|
2310
|
+
return stringify_default(rnds);
|
|
2311
|
+
}
|
|
2312
|
+
var v4_default = v4;
|
|
2313
|
+
|
|
2314
|
+
// src/targets/bigquery/service/cloudStorage.ts
|
|
2315
|
+
var uploadFileInBucket = async (streamId, gcsBuckerName, connectorId, filePath) => {
|
|
2316
|
+
try {
|
|
2317
|
+
const retryOptions = { autoRetry: true, maxRetries: 20 };
|
|
2318
|
+
let storage = new Storage({ retryOptions });
|
|
2319
|
+
const bucketRef = storage.bucket(gcsBuckerName);
|
|
2320
|
+
await bucketRef.get({ autoCreate: false });
|
|
2321
|
+
const destinationFileName = `${connectorId}/${streamId}-${v4_default()}.jsonnl`;
|
|
2322
|
+
await bucketRef.upload(filePath, {
|
|
2323
|
+
destination: destinationFileName
|
|
2324
|
+
});
|
|
2325
|
+
logger.info(`\u{1F5F3} Uploaded ${filePath} into ${gcsBuckerName}/${destinationFileName} GCS File`);
|
|
2326
|
+
return bucketRef.file(destinationFileName);
|
|
2327
|
+
} catch (err) {
|
|
2328
|
+
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
2329
|
+
|
|
2330
|
+
Error: ${err.message}
|
|
2331
|
+
Stack: ${err.stack}
|
|
2332
|
+
Code: ${err.code}
|
|
2333
|
+
`);
|
|
2334
|
+
if (err.code < 500) {
|
|
2335
|
+
await haltAndCatchFire(
|
|
2336
|
+
`unauthorized`,
|
|
2337
|
+
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
2338
|
+
|
|
2339
|
+
The error from Google is: '${err.message}' \u{1F440}
|
|
2340
|
+
|
|
2341
|
+
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
2342
|
+
`Got error from cloud storage lib`
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
throw err;
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2350
|
+
import chunk from "lodash/chunk.js";
|
|
2351
|
+
import { Semaphore as Semaphore2 } from "async-mutex";
|
|
2352
|
+
var semaphore2 = new Semaphore2(1);
|
|
2353
|
+
var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
2354
|
+
config;
|
|
2355
|
+
bqClient;
|
|
2356
|
+
bqDataset;
|
|
2357
|
+
bqTable;
|
|
2358
|
+
bqStagingTable;
|
|
2359
|
+
tableName;
|
|
2360
|
+
stagingTableName;
|
|
2361
|
+
sqlTableId;
|
|
2362
|
+
sqlStagingTableId;
|
|
2363
|
+
constructor(config, streamId, database, schema, table, renameColumnStore) {
|
|
2364
|
+
super(
|
|
2365
|
+
streamId,
|
|
2366
|
+
database,
|
|
2367
|
+
schema,
|
|
2368
|
+
table,
|
|
2369
|
+
renameColumnStore
|
|
2370
|
+
);
|
|
2371
|
+
this.config = config;
|
|
2372
|
+
this.bqClient = BqClientHolder.client(config);
|
|
2373
|
+
this.bqDataset = this.bqClient.dataset(schema, { location: config.location || "EU" });
|
|
2374
|
+
this.bqTable = this.bqDataset.table(table, { location: config.location || "EU" });
|
|
2375
|
+
this.tableName = table;
|
|
2376
|
+
this.stagingTableName = `${table}_temp`;
|
|
2377
|
+
this.bqStagingTable = this.bqDataset.table(this.stagingTableName, { location: config.location || "EU" });
|
|
2378
|
+
this.sqlTableId = `${schema}.${table}`;
|
|
2379
|
+
this.sqlStagingTableId = `${this.sqlTableId}_temp`;
|
|
2380
|
+
}
|
|
2381
|
+
// Implementation of abstract classes
|
|
2382
|
+
getSerializedRecord = (record) => {
|
|
2383
|
+
return JSON.stringify(record) + `
|
|
2384
|
+
`;
|
|
2385
|
+
};
|
|
2386
|
+
safeColumnName = safeColumnName;
|
|
2387
|
+
getWarehouseTypeFromJSONSchema = (definition) => {
|
|
2388
|
+
const type = definition.type;
|
|
2389
|
+
const format6 = definition.format;
|
|
2390
|
+
let typeArr;
|
|
2391
|
+
if (type instanceof Array) {
|
|
2392
|
+
typeArr = type;
|
|
2393
|
+
} else {
|
|
2394
|
+
typeArr = [type];
|
|
2395
|
+
}
|
|
2396
|
+
if (format6) {
|
|
2397
|
+
switch (format6) {
|
|
2398
|
+
case "date-time":
|
|
2399
|
+
return "TIMESTAMP";
|
|
2400
|
+
case "json":
|
|
2401
|
+
return "STRING";
|
|
2402
|
+
default:
|
|
2403
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format6}`);
|
|
2404
|
+
}
|
|
2405
|
+
} else if (typeArr.includes("number")) {
|
|
2406
|
+
return "NUMERIC";
|
|
2407
|
+
} else if (typeArr.includes("integer") && type.includes("string")) {
|
|
2408
|
+
return "NUMERIC";
|
|
2409
|
+
} else if (typeArr.includes("integer")) {
|
|
2410
|
+
return "INTEGER";
|
|
2411
|
+
} else if (typeArr.includes("boolean")) {
|
|
2412
|
+
return "BOOLEAN";
|
|
2413
|
+
} else if (typeArr.includes("string")) {
|
|
2414
|
+
return "STRING";
|
|
2415
|
+
} else if (typeArr.includes("array")) {
|
|
2416
|
+
const items = definition.items;
|
|
2417
|
+
if (items) {
|
|
2418
|
+
return this.getWarehouseTypeFromJSONSchema(items);
|
|
2419
|
+
}
|
|
2420
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported array schema field definition: ${JSON.stringify(definition)}`);
|
|
2421
|
+
} else {
|
|
2422
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported schema field definition: ${JSON.stringify(definition)}`);
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2425
|
+
getMergeQueries = () => {
|
|
2426
|
+
const primaryKeyCondition = this.primaryKeys.map((pk) => {
|
|
2427
|
+
return `\`sourced\`.\`${pk}\` = \`destination\`.\`${pk}\``;
|
|
2428
|
+
}).join(` AND `);
|
|
2429
|
+
const primaryKeys = this.primaryKeys.map((pk) => `\`${pk}\``).join(` ,`);
|
|
2430
|
+
const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2431
|
+
const columns = Object.keys(columnUnsafeToSafeMapping).map((column) => {
|
|
2432
|
+
return {
|
|
2433
|
+
isUnsafe: true,
|
|
2434
|
+
colName: column
|
|
2435
|
+
};
|
|
2436
|
+
});
|
|
2437
|
+
const columnsChunks = chunk(columns, 300).map((chunk2) => {
|
|
2438
|
+
this.primaryKeys.forEach((pk) => {
|
|
2439
|
+
if (!chunk2.map((c) => columnUnsafeToSafeMapping[c.colName]).includes(pk)) {
|
|
2440
|
+
chunk2.push({
|
|
2441
|
+
isUnsafe: false,
|
|
2442
|
+
colName: pk
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
return chunk2;
|
|
2447
|
+
});
|
|
2448
|
+
return columnsChunks.map((chunk2) => {
|
|
2449
|
+
const setValues = chunk2.map((column) => {
|
|
2450
|
+
if (column.isUnsafe) {
|
|
2451
|
+
return `\`destination\`.\`${columnUnsafeToSafeMapping[column.colName]}\` = \`sourced\`.\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2452
|
+
} else {
|
|
2453
|
+
return `\`destination\`.\`${column.colName}\` = \`sourced\`.\`${column.colName}\``;
|
|
2454
|
+
}
|
|
2455
|
+
}).join(`, `);
|
|
2456
|
+
const renamedCols = chunk2.map((column) => {
|
|
2457
|
+
if (column.isUnsafe) {
|
|
2458
|
+
return `\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2459
|
+
} else {
|
|
2460
|
+
return `\`${column.colName}\``;
|
|
2461
|
+
}
|
|
2462
|
+
}).join(`, `);
|
|
2463
|
+
const query = `
|
|
2464
|
+
MERGE ${this.sqlTableId} destination
|
|
2465
|
+
USING (
|
|
2466
|
+
WITH all_staging_rows AS (
|
|
2467
|
+
SELECT *
|
|
2468
|
+
FROM ${this.sqlStagingTableId}
|
|
2469
|
+
),
|
|
2470
|
+
numbered_rows AS (
|
|
2471
|
+
SELECT *,
|
|
2472
|
+
ROW_NUMBER() OVER (PARTITION BY ${primaryKeys}) as _wly_row_nb,
|
|
2473
|
+
COUNT(1) OVER (PARTITION BY ${primaryKeys}) AS _wly_partition_size,
|
|
2474
|
+
FROM all_staging_rows
|
|
2475
|
+
),
|
|
2476
|
+
staging_deduped AS (
|
|
2477
|
+
SELECT * EXCEPT(_wly_partition_size, _wly_row_nb)
|
|
2478
|
+
FROM numbered_rows
|
|
2479
|
+
WHERE _wly_partition_size = _wly_row_nb
|
|
2480
|
+
)
|
|
2481
|
+
SELECT *
|
|
2482
|
+
FROM staging_deduped
|
|
2483
|
+
) AS sourced
|
|
2484
|
+
ON ${primaryKeyCondition}
|
|
2485
|
+
WHEN MATCHED THEN
|
|
2486
|
+
UPDATE SET ${setValues}
|
|
2487
|
+
WHEN NOT MATCHED THEN
|
|
2488
|
+
INSERT (${renamedCols}) VALUES (${renamedCols});
|
|
2489
|
+
`;
|
|
2490
|
+
return query;
|
|
2491
|
+
});
|
|
2492
|
+
};
|
|
2493
|
+
getReplaceQueries = () => {
|
|
2494
|
+
return [
|
|
2495
|
+
`TRUNCATE TABLE ${this.sqlTableId};`,
|
|
2496
|
+
...this.getMergeQueries()
|
|
2497
|
+
];
|
|
2498
|
+
};
|
|
2499
|
+
createDatabaseAndSchemaIfNotExists = async (retryCount) => {
|
|
2500
|
+
try {
|
|
2501
|
+
await semaphore2.runExclusive(async () => {
|
|
2502
|
+
const [exists] = await this.bqDataset.exists();
|
|
2503
|
+
if (!exists) {
|
|
2504
|
+
logger.info(`\u{1F423} Creating dataset: ${this.bqDataset.id} on BigQuery`);
|
|
2505
|
+
await this.bqDataset.create();
|
|
2506
|
+
} else {
|
|
2507
|
+
const [metadata] = await this.bqDataset.getMetadata();
|
|
2508
|
+
const alreadyExistinglocation = metadata.location;
|
|
2509
|
+
if (this.config.location && this.config.location !== alreadyExistinglocation) {
|
|
2510
|
+
await haltAndCatchFire(
|
|
2511
|
+
`unknown`,
|
|
2512
|
+
`A BigQuery dataset called '${this.bqDataset.id}' already exists in location: '${alreadyExistinglocation}' \u{1F605}
|
|
2513
|
+
|
|
2514
|
+
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}
|
|
2515
|
+
|
|
2516
|
+
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}
|
|
2517
|
+
|
|
2518
|
+
Please relaunch the connector once it's done to fix the issue \u{1F917}
|
|
2519
|
+
`,
|
|
2520
|
+
`
|
|
2521
|
+
We have a conflict between an already existing dataset in location ${alreadyExistinglocation} and the configured location: ${this.config.location}
|
|
2522
|
+
`
|
|
2523
|
+
);
|
|
2524
|
+
throw Error();
|
|
2525
|
+
}
|
|
2526
|
+
logger.info(`\u{1F44B} Dataset: ${this.bqDataset.id} already exists on BigQuery, reusing it.`);
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
if (err.code === 400) {
|
|
2531
|
+
await haltAndCatchFire(
|
|
2532
|
+
`unauthorized`,
|
|
2533
|
+
`We can't connect to your BigQuery account \u{1F614}
|
|
2534
|
+
|
|
2535
|
+
Here is the message from BigQuery: ${err.message}
|
|
2536
|
+
|
|
2537
|
+
Can you check that your Warehouse credentials and Source target schema are properly configured and try to sync again the source? \u{1F64F}
|
|
2538
|
+
`,
|
|
2539
|
+
`Got error when trying to get Dataset from BigQuery: ${err.message} - ${err.code}`
|
|
2540
|
+
);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
if (retryCount && retryCount > this.maxRetryCount) {
|
|
2544
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. ${err.message} - ${err.code}`);
|
|
2545
|
+
gracefulExit(1);
|
|
2546
|
+
} else {
|
|
2547
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
|
|
2548
|
+
await new Promise((resolve, reject) => setTimeout(resolve, 1e3 * retryCount));
|
|
2549
|
+
await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return Promise.resolve();
|
|
2553
|
+
};
|
|
2554
|
+
createTable = async () => {
|
|
2555
|
+
try {
|
|
2556
|
+
const tableName = this.tableName;
|
|
2557
|
+
const schema = this.generateBigquerySchema();
|
|
2558
|
+
logger.info(`\u{1F423} Creating table: ${tableName} on BigQuery`);
|
|
2559
|
+
const createTableOptions = {
|
|
2560
|
+
schema
|
|
2561
|
+
};
|
|
2562
|
+
await this.bqDataset.createTable(
|
|
2563
|
+
tableName,
|
|
2564
|
+
createTableOptions
|
|
2565
|
+
);
|
|
2566
|
+
return;
|
|
2567
|
+
} catch (err) {
|
|
2568
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2569
|
+
|
|
2570
|
+
Error: ${err.message}
|
|
2571
|
+
Stack: ${err.stack}`);
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
addColumns = async (tableFields) => {
|
|
2575
|
+
if (tableFields.length > 0) {
|
|
2576
|
+
logger.info(`\u{1F195} Stream: ${this.streamId} - Adding following columns in BigQuery schema: ${JSON.stringify(tableFields)}`);
|
|
2577
|
+
const [metadata] = await this.bqTable.getMetadata();
|
|
2578
|
+
const bgTableFields = tableFields.map((tblField) => {
|
|
2579
|
+
return this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(tblField.name, tblField.definition);
|
|
2580
|
+
}).filter((fld) => {
|
|
2581
|
+
return !!fld;
|
|
2582
|
+
});
|
|
2583
|
+
const schema = metadata.schema;
|
|
2584
|
+
const new_schema = {
|
|
2585
|
+
...schema,
|
|
2586
|
+
fields: schema.fields.concat(bgTableFields)
|
|
2587
|
+
};
|
|
2588
|
+
metadata.schema = new_schema;
|
|
2589
|
+
await this.bqTable.setMetadata(metadata);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
createStagingArea = async () => {
|
|
2594
|
+
try {
|
|
2595
|
+
const tableName = this.stagingTableName;
|
|
2596
|
+
const [exists] = await this.bqDataset.table(tableName).exists();
|
|
2597
|
+
if (exists) {
|
|
2598
|
+
logger.info(`\u{1F474} Temporary table ${tableName} already exists, it should be a relic form the past.
|
|
2599
|
+
Dropping it.`);
|
|
2600
|
+
await this.deleteStagingArea();
|
|
2601
|
+
}
|
|
2602
|
+
const schema = this.generateBigquerySchema();
|
|
2603
|
+
const expirationTime = dayjs6().add(1, "day").valueOf().toString();
|
|
2604
|
+
const createTableOptions = {
|
|
2605
|
+
schema,
|
|
2606
|
+
expirationTime
|
|
2607
|
+
};
|
|
2608
|
+
logger.info(`\u{1F423} Creating staging table: ${tableName} on BigQuery`);
|
|
2609
|
+
await this.bqDataset.createTable(
|
|
2610
|
+
tableName,
|
|
2611
|
+
createTableOptions
|
|
2612
|
+
);
|
|
2613
|
+
return;
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2616
|
+
|
|
2617
|
+
Error: ${err.message}
|
|
2618
|
+
Stack: ${err.stack}`);
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
loadStreamInStagingArea = async (localFilePath) => {
|
|
2622
|
+
try {
|
|
2623
|
+
const file = await uploadFileInBucket(
|
|
2624
|
+
this.streamId,
|
|
2625
|
+
this.config.loading_deck_gcs_bucket_name,
|
|
2626
|
+
this.config.connector_id,
|
|
2627
|
+
localFilePath
|
|
2628
|
+
);
|
|
2629
|
+
const metadata = {
|
|
2630
|
+
sourceFormat: "NEWLINE_DELIMITED_JSON",
|
|
2631
|
+
writeDisposition: "WRITE_TRUNCATE"
|
|
2632
|
+
};
|
|
2633
|
+
await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
|
|
2634
|
+
} catch (err) {
|
|
2635
|
+
logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
|
|
2636
|
+
throw err;
|
|
2637
|
+
}
|
|
2638
|
+
};
|
|
2639
|
+
deleteStagingArea = () => {
|
|
2640
|
+
const query = `DROP TABLE IF EXISTS ${this.sqlStagingTableId};`;
|
|
2641
|
+
return this.runQueriesWithRetry([query]);
|
|
2642
|
+
};
|
|
2643
|
+
getTablesInSchema = async () => {
|
|
2644
|
+
const [bqTables] = await this.bqDataset.getTables();
|
|
2645
|
+
const foundTables = bqTables.filter((tbl) => {
|
|
2646
|
+
return !!tbl.id;
|
|
2647
|
+
}).map((bqTable) => {
|
|
2648
|
+
return {
|
|
2649
|
+
tableName: bqTable.id
|
|
2650
|
+
};
|
|
2651
|
+
});
|
|
2652
|
+
return foundTables;
|
|
2653
|
+
};
|
|
2654
|
+
getTableColumnsFromWarehouse = async () => {
|
|
2655
|
+
const [table] = await this.bqTable.getMetadata();
|
|
2656
|
+
return table.schema.fields;
|
|
2657
|
+
};
|
|
2658
|
+
runQueries = async (queries) => {
|
|
2659
|
+
const sqlQuery = `
|
|
2660
|
+
BEGIN
|
|
2661
|
+
${queries.join(`
|
|
2662
|
+
`)}
|
|
2663
|
+
END;
|
|
2664
|
+
`;
|
|
2665
|
+
await this.bqClient.query(sqlQuery);
|
|
2666
|
+
};
|
|
2667
|
+
// Specific methods
|
|
2668
|
+
/**
|
|
2669
|
+
*
|
|
2670
|
+
* Generate a TableField definition object that can be passed to BigQuery APIs to define a column on a table
|
|
2671
|
+
*
|
|
2672
|
+
* @param colName
|
|
2673
|
+
* @param schemaFieldDefinition
|
|
2674
|
+
* @returns
|
|
2675
|
+
*/
|
|
2676
|
+
getBigqueryTableFieldFromInternalSchemaFieldDefinition = (colName, schemaFieldDefinition) => {
|
|
2677
|
+
const name = colName;
|
|
2678
|
+
if (!schemaFieldDefinition) {
|
|
2679
|
+
throw new Error(`StreamId: ${this.streamId} - Schema is unknown for colName=${colName}, so we can't infer the proper BigQuery definition for this column.`);
|
|
2680
|
+
}
|
|
2681
|
+
const type = schemaFieldDefinition.type;
|
|
2682
|
+
let typeArr;
|
|
2683
|
+
if (type instanceof Array) {
|
|
2684
|
+
typeArr = type;
|
|
2685
|
+
} else {
|
|
2686
|
+
typeArr = [type];
|
|
2687
|
+
}
|
|
2688
|
+
if (typeArr.includes("array")) {
|
|
2689
|
+
return {
|
|
2690
|
+
name,
|
|
2691
|
+
mode: "REPEATED",
|
|
2692
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
2693
|
+
};
|
|
2694
|
+
} else {
|
|
2695
|
+
let nullableMode = "NULLABLE";
|
|
2696
|
+
return {
|
|
2697
|
+
name,
|
|
2698
|
+
mode: nullableMode,
|
|
2699
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
generateBigquerySchema = () => {
|
|
2704
|
+
const unsafeToSafeMappingColumnsFromColumnStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2705
|
+
if (!unsafeToSafeMappingColumnsFromColumnStore) {
|
|
2706
|
+
throw new Error(`StreamId: ${this.streamId} - There is no unsafe->safe column mapping`);
|
|
2707
|
+
}
|
|
2708
|
+
return Object.keys(unsafeToSafeMappingColumnsFromColumnStore).reduce((acc, unsafeKey) => {
|
|
2709
|
+
const renamedColName = this.renamedColumnStore.getColumnTranslation(this.streamId, unsafeKey);
|
|
2710
|
+
const bigqueryTableField = this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(renamedColName, this.flattenedSchema[unsafeKey]);
|
|
2711
|
+
if (bigqueryTableField) {
|
|
2712
|
+
acc.push(bigqueryTableField);
|
|
2713
|
+
}
|
|
2714
|
+
return acc;
|
|
2715
|
+
}, []);
|
|
2716
|
+
};
|
|
2717
|
+
loadGCSFileInTable = async (tableName, file, metadata) => {
|
|
2718
|
+
try {
|
|
2719
|
+
const [job] = await this.bqStagingTable.load(file, metadata);
|
|
2720
|
+
logger.info(`\u{1F69A} Loaded GCS file \`${file.id}\` in BigQuery table: ${tableName} with Job: \`${job.id}\``);
|
|
2721
|
+
logger.debug(`Upload Stats: ${JSON.stringify(job.statistics)}`);
|
|
2722
|
+
const errors2 = job.status?.errors;
|
|
2723
|
+
if (errors2 && errors2.length > 0) {
|
|
2724
|
+
throw errors2;
|
|
2725
|
+
}
|
|
2726
|
+
} catch (err) {
|
|
2727
|
+
logger.error(err);
|
|
2728
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when loading file in BigQuery table | fileName: ${file.name}, tableName: ${tableName}
|
|
2729
|
+
|
|
2730
|
+
Error: ${err.message}
|
|
2731
|
+
Stack: ${err.stack}`);
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
};
|
|
2735
|
+
|
|
2736
|
+
// src/targets/bigquery/main.ts
|
|
2737
|
+
var BigQueryTarget = class extends ITarget {
|
|
2738
|
+
constructor(config, stateProvider) {
|
|
2739
|
+
super(config, stateProvider);
|
|
2740
|
+
this.renameColumnStore.setSafeColumnNameConverter(this.safeColumnNameConverter);
|
|
2741
|
+
}
|
|
2742
|
+
static requiredConfigKeys = [
|
|
2743
|
+
"schema",
|
|
2744
|
+
"project_id",
|
|
2745
|
+
"loading_deck_gcs_bucket_name"
|
|
2746
|
+
];
|
|
2747
|
+
newDBSyncInstance = (streamId, database, schema, table) => {
|
|
2748
|
+
return new BigQueryDBSync(
|
|
2749
|
+
this.config,
|
|
2750
|
+
streamId,
|
|
2751
|
+
database,
|
|
2752
|
+
schema,
|
|
2753
|
+
table,
|
|
2754
|
+
this.renameColumnStore
|
|
2755
|
+
);
|
|
2756
|
+
};
|
|
2757
|
+
genTableName = safeTableName;
|
|
2758
|
+
safeColumnNameConverter = safeColumnName;
|
|
2759
|
+
validateDateRange = validateDateRange;
|
|
2760
|
+
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
2761
|
+
};
|
|
2762
|
+
|
|
2763
|
+
// src/sdk/service/gcs.ts
|
|
2764
|
+
import { Storage as Storage2 } from "@google-cloud/storage";
|
|
2765
|
+
import { format as format4 } from "util";
|
|
2766
|
+
var getStorage = () => new Storage2({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
2767
|
+
var readObjectAsString = async (bucketName, objectPath) => {
|
|
2768
|
+
try {
|
|
2769
|
+
const storage = getStorage();
|
|
2770
|
+
const bucketRef = storage.bucket(bucketName);
|
|
2771
|
+
await bucketRef.get({ autoCreate: false });
|
|
2772
|
+
const fileRef = bucketRef.file(objectPath);
|
|
2773
|
+
const [exists] = await fileRef.exists();
|
|
2774
|
+
if (!exists) {
|
|
2775
|
+
throw new Error(`GCS object not found: gs://${bucketName}/${objectPath}`);
|
|
2776
|
+
}
|
|
2777
|
+
const [contents] = await fileRef.download();
|
|
2778
|
+
return contents.toString("utf8");
|
|
2779
|
+
} catch (err) {
|
|
2780
|
+
throw new Error(format4(`error reading GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
var writeStringObject = async (bucketName, objectPath, contents) => {
|
|
2784
|
+
try {
|
|
2785
|
+
const storage = getStorage();
|
|
2786
|
+
const bucketRef = storage.bucket(bucketName);
|
|
2787
|
+
await bucketRef.get({ autoCreate: false });
|
|
2788
|
+
const fileRef = bucketRef.file(objectPath);
|
|
2789
|
+
await fileRef.save(contents, { contentType: "application/json" });
|
|
2790
|
+
logger.info(`\u{1F5F3} Uploaded object to gs://${bucketName}/${objectPath}`);
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
throw new Error(format4(`error writing GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
|
|
2796
|
+
// src/state-providers/gcs/main.ts
|
|
2797
|
+
import { format as format5 } from "util";
|
|
2798
|
+
var GCSStateProvider = class {
|
|
2799
|
+
bucketName;
|
|
2800
|
+
connectorId;
|
|
2801
|
+
constructor(connectorId, bucketName) {
|
|
2802
|
+
this.connectorId = connectorId;
|
|
2803
|
+
this.bucketName = bucketName;
|
|
2804
|
+
}
|
|
2805
|
+
getObjectPath() {
|
|
2806
|
+
return `${this.connectorId}/state.json`;
|
|
2807
|
+
}
|
|
2808
|
+
async getState() {
|
|
2809
|
+
const objectPath = this.getObjectPath();
|
|
2810
|
+
try {
|
|
2811
|
+
logger.info(`\u{1F4C1} Loading state from gs://${this.bucketName}/${objectPath}`);
|
|
2812
|
+
const raw = await readObjectAsString(this.bucketName, objectPath);
|
|
2813
|
+
const parsed = JSON.parse(raw);
|
|
2814
|
+
return { state: parsed };
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
logger.warn(format5(`error loading state from gs://${this.bucketName}/${objectPath}, will start with empty state, err: %s`, err?.message));
|
|
2817
|
+
return { state: void 0 };
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
async writeState(state) {
|
|
2821
|
+
const objectPath = this.getObjectPath();
|
|
2822
|
+
try {
|
|
2823
|
+
logger.info(`\u{1F4DD} Writing state to gs://${this.bucketName}/${objectPath}`);
|
|
2824
|
+
await writeStringObject(this.bucketName, objectPath, state);
|
|
2825
|
+
} catch (err) {
|
|
2826
|
+
logger.error(format5(`\u{1F4A5} Error while writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2827
|
+
throw new Error(format5(`error writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2728
2831
|
export {
|
|
2729
2832
|
ALL_STREAM_ID_KEYWORD,
|
|
2730
2833
|
API_CALLS_METRIC_NAME,
|
|
2731
2834
|
Authenticator,
|
|
2732
2835
|
BATCH_INTERVAL_MS,
|
|
2733
|
-
|
|
2836
|
+
BigQueryTarget,
|
|
2734
2837
|
CounterMetric,
|
|
2735
2838
|
DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
2736
2839
|
EXECUTION_TIME_METRIC_NAME,
|
|
2840
|
+
GCSStateProvider,
|
|
2737
2841
|
ITarget,
|
|
2738
2842
|
MissingFieldInSchemaError,
|
|
2739
2843
|
MissingSchemaError,
|
|
2740
|
-
|
|
2844
|
+
RESTStream,
|
|
2741
2845
|
ROWS_SYNCED_METRIC_NAME,
|
|
2742
2846
|
RecordMessage,
|
|
2743
2847
|
RenameColumnStore,
|
|
2848
|
+
ReplicationMethod,
|
|
2744
2849
|
ReplicationMethodMessage,
|
|
2745
|
-
Resolver,
|
|
2746
2850
|
SchemaMessage,
|
|
2747
2851
|
SchemaValidationError,
|
|
2748
2852
|
StateMessage,
|
|
2749
2853
|
StateService,
|
|
2750
|
-
|
|
2751
|
-
StreamV2,
|
|
2854
|
+
Stream,
|
|
2752
2855
|
StreamWarehouseSyncService,
|
|
2753
2856
|
Tap,
|
|
2754
2857
|
_greaterThan,
|
|
2755
2858
|
addWhalyFields,
|
|
2756
|
-
checkRequiredConfigKeys,
|
|
2757
2859
|
createTemporaryFileStream,
|
|
2758
2860
|
extractStateForStream,
|
|
2759
2861
|
finalizeStateProgressMarkers,
|
|
2760
|
-
findRelatedRelationships,
|
|
2761
2862
|
flattenSchema,
|
|
2762
2863
|
getAllMetrics,
|
|
2763
2864
|
getAxiosInstance,
|
|
@@ -2772,15 +2873,12 @@ export {
|
|
|
2772
2873
|
incrementStreamState,
|
|
2773
2874
|
loadJson,
|
|
2774
2875
|
loadSchema,
|
|
2775
|
-
loadShoreConfig,
|
|
2776
2876
|
logger,
|
|
2777
2877
|
postFormDataApiCall,
|
|
2778
2878
|
postJSONApiCall,
|
|
2779
2879
|
postUrlEncodedApiCall,
|
|
2780
2880
|
printMemoryFootprint,
|
|
2781
2881
|
removeParasiteProperties,
|
|
2782
|
-
safePath
|
|
2783
|
-
writeCatalogFile,
|
|
2784
|
-
writeStateFile
|
|
2882
|
+
safePath
|
|
2785
2883
|
};
|
|
2786
2884
|
//# sourceMappingURL=index.mjs.map
|