@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.js
CHANGED
|
@@ -34,34 +34,32 @@ __export(index_exports, {
|
|
|
34
34
|
API_CALLS_METRIC_NAME: () => API_CALLS_METRIC_NAME,
|
|
35
35
|
Authenticator: () => Authenticator,
|
|
36
36
|
BATCH_INTERVAL_MS: () => BATCH_INTERVAL_MS,
|
|
37
|
-
|
|
37
|
+
BigQueryTarget: () => BigQueryTarget,
|
|
38
38
|
CounterMetric: () => CounterMetric,
|
|
39
39
|
DEFAULT_MAX_CONCURRENT_STREAMS: () => DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
40
40
|
EXECUTION_TIME_METRIC_NAME: () => EXECUTION_TIME_METRIC_NAME,
|
|
41
|
+
GCSStateProvider: () => GCSStateProvider,
|
|
41
42
|
ITarget: () => ITarget,
|
|
42
43
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
43
44
|
MissingSchemaError: () => MissingSchemaError,
|
|
44
|
-
|
|
45
|
+
RESTStream: () => RESTStream,
|
|
45
46
|
ROWS_SYNCED_METRIC_NAME: () => ROWS_SYNCED_METRIC_NAME,
|
|
46
47
|
RecordMessage: () => RecordMessage,
|
|
47
48
|
RenameColumnStore: () => RenameColumnStore,
|
|
49
|
+
ReplicationMethod: () => ReplicationMethod,
|
|
48
50
|
ReplicationMethodMessage: () => ReplicationMethodMessage,
|
|
49
|
-
Resolver: () => Resolver,
|
|
50
51
|
SchemaMessage: () => SchemaMessage,
|
|
51
52
|
SchemaValidationError: () => SchemaValidationError,
|
|
52
53
|
StateMessage: () => StateMessage,
|
|
53
54
|
StateService: () => StateService,
|
|
54
|
-
|
|
55
|
-
StreamV2: () => StreamV2,
|
|
55
|
+
Stream: () => Stream,
|
|
56
56
|
StreamWarehouseSyncService: () => StreamWarehouseSyncService,
|
|
57
57
|
Tap: () => Tap,
|
|
58
58
|
_greaterThan: () => _greaterThan,
|
|
59
59
|
addWhalyFields: () => addWhalyFields,
|
|
60
|
-
checkRequiredConfigKeys: () => checkRequiredConfigKeys,
|
|
61
60
|
createTemporaryFileStream: () => createTemporaryFileStream,
|
|
62
61
|
extractStateForStream: () => extractStateForStream,
|
|
63
62
|
finalizeStateProgressMarkers: () => finalizeStateProgressMarkers,
|
|
64
|
-
findRelatedRelationships: () => findRelatedRelationships,
|
|
65
63
|
flattenSchema: () => flattenSchema,
|
|
66
64
|
getAllMetrics: () => getAllMetrics,
|
|
67
65
|
getAxiosInstance: () => getAxiosInstance,
|
|
@@ -76,260 +74,24 @@ __export(index_exports, {
|
|
|
76
74
|
incrementStreamState: () => incrementStreamState,
|
|
77
75
|
loadJson: () => loadJson,
|
|
78
76
|
loadSchema: () => loadSchema,
|
|
79
|
-
loadShoreConfig: () => loadShoreConfig,
|
|
80
77
|
logger: () => logger,
|
|
81
78
|
postFormDataApiCall: () => postFormDataApiCall,
|
|
82
79
|
postJSONApiCall: () => postJSONApiCall,
|
|
83
80
|
postUrlEncodedApiCall: () => postUrlEncodedApiCall,
|
|
84
81
|
printMemoryFootprint: () => printMemoryFootprint,
|
|
85
82
|
removeParasiteProperties: () => removeParasiteProperties,
|
|
86
|
-
safePath: () => safePath
|
|
87
|
-
writeCatalogFile: () => writeCatalogFile,
|
|
88
|
-
writeStateFile: () => writeStateFile
|
|
83
|
+
safePath: () => safePath
|
|
89
84
|
});
|
|
90
85
|
module.exports = __toCommonJS(index_exports);
|
|
91
86
|
|
|
92
87
|
// src/sdk/utils.ts
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
// src/sdk/service/logger.ts
|
|
96
|
-
var winston2 = __toESM(require("winston"));
|
|
97
|
-
|
|
98
|
-
// src/sdk/models/resolver.ts
|
|
99
|
-
var Resolver = class {
|
|
100
|
-
constructor() {
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// src/resolvers/local-file/services/files.ts
|
|
105
|
-
var import_fs = __toESM(require("fs"));
|
|
106
|
-
var writeFile = (path, content) => {
|
|
107
|
-
return Promise.resolve(import_fs.default.writeFileSync(path, content));
|
|
108
|
-
};
|
|
109
|
-
var readAndParseJSONFile = (path) => {
|
|
110
|
-
if (!import_fs.default.existsSync(path)) {
|
|
111
|
-
throw new Error(`File \`${path}\` wasn't found at: ${process.cwd()}`);
|
|
112
|
-
}
|
|
113
|
-
const raw = import_fs.default.readFileSync(path);
|
|
114
|
-
const parsed = JSON.parse(raw.toString());
|
|
115
|
-
return parsed;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/resolvers/local-file/hook/schemaHook.ts
|
|
119
|
-
var import_fs_extra = require("fs-extra");
|
|
120
|
-
var LocalSchemaHook = class {
|
|
121
|
-
async writeSchema(input) {
|
|
122
|
-
await (0, import_fs_extra.appendFile)("./schema.tmp", JSON.stringify(input) + "\n");
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// src/resolvers/local-file/resolver.ts
|
|
127
|
-
var import_winston = __toESM(require("winston"));
|
|
128
|
-
|
|
129
|
-
// src/sdk/service/metric.ts
|
|
130
|
-
var metricStore = {};
|
|
131
|
-
var ALL_STREAM_ID_KEYWORD = "all";
|
|
132
|
-
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
133
|
-
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
134
|
-
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
135
|
-
var getCounterMetrics = (name, streamIds) => {
|
|
136
|
-
return streamIds.map((streamId) => {
|
|
137
|
-
return getCounterMetric(name, streamId);
|
|
138
|
-
});
|
|
139
|
-
};
|
|
140
|
-
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
141
|
-
if (!metricStore[streamId]) {
|
|
142
|
-
metricStore[streamId] = {};
|
|
143
|
-
}
|
|
144
|
-
const streamMetricStore = metricStore[streamId];
|
|
145
|
-
if (!streamMetricStore[name]) {
|
|
146
|
-
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
147
|
-
}
|
|
148
|
-
return streamMetricStore[name];
|
|
149
|
-
};
|
|
150
|
-
var getAllMetrics = () => {
|
|
151
|
-
return Object.keys(metricStore).flatMap((streamId) => {
|
|
152
|
-
const streamMetricStore = metricStore[streamId];
|
|
153
|
-
if (!streamMetricStore) {
|
|
154
|
-
return [];
|
|
155
|
-
}
|
|
156
|
-
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
var CounterMetric = class {
|
|
160
|
-
value = 0;
|
|
161
|
-
name;
|
|
162
|
-
streamId;
|
|
163
|
-
constructor(name, streamId) {
|
|
164
|
-
this.name = name;
|
|
165
|
-
this.streamId = streamId;
|
|
166
|
-
}
|
|
167
|
-
increment(inc = 1) {
|
|
168
|
-
this.value = this.value + inc;
|
|
169
|
-
}
|
|
170
|
-
getStreamId() {
|
|
171
|
-
return this.streamId;
|
|
172
|
-
}
|
|
173
|
-
getName() {
|
|
174
|
-
return this.name;
|
|
175
|
-
}
|
|
176
|
-
getValue() {
|
|
177
|
-
return this.value;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// src/resolvers/local-file/resolver.ts
|
|
182
|
-
var { combine, prettyPrint, timestamp, errors, splat } = import_winston.default.format;
|
|
183
|
-
var configPath = "./config.json";
|
|
184
|
-
var targetConfigPath = "./target-config.json";
|
|
185
|
-
var catalogPath = "./catalog.json";
|
|
186
|
-
var statePath = "./state.json";
|
|
187
|
-
var metricsPath = "./metrics.json";
|
|
188
|
-
var LocalFilesResolver = class extends Resolver {
|
|
189
|
-
startVPNIfNeeded() {
|
|
190
|
-
return Promise.resolve();
|
|
191
|
-
}
|
|
192
|
-
checkIfCanSync() {
|
|
193
|
-
return Promise.resolve(true);
|
|
194
|
-
}
|
|
195
|
-
markSyncStarted() {
|
|
196
|
-
return Promise.resolve();
|
|
197
|
-
}
|
|
198
|
-
onError(errorType, errorText, errorDebugText) {
|
|
199
|
-
const path = `./error.json`;
|
|
200
|
-
return writeFile(path, JSON.stringify({
|
|
201
|
-
errorType,
|
|
202
|
-
errorText,
|
|
203
|
-
errorDebugText
|
|
204
|
-
}));
|
|
205
|
-
}
|
|
206
|
-
getSchemaHooks() {
|
|
207
|
-
const wlyHook = new LocalSchemaHook();
|
|
208
|
-
return [wlyHook];
|
|
209
|
-
}
|
|
210
|
-
writeSchema() {
|
|
211
|
-
return Promise.resolve();
|
|
212
|
-
}
|
|
213
|
-
constructor() {
|
|
214
|
-
super();
|
|
215
|
-
}
|
|
216
|
-
getConfig(command) {
|
|
217
|
-
logger.info(`\u{1F4C1} Using the LOCAL resolver`);
|
|
218
|
-
const config = readAndParseJSONFile(configPath);
|
|
219
|
-
const targetConfig = readAndParseJSONFile(targetConfigPath);
|
|
220
|
-
const destination = process.env.SHORE__DESTINATION;
|
|
221
|
-
if (!destination) {
|
|
222
|
-
throw new Error(`\u274C Env variable \`SHORE__DESTINATION\` is not set.
|
|
223
|
-
Did you forget to configure it?
|
|
224
|
-
|
|
225
|
-
Possible values are: \`GOOGLE_BIGQUERY\`, \`SNOWFLAKE\``);
|
|
226
|
-
}
|
|
227
|
-
if (command === `READ`) {
|
|
228
|
-
if (destination === `GOOGLE_BIGQUERY`) {
|
|
229
|
-
const googleCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
230
|
-
if (!googleCredentials) {
|
|
231
|
-
throw new Error(`Env variable \`GOOGLE_APPLICATION_CREDENTIALS\` is not set.
|
|
232
|
-
Did you forget to configure it?
|
|
233
|
-
|
|
234
|
-
This variable should link to a file containing the proper Google Service Account credentials.
|
|
235
|
-
`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const catalog = readAndParseJSONFile(catalogPath);
|
|
239
|
-
const state = readAndParseJSONFile(statePath);
|
|
240
|
-
return Promise.resolve({
|
|
241
|
-
command,
|
|
242
|
-
destination,
|
|
243
|
-
config,
|
|
244
|
-
targetConfig,
|
|
245
|
-
catalog,
|
|
246
|
-
state
|
|
247
|
-
});
|
|
248
|
-
} else {
|
|
249
|
-
return Promise.resolve({
|
|
250
|
-
command,
|
|
251
|
-
destination,
|
|
252
|
-
config,
|
|
253
|
-
targetConfig
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
writeCatalog(catalog) {
|
|
258
|
-
return writeFile(catalogPath, JSON.stringify(catalog));
|
|
259
|
-
}
|
|
260
|
-
writeState(state) {
|
|
261
|
-
return writeFile(statePath, state);
|
|
262
|
-
}
|
|
263
|
-
markSyncFailed() {
|
|
264
|
-
logger.info(`\u{1F622} Sync has failed.`);
|
|
265
|
-
return Promise.resolve();
|
|
266
|
-
}
|
|
267
|
-
markSyncComplete() {
|
|
268
|
-
logger.info(`\u{1F389} Sync is complete.`);
|
|
269
|
-
return Promise.resolve();
|
|
270
|
-
}
|
|
271
|
-
markDiscoveryComplete() {
|
|
272
|
-
logger.info(`\u{1F389} Discovery is complete.`);
|
|
273
|
-
return Promise.resolve();
|
|
274
|
-
}
|
|
275
|
-
async flushMetrics() {
|
|
276
|
-
const allMetrics = getAllMetrics();
|
|
277
|
-
const metricsOuput = allMetrics.map((metric) => {
|
|
278
|
-
return {
|
|
279
|
-
streamId: metric.getStreamId(),
|
|
280
|
-
name: metric.getName(),
|
|
281
|
-
value: metric.getValue()
|
|
282
|
-
};
|
|
283
|
-
}).map((obj) => JSON.stringify(obj)).join(`
|
|
284
|
-
`);
|
|
285
|
-
return writeFile(metricsPath, metricsOuput);
|
|
286
|
-
}
|
|
287
|
-
updateSourceValue(optionKey, optionValue) {
|
|
288
|
-
logger.info(`Updating source value ${optionKey} with value ${optionValue}`, { private: true });
|
|
289
|
-
const config = readAndParseJSONFile(configPath);
|
|
290
|
-
config[optionKey] = optionValue;
|
|
291
|
-
return writeFile(configPath, JSON.stringify(config));
|
|
292
|
-
}
|
|
293
|
-
getLogFormat() {
|
|
294
|
-
return combine(
|
|
295
|
-
timestamp(),
|
|
296
|
-
errors({ stack: true }),
|
|
297
|
-
splat(),
|
|
298
|
-
prettyPrint()
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
// src/sdk/service/resolver.ts
|
|
304
|
-
function initResolver(resolverType) {
|
|
305
|
-
if (resolverType === "LOCAL") {
|
|
306
|
-
return new LocalFilesResolver();
|
|
307
|
-
} else {
|
|
308
|
-
throw new Error(`\u274C Unsupported configResolver: ${resolverType}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
var loadResolver = () => {
|
|
312
|
-
const configResolver = process.env.SHORE__CONFIG_RESOLVER;
|
|
313
|
-
if (!configResolver) {
|
|
314
|
-
throw new Error(`Env variable \`SHORE__CONFIG_RESOLVER\` is not set.
|
|
315
|
-
Did you forget to configure it?
|
|
316
|
-
|
|
317
|
-
Possible values are \`LOCAL\`, \`WHALY\`.
|
|
318
|
-
Please read the documentation to get the full description of the behavior of each.
|
|
319
|
-
`);
|
|
320
|
-
}
|
|
321
|
-
if (configResolver === "WHALY" || configResolver === "LOCAL") {
|
|
322
|
-
return initResolver(configResolver);
|
|
323
|
-
} else {
|
|
324
|
-
throw new Error(`Resolver: ${configResolver} is not supported.
|
|
325
|
-
Did you properly configure \`SHORE__CONFIG_RESOLVER\`?`);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
88
|
+
var import_fs = require("fs");
|
|
328
89
|
|
|
329
90
|
// src/sdk/service/logger.ts
|
|
91
|
+
var winston = __toESM(require("winston"));
|
|
330
92
|
var BATCH_INTERVAL_MS = 100;
|
|
331
93
|
var transports2 = {
|
|
332
|
-
console: new
|
|
94
|
+
console: new winston.transports.Console({
|
|
333
95
|
stderrLevels: [],
|
|
334
96
|
level: "info"
|
|
335
97
|
})
|
|
@@ -339,8 +101,14 @@ var getTransports = () => {
|
|
|
339
101
|
transports2.console
|
|
340
102
|
];
|
|
341
103
|
};
|
|
342
|
-
var
|
|
343
|
-
|
|
104
|
+
var { combine, prettyPrint, timestamp, errors, splat } = winston.format;
|
|
105
|
+
var winstonLogger = winston.createLogger({
|
|
106
|
+
format: combine(
|
|
107
|
+
timestamp(),
|
|
108
|
+
errors({ stack: true }),
|
|
109
|
+
splat(),
|
|
110
|
+
prettyPrint()
|
|
111
|
+
),
|
|
344
112
|
transports: getTransports()
|
|
345
113
|
});
|
|
346
114
|
var isWinstonOpen = true;
|
|
@@ -369,37 +137,9 @@ var logger = {
|
|
|
369
137
|
};
|
|
370
138
|
|
|
371
139
|
// src/sdk/utils.ts
|
|
372
|
-
var writeStateFile = (resolver, state) => {
|
|
373
|
-
logger.info(`\u{1F4DD} Writing the state file.`);
|
|
374
|
-
return resolver.writeState(state);
|
|
375
|
-
};
|
|
376
|
-
var writeCatalogFile = (resolver, catalog) => {
|
|
377
|
-
logger.info(`\u{1F4DD} Writing the catalog`);
|
|
378
|
-
return resolver.writeCatalog(catalog);
|
|
379
|
-
};
|
|
380
|
-
var loadShoreConfig = (resolver) => {
|
|
381
|
-
const command = process.env.SHORE__COMMAND;
|
|
382
|
-
if (!command) {
|
|
383
|
-
throw new Error(`Env variable \`SHORE__COMMAND\` is not set.
|
|
384
|
-
Did you forget to configure it?
|
|
385
|
-
|
|
386
|
-
Possible values are: \`DISCOVER\`, \`READ\``);
|
|
387
|
-
}
|
|
388
|
-
if (command === `DISCOVER`) {
|
|
389
|
-
logger.info(`\u{1F50E} Mode: discovery
|
|
390
|
-
Shore will detect the available streams in the source and their configuration.`);
|
|
391
|
-
} else if (command === `READ`) {
|
|
392
|
-
logger.info(`\u{1F501} Mode: Synchronizing data
|
|
393
|
-
Shore will extract and push data from the source to the destination.`);
|
|
394
|
-
} else {
|
|
395
|
-
throw new Error(`Command: \`${command}\` is not supported.
|
|
396
|
-
Did you properly configured env variable \`SHORE__COMMAND\`?`);
|
|
397
|
-
}
|
|
398
|
-
return resolver.getConfig(command);
|
|
399
|
-
};
|
|
400
140
|
function readFile(fileName, filePath) {
|
|
401
141
|
try {
|
|
402
|
-
return (0,
|
|
142
|
+
return (0, import_fs.readFileSync)(filePath);
|
|
403
143
|
} catch (err) {
|
|
404
144
|
logger.error(`Can't open file: \`${fileName}\` at path: \`${filePath}\`. Is it at the proper place?`);
|
|
405
145
|
throw err;
|
|
@@ -422,18 +162,11 @@ var loadSchema = (streamId) => {
|
|
|
422
162
|
const schema = loadJson(`schemas/${streamId}.json`);
|
|
423
163
|
return schema;
|
|
424
164
|
};
|
|
425
|
-
var checkRequiredConfigKeys = (args, requiredConfigKeys) => {
|
|
426
|
-
requiredConfigKeys.forEach((configKey) => {
|
|
427
|
-
if (!args[configKey]) {
|
|
428
|
-
throw new Error(`Config is missing required key: ${configKey}`);
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
165
|
|
|
433
166
|
// src/sdk/service/network.ts
|
|
434
167
|
var import_axios = __toESM(require("axios"));
|
|
435
168
|
var import_url = require("url");
|
|
436
|
-
var
|
|
169
|
+
var fs = __toESM(require("fs"));
|
|
437
170
|
var qs = __toESM(require("qs"));
|
|
438
171
|
var import_axios_retry = __toESM(require("axios-retry"));
|
|
439
172
|
var shouldRetry = (error) => {
|
|
@@ -546,7 +279,7 @@ var getJSONApiCall = async (endpoint, config, privateLogging) => {
|
|
|
546
279
|
};
|
|
547
280
|
var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) => {
|
|
548
281
|
const instance = getAxiosInstance(config.retryCount);
|
|
549
|
-
const writer =
|
|
282
|
+
const writer = fs.createWriteStream(output);
|
|
550
283
|
const paramsSerializer = (params) => {
|
|
551
284
|
return qs.stringify(params, { arrayFormat: "repeat" });
|
|
552
285
|
};
|
|
@@ -640,6 +373,58 @@ var postUrlEncodedApiCall = async (endpoint, config, payload) => {
|
|
|
640
373
|
}
|
|
641
374
|
};
|
|
642
375
|
|
|
376
|
+
// src/sdk/service/metric.ts
|
|
377
|
+
var metricStore = {};
|
|
378
|
+
var ALL_STREAM_ID_KEYWORD = "all";
|
|
379
|
+
var ROWS_SYNCED_METRIC_NAME = "rows_synced_count";
|
|
380
|
+
var API_CALLS_METRIC_NAME = "api_calls_count";
|
|
381
|
+
var EXECUTION_TIME_METRIC_NAME = "execution_time_ms";
|
|
382
|
+
var getCounterMetrics = (name, streamIds) => {
|
|
383
|
+
return streamIds.map((streamId) => {
|
|
384
|
+
return getCounterMetric(name, streamId);
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
var getCounterMetric = (name, streamId = ALL_STREAM_ID_KEYWORD) => {
|
|
388
|
+
if (!metricStore[streamId]) {
|
|
389
|
+
metricStore[streamId] = {};
|
|
390
|
+
}
|
|
391
|
+
const streamMetricStore = metricStore[streamId];
|
|
392
|
+
if (!streamMetricStore[name]) {
|
|
393
|
+
streamMetricStore[name] = new CounterMetric(name, streamId);
|
|
394
|
+
}
|
|
395
|
+
return streamMetricStore[name];
|
|
396
|
+
};
|
|
397
|
+
var getAllMetrics = () => {
|
|
398
|
+
return Object.keys(metricStore).flatMap((streamId) => {
|
|
399
|
+
const streamMetricStore = metricStore[streamId];
|
|
400
|
+
if (!streamMetricStore) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
return Object.keys(streamMetricStore).map((metricName) => streamMetricStore[metricName]).filter((m) => m !== void 0);
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
var CounterMetric = class {
|
|
407
|
+
value = 0;
|
|
408
|
+
name;
|
|
409
|
+
streamId;
|
|
410
|
+
constructor(name, streamId) {
|
|
411
|
+
this.name = name;
|
|
412
|
+
this.streamId = streamId;
|
|
413
|
+
}
|
|
414
|
+
increment(inc = 1) {
|
|
415
|
+
this.value = this.value + inc;
|
|
416
|
+
}
|
|
417
|
+
getStreamId() {
|
|
418
|
+
return this.streamId;
|
|
419
|
+
}
|
|
420
|
+
getName() {
|
|
421
|
+
return this.name;
|
|
422
|
+
}
|
|
423
|
+
getValue() {
|
|
424
|
+
return this.value;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
643
428
|
// src/sdk/service/memory.ts
|
|
644
429
|
var formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
|
|
645
430
|
var printMemoryFootprint = (prefix) => {
|
|
@@ -655,9 +440,6 @@ var printMemoryFootprint = (prefix) => {
|
|
|
655
440
|
|
|
656
441
|
// src/sdk/service/exit.ts
|
|
657
442
|
async function gracefulExit(exitCode) {
|
|
658
|
-
if (exitCode !== 0) {
|
|
659
|
-
await loadResolver().markSyncFailed();
|
|
660
|
-
}
|
|
661
443
|
const loggerFinish = new Promise((resolve, reject) => {
|
|
662
444
|
logger.closeWinston(resolve);
|
|
663
445
|
});
|
|
@@ -677,207 +459,15 @@ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
|
|
|
677
459
|
errorText,
|
|
678
460
|
errorDebugText
|
|
679
461
|
);
|
|
680
|
-
const resolver = loadResolver();
|
|
681
|
-
await resolver.onError(errorType, errorText, errorDebugText);
|
|
682
462
|
await gracefulExit(0);
|
|
683
463
|
};
|
|
684
464
|
|
|
685
|
-
// src/sdk/
|
|
686
|
-
var
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
to: rel.to
|
|
692
|
-
};
|
|
693
|
-
if (rel.left === streamId) {
|
|
694
|
-
acc.right.push({
|
|
695
|
-
streamId: rel.right,
|
|
696
|
-
...base
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
if (rel.right === streamId) {
|
|
700
|
-
acc.left.push({
|
|
701
|
-
streamId: rel.left,
|
|
702
|
-
...base
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
return acc;
|
|
706
|
-
}, { left: [], right: [] });
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
// src/sdk/models/metadata.ts
|
|
710
|
-
var MetadataMap = class extends Map {
|
|
711
|
-
areEquals(array1, array2) {
|
|
712
|
-
return array1.length === array2.length && array1.every(function(value, index) {
|
|
713
|
-
return value === array2[index];
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
findExistingEntry(key) {
|
|
717
|
-
return Array.from(super.entries()).find((entry) => {
|
|
718
|
-
if (this.areEquals(entry[0], key)) {
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
get(key) {
|
|
724
|
-
const existingEntry = this.findExistingEntry(key);
|
|
725
|
-
return existingEntry?.[1];
|
|
726
|
-
}
|
|
727
|
-
set(key, value) {
|
|
728
|
-
const existingKey = this.findExistingEntry(key);
|
|
729
|
-
if (existingKey) {
|
|
730
|
-
return super.set(existingKey?.[0], value);
|
|
731
|
-
} else {
|
|
732
|
-
return super.set(key, value);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
var StreamMetadata = class {
|
|
737
|
-
metadataByBreadcrumb;
|
|
738
|
-
constructor() {
|
|
739
|
-
this.metadataByBreadcrumb = new MetadataMap();
|
|
740
|
-
}
|
|
741
|
-
fromCatalogStreamMetadata(metadataArr) {
|
|
742
|
-
metadataArr.forEach((metadata) => {
|
|
743
|
-
this.metadataByBreadcrumb.set(metadata.breadcrumb, metadata.metadata);
|
|
744
|
-
});
|
|
745
|
-
return this;
|
|
746
|
-
}
|
|
747
|
-
toString() {
|
|
748
|
-
return this.metadataByBreadcrumb;
|
|
749
|
-
}
|
|
750
|
-
get(breadcrumb) {
|
|
751
|
-
return this.metadataByBreadcrumb.get(breadcrumb);
|
|
752
|
-
}
|
|
753
|
-
write(breadcrumb, key, value) {
|
|
754
|
-
if (value === void 0) {
|
|
755
|
-
throw new Error(`The value you're trying to write on the metadata at breadcrumb: \`${breadcrumb}\` and key: \`${key}\` is undefined.
|
|
756
|
-
|
|
757
|
-
Writing an undefined value is not a valid operation. Is there something wrong with your value?`);
|
|
758
|
-
}
|
|
759
|
-
if (!this.metadataByBreadcrumb.get(breadcrumb)) {
|
|
760
|
-
this.metadataByBreadcrumb.set(breadcrumb, {});
|
|
761
|
-
}
|
|
762
|
-
const previousValue = this.metadataByBreadcrumb.get(breadcrumb);
|
|
763
|
-
const newValue = {};
|
|
764
|
-
newValue[key] = value;
|
|
765
|
-
this.metadataByBreadcrumb.set(breadcrumb, { ...previousValue, ...newValue });
|
|
766
|
-
return this;
|
|
767
|
-
}
|
|
768
|
-
getRootBreadcrumbMetadata() {
|
|
769
|
-
const rootBreadcrumbMetadata = this.metadataByBreadcrumb.get([]);
|
|
770
|
-
if (!rootBreadcrumbMetadata) {
|
|
771
|
-
throw new Error(`No metadata was attached to the root breadcrumb \`[\`] while we need one to store metadata at the table level.
|
|
772
|
-
|
|
773
|
-
Is your catalog properly generated?`);
|
|
774
|
-
}
|
|
775
|
-
return rootBreadcrumbMetadata;
|
|
776
|
-
}
|
|
777
|
-
isStreamSelected() {
|
|
778
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
779
|
-
if (rootBreadcrumbMetadata.selected === false) {
|
|
780
|
-
return false;
|
|
781
|
-
}
|
|
782
|
-
if (rootBreadcrumbMetadata.selected === true || rootBreadcrumbMetadata["selected-by-default"] === true) {
|
|
783
|
-
return true;
|
|
784
|
-
}
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
getKeyProperties() {
|
|
788
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
789
|
-
if (!rootBreadcrumbMetadata["table-key-properties"]) {
|
|
790
|
-
return [];
|
|
791
|
-
}
|
|
792
|
-
return rootBreadcrumbMetadata["table-key-properties"];
|
|
793
|
-
}
|
|
794
|
-
getReplicationKey() {
|
|
795
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
796
|
-
if (!rootBreadcrumbMetadata["replication-key"]) {
|
|
797
|
-
return void 0;
|
|
798
|
-
}
|
|
799
|
-
return rootBreadcrumbMetadata["replication-key"];
|
|
800
|
-
}
|
|
801
|
-
getReplicationMethod() {
|
|
802
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
803
|
-
if (!rootBreadcrumbMetadata["replication-method"]) {
|
|
804
|
-
return void 0;
|
|
805
|
-
}
|
|
806
|
-
return rootBreadcrumbMetadata["replication-method"];
|
|
807
|
-
}
|
|
808
|
-
getForcedReplicationMethod() {
|
|
809
|
-
const rootBreadcrumbMetadata = this.getRootBreadcrumbMetadata();
|
|
810
|
-
if (!rootBreadcrumbMetadata["forced-replication-method"]) {
|
|
811
|
-
return void 0;
|
|
812
|
-
}
|
|
813
|
-
return rootBreadcrumbMetadata["forced-replication-method"];
|
|
814
|
-
}
|
|
815
|
-
toList() {
|
|
816
|
-
return Array.from(this.metadataByBreadcrumb.keys()).map((breadcrumb) => {
|
|
817
|
-
const metadata = this.metadataByBreadcrumb.get(breadcrumb);
|
|
818
|
-
if (!metadata) {
|
|
819
|
-
throw new Error(`There was no metadata entry for breadcrumb: \`${breadcrumb}\` in metadata map: ${this.metadataByBreadcrumb.toString()}`);
|
|
820
|
-
}
|
|
821
|
-
return {
|
|
822
|
-
breadcrumb,
|
|
823
|
-
metadata
|
|
824
|
-
};
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
// src/sdk/models/catalog.ts
|
|
830
|
-
var _ = __toESM(require("lodash"));
|
|
831
|
-
var Catalog = class _Catalog {
|
|
832
|
-
streams;
|
|
833
|
-
static fromFile(fileName) {
|
|
834
|
-
const catalogFile = loadJson(fileName);
|
|
835
|
-
const catalog = new _Catalog();
|
|
836
|
-
catalog.streams = catalogFile.streams;
|
|
837
|
-
return catalog;
|
|
838
|
-
}
|
|
839
|
-
static fromCatalogFile(catalogFile) {
|
|
840
|
-
const catalog = new _Catalog();
|
|
841
|
-
catalog.streams = catalogFile.streams;
|
|
842
|
-
return catalog;
|
|
843
|
-
}
|
|
844
|
-
getSelectedStreams() {
|
|
845
|
-
if (!this.streams) {
|
|
846
|
-
return [];
|
|
847
|
-
}
|
|
848
|
-
const directSelectedStreams = this.streams.filter((stream) => {
|
|
849
|
-
const metadata = new StreamMetadata();
|
|
850
|
-
metadata.fromCatalogStreamMetadata(stream.metadata);
|
|
851
|
-
return metadata.isStreamSelected();
|
|
852
|
-
});
|
|
853
|
-
logger.debug(`Directly selected streams: ${directSelectedStreams.map((stream) => stream.stream).join(",")}`);
|
|
854
|
-
const getIndirectSelectedStreams = (stream, ancestorsIds) => {
|
|
855
|
-
const currAncestorsIds = ancestorsIds || [];
|
|
856
|
-
const rootBreadcrumbMetadata = stream.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
857
|
-
const parentStreamId = rootBreadcrumbMetadata?.metadata?.["whaly-parent-stream"];
|
|
858
|
-
if (parentStreamId) {
|
|
859
|
-
const parentStream = this.streams?.find((stream2) => stream2.stream === parentStreamId);
|
|
860
|
-
if (parentStream) {
|
|
861
|
-
currAncestorsIds.push(parentStreamId);
|
|
862
|
-
return getIndirectSelectedStreams(parentStream, currAncestorsIds);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
return currAncestorsIds;
|
|
866
|
-
};
|
|
867
|
-
const indirectSelectedStreamIds = _.uniq(
|
|
868
|
-
directSelectedStreams.flatMap((stream) => {
|
|
869
|
-
return getIndirectSelectedStreams(stream);
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
logger.debug(`Indirectly selected streams: ${indirectSelectedStreamIds.join(",")}`);
|
|
873
|
-
const indirectSelectedStreams = indirectSelectedStreamIds.map((streamId) => {
|
|
874
|
-
if (!directSelectedStreams.find((stream) => stream.stream === streamId)) {
|
|
875
|
-
return this.streams?.find((stream) => stream.stream === streamId);
|
|
876
|
-
}
|
|
877
|
-
}).filter((stream) => !!stream);
|
|
878
|
-
return directSelectedStreams.concat(indirectSelectedStreams);
|
|
879
|
-
}
|
|
880
|
-
};
|
|
465
|
+
// src/sdk/models/replication.ts
|
|
466
|
+
var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
|
|
467
|
+
ReplicationMethod2["INCREMENTAL"] = "INCREMENTAL";
|
|
468
|
+
ReplicationMethod2["FULL_TABLE"] = "FULL_TABLE";
|
|
469
|
+
return ReplicationMethod2;
|
|
470
|
+
})(ReplicationMethod || {});
|
|
881
471
|
|
|
882
472
|
// src/sdk/models/messages.ts
|
|
883
473
|
var RecordMessage = class {
|
|
@@ -937,7 +527,6 @@ var SchemaMessage = class {
|
|
|
937
527
|
displayLabel;
|
|
938
528
|
description;
|
|
939
529
|
propertiesMetadata;
|
|
940
|
-
relationships;
|
|
941
530
|
constructor(opts) {
|
|
942
531
|
const {
|
|
943
532
|
stream,
|
|
@@ -946,7 +535,6 @@ var SchemaMessage = class {
|
|
|
946
535
|
bookmarkProperties,
|
|
947
536
|
displayLabel,
|
|
948
537
|
description,
|
|
949
|
-
relationships,
|
|
950
538
|
propertiesMetadata
|
|
951
539
|
} = opts;
|
|
952
540
|
this.stream = stream;
|
|
@@ -955,7 +543,6 @@ var SchemaMessage = class {
|
|
|
955
543
|
this.bookmarkProperties = bookmarkProperties;
|
|
956
544
|
this.displayLabel = displayLabel;
|
|
957
545
|
this.description = description;
|
|
958
|
-
this.relationships = relationships;
|
|
959
546
|
this.propertiesMetadata = propertiesMetadata;
|
|
960
547
|
}
|
|
961
548
|
toString() {
|
|
@@ -965,8 +552,7 @@ var SchemaMessage = class {
|
|
|
965
552
|
schema: this.schema,
|
|
966
553
|
key_properties: this.keyProperties,
|
|
967
554
|
label: this.displayLabel,
|
|
968
|
-
description: this.description
|
|
969
|
-
relationships: this.relationships
|
|
555
|
+
description: this.description
|
|
970
556
|
};
|
|
971
557
|
if (this.bookmarkProperties) {
|
|
972
558
|
result["bookmark_properties"] = this.bookmarkProperties;
|
|
@@ -976,7 +562,7 @@ var SchemaMessage = class {
|
|
|
976
562
|
};
|
|
977
563
|
|
|
978
564
|
// src/sdk/models/state.ts
|
|
979
|
-
var
|
|
565
|
+
var import_dayjs = __toESM(require("dayjs"));
|
|
980
566
|
|
|
981
567
|
// src/sdk/constants/date.ts
|
|
982
568
|
var defaultDateTimeFormat = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ";
|
|
@@ -991,12 +577,12 @@ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
|
991
577
|
if (!newRkValue) {
|
|
992
578
|
throw new Error((0, import_util.format)(`No value for replicationKey: ${replicationKey} in record with keys: %j`, Object.keys(latestRecord)));
|
|
993
579
|
}
|
|
994
|
-
let newRkValueMoment = (0,
|
|
580
|
+
let newRkValueMoment = (0, import_dayjs.default)(newRkValue);
|
|
995
581
|
let prevRkValue;
|
|
996
582
|
let prevRkValueMoment;
|
|
997
583
|
if (isSorted) {
|
|
998
584
|
prevRkValue = state["replicationKeyValue"];
|
|
999
|
-
prevRkValueMoment = (0,
|
|
585
|
+
prevRkValueMoment = (0, import_dayjs.default)(prevRkValue);
|
|
1000
586
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
1001
587
|
throw new Error(
|
|
1002
588
|
`Unsorted data detected in stream. Latest value '${newRkValue}' is
|
|
@@ -1011,24 +597,24 @@ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
|
|
|
1011
597
|
state[PROGRESS_MARKERS] = marker;
|
|
1012
598
|
}
|
|
1013
599
|
prevRkValue = state[PROGRESS_MARKERS]["replicationKeyValue"];
|
|
1014
|
-
prevRkValueMoment = (0,
|
|
600
|
+
prevRkValueMoment = (0, import_dayjs.default)(prevRkValue || state["replicationKeyValue"]);
|
|
1015
601
|
if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
|
|
1016
602
|
newRkValueMoment = prevRkValueMoment;
|
|
1017
603
|
newRkValue = prevRkValue;
|
|
1018
604
|
}
|
|
1019
605
|
}
|
|
1020
606
|
const signpostMarker = state[SIGNPOST_MARKER];
|
|
1021
|
-
const replicationKeySignpostMoment = (0,
|
|
607
|
+
const replicationKeySignpostMoment = (0, import_dayjs.default)(signpostMarker);
|
|
1022
608
|
if (replicationKeySignpostMoment && replicationKeySignpostMoment.isBefore(newRkValueMoment)) {
|
|
1023
609
|
newRkValueMoment = replicationKeySignpostMoment;
|
|
1024
610
|
newRkValue = replicationKeySignpostMoment.format(defaultDateTimeFormat);
|
|
1025
611
|
}
|
|
1026
612
|
if (isSorted === false) {
|
|
1027
613
|
state[PROGRESS_MARKERS]["replicationKey"] = replicationKey;
|
|
1028
|
-
state[PROGRESS_MARKERS]["replicationKeyValue"] = (0,
|
|
614
|
+
state[PROGRESS_MARKERS]["replicationKeyValue"] = (0, import_dayjs.default)(newRkValueMoment).format(defaultDateTimeFormat);
|
|
1029
615
|
} else {
|
|
1030
616
|
state["replicationKey"] = replicationKey;
|
|
1031
|
-
state["replicationKeyValue"] = (0,
|
|
617
|
+
state["replicationKeyValue"] = (0, import_dayjs.default)(newRkValueMoment).format(defaultDateTimeFormat);
|
|
1032
618
|
}
|
|
1033
619
|
return state;
|
|
1034
620
|
};
|
|
@@ -1050,7 +636,7 @@ var finalizeStateProgressMarkers = (state) => {
|
|
|
1050
636
|
return state;
|
|
1051
637
|
};
|
|
1052
638
|
var _greaterThan = (aValue, bValue) => {
|
|
1053
|
-
return (0,
|
|
639
|
+
return (0, import_dayjs.default)(aValue).isAfter((0, import_dayjs.default)(bValue));
|
|
1054
640
|
};
|
|
1055
641
|
var extractStateForStream = (state, streamId) => {
|
|
1056
642
|
if (!state.bookmarks) {
|
|
@@ -1073,26 +659,16 @@ var StateService = class {
|
|
|
1073
659
|
}
|
|
1074
660
|
return this.instance;
|
|
1075
661
|
}
|
|
1076
|
-
|
|
1077
|
-
setBookmark(streamId, ts) {
|
|
1078
|
-
this.bookmarks[streamId] = ts.format(defaultDateTimeFormat);
|
|
1079
|
-
}
|
|
1080
|
-
setBookmarkV2(streamId, streamState) {
|
|
662
|
+
setBookmark(streamId, streamState) {
|
|
1081
663
|
this.bookmarks[streamId] = streamState;
|
|
1082
664
|
}
|
|
1083
|
-
|
|
665
|
+
setBookmarkSignpost(streamId, signpostValue) {
|
|
1084
666
|
if (!this.bookmarks[streamId]) {
|
|
1085
667
|
this.bookmarks[streamId] = {};
|
|
1086
668
|
}
|
|
1087
669
|
this.bookmarks[streamId][SIGNPOST_MARKER] = signpostValue;
|
|
1088
670
|
}
|
|
1089
671
|
getBookmark(streamId) {
|
|
1090
|
-
if (!this.bookmarks[streamId]) {
|
|
1091
|
-
return (0, import_moment.default)(0);
|
|
1092
|
-
}
|
|
1093
|
-
return (0, import_moment.default)(this.bookmarks[streamId]);
|
|
1094
|
-
}
|
|
1095
|
-
getBookmarkV2(streamId) {
|
|
1096
672
|
if (this.bookmarks[streamId]) {
|
|
1097
673
|
return this.bookmarks[streamId];
|
|
1098
674
|
} else {
|
|
@@ -1132,7 +708,7 @@ var import_axios2 = __toESM(require("axios"));
|
|
|
1132
708
|
var qs2 = __toESM(require("qs"));
|
|
1133
709
|
|
|
1134
710
|
// src/sdk/models/tap/stream.ts
|
|
1135
|
-
var
|
|
711
|
+
var import_dayjs2 = __toESM(require("dayjs"));
|
|
1136
712
|
|
|
1137
713
|
// src/sdk/helpers/typing.ts
|
|
1138
714
|
var isDatetimeType = (typeDef) => {
|
|
@@ -1155,186 +731,56 @@ var isDatetimeType = (typeDef) => {
|
|
|
1155
731
|
};
|
|
1156
732
|
|
|
1157
733
|
// src/sdk/models/tap/stream.ts
|
|
1158
|
-
var
|
|
1159
|
-
var import_bluebird2 = require("bluebird");
|
|
734
|
+
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"));
|
|
735
|
+
var import_bluebird2 = __toESM(require("bluebird"));
|
|
1160
736
|
|
|
1161
737
|
// src/sdk/models/tap/tap.ts
|
|
1162
|
-
var
|
|
1163
|
-
var
|
|
738
|
+
var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"));
|
|
739
|
+
var import_bluebird = __toESM(require("bluebird"));
|
|
1164
740
|
var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
|
|
1165
741
|
var Tap = class {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
this.
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
const buildDependencyGraph = (streams2, parentId) => {
|
|
1179
|
-
streams2.forEach((s) => {
|
|
1180
|
-
streamDepGraph.addNode(s.streamId, s);
|
|
1181
|
-
if (parentId) {
|
|
1182
|
-
streamDepGraph.addDependency(s.streamId, parentId);
|
|
1183
|
-
}
|
|
1184
|
-
if (s.children) {
|
|
1185
|
-
return buildDependencyGraph(s.children, s.streamId);
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
};
|
|
1189
|
-
buildDependencyGraph(this._streams);
|
|
1190
|
-
const discoveredStreamCatalogEntries = await import_bluebird.Promise.map(
|
|
1191
|
-
streamDepGraph.overallOrder(),
|
|
1192
|
-
async (streamId) => {
|
|
1193
|
-
logger.info(`\u2728 Loading schema for ${streamId}`);
|
|
1194
|
-
const stream = streamDepGraph.getNodeData(streamId);
|
|
1195
|
-
const schema = await stream.getSchema();
|
|
1196
|
-
const parentStreamIds = streamDepGraph.directDependenciesOf(streamId);
|
|
1197
|
-
if (parentStreamIds.length > 1) {
|
|
1198
|
-
throw new Error(`StreamId=${streamId} directly depends on more than 1 stream, e.g. ${parentStreamIds.join(",")} which is not supported!`);
|
|
1199
|
-
}
|
|
1200
|
-
const parentStreamId = parentStreamIds?.[0];
|
|
1201
|
-
const { metadata } = await this.generateMetadataFromStream(stream, parentStreamId);
|
|
1202
|
-
return {
|
|
1203
|
-
schema,
|
|
1204
|
-
metadata,
|
|
1205
|
-
stream: stream.streamId,
|
|
1206
|
-
tap_stream_id: stream.streamId
|
|
1207
|
-
};
|
|
1208
|
-
},
|
|
1209
|
-
{ concurrency: this._concurrency }
|
|
1210
|
-
);
|
|
1211
|
-
const existingCatalogStreamIds = (existingCatalog?.streams || []).map((entry) => {
|
|
1212
|
-
return entry.stream;
|
|
1213
|
-
});
|
|
1214
|
-
const newlyDiscoveredStreamCatalogEntries = discoveredStreamCatalogEntries.filter((entry) => {
|
|
1215
|
-
if (existingCatalogStreamIds.includes(entry.stream)) {
|
|
1216
|
-
return false;
|
|
1217
|
-
} else {
|
|
1218
|
-
return true;
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
logger.debug(`Newly discovered streams: %j`, newlyDiscoveredStreamCatalogEntries);
|
|
1222
|
-
const allDiscoveredStreamIds = discoveredStreamCatalogEntries.map((entry) => {
|
|
1223
|
-
return entry.stream;
|
|
1224
|
-
});
|
|
1225
|
-
const existingCatalogMinusDeletedStreams = (existingCatalog?.streams || []).filter((entry) => {
|
|
1226
|
-
if (!allDiscoveredStreamIds.includes(entry.stream)) {
|
|
1227
|
-
logger.debug(`Stream %s was deleted, removing it from the catalog`, entry.stream);
|
|
1228
|
-
return false;
|
|
1229
|
-
} else {
|
|
1230
|
-
return true;
|
|
1231
|
-
}
|
|
1232
|
-
});
|
|
1233
|
-
const updatedExistingCatalog = existingCatalogMinusDeletedStreams.map((existingCatalogEntry) => {
|
|
1234
|
-
const matchingDiscoveredCatalogEntry = discoveredStreamCatalogEntries.find((entry) => entry.stream === existingCatalogEntry.stream);
|
|
1235
|
-
if (matchingDiscoveredCatalogEntry) {
|
|
1236
|
-
const existingRootMetadata = existingCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
1237
|
-
const discoveredRootMetadata = matchingDiscoveredCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
|
|
1238
|
-
if (existingRootMetadata) {
|
|
1239
|
-
const parentStreamVal = discoveredRootMetadata?.metadata["whaly-parent-stream"];
|
|
1240
|
-
if (parentStreamVal !== void 0) {
|
|
1241
|
-
existingRootMetadata.metadata["whaly-parent-stream"] = parentStreamVal;
|
|
1242
|
-
}
|
|
1243
|
-
const forcedReplicationMethodVal = discoveredRootMetadata?.metadata["forced-replication-method"];
|
|
1244
|
-
if (forcedReplicationMethodVal !== void 0) {
|
|
1245
|
-
existingRootMetadata.metadata["forced-replication-method"] = forcedReplicationMethodVal;
|
|
1246
|
-
}
|
|
1247
|
-
const forcedReplicationKeyVal = discoveredRootMetadata?.metadata["forced-replication-key"];
|
|
1248
|
-
if (forcedReplicationKeyVal !== void 0) {
|
|
1249
|
-
existingRootMetadata.metadata["forced-replication-key"] = forcedReplicationKeyVal;
|
|
1250
|
-
}
|
|
1251
|
-
const validReplicationKeysVal = discoveredRootMetadata?.metadata["valid-replication-keys"];
|
|
1252
|
-
if (validReplicationKeysVal !== void 0) {
|
|
1253
|
-
existingRootMetadata.metadata["valid-replication-keys"] = validReplicationKeysVal;
|
|
1254
|
-
}
|
|
1255
|
-
const rowCountVal = discoveredRootMetadata?.metadata["row-count"];
|
|
1256
|
-
if (rowCountVal !== void 0) {
|
|
1257
|
-
existingRootMetadata.metadata["row-count"] = rowCountVal;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
return existingCatalogEntry;
|
|
1262
|
-
});
|
|
1263
|
-
const streams = updatedExistingCatalog.concat(newlyDiscoveredStreamCatalogEntries);
|
|
1264
|
-
return { streams };
|
|
1265
|
-
};
|
|
1266
|
-
sync = async (catalog) => {
|
|
1267
|
-
await this.initiateSync(catalog);
|
|
1268
|
-
logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
|
|
1269
|
-
await this._target.complete();
|
|
1270
|
-
return Promise.resolve();
|
|
1271
|
-
};
|
|
1272
|
-
async generateMetadataFromStream(stream, parentStreamId) {
|
|
1273
|
-
const metadata = new StreamMetadata();
|
|
1274
|
-
metadata.write([], "table-key-properties", stream.primaryKey);
|
|
1275
|
-
const defaultReplicationConfig = stream.defaultReplicationConfig();
|
|
1276
|
-
metadata.write([], "replication-method", defaultReplicationConfig.replicationMethod);
|
|
1277
|
-
const validReplicationkeys = await stream.getValidReplicationKeys();
|
|
1278
|
-
metadata.write([], "valid-replication-keys", validReplicationkeys);
|
|
1279
|
-
if (defaultReplicationConfig.isForced) {
|
|
1280
|
-
metadata.write([], "forced-replication-method", defaultReplicationConfig.replicationMethod);
|
|
1281
|
-
if (stream.forcedReplicationKey) {
|
|
1282
|
-
metadata.write([], "forced-replication-key", stream.forcedReplicationKey);
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
const rowCount = await stream.getRowCount();
|
|
1286
|
-
if (rowCount !== void 0) {
|
|
1287
|
-
metadata.write([], "row-count", rowCount);
|
|
1288
|
-
}
|
|
1289
|
-
if (stream.isSilent === true) {
|
|
1290
|
-
metadata.write([], "whaly-is-hidden", stream.isSilent);
|
|
1291
|
-
}
|
|
1292
|
-
if (stream.isSilent === false) {
|
|
1293
|
-
metadata.write([], "selected-by-default", stream.selectedByDefault);
|
|
1294
|
-
}
|
|
1295
|
-
if (stream.displayLabel) {
|
|
1296
|
-
metadata.write([], "whaly-display-label", stream.displayLabel);
|
|
1297
|
-
}
|
|
1298
|
-
if (stream.description) {
|
|
1299
|
-
metadata.write([], "whaly-description", stream.description);
|
|
1300
|
-
}
|
|
1301
|
-
if (parentStreamId !== void 0) {
|
|
1302
|
-
metadata.write([], "whaly-parent-stream", parentStreamId);
|
|
1303
|
-
}
|
|
1304
|
-
return { metadata: metadata.toList() };
|
|
742
|
+
target;
|
|
743
|
+
config;
|
|
744
|
+
streams;
|
|
745
|
+
concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
|
|
746
|
+
stateProvider;
|
|
747
|
+
tapState;
|
|
748
|
+
constructor(target, config, stateProvider) {
|
|
749
|
+
this.streams = [];
|
|
750
|
+
this.target = target;
|
|
751
|
+
this.config = config;
|
|
752
|
+
this.stateProvider = stateProvider;
|
|
753
|
+
this.tapState = { bookmarks: {} };
|
|
1305
754
|
}
|
|
1306
|
-
async
|
|
755
|
+
sync = async (options) => {
|
|
756
|
+
const initialState = await this.stateProvider.getState();
|
|
757
|
+
if (initialState.state && initialState.state.bookmarks) {
|
|
758
|
+
this.tapState = (0, import_cloneDeep.default)(initialState.state);
|
|
759
|
+
}
|
|
760
|
+
await this.init();
|
|
1307
761
|
logger.info(`\u{1F680} Start syncing`);
|
|
1308
762
|
const state = StateService.getInstance().get();
|
|
1309
763
|
logger.info(`\u{1F4CD} Received state: %j`, state);
|
|
1310
|
-
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
return stream.sync();
|
|
1331
|
-
}
|
|
1332
|
-
},
|
|
1333
|
-
{
|
|
1334
|
-
concurrency: this._concurrency
|
|
1335
|
-
}
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
764
|
+
const envInclude = process.env.TAP_STREAMS ? process.env.TAP_STREAMS.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
765
|
+
const include = options?.include && options.include.length > 0 ? options.include : envInclude;
|
|
766
|
+
const exclude = new Set((options?.exclude || []).map((s) => s.trim()));
|
|
767
|
+
let streamsToRun = this.streams.filter((s) => !s.isSilent);
|
|
768
|
+
if (include && include.length > 0) {
|
|
769
|
+
const includeSet = new Set(include);
|
|
770
|
+
streamsToRun = streamsToRun.filter((s) => includeSet.has(s.streamId));
|
|
771
|
+
}
|
|
772
|
+
if (exclude.size > 0) {
|
|
773
|
+
streamsToRun = streamsToRun.filter((s) => !exclude.has(s.streamId));
|
|
774
|
+
}
|
|
775
|
+
const selectedStreamIds = streamsToRun.map((s) => s.streamId);
|
|
776
|
+
logger.info(`\u{1F973} Syncing streams: %s`, selectedStreamIds.join(","));
|
|
777
|
+
await import_bluebird.default.map(streamsToRun, (stream) => {
|
|
778
|
+
return stream.sync();
|
|
779
|
+
}, { concurrency: this.concurrency });
|
|
780
|
+
logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
|
|
781
|
+
await this.target.complete();
|
|
782
|
+
return Promise.resolve();
|
|
783
|
+
};
|
|
1338
784
|
// Used to clean up any ressource before exit
|
|
1339
785
|
end() {
|
|
1340
786
|
return Promise.resolve();
|
|
@@ -1343,18 +789,12 @@ var Tap = class {
|
|
|
1343
789
|
|
|
1344
790
|
// src/sdk/models/tap/stream.ts
|
|
1345
791
|
var import_util2 = require("util");
|
|
1346
|
-
var
|
|
792
|
+
var Stream = class {
|
|
1347
793
|
// Runtime values, can't be overriden
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
// Contains the root metadata entry of the catalog stream configuration
|
|
1353
|
-
// contains some end user schema configuration
|
|
1354
|
-
rootMetadataEntry;
|
|
1355
|
-
// Compile time values, can be overriden
|
|
1356
|
-
// Replication method that can be forced in the stream implemenation. Will prevail over any user/catalog choice
|
|
1357
|
-
forcedReplicationMethod = void 0;
|
|
794
|
+
config;
|
|
795
|
+
tapState;
|
|
796
|
+
target;
|
|
797
|
+
replicationMethod = void 0;
|
|
1358
798
|
selectedByDefault = true;
|
|
1359
799
|
STATE_MSG_FREQUENCY = 10;
|
|
1360
800
|
streamId = "default";
|
|
@@ -1362,10 +802,9 @@ var StreamV2 = class {
|
|
|
1362
802
|
displayLabel = void 0;
|
|
1363
803
|
description = void 0;
|
|
1364
804
|
primaryKey = [];
|
|
1365
|
-
|
|
805
|
+
replicationKey = void 0;
|
|
1366
806
|
// When set, use the state from another stream for this stream
|
|
1367
807
|
useStateFromStreamId = void 0;
|
|
1368
|
-
relationships = [];
|
|
1369
808
|
children = [];
|
|
1370
809
|
// If true, it means that the records in this stream are sorted by replicationKey.
|
|
1371
810
|
// If false, then the records are coming in an unsorted manner.
|
|
@@ -1377,11 +816,10 @@ var StreamV2 = class {
|
|
|
1377
816
|
// Metrics configuration
|
|
1378
817
|
rowsSyncedMetricsConf;
|
|
1379
818
|
executionTimeMetricsConf;
|
|
1380
|
-
constructor(
|
|
1381
|
-
this.
|
|
1382
|
-
this.
|
|
1383
|
-
this.
|
|
1384
|
-
this._target = target;
|
|
819
|
+
constructor(config, tapState, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
820
|
+
this.config = config;
|
|
821
|
+
this.tapState = (0, import_cloneDeep2.default)(tapState);
|
|
822
|
+
this.target = target;
|
|
1385
823
|
this.childConcurrency = childConcurrency;
|
|
1386
824
|
this.rowsSyncedMetricsConf = {
|
|
1387
825
|
isEnabled: () => !this.isSilent,
|
|
@@ -1392,9 +830,7 @@ var StreamV2 = class {
|
|
|
1392
830
|
getStreamIds: () => [this.streamId]
|
|
1393
831
|
};
|
|
1394
832
|
}
|
|
1395
|
-
|
|
1396
|
-
this.rootMetadataEntry = rootMetadataEntry;
|
|
1397
|
-
};
|
|
833
|
+
// No root metadata: replication config is derived from stream defaults only
|
|
1398
834
|
/**
|
|
1399
835
|
* Sync this stream
|
|
1400
836
|
* Called only for streams with no parents, aka. "root tap streams"
|
|
@@ -1425,7 +861,7 @@ var StreamV2 = class {
|
|
|
1425
861
|
`\u{1F6BD} Flushing ${replicationMethod} sync of stream '${this.streamId}' + all of its children...`
|
|
1426
862
|
);
|
|
1427
863
|
await this._flush();
|
|
1428
|
-
await import_bluebird2.
|
|
864
|
+
await import_bluebird2.default.map(this.children, (child) => {
|
|
1429
865
|
return child.flush();
|
|
1430
866
|
}, { concurrency: 3 });
|
|
1431
867
|
};
|
|
@@ -1435,7 +871,7 @@ var StreamV2 = class {
|
|
|
1435
871
|
*/
|
|
1436
872
|
_writeStateMessage = () => {
|
|
1437
873
|
const message = new StateMessage(StateService.getInstance().get());
|
|
1438
|
-
return this.
|
|
874
|
+
return this.target.state(message);
|
|
1439
875
|
};
|
|
1440
876
|
/**
|
|
1441
877
|
* Write out a ACTIVATE_VERSION message.
|
|
@@ -1443,10 +879,10 @@ var StreamV2 = class {
|
|
|
1443
879
|
_writeReplicationMethodMessage = async () => {
|
|
1444
880
|
const method = this.configuredReplicationConfig().replicationMethod;
|
|
1445
881
|
const message = new ReplicationMethodMessage(this.streamId, method);
|
|
1446
|
-
await import_bluebird2.
|
|
882
|
+
await import_bluebird2.default.map(this.children, async (c) => {
|
|
1447
883
|
return c._writeReplicationMethodMessage();
|
|
1448
884
|
}, { concurrency: 3 });
|
|
1449
|
-
await this.
|
|
885
|
+
await this.target.replicationMethod(message);
|
|
1450
886
|
};
|
|
1451
887
|
/**
|
|
1452
888
|
* Write out a SCHEMA message with the stream schema.
|
|
@@ -1455,23 +891,21 @@ var StreamV2 = class {
|
|
|
1455
891
|
if (!this.isSilent) {
|
|
1456
892
|
const schema = await this.getSchema();
|
|
1457
893
|
if (schema !== void 0) {
|
|
1458
|
-
const relatedRels = findRelatedRelationships(this.streamId, this.relationships);
|
|
1459
894
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1460
895
|
const message = new SchemaMessage({
|
|
1461
896
|
keyProperties: this.primaryKey,
|
|
1462
897
|
stream: this.streamId,
|
|
1463
898
|
schema: schema.jsonSchema,
|
|
1464
|
-
relationships: relatedRels,
|
|
1465
899
|
...replicationConfig.replicationKey ? { bookmarkProperties: [replicationConfig.replicationKey] } : {},
|
|
1466
900
|
...this.displayLabel ? { displayLabel: this.displayLabel } : {},
|
|
1467
901
|
...this.description ? { description: this.description } : {},
|
|
1468
902
|
...schema.propertiesMetadata ? { propertiesMetadata: schema.propertiesMetadata } : {}
|
|
1469
903
|
});
|
|
1470
|
-
await this.
|
|
904
|
+
await this.target.schema(message);
|
|
1471
905
|
}
|
|
1472
906
|
}
|
|
1473
907
|
if (skipChildrenSchema !== true) {
|
|
1474
|
-
await import_bluebird2.
|
|
908
|
+
await import_bluebird2.default.map(this.children, async (child) => {
|
|
1475
909
|
await child._writeSchemaMessage();
|
|
1476
910
|
}, { concurrency: 3 });
|
|
1477
911
|
}
|
|
@@ -1481,7 +915,7 @@ var StreamV2 = class {
|
|
|
1481
915
|
const signpostMoment = await this.getReplicationKeySignpost();
|
|
1482
916
|
const signpostValue = signpostMoment?.format(defaultDateTimeFormat);
|
|
1483
917
|
if (signpostValue) {
|
|
1484
|
-
StateService.getInstance().
|
|
918
|
+
StateService.getInstance().setBookmarkSignpost(this.streamId, signpostValue);
|
|
1485
919
|
}
|
|
1486
920
|
};
|
|
1487
921
|
/**
|
|
@@ -1497,7 +931,7 @@ var StreamV2 = class {
|
|
|
1497
931
|
if (this.useStateFromStreamId) {
|
|
1498
932
|
return;
|
|
1499
933
|
}
|
|
1500
|
-
if (this.
|
|
934
|
+
if (this.replicationMethod && !this.replicationKey) {
|
|
1501
935
|
return;
|
|
1502
936
|
}
|
|
1503
937
|
if (!replicationConfig.replicationKey) {
|
|
@@ -1507,14 +941,14 @@ var StreamV2 = class {
|
|
|
1507
941
|
);
|
|
1508
942
|
}
|
|
1509
943
|
const stateServiceInst = StateService.getInstance();
|
|
1510
|
-
const state = stateServiceInst.
|
|
944
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1511
945
|
const newState = incrementStreamState(
|
|
1512
946
|
state,
|
|
1513
947
|
replicationConfig.replicationKey,
|
|
1514
948
|
latestRecord,
|
|
1515
949
|
this.isSorted
|
|
1516
950
|
);
|
|
1517
|
-
StateService.getInstance().
|
|
951
|
+
StateService.getInstance().setBookmark(this.streamId, newState);
|
|
1518
952
|
}
|
|
1519
953
|
}
|
|
1520
954
|
};
|
|
@@ -1546,27 +980,27 @@ var StreamV2 = class {
|
|
|
1546
980
|
**/
|
|
1547
981
|
getStartingTimestamp() {
|
|
1548
982
|
const streamIdToReadFrom = this.useStateFromStreamId ? this.useStateFromStreamId : this.streamId;
|
|
1549
|
-
const state = extractStateForStream(this.
|
|
1550
|
-
const startDate = this.
|
|
983
|
+
const state = extractStateForStream(this.tapState, streamIdToReadFrom);
|
|
984
|
+
const startDate = this.config.start_date;
|
|
1551
985
|
const replicationConfig = this.configuredReplicationConfig();
|
|
1552
986
|
logger.debug(`${this.streamId}: state.replicationKeyValue: ${state.replicationKeyValue},
|
|
1553
987
|
state.replicationKey: ${state.replicationKey},
|
|
1554
988
|
this.replicationKey: ${replicationConfig.replicationKey}`);
|
|
1555
989
|
if (state.replicationKeyValue) {
|
|
1556
990
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning replication key value: ${state.replicationKeyValue}`);
|
|
1557
|
-
return (0,
|
|
991
|
+
return (0, import_dayjs2.default)(state.replicationKeyValue);
|
|
1558
992
|
} else if (startDate) {
|
|
1559
993
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning config start date: ${startDate}`);
|
|
1560
|
-
return (0,
|
|
994
|
+
return (0, import_dayjs2.default)(startDate);
|
|
1561
995
|
} else {
|
|
1562
996
|
logger.debug(`${this.streamId} - getStartingTimestamp -> Returning EPOCH (1970)`);
|
|
1563
|
-
return (0,
|
|
997
|
+
return (0, import_dayjs2.default)(0);
|
|
1564
998
|
}
|
|
1565
999
|
}
|
|
1566
1000
|
/**
|
|
1567
1001
|
* Return the max allowable bookmark value for this stream's replication key.
|
|
1568
1002
|
*
|
|
1569
|
-
* For timestamp-based replication keys, this defaults to `
|
|
1003
|
+
* For timestamp-based replication keys, this defaults to `dayjs()`. For
|
|
1570
1004
|
* non-timestamp replication keys, default to `undefined`.
|
|
1571
1005
|
*
|
|
1572
1006
|
* Override this value to prevent bookmarks from being advanced in cases where we
|
|
@@ -1574,7 +1008,7 @@ var StreamV2 = class {
|
|
|
1574
1008
|
*/
|
|
1575
1009
|
getReplicationKeySignpost = async () => {
|
|
1576
1010
|
if (await this.isTimestampReplicationKey()) {
|
|
1577
|
-
return (0,
|
|
1011
|
+
return (0, import_dayjs2.default)();
|
|
1578
1012
|
}
|
|
1579
1013
|
return void 0;
|
|
1580
1014
|
};
|
|
@@ -1582,22 +1016,22 @@ var StreamV2 = class {
|
|
|
1582
1016
|
* Return the default replication method for the stream that will be used to write the catalog.
|
|
1583
1017
|
*/
|
|
1584
1018
|
defaultReplicationConfig = () => {
|
|
1585
|
-
if (this.
|
|
1019
|
+
if (this.replicationMethod) {
|
|
1586
1020
|
return {
|
|
1587
|
-
replicationMethod: this.
|
|
1588
|
-
replicationKey: this.
|
|
1021
|
+
replicationMethod: this.replicationMethod,
|
|
1022
|
+
replicationKey: this.replicationKey,
|
|
1589
1023
|
isForced: true
|
|
1590
1024
|
};
|
|
1591
1025
|
}
|
|
1592
|
-
if (this.
|
|
1026
|
+
if (this.replicationKey || this.useStateFromStreamId) {
|
|
1593
1027
|
return {
|
|
1594
|
-
replicationMethod: "INCREMENTAL"
|
|
1595
|
-
replicationKey: this.
|
|
1028
|
+
replicationMethod: "INCREMENTAL" /* INCREMENTAL */,
|
|
1029
|
+
replicationKey: this.replicationKey,
|
|
1596
1030
|
isForced: true
|
|
1597
1031
|
};
|
|
1598
1032
|
}
|
|
1599
1033
|
return {
|
|
1600
|
-
replicationMethod: "FULL_TABLE"
|
|
1034
|
+
replicationMethod: "FULL_TABLE" /* FULL_TABLE */,
|
|
1601
1035
|
replicationKey: void 0,
|
|
1602
1036
|
isForced: false
|
|
1603
1037
|
};
|
|
@@ -1606,16 +1040,10 @@ var StreamV2 = class {
|
|
|
1606
1040
|
* Return the configured replication method that will be used for the sync.
|
|
1607
1041
|
*/
|
|
1608
1042
|
configuredReplicationConfig = () => {
|
|
1609
|
-
const
|
|
1610
|
-
if (defaultReplicationMethod.isForced) {
|
|
1611
|
-
return {
|
|
1612
|
-
replicationMethod: defaultReplicationMethod.replicationMethod,
|
|
1613
|
-
replicationKey: defaultReplicationMethod.replicationKey
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1043
|
+
const defaults = this.defaultReplicationConfig();
|
|
1616
1044
|
return {
|
|
1617
|
-
replicationMethod:
|
|
1618
|
-
replicationKey:
|
|
1045
|
+
replicationMethod: defaults.replicationMethod,
|
|
1046
|
+
replicationKey: defaults.replicationKey
|
|
1619
1047
|
};
|
|
1620
1048
|
};
|
|
1621
1049
|
// Private sync methods:
|
|
@@ -1645,9 +1073,9 @@ var StreamV2 = class {
|
|
|
1645
1073
|
this.streamId
|
|
1646
1074
|
);
|
|
1647
1075
|
if (!this.isSilent) {
|
|
1648
|
-
await this.
|
|
1076
|
+
await this.target.record(recordMessage);
|
|
1649
1077
|
}
|
|
1650
|
-
await import_bluebird2.
|
|
1078
|
+
await import_bluebird2.default.map(
|
|
1651
1079
|
this.children,
|
|
1652
1080
|
async (child) => {
|
|
1653
1081
|
await child.asyncInit(row);
|
|
@@ -1664,9 +1092,9 @@ var StreamV2 = class {
|
|
|
1664
1092
|
rowsSent += 1;
|
|
1665
1093
|
}
|
|
1666
1094
|
const stateServiceInst = StateService.getInstance();
|
|
1667
|
-
const state = stateServiceInst.
|
|
1095
|
+
const state = stateServiceInst.getBookmark(this.streamId);
|
|
1668
1096
|
const newState = finalizeStateProgressMarkers(state);
|
|
1669
|
-
stateServiceInst.
|
|
1097
|
+
stateServiceInst.setBookmark(this.streamId, newState);
|
|
1670
1098
|
if (!parent) {
|
|
1671
1099
|
logger.info(`\u2705 Completed sync for stream: ${this.streamId} (${rowsSent} records)`);
|
|
1672
1100
|
} else {
|
|
@@ -1691,7 +1119,7 @@ var StreamV2 = class {
|
|
|
1691
1119
|
getSchema() {
|
|
1692
1120
|
logger.debug(`stream: ${this.streamId} - getSchema() is called`);
|
|
1693
1121
|
if (this.schemaPath) {
|
|
1694
|
-
const schema = loadJson(`schemas/${this.
|
|
1122
|
+
const schema = loadJson(`schemas/${this.schemaPath}`);
|
|
1695
1123
|
return Promise.resolve({ jsonSchema: schema });
|
|
1696
1124
|
} else {
|
|
1697
1125
|
if (this.isSilent) {
|
|
@@ -1728,7 +1156,7 @@ var StreamV2 = class {
|
|
|
1728
1156
|
};
|
|
1729
1157
|
|
|
1730
1158
|
// src/sdk/helpers/qs-logger.ts
|
|
1731
|
-
var
|
|
1159
|
+
var import_isPlainObject = __toESM(require("lodash/isPlainObject.js"));
|
|
1732
1160
|
var MAX_QS_KEYS = 10;
|
|
1733
1161
|
var MAX_QS_STRING_LENGTH = 200;
|
|
1734
1162
|
var MAX_QS_ARRAY_ITEMS = 5;
|
|
@@ -1749,7 +1177,7 @@ function truncateQueryParams(value, depth = 0) {
|
|
|
1749
1177
|
}
|
|
1750
1178
|
return items;
|
|
1751
1179
|
}
|
|
1752
|
-
if (
|
|
1180
|
+
if ((0, import_isPlainObject.default)(value)) {
|
|
1753
1181
|
const keys = Object.keys(value);
|
|
1754
1182
|
const result = {};
|
|
1755
1183
|
for (const key of keys.slice(0, MAX_QS_KEYS)) {
|
|
@@ -1772,11 +1200,12 @@ function getTruncatedParamsForLog(params) {
|
|
|
1772
1200
|
}
|
|
1773
1201
|
|
|
1774
1202
|
// src/sdk/models/tap/restStream.ts
|
|
1775
|
-
var
|
|
1776
|
-
var
|
|
1203
|
+
var import_cloneDeep3 = __toESM(require("lodash/cloneDeep.js"));
|
|
1204
|
+
var import_isEqual = __toESM(require("lodash/isEqual.js"));
|
|
1205
|
+
var RESTStream = class extends Stream {
|
|
1777
1206
|
axiosInstance;
|
|
1778
|
-
constructor(
|
|
1779
|
-
super(
|
|
1207
|
+
constructor(config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
|
|
1208
|
+
super(config, state, target, childConcurrency);
|
|
1780
1209
|
this.apiCallsMetricsConf = {
|
|
1781
1210
|
isEnabled: () => !this.isSilent,
|
|
1782
1211
|
getStreamIds: () => [this.streamId]
|
|
@@ -1792,14 +1221,25 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1792
1221
|
*/
|
|
1793
1222
|
_prepareRequest = async (nextPageToken, parent) => {
|
|
1794
1223
|
const method = this.httpMethod;
|
|
1795
|
-
const url = this.
|
|
1224
|
+
const url = this.getNextUrl(nextPageToken, parent);
|
|
1796
1225
|
if (!url) {
|
|
1797
1226
|
throw new Error(`StreamId: ${this.streamId} - URL is undefined. Did your properly define the URL for this stream?`);
|
|
1798
1227
|
}
|
|
1799
1228
|
const authenticator = this.authenticator;
|
|
1229
|
+
let finalUrl = url;
|
|
1230
|
+
let urlQueryParams = {};
|
|
1231
|
+
const queryIndex = url.indexOf("?");
|
|
1232
|
+
if (queryIndex !== -1) {
|
|
1233
|
+
finalUrl = url.substring(0, queryIndex);
|
|
1234
|
+
const rawQueryString = url.substring(queryIndex + 1);
|
|
1235
|
+
urlQueryParams = qs2.parse(rawQueryString);
|
|
1236
|
+
}
|
|
1800
1237
|
const params = {
|
|
1801
1238
|
...this.getNextUrlParams(nextPageToken, parent),
|
|
1802
|
-
|
|
1239
|
+
// Query params embedded in URL should override computed pagination params
|
|
1240
|
+
// but should not override auth params which are appended last
|
|
1241
|
+
...urlQueryParams,
|
|
1242
|
+
...await authenticator?.getAuthQS(this.config)
|
|
1803
1243
|
};
|
|
1804
1244
|
const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
|
|
1805
1245
|
let headers = this.getCustomHTTPHeaders(parent);
|
|
@@ -1807,7 +1247,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1807
1247
|
headers = {};
|
|
1808
1248
|
}
|
|
1809
1249
|
if (authenticator) {
|
|
1810
|
-
const authHeaders = await authenticator.getAuthHeaders(this.
|
|
1250
|
+
const authHeaders = await authenticator.getAuthHeaders(this.config);
|
|
1811
1251
|
headers = {
|
|
1812
1252
|
...headers,
|
|
1813
1253
|
...authHeaders
|
|
@@ -1817,7 +1257,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1817
1257
|
return qs2.stringify(params2, { arrayFormat: "repeat" });
|
|
1818
1258
|
};
|
|
1819
1259
|
const request = {
|
|
1820
|
-
url,
|
|
1260
|
+
url: finalUrl,
|
|
1821
1261
|
method,
|
|
1822
1262
|
data: requestBody,
|
|
1823
1263
|
params,
|
|
@@ -1890,9 +1330,9 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1890
1330
|
for await (const row of rows) {
|
|
1891
1331
|
yield row;
|
|
1892
1332
|
}
|
|
1893
|
-
const previousToken =
|
|
1333
|
+
const previousToken = (0, import_cloneDeep3.default)(nextPageToken);
|
|
1894
1334
|
nextPageToken = this.getNextPageToken(resp, previousToken);
|
|
1895
|
-
if (nextPageToken &&
|
|
1335
|
+
if (nextPageToken && (0, import_isEqual.default)(nextPageToken, previousToken)) {
|
|
1896
1336
|
throw new Error(
|
|
1897
1337
|
`${this.streamId} - Loop detected in pagination. "
|
|
1898
1338
|
Pagination token: \`${JSON.stringify(nextPageToken)}\` is identical to prior token \`${JSON.stringify(previousToken)}\`.`
|
|
@@ -1969,7 +1409,7 @@ var RESTStreamV2 = class extends StreamV2 {
|
|
|
1969
1409
|
* TODO: Make URL + Path templatisable and replace those with Extractor (or Tap) config
|
|
1970
1410
|
* @returns
|
|
1971
1411
|
*/
|
|
1972
|
-
|
|
1412
|
+
getNextUrl(previousToken, parent) {
|
|
1973
1413
|
const urlPattern = [this.baseUrl, this.path].join("");
|
|
1974
1414
|
return urlPattern;
|
|
1975
1415
|
}
|
|
@@ -2093,20 +1533,20 @@ var StreamWarehouseSyncService = class {
|
|
|
2093
1533
|
const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
|
|
2094
1534
|
const columnsMappingStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2095
1535
|
const columnsToAdd = Object.keys(columnsMappingStore).filter((unsafeColumnKey) => {
|
|
2096
|
-
const
|
|
2097
|
-
if (!
|
|
1536
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
1537
|
+
if (!safeColumnName2) {
|
|
2098
1538
|
return false;
|
|
2099
1539
|
}
|
|
2100
|
-
return !columnNamesFromWarehouse.includes(
|
|
1540
|
+
return !columnNamesFromWarehouse.includes(safeColumnName2);
|
|
2101
1541
|
}).map((unsafeColumnKey) => {
|
|
2102
|
-
const
|
|
1542
|
+
const safeColumnName2 = columnsMappingStore[unsafeColumnKey];
|
|
2103
1543
|
const definition = this.flattenedSchema[unsafeColumnKey];
|
|
2104
|
-
if (!
|
|
1544
|
+
if (!safeColumnName2 || !definition) {
|
|
2105
1545
|
return void 0;
|
|
2106
1546
|
}
|
|
2107
1547
|
return {
|
|
2108
1548
|
unsafeName: unsafeColumnKey,
|
|
2109
|
-
name:
|
|
1549
|
+
name: safeColumnName2,
|
|
2110
1550
|
definition
|
|
2111
1551
|
};
|
|
2112
1552
|
}).filter((item) => !!item);
|
|
@@ -2321,18 +1761,18 @@ var flattenSchema = (streamId, schema) => {
|
|
|
2321
1761
|
|
|
2322
1762
|
// src/sdk/models/target/target.ts
|
|
2323
1763
|
var import_jsonschema = require("jsonschema");
|
|
2324
|
-
var
|
|
1764
|
+
var import_dayjs4 = __toESM(require("dayjs"));
|
|
2325
1765
|
|
|
2326
1766
|
// src/sdk/models/target/streamDbState.ts
|
|
2327
|
-
var
|
|
1767
|
+
var import_dayjs3 = __toESM(require("dayjs"));
|
|
2328
1768
|
|
|
2329
1769
|
// src/sdk/models/target/temporaryFile.ts
|
|
2330
|
-
var
|
|
2331
|
-
var
|
|
1770
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
1771
|
+
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
2332
1772
|
var createTemporaryFileStream = (streamId) => {
|
|
2333
|
-
|
|
2334
|
-
const path = `./tmp/${safePath(streamId)}-${(0,
|
|
2335
|
-
const writeStream =
|
|
1773
|
+
import_fs_extra.default.ensureDirSync("./tmp");
|
|
1774
|
+
const path = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
1775
|
+
const writeStream = import_fs_extra.default.createWriteStream(path, { flags: "w" });
|
|
2336
1776
|
return {
|
|
2337
1777
|
stream: writeStream,
|
|
2338
1778
|
path
|
|
@@ -2380,7 +1820,7 @@ function replaceSymbols(str) {
|
|
|
2380
1820
|
}
|
|
2381
1821
|
|
|
2382
1822
|
// src/sdk/models/target/streamDbState.ts
|
|
2383
|
-
var
|
|
1823
|
+
var fs3 = __toESM(require("fs"));
|
|
2384
1824
|
var StreamState = class {
|
|
2385
1825
|
streamId;
|
|
2386
1826
|
schema;
|
|
@@ -2405,7 +1845,7 @@ var StreamState = class {
|
|
|
2405
1845
|
this.fileToLoad = createTemporaryFileStream(streamId);
|
|
2406
1846
|
this.syncedRowCount = 0;
|
|
2407
1847
|
this.keyProperties = [];
|
|
2408
|
-
this.batchDate = (0,
|
|
1848
|
+
this.batchDate = (0, import_dayjs3.default)();
|
|
2409
1849
|
this.replicationMethod = replicationMethod;
|
|
2410
1850
|
this._hasBeenLoadedYetDuringThisSync = false;
|
|
2411
1851
|
}
|
|
@@ -2430,7 +1870,7 @@ var StreamState = class {
|
|
|
2430
1870
|
resetFileToLoad() {
|
|
2431
1871
|
this.fileToLoad.stream.close();
|
|
2432
1872
|
const oldPath = this.fileToLoad.path;
|
|
2433
|
-
|
|
1873
|
+
fs3.unlinkSync(oldPath);
|
|
2434
1874
|
this.fileToLoad = createTemporaryFileStream(this.streamId);
|
|
2435
1875
|
this.batchedRowCount = 0;
|
|
2436
1876
|
}
|
|
@@ -2456,13 +1896,13 @@ var StreamState = class {
|
|
|
2456
1896
|
|
|
2457
1897
|
// src/sdk/models/target/target.ts
|
|
2458
1898
|
var import_async_mutex = require("async-mutex");
|
|
2459
|
-
var import_bluebird3 = require("bluebird");
|
|
1899
|
+
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2460
1900
|
var semaphore = new import_async_mutex.Semaphore(1);
|
|
2461
1901
|
var ITarget = class _ITarget {
|
|
2462
1902
|
config;
|
|
2463
1903
|
schemaHooks;
|
|
2464
|
-
resolver;
|
|
2465
1904
|
syncTime;
|
|
1905
|
+
stateProvider;
|
|
2466
1906
|
// Latest state message received from the Tap
|
|
2467
1907
|
// Not yet flushed as we didn't upload the stream data since receiving it
|
|
2468
1908
|
batchedState;
|
|
@@ -2473,15 +1913,15 @@ var ITarget = class _ITarget {
|
|
|
2473
1913
|
streams;
|
|
2474
1914
|
// JSON Schema validator
|
|
2475
1915
|
validator;
|
|
2476
|
-
constructor(config,
|
|
1916
|
+
constructor(config, stateProvider) {
|
|
2477
1917
|
this.config = config;
|
|
2478
|
-
this.
|
|
2479
|
-
this.schemaHooks =
|
|
1918
|
+
this.stateProvider = stateProvider;
|
|
1919
|
+
this.schemaHooks = [];
|
|
2480
1920
|
this.streams = {};
|
|
2481
1921
|
this.batchedState = {};
|
|
2482
1922
|
this.flushedState = {};
|
|
2483
1923
|
this.validator = new import_jsonschema.Validator();
|
|
2484
|
-
this.syncTime = (0,
|
|
1924
|
+
this.syncTime = (0, import_dayjs4.default)();
|
|
2485
1925
|
this.renameColumnStore = new RenameColumnStore();
|
|
2486
1926
|
}
|
|
2487
1927
|
///////////////////////////////////////////
|
|
@@ -2590,7 +2030,7 @@ var ITarget = class _ITarget {
|
|
|
2590
2030
|
this.streams[streamId] = new StreamState(
|
|
2591
2031
|
streamId,
|
|
2592
2032
|
dbSyncInstance,
|
|
2593
|
-
replicationMethod || "FULL_TABLE"
|
|
2033
|
+
replicationMethod || "FULL_TABLE" /* FULL_TABLE */
|
|
2594
2034
|
);
|
|
2595
2035
|
}
|
|
2596
2036
|
replicationMethod = (message) => {
|
|
@@ -2667,41 +2107,9 @@ var ITarget = class _ITarget {
|
|
|
2667
2107
|
return translation;
|
|
2668
2108
|
}
|
|
2669
2109
|
return k;
|
|
2670
|
-
})
|
|
2671
|
-
relationships: {
|
|
2672
|
-
left: message.relationships.left.flatMap((l) => {
|
|
2673
|
-
if (this.renameColumnStore.isReady(l.streamId) && this.renameColumnStore.isReady(message.stream)) {
|
|
2674
|
-
const from = this.renameColumnStore.getColumnTranslation(l.streamId, l.from);
|
|
2675
|
-
const to = this.renameColumnStore.getColumnTranslation(message.stream, l.to);
|
|
2676
|
-
if (from && to) {
|
|
2677
|
-
return [{
|
|
2678
|
-
type: l.type,
|
|
2679
|
-
streamId: l.streamId,
|
|
2680
|
-
from,
|
|
2681
|
-
to
|
|
2682
|
-
}];
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
return [];
|
|
2686
|
-
}),
|
|
2687
|
-
right: message.relationships.right.flatMap((r) => {
|
|
2688
|
-
if (this.renameColumnStore.isReady(message.stream) && this.renameColumnStore.isReady(r.streamId)) {
|
|
2689
|
-
const from = this.renameColumnStore.getColumnTranslation(message.stream, r.from);
|
|
2690
|
-
const to = this.renameColumnStore.getColumnTranslation(r.streamId, r.to);
|
|
2691
|
-
if (from && to) {
|
|
2692
|
-
return [{
|
|
2693
|
-
type: r.type,
|
|
2694
|
-
streamId: r.streamId,
|
|
2695
|
-
from,
|
|
2696
|
-
to
|
|
2697
|
-
}];
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
return [];
|
|
2701
|
-
})
|
|
2702
|
-
}
|
|
2110
|
+
})
|
|
2703
2111
|
};
|
|
2704
|
-
await import_bluebird3.
|
|
2112
|
+
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2705
2113
|
const input = {
|
|
2706
2114
|
databaseName,
|
|
2707
2115
|
schemaName,
|
|
@@ -2732,9 +2140,6 @@ var ITarget = class _ITarget {
|
|
|
2732
2140
|
try {
|
|
2733
2141
|
logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
|
|
2734
2142
|
await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
|
|
2735
|
-
const configResolver = loadResolver();
|
|
2736
|
-
await configResolver.markSyncComplete();
|
|
2737
|
-
await configResolver.flushMetrics();
|
|
2738
2143
|
return Promise.resolve();
|
|
2739
2144
|
} catch (err) {
|
|
2740
2145
|
logger.error(`Error while handling complete event.`);
|
|
@@ -2777,12 +2182,11 @@ var ITarget = class _ITarget {
|
|
|
2777
2182
|
}
|
|
2778
2183
|
};
|
|
2779
2184
|
emitState = (state) => {
|
|
2780
|
-
|
|
2781
|
-
return writeStateFile(configResolver, JSON.stringify(state));
|
|
2185
|
+
return this.stateProvider.writeState(JSON.stringify(state));
|
|
2782
2186
|
};
|
|
2783
2187
|
uploadStreamsToWarehouse = async (streamsToUpload) => {
|
|
2784
2188
|
try {
|
|
2785
|
-
await import_bluebird3.
|
|
2189
|
+
await import_bluebird3.default.map(streamsToUpload, async (streamId) => {
|
|
2786
2190
|
logger.info(`\u{1F4E4} Upload stream: ${streamId} to Warehouse`);
|
|
2787
2191
|
const streamState = this.streams[streamId];
|
|
2788
2192
|
if (streamState) {
|
|
@@ -2816,40 +2220,732 @@ var ITarget = class _ITarget {
|
|
|
2816
2220
|
}
|
|
2817
2221
|
};
|
|
2818
2222
|
};
|
|
2223
|
+
|
|
2224
|
+
// src/targets/bigquery/helpers.ts
|
|
2225
|
+
var safeColumnName = (key) => {
|
|
2226
|
+
let returnString = key;
|
|
2227
|
+
const shouldNotStartWith = ["_TABLE_", "_FILE_", "_PARTITION"];
|
|
2228
|
+
shouldNotStartWith.forEach((snm) => {
|
|
2229
|
+
if (returnString.startsWith(snm)) {
|
|
2230
|
+
returnString = returnString.replace(snm, "");
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
returnString = returnString.replace(/^[-\d\s]*/g, "");
|
|
2234
|
+
returnString = returnString.toLowerCase();
|
|
2235
|
+
returnString = returnString.replace("`", "");
|
|
2236
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2237
|
+
if (returnString.length > 300) {
|
|
2238
|
+
returnString = returnString.substr(0, 300);
|
|
2239
|
+
}
|
|
2240
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2241
|
+
return returnString.replace(pattern, "_");
|
|
2242
|
+
};
|
|
2243
|
+
var safeTableName = (key) => {
|
|
2244
|
+
let returnString = key;
|
|
2245
|
+
returnString = returnString.toLowerCase();
|
|
2246
|
+
returnString = returnString.replace("`", "");
|
|
2247
|
+
returnString = returnString.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2248
|
+
if (returnString.length > 1024) {
|
|
2249
|
+
returnString = returnString.substr(0, 1024);
|
|
2250
|
+
}
|
|
2251
|
+
const pattern = /[^a-zA-Z0-9_]/gm;
|
|
2252
|
+
return returnString.replace(pattern, "_");
|
|
2253
|
+
};
|
|
2254
|
+
|
|
2255
|
+
// src/targets/bigquery/service/record.ts
|
|
2256
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
2257
|
+
var import_dayjs5 = __toESM(require("dayjs"));
|
|
2258
|
+
var validateDateRange = (record, schema) => {
|
|
2259
|
+
Object.keys(schema).forEach((key) => {
|
|
2260
|
+
const { format: format6 } = schema[key];
|
|
2261
|
+
if (format6 === "date-time") {
|
|
2262
|
+
if (record[key]) {
|
|
2263
|
+
const formattedDate = (0, import_dayjs5.default)(record[key]).format(defaultDateTimeFormat);
|
|
2264
|
+
const isNotTooMuchInTheFuture = (0, import_dayjs5.default)(record[key]).isBefore((0, import_dayjs5.default)("9999-12-31 23:59:59.999999"));
|
|
2265
|
+
const isNotTooMuchInThePast = (0, import_dayjs5.default)(record[key]).isAfter((0, import_dayjs5.default)("0001-01-01 00:00:00"));
|
|
2266
|
+
if (formattedDate !== "Invalid date" && isNotTooMuchInTheFuture && isNotTooMuchInThePast) {
|
|
2267
|
+
record[key] = formattedDate;
|
|
2268
|
+
} else {
|
|
2269
|
+
record[key] = void 0;
|
|
2270
|
+
logger.info("Skipping date for key %s for being invalid", key);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
return record;
|
|
2276
|
+
};
|
|
2277
|
+
var maxBQNumericValue = new import_decimal.default("9.9999999999999999999999999999999999999E+28");
|
|
2278
|
+
var convertNumberIntoDecimal = (record, schema) => {
|
|
2279
|
+
Object.keys(record).forEach((key) => {
|
|
2280
|
+
if (schema[key]) {
|
|
2281
|
+
const { type, items } = schema[key];
|
|
2282
|
+
const types = type instanceof Array ? type : [type];
|
|
2283
|
+
if (types.includes("number")) {
|
|
2284
|
+
if (typeof record[key] === "number" || record[key] instanceof import_decimal.default) {
|
|
2285
|
+
const decimal = new import_decimal.default(record[key]).toDecimalPlaces(9);
|
|
2286
|
+
if (decimal.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2287
|
+
record[key] = null;
|
|
2288
|
+
} else {
|
|
2289
|
+
record[key] = decimal;
|
|
2290
|
+
}
|
|
2291
|
+
} else {
|
|
2292
|
+
record[key] = null;
|
|
2293
|
+
}
|
|
2294
|
+
} else if (types.includes("array") && items?.type.includes("number")) {
|
|
2295
|
+
if (Array.isArray(record[key])) {
|
|
2296
|
+
const rawArr = record[key];
|
|
2297
|
+
const newArr = rawArr.map((item) => {
|
|
2298
|
+
if (typeof item === "number" || item instanceof import_decimal.default) {
|
|
2299
|
+
const parsed = new import_decimal.default(item).toDecimalPlaces(9);
|
|
2300
|
+
if (parsed.greaterThanOrEqualTo(maxBQNumericValue)) {
|
|
2301
|
+
return void 0;
|
|
2302
|
+
} else {
|
|
2303
|
+
return parsed;
|
|
2304
|
+
}
|
|
2305
|
+
} else {
|
|
2306
|
+
return void 0;
|
|
2307
|
+
}
|
|
2308
|
+
}).filter((item) => !!item);
|
|
2309
|
+
record[key] = newArr;
|
|
2310
|
+
} else {
|
|
2311
|
+
record[key] = null;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
return record;
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2320
|
+
var import_dayjs6 = __toESM(require("dayjs"));
|
|
2321
|
+
|
|
2322
|
+
// src/targets/bigquery/service/bigquery.ts
|
|
2323
|
+
var import_bigquery = require("@google-cloud/bigquery");
|
|
2324
|
+
var BqClientHolder = class {
|
|
2325
|
+
static instance;
|
|
2326
|
+
client;
|
|
2327
|
+
constructor(config) {
|
|
2328
|
+
const retryOptions = {
|
|
2329
|
+
autoRetry: true,
|
|
2330
|
+
maxRetries: 3
|
|
2331
|
+
};
|
|
2332
|
+
this.client = new import_bigquery.BigQuery({
|
|
2333
|
+
projectId: config.database.trim(),
|
|
2334
|
+
...retryOptions
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
static client(config) {
|
|
2338
|
+
if (!this.instance) {
|
|
2339
|
+
this.instance = new this(config);
|
|
2340
|
+
}
|
|
2341
|
+
return this.instance.client;
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
// src/targets/bigquery/service/cloudStorage.ts
|
|
2346
|
+
var import_storage = require("@google-cloud/storage");
|
|
2347
|
+
|
|
2348
|
+
// node_modules/uuid/dist/esm-node/rng.js
|
|
2349
|
+
var import_crypto = __toESM(require("crypto"));
|
|
2350
|
+
var rnds8Pool = new Uint8Array(256);
|
|
2351
|
+
var poolPtr = rnds8Pool.length;
|
|
2352
|
+
function rng() {
|
|
2353
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
2354
|
+
import_crypto.default.randomFillSync(rnds8Pool);
|
|
2355
|
+
poolPtr = 0;
|
|
2356
|
+
}
|
|
2357
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// node_modules/uuid/dist/esm-node/regex.js
|
|
2361
|
+
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
2362
|
+
|
|
2363
|
+
// node_modules/uuid/dist/esm-node/validate.js
|
|
2364
|
+
function validate(uuid) {
|
|
2365
|
+
return typeof uuid === "string" && regex_default.test(uuid);
|
|
2366
|
+
}
|
|
2367
|
+
var validate_default = validate;
|
|
2368
|
+
|
|
2369
|
+
// node_modules/uuid/dist/esm-node/stringify.js
|
|
2370
|
+
var byteToHex = [];
|
|
2371
|
+
for (let i = 0; i < 256; ++i) {
|
|
2372
|
+
byteToHex.push((i + 256).toString(16).substr(1));
|
|
2373
|
+
}
|
|
2374
|
+
function stringify3(arr, offset = 0) {
|
|
2375
|
+
const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
2376
|
+
if (!validate_default(uuid)) {
|
|
2377
|
+
throw TypeError("Stringified UUID is invalid");
|
|
2378
|
+
}
|
|
2379
|
+
return uuid;
|
|
2380
|
+
}
|
|
2381
|
+
var stringify_default = stringify3;
|
|
2382
|
+
|
|
2383
|
+
// node_modules/uuid/dist/esm-node/v4.js
|
|
2384
|
+
function v4(options, buf, offset) {
|
|
2385
|
+
options = options || {};
|
|
2386
|
+
const rnds = options.random || (options.rng || rng)();
|
|
2387
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
2388
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
2389
|
+
if (buf) {
|
|
2390
|
+
offset = offset || 0;
|
|
2391
|
+
for (let i = 0; i < 16; ++i) {
|
|
2392
|
+
buf[offset + i] = rnds[i];
|
|
2393
|
+
}
|
|
2394
|
+
return buf;
|
|
2395
|
+
}
|
|
2396
|
+
return stringify_default(rnds);
|
|
2397
|
+
}
|
|
2398
|
+
var v4_default = v4;
|
|
2399
|
+
|
|
2400
|
+
// src/targets/bigquery/service/cloudStorage.ts
|
|
2401
|
+
var uploadFileInBucket = async (streamId, gcsBuckerName, connectorId, filePath) => {
|
|
2402
|
+
try {
|
|
2403
|
+
const retryOptions = { autoRetry: true, maxRetries: 20 };
|
|
2404
|
+
let storage = new import_storage.Storage({ retryOptions });
|
|
2405
|
+
const bucketRef = storage.bucket(gcsBuckerName);
|
|
2406
|
+
await bucketRef.get({ autoCreate: false });
|
|
2407
|
+
const destinationFileName = `${connectorId}/${streamId}-${v4_default()}.jsonnl`;
|
|
2408
|
+
await bucketRef.upload(filePath, {
|
|
2409
|
+
destination: destinationFileName
|
|
2410
|
+
});
|
|
2411
|
+
logger.info(`\u{1F5F3} Uploaded ${filePath} into ${gcsBuckerName}/${destinationFileName} GCS File`);
|
|
2412
|
+
return bucketRef.file(destinationFileName);
|
|
2413
|
+
} catch (err) {
|
|
2414
|
+
logger.error(`Issue when uploading file into GCS bucket for stream: ${streamId}
|
|
2415
|
+
|
|
2416
|
+
Error: ${err.message}
|
|
2417
|
+
Stack: ${err.stack}
|
|
2418
|
+
Code: ${err.code}
|
|
2419
|
+
`);
|
|
2420
|
+
if (err.code < 500) {
|
|
2421
|
+
await haltAndCatchFire(
|
|
2422
|
+
`unauthorized`,
|
|
2423
|
+
`We couldn't connect to your Cloud Storage bucket \u{1F614}
|
|
2424
|
+
|
|
2425
|
+
The error from Google is: '${err.message}' \u{1F440}
|
|
2426
|
+
|
|
2427
|
+
Could you troubleshoot your Cloud Storage configuration in Google Cloud and sync again the source? \u{1F64F}`,
|
|
2428
|
+
`Got error from cloud storage lib`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
throw err;
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2435
|
+
// src/targets/bigquery/service/dbSync.ts
|
|
2436
|
+
var import_chunk = __toESM(require("lodash/chunk.js"));
|
|
2437
|
+
var import_async_mutex2 = require("async-mutex");
|
|
2438
|
+
var semaphore2 = new import_async_mutex2.Semaphore(1);
|
|
2439
|
+
var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
2440
|
+
config;
|
|
2441
|
+
bqClient;
|
|
2442
|
+
bqDataset;
|
|
2443
|
+
bqTable;
|
|
2444
|
+
bqStagingTable;
|
|
2445
|
+
tableName;
|
|
2446
|
+
stagingTableName;
|
|
2447
|
+
sqlTableId;
|
|
2448
|
+
sqlStagingTableId;
|
|
2449
|
+
constructor(config, streamId, database, schema, table, renameColumnStore) {
|
|
2450
|
+
super(
|
|
2451
|
+
streamId,
|
|
2452
|
+
database,
|
|
2453
|
+
schema,
|
|
2454
|
+
table,
|
|
2455
|
+
renameColumnStore
|
|
2456
|
+
);
|
|
2457
|
+
this.config = config;
|
|
2458
|
+
this.bqClient = BqClientHolder.client(config);
|
|
2459
|
+
this.bqDataset = this.bqClient.dataset(schema, { location: config.location || "EU" });
|
|
2460
|
+
this.bqTable = this.bqDataset.table(table, { location: config.location || "EU" });
|
|
2461
|
+
this.tableName = table;
|
|
2462
|
+
this.stagingTableName = `${table}_temp`;
|
|
2463
|
+
this.bqStagingTable = this.bqDataset.table(this.stagingTableName, { location: config.location || "EU" });
|
|
2464
|
+
this.sqlTableId = `${schema}.${table}`;
|
|
2465
|
+
this.sqlStagingTableId = `${this.sqlTableId}_temp`;
|
|
2466
|
+
}
|
|
2467
|
+
// Implementation of abstract classes
|
|
2468
|
+
getSerializedRecord = (record) => {
|
|
2469
|
+
return JSON.stringify(record) + `
|
|
2470
|
+
`;
|
|
2471
|
+
};
|
|
2472
|
+
safeColumnName = safeColumnName;
|
|
2473
|
+
getWarehouseTypeFromJSONSchema = (definition) => {
|
|
2474
|
+
const type = definition.type;
|
|
2475
|
+
const format6 = definition.format;
|
|
2476
|
+
let typeArr;
|
|
2477
|
+
if (type instanceof Array) {
|
|
2478
|
+
typeArr = type;
|
|
2479
|
+
} else {
|
|
2480
|
+
typeArr = [type];
|
|
2481
|
+
}
|
|
2482
|
+
if (format6) {
|
|
2483
|
+
switch (format6) {
|
|
2484
|
+
case "date-time":
|
|
2485
|
+
return "TIMESTAMP";
|
|
2486
|
+
case "json":
|
|
2487
|
+
return "STRING";
|
|
2488
|
+
default:
|
|
2489
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported format: ${format6}`);
|
|
2490
|
+
}
|
|
2491
|
+
} else if (typeArr.includes("number")) {
|
|
2492
|
+
return "NUMERIC";
|
|
2493
|
+
} else if (typeArr.includes("integer") && type.includes("string")) {
|
|
2494
|
+
return "NUMERIC";
|
|
2495
|
+
} else if (typeArr.includes("integer")) {
|
|
2496
|
+
return "INTEGER";
|
|
2497
|
+
} else if (typeArr.includes("boolean")) {
|
|
2498
|
+
return "BOOLEAN";
|
|
2499
|
+
} else if (typeArr.includes("string")) {
|
|
2500
|
+
return "STRING";
|
|
2501
|
+
} else if (typeArr.includes("array")) {
|
|
2502
|
+
const items = definition.items;
|
|
2503
|
+
if (items) {
|
|
2504
|
+
return this.getWarehouseTypeFromJSONSchema(items);
|
|
2505
|
+
}
|
|
2506
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported array schema field definition: ${JSON.stringify(definition)}`);
|
|
2507
|
+
} else {
|
|
2508
|
+
throw new Error(`StreamId: ${this.streamId} - Unsupported schema field definition: ${JSON.stringify(definition)}`);
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
getMergeQueries = () => {
|
|
2512
|
+
const primaryKeyCondition = this.primaryKeys.map((pk) => {
|
|
2513
|
+
return `\`sourced\`.\`${pk}\` = \`destination\`.\`${pk}\``;
|
|
2514
|
+
}).join(` AND `);
|
|
2515
|
+
const primaryKeys = this.primaryKeys.map((pk) => `\`${pk}\``).join(` ,`);
|
|
2516
|
+
const columnUnsafeToSafeMapping = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2517
|
+
const columns = Object.keys(columnUnsafeToSafeMapping).map((column) => {
|
|
2518
|
+
return {
|
|
2519
|
+
isUnsafe: true,
|
|
2520
|
+
colName: column
|
|
2521
|
+
};
|
|
2522
|
+
});
|
|
2523
|
+
const columnsChunks = (0, import_chunk.default)(columns, 300).map((chunk2) => {
|
|
2524
|
+
this.primaryKeys.forEach((pk) => {
|
|
2525
|
+
if (!chunk2.map((c) => columnUnsafeToSafeMapping[c.colName]).includes(pk)) {
|
|
2526
|
+
chunk2.push({
|
|
2527
|
+
isUnsafe: false,
|
|
2528
|
+
colName: pk
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
});
|
|
2532
|
+
return chunk2;
|
|
2533
|
+
});
|
|
2534
|
+
return columnsChunks.map((chunk2) => {
|
|
2535
|
+
const setValues = chunk2.map((column) => {
|
|
2536
|
+
if (column.isUnsafe) {
|
|
2537
|
+
return `\`destination\`.\`${columnUnsafeToSafeMapping[column.colName]}\` = \`sourced\`.\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2538
|
+
} else {
|
|
2539
|
+
return `\`destination\`.\`${column.colName}\` = \`sourced\`.\`${column.colName}\``;
|
|
2540
|
+
}
|
|
2541
|
+
}).join(`, `);
|
|
2542
|
+
const renamedCols = chunk2.map((column) => {
|
|
2543
|
+
if (column.isUnsafe) {
|
|
2544
|
+
return `\`${columnUnsafeToSafeMapping[column.colName]}\``;
|
|
2545
|
+
} else {
|
|
2546
|
+
return `\`${column.colName}\``;
|
|
2547
|
+
}
|
|
2548
|
+
}).join(`, `);
|
|
2549
|
+
const query = `
|
|
2550
|
+
MERGE ${this.sqlTableId} destination
|
|
2551
|
+
USING (
|
|
2552
|
+
WITH all_staging_rows AS (
|
|
2553
|
+
SELECT *
|
|
2554
|
+
FROM ${this.sqlStagingTableId}
|
|
2555
|
+
),
|
|
2556
|
+
numbered_rows AS (
|
|
2557
|
+
SELECT *,
|
|
2558
|
+
ROW_NUMBER() OVER (PARTITION BY ${primaryKeys}) as _wly_row_nb,
|
|
2559
|
+
COUNT(1) OVER (PARTITION BY ${primaryKeys}) AS _wly_partition_size,
|
|
2560
|
+
FROM all_staging_rows
|
|
2561
|
+
),
|
|
2562
|
+
staging_deduped AS (
|
|
2563
|
+
SELECT * EXCEPT(_wly_partition_size, _wly_row_nb)
|
|
2564
|
+
FROM numbered_rows
|
|
2565
|
+
WHERE _wly_partition_size = _wly_row_nb
|
|
2566
|
+
)
|
|
2567
|
+
SELECT *
|
|
2568
|
+
FROM staging_deduped
|
|
2569
|
+
) AS sourced
|
|
2570
|
+
ON ${primaryKeyCondition}
|
|
2571
|
+
WHEN MATCHED THEN
|
|
2572
|
+
UPDATE SET ${setValues}
|
|
2573
|
+
WHEN NOT MATCHED THEN
|
|
2574
|
+
INSERT (${renamedCols}) VALUES (${renamedCols});
|
|
2575
|
+
`;
|
|
2576
|
+
return query;
|
|
2577
|
+
});
|
|
2578
|
+
};
|
|
2579
|
+
getReplaceQueries = () => {
|
|
2580
|
+
return [
|
|
2581
|
+
`TRUNCATE TABLE ${this.sqlTableId};`,
|
|
2582
|
+
...this.getMergeQueries()
|
|
2583
|
+
];
|
|
2584
|
+
};
|
|
2585
|
+
createDatabaseAndSchemaIfNotExists = async (retryCount) => {
|
|
2586
|
+
try {
|
|
2587
|
+
await semaphore2.runExclusive(async () => {
|
|
2588
|
+
const [exists] = await this.bqDataset.exists();
|
|
2589
|
+
if (!exists) {
|
|
2590
|
+
logger.info(`\u{1F423} Creating dataset: ${this.bqDataset.id} on BigQuery`);
|
|
2591
|
+
await this.bqDataset.create();
|
|
2592
|
+
} else {
|
|
2593
|
+
const [metadata] = await this.bqDataset.getMetadata();
|
|
2594
|
+
const alreadyExistinglocation = metadata.location;
|
|
2595
|
+
if (this.config.location && this.config.location !== alreadyExistinglocation) {
|
|
2596
|
+
await haltAndCatchFire(
|
|
2597
|
+
`unknown`,
|
|
2598
|
+
`A BigQuery dataset called '${this.bqDataset.id}' already exists in location: '${alreadyExistinglocation}' \u{1F605}
|
|
2599
|
+
|
|
2600
|
+
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}
|
|
2601
|
+
|
|
2602
|
+
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}
|
|
2603
|
+
|
|
2604
|
+
Please relaunch the connector once it's done to fix the issue \u{1F917}
|
|
2605
|
+
`,
|
|
2606
|
+
`
|
|
2607
|
+
We have a conflict between an already existing dataset in location ${alreadyExistinglocation} and the configured location: ${this.config.location}
|
|
2608
|
+
`
|
|
2609
|
+
);
|
|
2610
|
+
throw Error();
|
|
2611
|
+
}
|
|
2612
|
+
logger.info(`\u{1F44B} Dataset: ${this.bqDataset.id} already exists on BigQuery, reusing it.`);
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
if (err.code === 400) {
|
|
2617
|
+
await haltAndCatchFire(
|
|
2618
|
+
`unauthorized`,
|
|
2619
|
+
`We can't connect to your BigQuery account \u{1F614}
|
|
2620
|
+
|
|
2621
|
+
Here is the message from BigQuery: ${err.message}
|
|
2622
|
+
|
|
2623
|
+
Can you check that your Warehouse credentials and Source target schema are properly configured and try to sync again the source? \u{1F64F}
|
|
2624
|
+
`,
|
|
2625
|
+
`Got error when trying to get Dataset from BigQuery: ${err.message} - ${err.code}`
|
|
2626
|
+
);
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (retryCount && retryCount > this.maxRetryCount) {
|
|
2630
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. ${err.message} - ${err.code}`);
|
|
2631
|
+
gracefulExit(1);
|
|
2632
|
+
} else {
|
|
2633
|
+
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
|
|
2634
|
+
await new Promise((resolve, reject) => setTimeout(resolve, 1e3 * retryCount));
|
|
2635
|
+
await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
return Promise.resolve();
|
|
2639
|
+
};
|
|
2640
|
+
createTable = async () => {
|
|
2641
|
+
try {
|
|
2642
|
+
const tableName = this.tableName;
|
|
2643
|
+
const schema = this.generateBigquerySchema();
|
|
2644
|
+
logger.info(`\u{1F423} Creating table: ${tableName} on BigQuery`);
|
|
2645
|
+
const createTableOptions = {
|
|
2646
|
+
schema
|
|
2647
|
+
};
|
|
2648
|
+
await this.bqDataset.createTable(
|
|
2649
|
+
tableName,
|
|
2650
|
+
createTableOptions
|
|
2651
|
+
);
|
|
2652
|
+
return;
|
|
2653
|
+
} catch (err) {
|
|
2654
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2655
|
+
|
|
2656
|
+
Error: ${err.message}
|
|
2657
|
+
Stack: ${err.stack}`);
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
addColumns = async (tableFields) => {
|
|
2661
|
+
if (tableFields.length > 0) {
|
|
2662
|
+
logger.info(`\u{1F195} Stream: ${this.streamId} - Adding following columns in BigQuery schema: ${JSON.stringify(tableFields)}`);
|
|
2663
|
+
const [metadata] = await this.bqTable.getMetadata();
|
|
2664
|
+
const bgTableFields = tableFields.map((tblField) => {
|
|
2665
|
+
return this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(tblField.name, tblField.definition);
|
|
2666
|
+
}).filter((fld) => {
|
|
2667
|
+
return !!fld;
|
|
2668
|
+
});
|
|
2669
|
+
const schema = metadata.schema;
|
|
2670
|
+
const new_schema = {
|
|
2671
|
+
...schema,
|
|
2672
|
+
fields: schema.fields.concat(bgTableFields)
|
|
2673
|
+
};
|
|
2674
|
+
metadata.schema = new_schema;
|
|
2675
|
+
await this.bqTable.setMetadata(metadata);
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
};
|
|
2679
|
+
createStagingArea = async () => {
|
|
2680
|
+
try {
|
|
2681
|
+
const tableName = this.stagingTableName;
|
|
2682
|
+
const [exists] = await this.bqDataset.table(tableName).exists();
|
|
2683
|
+
if (exists) {
|
|
2684
|
+
logger.info(`\u{1F474} Temporary table ${tableName} already exists, it should be a relic form the past.
|
|
2685
|
+
Dropping it.`);
|
|
2686
|
+
await this.deleteStagingArea();
|
|
2687
|
+
}
|
|
2688
|
+
const schema = this.generateBigquerySchema();
|
|
2689
|
+
const expirationTime = (0, import_dayjs6.default)().add(1, "day").valueOf().toString();
|
|
2690
|
+
const createTableOptions = {
|
|
2691
|
+
schema,
|
|
2692
|
+
expirationTime
|
|
2693
|
+
};
|
|
2694
|
+
logger.info(`\u{1F423} Creating staging table: ${tableName} on BigQuery`);
|
|
2695
|
+
await this.bqDataset.createTable(
|
|
2696
|
+
tableName,
|
|
2697
|
+
createTableOptions
|
|
2698
|
+
);
|
|
2699
|
+
return;
|
|
2700
|
+
} catch (err) {
|
|
2701
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when creating table in BigQuery
|
|
2702
|
+
|
|
2703
|
+
Error: ${err.message}
|
|
2704
|
+
Stack: ${err.stack}`);
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
loadStreamInStagingArea = async (localFilePath) => {
|
|
2708
|
+
try {
|
|
2709
|
+
const file = await uploadFileInBucket(
|
|
2710
|
+
this.streamId,
|
|
2711
|
+
this.config.loading_deck_gcs_bucket_name,
|
|
2712
|
+
this.config.connector_id,
|
|
2713
|
+
localFilePath
|
|
2714
|
+
);
|
|
2715
|
+
const metadata = {
|
|
2716
|
+
sourceFormat: "NEWLINE_DELIMITED_JSON",
|
|
2717
|
+
writeDisposition: "WRITE_TRUNCATE"
|
|
2718
|
+
};
|
|
2719
|
+
await this.loadGCSFileInTable(this.sqlStagingTableId, file, metadata);
|
|
2720
|
+
} catch (err) {
|
|
2721
|
+
logger.error(`StreamId: ${this.streamId} - Error while uploading stream.`);
|
|
2722
|
+
throw err;
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
deleteStagingArea = () => {
|
|
2726
|
+
const query = `DROP TABLE IF EXISTS ${this.sqlStagingTableId};`;
|
|
2727
|
+
return this.runQueriesWithRetry([query]);
|
|
2728
|
+
};
|
|
2729
|
+
getTablesInSchema = async () => {
|
|
2730
|
+
const [bqTables] = await this.bqDataset.getTables();
|
|
2731
|
+
const foundTables = bqTables.filter((tbl) => {
|
|
2732
|
+
return !!tbl.id;
|
|
2733
|
+
}).map((bqTable) => {
|
|
2734
|
+
return {
|
|
2735
|
+
tableName: bqTable.id
|
|
2736
|
+
};
|
|
2737
|
+
});
|
|
2738
|
+
return foundTables;
|
|
2739
|
+
};
|
|
2740
|
+
getTableColumnsFromWarehouse = async () => {
|
|
2741
|
+
const [table] = await this.bqTable.getMetadata();
|
|
2742
|
+
return table.schema.fields;
|
|
2743
|
+
};
|
|
2744
|
+
runQueries = async (queries) => {
|
|
2745
|
+
const sqlQuery = `
|
|
2746
|
+
BEGIN
|
|
2747
|
+
${queries.join(`
|
|
2748
|
+
`)}
|
|
2749
|
+
END;
|
|
2750
|
+
`;
|
|
2751
|
+
await this.bqClient.query(sqlQuery);
|
|
2752
|
+
};
|
|
2753
|
+
// Specific methods
|
|
2754
|
+
/**
|
|
2755
|
+
*
|
|
2756
|
+
* Generate a TableField definition object that can be passed to BigQuery APIs to define a column on a table
|
|
2757
|
+
*
|
|
2758
|
+
* @param colName
|
|
2759
|
+
* @param schemaFieldDefinition
|
|
2760
|
+
* @returns
|
|
2761
|
+
*/
|
|
2762
|
+
getBigqueryTableFieldFromInternalSchemaFieldDefinition = (colName, schemaFieldDefinition) => {
|
|
2763
|
+
const name = colName;
|
|
2764
|
+
if (!schemaFieldDefinition) {
|
|
2765
|
+
throw new Error(`StreamId: ${this.streamId} - Schema is unknown for colName=${colName}, so we can't infer the proper BigQuery definition for this column.`);
|
|
2766
|
+
}
|
|
2767
|
+
const type = schemaFieldDefinition.type;
|
|
2768
|
+
let typeArr;
|
|
2769
|
+
if (type instanceof Array) {
|
|
2770
|
+
typeArr = type;
|
|
2771
|
+
} else {
|
|
2772
|
+
typeArr = [type];
|
|
2773
|
+
}
|
|
2774
|
+
if (typeArr.includes("array")) {
|
|
2775
|
+
return {
|
|
2776
|
+
name,
|
|
2777
|
+
mode: "REPEATED",
|
|
2778
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
2779
|
+
};
|
|
2780
|
+
} else {
|
|
2781
|
+
let nullableMode = "NULLABLE";
|
|
2782
|
+
return {
|
|
2783
|
+
name,
|
|
2784
|
+
mode: nullableMode,
|
|
2785
|
+
type: this.getWarehouseTypeFromJSONSchema(schemaFieldDefinition)
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
};
|
|
2789
|
+
generateBigquerySchema = () => {
|
|
2790
|
+
const unsafeToSafeMappingColumnsFromColumnStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
|
|
2791
|
+
if (!unsafeToSafeMappingColumnsFromColumnStore) {
|
|
2792
|
+
throw new Error(`StreamId: ${this.streamId} - There is no unsafe->safe column mapping`);
|
|
2793
|
+
}
|
|
2794
|
+
return Object.keys(unsafeToSafeMappingColumnsFromColumnStore).reduce((acc, unsafeKey) => {
|
|
2795
|
+
const renamedColName = this.renamedColumnStore.getColumnTranslation(this.streamId, unsafeKey);
|
|
2796
|
+
const bigqueryTableField = this.getBigqueryTableFieldFromInternalSchemaFieldDefinition(renamedColName, this.flattenedSchema[unsafeKey]);
|
|
2797
|
+
if (bigqueryTableField) {
|
|
2798
|
+
acc.push(bigqueryTableField);
|
|
2799
|
+
}
|
|
2800
|
+
return acc;
|
|
2801
|
+
}, []);
|
|
2802
|
+
};
|
|
2803
|
+
loadGCSFileInTable = async (tableName, file, metadata) => {
|
|
2804
|
+
try {
|
|
2805
|
+
const [job] = await this.bqStagingTable.load(file, metadata);
|
|
2806
|
+
logger.info(`\u{1F69A} Loaded GCS file \`${file.id}\` in BigQuery table: ${tableName} with Job: \`${job.id}\``);
|
|
2807
|
+
logger.debug(`Upload Stats: ${JSON.stringify(job.statistics)}`);
|
|
2808
|
+
const errors2 = job.status?.errors;
|
|
2809
|
+
if (errors2 && errors2.length > 0) {
|
|
2810
|
+
throw errors2;
|
|
2811
|
+
}
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
logger.error(err);
|
|
2814
|
+
throw new Error(`StreamId: ${this.streamId} - Issue when loading file in BigQuery table | fileName: ${file.name}, tableName: ${tableName}
|
|
2815
|
+
|
|
2816
|
+
Error: ${err.message}
|
|
2817
|
+
Stack: ${err.stack}`);
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// src/targets/bigquery/main.ts
|
|
2823
|
+
var BigQueryTarget = class extends ITarget {
|
|
2824
|
+
constructor(config, stateProvider) {
|
|
2825
|
+
super(config, stateProvider);
|
|
2826
|
+
this.renameColumnStore.setSafeColumnNameConverter(this.safeColumnNameConverter);
|
|
2827
|
+
}
|
|
2828
|
+
static requiredConfigKeys = [
|
|
2829
|
+
"schema",
|
|
2830
|
+
"project_id",
|
|
2831
|
+
"loading_deck_gcs_bucket_name"
|
|
2832
|
+
];
|
|
2833
|
+
newDBSyncInstance = (streamId, database, schema, table) => {
|
|
2834
|
+
return new BigQueryDBSync(
|
|
2835
|
+
this.config,
|
|
2836
|
+
streamId,
|
|
2837
|
+
database,
|
|
2838
|
+
schema,
|
|
2839
|
+
table,
|
|
2840
|
+
this.renameColumnStore
|
|
2841
|
+
);
|
|
2842
|
+
};
|
|
2843
|
+
genTableName = safeTableName;
|
|
2844
|
+
safeColumnNameConverter = safeColumnName;
|
|
2845
|
+
validateDateRange = validateDateRange;
|
|
2846
|
+
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// src/sdk/service/gcs.ts
|
|
2850
|
+
var import_storage2 = require("@google-cloud/storage");
|
|
2851
|
+
var import_util3 = require("util");
|
|
2852
|
+
var getStorage = () => new import_storage2.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
2853
|
+
var readObjectAsString = async (bucketName, objectPath) => {
|
|
2854
|
+
try {
|
|
2855
|
+
const storage = getStorage();
|
|
2856
|
+
const bucketRef = storage.bucket(bucketName);
|
|
2857
|
+
await bucketRef.get({ autoCreate: false });
|
|
2858
|
+
const fileRef = bucketRef.file(objectPath);
|
|
2859
|
+
const [exists] = await fileRef.exists();
|
|
2860
|
+
if (!exists) {
|
|
2861
|
+
throw new Error(`GCS object not found: gs://${bucketName}/${objectPath}`);
|
|
2862
|
+
}
|
|
2863
|
+
const [contents] = await fileRef.download();
|
|
2864
|
+
return contents.toString("utf8");
|
|
2865
|
+
} catch (err) {
|
|
2866
|
+
throw new Error((0, import_util3.format)(`error reading GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
var writeStringObject = async (bucketName, objectPath, contents) => {
|
|
2870
|
+
try {
|
|
2871
|
+
const storage = getStorage();
|
|
2872
|
+
const bucketRef = storage.bucket(bucketName);
|
|
2873
|
+
await bucketRef.get({ autoCreate: false });
|
|
2874
|
+
const fileRef = bucketRef.file(objectPath);
|
|
2875
|
+
await fileRef.save(contents, { contentType: "application/json" });
|
|
2876
|
+
logger.info(`\u{1F5F3} Uploaded object to gs://${bucketName}/${objectPath}`);
|
|
2877
|
+
} catch (err) {
|
|
2878
|
+
throw new Error((0, import_util3.format)(`error writing GCS object gs://${bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
|
|
2882
|
+
// src/state-providers/gcs/main.ts
|
|
2883
|
+
var import_util4 = require("util");
|
|
2884
|
+
var GCSStateProvider = class {
|
|
2885
|
+
bucketName;
|
|
2886
|
+
connectorId;
|
|
2887
|
+
constructor(connectorId, bucketName) {
|
|
2888
|
+
this.connectorId = connectorId;
|
|
2889
|
+
this.bucketName = bucketName;
|
|
2890
|
+
}
|
|
2891
|
+
getObjectPath() {
|
|
2892
|
+
return `${this.connectorId}/state.json`;
|
|
2893
|
+
}
|
|
2894
|
+
async getState() {
|
|
2895
|
+
const objectPath = this.getObjectPath();
|
|
2896
|
+
try {
|
|
2897
|
+
logger.info(`\u{1F4C1} Loading state from gs://${this.bucketName}/${objectPath}`);
|
|
2898
|
+
const raw = await readObjectAsString(this.bucketName, objectPath);
|
|
2899
|
+
const parsed = JSON.parse(raw);
|
|
2900
|
+
return { state: parsed };
|
|
2901
|
+
} catch (err) {
|
|
2902
|
+
logger.warn((0, import_util4.format)(`error loading state from gs://${this.bucketName}/${objectPath}, will start with empty state, err: %s`, err?.message));
|
|
2903
|
+
return { state: void 0 };
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async writeState(state) {
|
|
2907
|
+
const objectPath = this.getObjectPath();
|
|
2908
|
+
try {
|
|
2909
|
+
logger.info(`\u{1F4DD} Writing state to gs://${this.bucketName}/${objectPath}`);
|
|
2910
|
+
await writeStringObject(this.bucketName, objectPath, state);
|
|
2911
|
+
} catch (err) {
|
|
2912
|
+
logger.error((0, import_util4.format)(`\u{1F4A5} Error while writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2913
|
+
throw new Error((0, import_util4.format)(`error writing state to gs://${this.bucketName}/${objectPath}, err: %s`, err?.message));
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2819
2917
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2820
2918
|
0 && (module.exports = {
|
|
2821
2919
|
ALL_STREAM_ID_KEYWORD,
|
|
2822
2920
|
API_CALLS_METRIC_NAME,
|
|
2823
2921
|
Authenticator,
|
|
2824
2922
|
BATCH_INTERVAL_MS,
|
|
2825
|
-
|
|
2923
|
+
BigQueryTarget,
|
|
2826
2924
|
CounterMetric,
|
|
2827
2925
|
DEFAULT_MAX_CONCURRENT_STREAMS,
|
|
2828
2926
|
EXECUTION_TIME_METRIC_NAME,
|
|
2927
|
+
GCSStateProvider,
|
|
2829
2928
|
ITarget,
|
|
2830
2929
|
MissingFieldInSchemaError,
|
|
2831
2930
|
MissingSchemaError,
|
|
2832
|
-
|
|
2931
|
+
RESTStream,
|
|
2833
2932
|
ROWS_SYNCED_METRIC_NAME,
|
|
2834
2933
|
RecordMessage,
|
|
2835
2934
|
RenameColumnStore,
|
|
2935
|
+
ReplicationMethod,
|
|
2836
2936
|
ReplicationMethodMessage,
|
|
2837
|
-
Resolver,
|
|
2838
2937
|
SchemaMessage,
|
|
2839
2938
|
SchemaValidationError,
|
|
2840
2939
|
StateMessage,
|
|
2841
2940
|
StateService,
|
|
2842
|
-
|
|
2843
|
-
StreamV2,
|
|
2941
|
+
Stream,
|
|
2844
2942
|
StreamWarehouseSyncService,
|
|
2845
2943
|
Tap,
|
|
2846
2944
|
_greaterThan,
|
|
2847
2945
|
addWhalyFields,
|
|
2848
|
-
checkRequiredConfigKeys,
|
|
2849
2946
|
createTemporaryFileStream,
|
|
2850
2947
|
extractStateForStream,
|
|
2851
2948
|
finalizeStateProgressMarkers,
|
|
2852
|
-
findRelatedRelationships,
|
|
2853
2949
|
flattenSchema,
|
|
2854
2950
|
getAllMetrics,
|
|
2855
2951
|
getAxiosInstance,
|
|
@@ -2864,15 +2960,12 @@ var ITarget = class _ITarget {
|
|
|
2864
2960
|
incrementStreamState,
|
|
2865
2961
|
loadJson,
|
|
2866
2962
|
loadSchema,
|
|
2867
|
-
loadShoreConfig,
|
|
2868
2963
|
logger,
|
|
2869
2964
|
postFormDataApiCall,
|
|
2870
2965
|
postJSONApiCall,
|
|
2871
2966
|
postUrlEncodedApiCall,
|
|
2872
2967
|
printMemoryFootprint,
|
|
2873
2968
|
removeParasiteProperties,
|
|
2874
|
-
safePath
|
|
2875
|
-
writeCatalogFile,
|
|
2876
|
-
writeStateFile
|
|
2969
|
+
safePath
|
|
2877
2970
|
});
|
|
2878
2971
|
//# sourceMappingURL=index.js.map
|