@whaly/connector-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2878 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ALL_STREAM_ID_KEYWORD: () => ALL_STREAM_ID_KEYWORD,
34
+ API_CALLS_METRIC_NAME: () => API_CALLS_METRIC_NAME,
35
+ Authenticator: () => Authenticator,
36
+ BATCH_INTERVAL_MS: () => BATCH_INTERVAL_MS,
37
+ Catalog: () => Catalog,
38
+ CounterMetric: () => CounterMetric,
39
+ DEFAULT_MAX_CONCURRENT_STREAMS: () => DEFAULT_MAX_CONCURRENT_STREAMS,
40
+ EXECUTION_TIME_METRIC_NAME: () => EXECUTION_TIME_METRIC_NAME,
41
+ ITarget: () => ITarget,
42
+ MissingFieldInSchemaError: () => MissingFieldInSchemaError,
43
+ MissingSchemaError: () => MissingSchemaError,
44
+ RESTStreamV2: () => RESTStreamV2,
45
+ ROWS_SYNCED_METRIC_NAME: () => ROWS_SYNCED_METRIC_NAME,
46
+ RecordMessage: () => RecordMessage,
47
+ RenameColumnStore: () => RenameColumnStore,
48
+ ReplicationMethodMessage: () => ReplicationMethodMessage,
49
+ Resolver: () => Resolver,
50
+ SchemaMessage: () => SchemaMessage,
51
+ SchemaValidationError: () => SchemaValidationError,
52
+ StateMessage: () => StateMessage,
53
+ StateService: () => StateService,
54
+ StreamMetadata: () => StreamMetadata,
55
+ StreamV2: () => StreamV2,
56
+ StreamWarehouseSyncService: () => StreamWarehouseSyncService,
57
+ Tap: () => Tap,
58
+ _greaterThan: () => _greaterThan,
59
+ addWhalyFields: () => addWhalyFields,
60
+ checkRequiredConfigKeys: () => checkRequiredConfigKeys,
61
+ createTemporaryFileStream: () => createTemporaryFileStream,
62
+ extractStateForStream: () => extractStateForStream,
63
+ finalizeStateProgressMarkers: () => finalizeStateProgressMarkers,
64
+ findRelatedRelationships: () => findRelatedRelationships,
65
+ flattenSchema: () => flattenSchema,
66
+ getAllMetrics: () => getAllMetrics,
67
+ getAxiosInstance: () => getAxiosInstance,
68
+ getCounterMetric: () => getCounterMetric,
69
+ getCounterMetrics: () => getCounterMetrics,
70
+ getDownloadFileApiCall: () => getDownloadFileApiCall,
71
+ getJSONApiCall: () => getJSONApiCall,
72
+ getJSONApiCallWithFullResponse: () => getJSONApiCallWithFullResponse,
73
+ getRawJSONApiCall: () => getRawJSONApiCall,
74
+ gracefulExit: () => gracefulExit,
75
+ haltAndCatchFire: () => haltAndCatchFire,
76
+ incrementStreamState: () => incrementStreamState,
77
+ loadJson: () => loadJson,
78
+ loadSchema: () => loadSchema,
79
+ loadShoreConfig: () => loadShoreConfig,
80
+ logger: () => logger,
81
+ postFormDataApiCall: () => postFormDataApiCall,
82
+ postJSONApiCall: () => postJSONApiCall,
83
+ postUrlEncodedApiCall: () => postUrlEncodedApiCall,
84
+ printMemoryFootprint: () => printMemoryFootprint,
85
+ removeParasiteProperties: () => removeParasiteProperties,
86
+ safePath: () => safePath,
87
+ writeCatalogFile: () => writeCatalogFile,
88
+ writeStateFile: () => writeStateFile
89
+ });
90
+ module.exports = __toCommonJS(index_exports);
91
+
92
+ // src/sdk/utils.ts
93
+ var import_fs2 = require("fs");
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
+ };
328
+
329
+ // src/sdk/service/logger.ts
330
+ var BATCH_INTERVAL_MS = 100;
331
+ var transports2 = {
332
+ console: new winston2.transports.Console({
333
+ stderrLevels: [],
334
+ level: "info"
335
+ })
336
+ };
337
+ var getTransports = () => {
338
+ return [
339
+ transports2.console
340
+ ];
341
+ };
342
+ var winstonLogger = winston2.createLogger({
343
+ format: loadResolver().getLogFormat(),
344
+ transports: getTransports()
345
+ });
346
+ var isWinstonOpen = true;
347
+ var closeWinston = (cb) => {
348
+ isWinstonOpen = false;
349
+ winstonLogger.end();
350
+ winstonLogger.on("finish", () => {
351
+ setTimeout(() => {
352
+ return cb();
353
+ }, BATCH_INTERVAL_MS * 2);
354
+ });
355
+ };
356
+ var log = (level, message, ...args) => {
357
+ if (isWinstonOpen) {
358
+ winstonLogger[level](message, ...args);
359
+ } else {
360
+ console[level](message, ...args);
361
+ }
362
+ };
363
+ var logger = {
364
+ info: (message, ...args) => log("info", message, ...args),
365
+ error: (message, ...args) => log("error", message, ...args),
366
+ debug: (message, ...args) => log("debug", message, ...args),
367
+ warn: (message, ...args) => log("warn", message, ...args),
368
+ closeWinston
369
+ };
370
+
371
+ // 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
+ function readFile(fileName, filePath) {
401
+ try {
402
+ return (0, import_fs2.readFileSync)(filePath);
403
+ } catch (err) {
404
+ logger.error(`Can't open file: \`${fileName}\` at path: \`${filePath}\`. Is it at the proper place?`);
405
+ throw err;
406
+ }
407
+ }
408
+ function parseFileContent(fileName, fileContent) {
409
+ try {
410
+ return JSON.parse(fileContent.toString());
411
+ } catch (err) {
412
+ logger.error(`Can't parse file: \`${fileName}\` as JSON. Is it properly formatted?`);
413
+ throw err;
414
+ }
415
+ }
416
+ var loadJson = (fileName) => {
417
+ const filePath = process.cwd() + `/` + fileName;
418
+ const fileContent = readFile(fileName, filePath);
419
+ return parseFileContent(fileName, fileContent);
420
+ };
421
+ var loadSchema = (streamId) => {
422
+ const schema = loadJson(`schemas/${streamId}.json`);
423
+ return schema;
424
+ };
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
+
433
+ // src/sdk/service/network.ts
434
+ var import_axios = __toESM(require("axios"));
435
+ var import_url = require("url");
436
+ var fs2 = __toESM(require("fs"));
437
+ var qs = __toESM(require("qs"));
438
+ var import_axios_retry = __toESM(require("axios-retry"));
439
+ var shouldRetry = (error) => {
440
+ const statusCode = error.response ? error.response.status : 0;
441
+ return error.code !== "ECONNABORTED" && (!error.response || statusCode === 429 || statusCode === 409 || statusCode === 400 || statusCode >= 500 && statusCode < 599);
442
+ };
443
+ var getAxiosInstance = (retryCount) => {
444
+ const instance = import_axios.default.create({
445
+ timeout: 12e4
446
+ });
447
+ (0, import_axios_retry.default)(
448
+ instance,
449
+ {
450
+ retries: retryCount || 24,
451
+ retryDelay: (retryCount2, error) => {
452
+ logger.info(
453
+ `\u{1F501} Retrying for the ${retryCount2}th time the call for
454
+ method=${error.config?.method}
455
+ url=${error.config?.url}
456
+ params=%j
457
+ body=%j`,
458
+ error.config?.params,
459
+ error.config?.data
460
+ );
461
+ if (retryCount2 > 6) {
462
+ return 1e4;
463
+ }
464
+ return import_axios_retry.default.exponentialDelay(retryCount2);
465
+ },
466
+ retryCondition: (error) => {
467
+ logger.debug(`\u{1F631} Got an error for ${error.config?.method} ${error.config?.url}
468
+
469
+ Status: ${error?.response?.status}
470
+ Error code ${error.code}
471
+ Error config method ${error}`);
472
+ return (0, import_axios_retry.isNetworkOrIdempotentRequestError)(error) || shouldRetry(error);
473
+ }
474
+ }
475
+ );
476
+ return instance;
477
+ };
478
+ var getRawJSONApiCall = async (endpoint, config, privateLogging) => {
479
+ const instance = getAxiosInstance(config.retryCount);
480
+ const paramsSerializer = (params) => {
481
+ return qs.stringify(params, { arrayFormat: "repeat" });
482
+ };
483
+ try {
484
+ logger.info(`\u{1F916} GET ${endpoint}`, { private: privateLogging });
485
+ const requestConfig = {
486
+ params: config.qs,
487
+ paramsSerializer: { serialize: (params) => qs.stringify(params, { arrayFormat: "repeat" }) }
488
+ };
489
+ if (config.headers) {
490
+ requestConfig.headers = config.headers;
491
+ }
492
+ const response = await instance.get(
493
+ endpoint,
494
+ requestConfig
495
+ );
496
+ return response;
497
+ } catch (err) {
498
+ if (err.response.status > 400) {
499
+ throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
500
+
501
+ headers response: ${JSON.stringify(err.response.headers)}
502
+
503
+ Response body from the server: ${JSON.stringify(err.response.data)}`);
504
+ }
505
+ throw err;
506
+ }
507
+ };
508
+ var getJSONApiCallWithFullResponse = async (endpoint, config, privateLogging) => {
509
+ const instance = getAxiosInstance(config.retryCount);
510
+ const paramsSerializer = (params) => {
511
+ return qs.stringify(params, { arrayFormat: "repeat" });
512
+ };
513
+ try {
514
+ logger.info(`\u{1F916} GET ${endpoint} with params: %s`, config.qs, { private: privateLogging });
515
+ const requestConfig = {
516
+ params: config.qs,
517
+ paramsSerializer: { serialize: (params) => qs.stringify(params, { arrayFormat: "repeat" }) }
518
+ };
519
+ if (config.headers) {
520
+ requestConfig.headers = config.headers;
521
+ }
522
+ const response = await instance.get(endpoint, requestConfig);
523
+ return response;
524
+ } catch (err) {
525
+ if (err instanceof Error) {
526
+ if (import_axios.default.isAxiosError(err)) {
527
+ if ((err.response?.status || 0) > 400) {
528
+ logger.warn(`We got a \`${err.response?.status}\` status code from endpoint: \`${endpoint}\`.
529
+
530
+ Headers response: ${JSON.stringify(err.response?.headers)}
531
+
532
+ Response body from the server: ${JSON.stringify(err.response?.data)}`);
533
+ }
534
+ }
535
+ }
536
+ throw err;
537
+ }
538
+ };
539
+ var getJSONApiCall = async (endpoint, config, privateLogging) => {
540
+ const fullResponse = await getJSONApiCallWithFullResponse(
541
+ endpoint,
542
+ config,
543
+ privateLogging
544
+ );
545
+ return fullResponse.data;
546
+ };
547
+ var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) => {
548
+ const instance = getAxiosInstance(config.retryCount);
549
+ const writer = fs2.createWriteStream(output);
550
+ const paramsSerializer = (params) => {
551
+ return qs.stringify(params, { arrayFormat: "repeat" });
552
+ };
553
+ logger.info(`\u{1F916} GET ${endpoint}`, { private: privateLogging });
554
+ return instance({
555
+ method: "GET",
556
+ url: endpoint,
557
+ responseType: "stream",
558
+ params: config.qs,
559
+ ...config.headers ? { headers: config.headers } : {},
560
+ paramsSerializer: { serialize: (params) => qs.stringify(params, { arrayFormat: "repeat" }) }
561
+ }).then((response) => {
562
+ return new Promise((resolve, reject) => {
563
+ response.data.pipe(writer);
564
+ let error = null;
565
+ writer.on("error", (err) => {
566
+ error = err;
567
+ writer.close();
568
+ reject(err);
569
+ });
570
+ writer.on("close", () => {
571
+ if (!error) {
572
+ resolve({ outputDir: output });
573
+ }
574
+ });
575
+ });
576
+ });
577
+ };
578
+ var postJSONApiCall = async (endpoint, config, payload) => {
579
+ const instance = getAxiosInstance(config.retryCount);
580
+ try {
581
+ const requestConfig = { params: config.qs };
582
+ if (config.headers) {
583
+ requestConfig.headers = config.headers;
584
+ }
585
+ const response = await instance.post(
586
+ endpoint,
587
+ payload,
588
+ requestConfig
589
+ );
590
+ return response.data;
591
+ } catch (err) {
592
+ if (err?.response?.status >= 400) {
593
+ throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
594
+
595
+ Response body from the server: ${JSON.stringify(err.response.data)}`);
596
+ }
597
+ throw err;
598
+ }
599
+ };
600
+ var postFormDataApiCall = async (endpoint, config, payload) => {
601
+ const instance = getAxiosInstance(config.retryCount);
602
+ try {
603
+ const response = await instance({
604
+ method: "post",
605
+ url: endpoint,
606
+ headers: {
607
+ ...payload.getHeaders()
608
+ },
609
+ data: payload
610
+ });
611
+ return response.data;
612
+ } catch (err) {
613
+ if (err.response.status > 400) {
614
+ throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
615
+
616
+ Response body from the server: ${JSON.stringify(err.response.data)}`);
617
+ }
618
+ throw err;
619
+ }
620
+ };
621
+ var postUrlEncodedApiCall = async (endpoint, config, payload) => {
622
+ const instance = getAxiosInstance(config.retryCount);
623
+ try {
624
+ const params = new import_url.URLSearchParams(payload);
625
+ const response = await instance.post(
626
+ endpoint,
627
+ params.toString(),
628
+ {
629
+ ...config.headers ? { headers: config.headers } : {}
630
+ }
631
+ );
632
+ return response.data;
633
+ } catch (err) {
634
+ if (err.response.status > 400) {
635
+ throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
636
+
637
+ Response body from the server: ${JSON.stringify(err.response.data)}`);
638
+ }
639
+ throw err;
640
+ }
641
+ };
642
+
643
+ // src/sdk/service/memory.ts
644
+ var formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
645
+ var printMemoryFootprint = (prefix) => {
646
+ const memoryData = process.memoryUsage();
647
+ const memoryUsage = {
648
+ rss: `${formatMemoryUsage(memoryData.rss)} -> Resident Set Size - total memory allocated for the process execution`,
649
+ heapTotal: `${formatMemoryUsage(memoryData.heapTotal)} -> total size of the allocated heap`,
650
+ heapUsed: `${formatMemoryUsage(memoryData.heapUsed)} -> actual memory used during the execution`,
651
+ external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`
652
+ };
653
+ logger.info(`${prefix}: %j`, memoryUsage);
654
+ };
655
+
656
+ // src/sdk/service/exit.ts
657
+ async function gracefulExit(exitCode) {
658
+ if (exitCode !== 0) {
659
+ await loadResolver().markSyncFailed();
660
+ }
661
+ const loggerFinish = new Promise((resolve, reject) => {
662
+ logger.closeWinston(resolve);
663
+ });
664
+ await loggerFinish;
665
+ process.exit(exitCode);
666
+ }
667
+
668
+ // src/sdk/service/error.ts
669
+ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
670
+ logger.error(
671
+ `\u{1F4A5} Got a fatal error that will reported to the end user. Process will stop.
672
+ errorType=%s
673
+ errorText=%s
674
+ errorDebugText=%s
675
+ `,
676
+ errorType,
677
+ errorText,
678
+ errorDebugText
679
+ );
680
+ const resolver = loadResolver();
681
+ await resolver.onError(errorType, errorText, errorDebugText);
682
+ await gracefulExit(0);
683
+ };
684
+
685
+ // src/sdk/service/relationship.ts
686
+ var findRelatedRelationships = (streamId, allRels) => {
687
+ return allRels.reduce((acc, rel) => {
688
+ const base = {
689
+ type: rel.type,
690
+ from: rel.from,
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
+ };
881
+
882
+ // src/sdk/models/messages.ts
883
+ var RecordMessage = class {
884
+ type = "RECORD";
885
+ stream;
886
+ record;
887
+ constructor(record, stream) {
888
+ this.record = record;
889
+ this.stream = stream;
890
+ }
891
+ toString() {
892
+ const result = {
893
+ type: "RECORD",
894
+ record: this.record,
895
+ stream: this.stream
896
+ };
897
+ return JSON.stringify(result);
898
+ }
899
+ };
900
+ var StateMessage = class {
901
+ type = "STATE";
902
+ value;
903
+ constructor(value) {
904
+ this.value = value;
905
+ }
906
+ toString() {
907
+ const result = {
908
+ type: "STATE",
909
+ value: this.value
910
+ };
911
+ return JSON.stringify(result);
912
+ }
913
+ };
914
+ var ReplicationMethodMessage = class {
915
+ type = "REPLICATION_METHOD";
916
+ stream;
917
+ replication;
918
+ constructor(stream, version) {
919
+ this.stream = stream;
920
+ this.replication = version;
921
+ }
922
+ toString() {
923
+ const result = {
924
+ type: "REPLICATION_METHOD",
925
+ replication: this.replication,
926
+ stream: this.stream
927
+ };
928
+ return JSON.stringify(result);
929
+ }
930
+ };
931
+ var SchemaMessage = class {
932
+ type = "SCHEMA";
933
+ stream;
934
+ schema;
935
+ keyProperties;
936
+ bookmarkProperties;
937
+ displayLabel;
938
+ description;
939
+ propertiesMetadata;
940
+ relationships;
941
+ constructor(opts) {
942
+ const {
943
+ stream,
944
+ schema,
945
+ keyProperties,
946
+ bookmarkProperties,
947
+ displayLabel,
948
+ description,
949
+ relationships,
950
+ propertiesMetadata
951
+ } = opts;
952
+ this.stream = stream;
953
+ this.schema = schema;
954
+ this.keyProperties = keyProperties;
955
+ this.bookmarkProperties = bookmarkProperties;
956
+ this.displayLabel = displayLabel;
957
+ this.description = description;
958
+ this.relationships = relationships;
959
+ this.propertiesMetadata = propertiesMetadata;
960
+ }
961
+ toString() {
962
+ const result = {
963
+ type: "SCHEMA",
964
+ stream: this.stream,
965
+ schema: this.schema,
966
+ key_properties: this.keyProperties,
967
+ label: this.displayLabel,
968
+ description: this.description,
969
+ relationships: this.relationships
970
+ };
971
+ if (this.bookmarkProperties) {
972
+ result["bookmark_properties"] = this.bookmarkProperties;
973
+ }
974
+ return JSON.stringify(result);
975
+ }
976
+ };
977
+
978
+ // src/sdk/models/state.ts
979
+ var import_moment = __toESM(require("moment"));
980
+
981
+ // src/sdk/constants/date.ts
982
+ var defaultDateTimeFormat = "YYYY-MM-DDTHH:mm:ss.SSSSSSZ";
983
+
984
+ // src/sdk/models/state.ts
985
+ var import_util = require("util");
986
+ var PROGRESS_MARKERS = "progressMarkers";
987
+ var PROGRESS_MARKER_NOTE = "Note";
988
+ var SIGNPOST_MARKER = "replicationKeySignpost";
989
+ var incrementStreamState = (state, replicationKey, latestRecord, isSorted) => {
990
+ let newRkValue = latestRecord[replicationKey];
991
+ if (!newRkValue) {
992
+ throw new Error((0, import_util.format)(`No value for replicationKey: ${replicationKey} in record with keys: %j`, Object.keys(latestRecord)));
993
+ }
994
+ let newRkValueMoment = (0, import_moment.default)(newRkValue);
995
+ let prevRkValue;
996
+ let prevRkValueMoment;
997
+ if (isSorted) {
998
+ prevRkValue = state["replicationKeyValue"];
999
+ prevRkValueMoment = (0, import_moment.default)(prevRkValue);
1000
+ if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
1001
+ throw new Error(
1002
+ `Unsorted data detected in stream. Latest value '${newRkValue}' is
1003
+ smaller than previous max '${prevRkValue}'.`
1004
+ );
1005
+ }
1006
+ }
1007
+ if (!isSorted) {
1008
+ if (!state[PROGRESS_MARKERS]) {
1009
+ const marker = {};
1010
+ marker[PROGRESS_MARKER_NOTE] = "Progress is not resumable if interrupted.";
1011
+ state[PROGRESS_MARKERS] = marker;
1012
+ }
1013
+ prevRkValue = state[PROGRESS_MARKERS]["replicationKeyValue"];
1014
+ prevRkValueMoment = (0, import_moment.default)(prevRkValue || state["replicationKeyValue"]);
1015
+ if (prevRkValue && prevRkValueMoment.isAfter(newRkValueMoment)) {
1016
+ newRkValueMoment = prevRkValueMoment;
1017
+ newRkValue = prevRkValue;
1018
+ }
1019
+ }
1020
+ const signpostMarker = state[SIGNPOST_MARKER];
1021
+ const replicationKeySignpostMoment = (0, import_moment.default)(signpostMarker);
1022
+ if (replicationKeySignpostMoment && replicationKeySignpostMoment.isBefore(newRkValueMoment)) {
1023
+ newRkValueMoment = replicationKeySignpostMoment;
1024
+ newRkValue = replicationKeySignpostMoment.format(defaultDateTimeFormat);
1025
+ }
1026
+ if (isSorted === false) {
1027
+ state[PROGRESS_MARKERS]["replicationKey"] = replicationKey;
1028
+ state[PROGRESS_MARKERS]["replicationKeyValue"] = (0, import_moment.default)(newRkValueMoment).format(defaultDateTimeFormat);
1029
+ } else {
1030
+ state["replicationKey"] = replicationKey;
1031
+ state["replicationKeyValue"] = (0, import_moment.default)(newRkValueMoment).format(defaultDateTimeFormat);
1032
+ }
1033
+ return state;
1034
+ };
1035
+ var finalizeStateProgressMarkers = (state) => {
1036
+ const signpostValue = state[SIGNPOST_MARKER];
1037
+ const progressMarkers = state[PROGRESS_MARKERS];
1038
+ if (progressMarkers && progressMarkers["replicationKey"] && progressMarkers["replicationKeyValue"]) {
1039
+ state["replicationKey"] = progressMarkers["replicationKey"];
1040
+ let newRkValue = progressMarkers["replicationKeyValue"];
1041
+ if (signpostValue && _greaterThan(newRkValue, signpostValue)) {
1042
+ newRkValue = signpostValue;
1043
+ }
1044
+ const previousReplicationValue = state["replicationKeyValue"];
1045
+ if (!previousReplicationValue || previousReplicationValue && _greaterThan(newRkValue, previousReplicationValue)) {
1046
+ state["replicationKeyValue"] = newRkValue;
1047
+ }
1048
+ }
1049
+ delete state[PROGRESS_MARKERS];
1050
+ return state;
1051
+ };
1052
+ var _greaterThan = (aValue, bValue) => {
1053
+ return (0, import_moment.default)(aValue).isAfter((0, import_moment.default)(bValue));
1054
+ };
1055
+ var extractStateForStream = (state, streamId) => {
1056
+ if (!state.bookmarks) {
1057
+ state.bookmarks = {};
1058
+ }
1059
+ if (!state.bookmarks[streamId]) {
1060
+ state.bookmarks[streamId] = {};
1061
+ }
1062
+ return state.bookmarks[streamId];
1063
+ };
1064
+ var StateService = class {
1065
+ static instance;
1066
+ bookmarks;
1067
+ constructor() {
1068
+ this.bookmarks = {};
1069
+ }
1070
+ static getInstance() {
1071
+ if (!this.instance) {
1072
+ this.instance = new this();
1073
+ }
1074
+ return this.instance;
1075
+ }
1076
+ // To be deprecated
1077
+ setBookmark(streamId, ts) {
1078
+ this.bookmarks[streamId] = ts.format(defaultDateTimeFormat);
1079
+ }
1080
+ setBookmarkV2(streamId, streamState) {
1081
+ this.bookmarks[streamId] = streamState;
1082
+ }
1083
+ setBookmarkSignpostV2(streamId, signpostValue) {
1084
+ if (!this.bookmarks[streamId]) {
1085
+ this.bookmarks[streamId] = {};
1086
+ }
1087
+ this.bookmarks[streamId][SIGNPOST_MARKER] = signpostValue;
1088
+ }
1089
+ 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
+ if (this.bookmarks[streamId]) {
1097
+ return this.bookmarks[streamId];
1098
+ } else {
1099
+ return {};
1100
+ }
1101
+ }
1102
+ clearState() {
1103
+ this.bookmarks = {};
1104
+ }
1105
+ get() {
1106
+ const state = {
1107
+ bookmarks: this.bookmarks
1108
+ };
1109
+ return state;
1110
+ }
1111
+ async forwardStateToTarget(target) {
1112
+ const state = {
1113
+ bookmarks: this.bookmarks
1114
+ };
1115
+ const message = new StateMessage(state);
1116
+ await target.state(message);
1117
+ }
1118
+ };
1119
+
1120
+ // src/sdk/models/tap/authenticator.ts
1121
+ var Authenticator = class {
1122
+ getAuthHeaders(config) {
1123
+ return Promise.resolve({});
1124
+ }
1125
+ getAuthQS(config) {
1126
+ return Promise.resolve({});
1127
+ }
1128
+ };
1129
+
1130
+ // src/sdk/models/tap/restStream.ts
1131
+ var import_axios2 = __toESM(require("axios"));
1132
+ var qs2 = __toESM(require("qs"));
1133
+
1134
+ // src/sdk/models/tap/stream.ts
1135
+ var import_moment2 = __toESM(require("moment"));
1136
+
1137
+ // src/sdk/helpers/typing.ts
1138
+ var isDatetimeType = (typeDef) => {
1139
+ if (!typeDef) {
1140
+ throw new Error("Could not detect type from empty typeDef param.");
1141
+ }
1142
+ if (typeDef["anyOf"] !== void 0) {
1143
+ for (const subTypeDef of typeDef["anyOf"]) {
1144
+ if (isDatetimeType(subTypeDef)) {
1145
+ return true;
1146
+ }
1147
+ }
1148
+ return false;
1149
+ } else if (typeDef["type"] !== void 0) {
1150
+ return typeDef["format"] === "date-time";
1151
+ }
1152
+ throw new Error(
1153
+ `Could not detect type of key using schema '${typeDef}'`
1154
+ );
1155
+ };
1156
+
1157
+ // src/sdk/models/tap/stream.ts
1158
+ var _2 = __toESM(require("lodash"));
1159
+ var import_bluebird2 = require("bluebird");
1160
+
1161
+ // src/sdk/models/tap/tap.ts
1162
+ var import_bluebird = require("bluebird");
1163
+ var import_dependency_graph = require("dependency-graph");
1164
+ var DEFAULT_MAX_CONCURRENT_STREAMS = 5;
1165
+ var Tap = class {
1166
+ _target;
1167
+ _config;
1168
+ _streams;
1169
+ _concurrency = DEFAULT_MAX_CONCURRENT_STREAMS;
1170
+ constructor(target, config) {
1171
+ this._streams = [];
1172
+ this._target = target;
1173
+ this._config = config;
1174
+ }
1175
+ discover = async (existingCatalog) => {
1176
+ logger.debug("Loading schemas");
1177
+ const streamDepGraph = new import_dependency_graph.DepGraph();
1178
+ const buildDependencyGraph = (streams2, parentId) => {
1179
+ streams2.forEach((s) => {
1180
+ streamDepGraph.addNode(s.streamId, s);
1181
+ if (parentId) {
1182
+ streamDepGraph.addDependency(s.streamId, parentId);
1183
+ }
1184
+ if (s.children) {
1185
+ return buildDependencyGraph(s.children, s.streamId);
1186
+ }
1187
+ });
1188
+ };
1189
+ buildDependencyGraph(this._streams);
1190
+ const discoveredStreamCatalogEntries = await import_bluebird.Promise.map(
1191
+ streamDepGraph.overallOrder(),
1192
+ async (streamId) => {
1193
+ logger.info(`\u2728 Loading schema for ${streamId}`);
1194
+ const stream = streamDepGraph.getNodeData(streamId);
1195
+ const schema = await stream.getSchema();
1196
+ const parentStreamIds = streamDepGraph.directDependenciesOf(streamId);
1197
+ if (parentStreamIds.length > 1) {
1198
+ throw new Error(`StreamId=${streamId} directly depends on more than 1 stream, e.g. ${parentStreamIds.join(",")} which is not supported!`);
1199
+ }
1200
+ const parentStreamId = parentStreamIds?.[0];
1201
+ const { metadata } = await this.generateMetadataFromStream(stream, parentStreamId);
1202
+ return {
1203
+ schema,
1204
+ metadata,
1205
+ stream: stream.streamId,
1206
+ tap_stream_id: stream.streamId
1207
+ };
1208
+ },
1209
+ { concurrency: this._concurrency }
1210
+ );
1211
+ const existingCatalogStreamIds = (existingCatalog?.streams || []).map((entry) => {
1212
+ return entry.stream;
1213
+ });
1214
+ const newlyDiscoveredStreamCatalogEntries = discoveredStreamCatalogEntries.filter((entry) => {
1215
+ if (existingCatalogStreamIds.includes(entry.stream)) {
1216
+ return false;
1217
+ } else {
1218
+ return true;
1219
+ }
1220
+ });
1221
+ logger.debug(`Newly discovered streams: %j`, newlyDiscoveredStreamCatalogEntries);
1222
+ const allDiscoveredStreamIds = discoveredStreamCatalogEntries.map((entry) => {
1223
+ return entry.stream;
1224
+ });
1225
+ const existingCatalogMinusDeletedStreams = (existingCatalog?.streams || []).filter((entry) => {
1226
+ if (!allDiscoveredStreamIds.includes(entry.stream)) {
1227
+ logger.debug(`Stream %s was deleted, removing it from the catalog`, entry.stream);
1228
+ return false;
1229
+ } else {
1230
+ return true;
1231
+ }
1232
+ });
1233
+ const updatedExistingCatalog = existingCatalogMinusDeletedStreams.map((existingCatalogEntry) => {
1234
+ const matchingDiscoveredCatalogEntry = discoveredStreamCatalogEntries.find((entry) => entry.stream === existingCatalogEntry.stream);
1235
+ if (matchingDiscoveredCatalogEntry) {
1236
+ const existingRootMetadata = existingCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
1237
+ const discoveredRootMetadata = matchingDiscoveredCatalogEntry.metadata.find((metadata) => metadata.breadcrumb.length === 0);
1238
+ if (existingRootMetadata) {
1239
+ const parentStreamVal = discoveredRootMetadata?.metadata["whaly-parent-stream"];
1240
+ if (parentStreamVal !== void 0) {
1241
+ existingRootMetadata.metadata["whaly-parent-stream"] = parentStreamVal;
1242
+ }
1243
+ const forcedReplicationMethodVal = discoveredRootMetadata?.metadata["forced-replication-method"];
1244
+ if (forcedReplicationMethodVal !== void 0) {
1245
+ existingRootMetadata.metadata["forced-replication-method"] = forcedReplicationMethodVal;
1246
+ }
1247
+ const forcedReplicationKeyVal = discoveredRootMetadata?.metadata["forced-replication-key"];
1248
+ if (forcedReplicationKeyVal !== void 0) {
1249
+ existingRootMetadata.metadata["forced-replication-key"] = forcedReplicationKeyVal;
1250
+ }
1251
+ const validReplicationKeysVal = discoveredRootMetadata?.metadata["valid-replication-keys"];
1252
+ if (validReplicationKeysVal !== void 0) {
1253
+ existingRootMetadata.metadata["valid-replication-keys"] = validReplicationKeysVal;
1254
+ }
1255
+ const rowCountVal = discoveredRootMetadata?.metadata["row-count"];
1256
+ if (rowCountVal !== void 0) {
1257
+ existingRootMetadata.metadata["row-count"] = rowCountVal;
1258
+ }
1259
+ }
1260
+ }
1261
+ return existingCatalogEntry;
1262
+ });
1263
+ const streams = updatedExistingCatalog.concat(newlyDiscoveredStreamCatalogEntries);
1264
+ return { streams };
1265
+ };
1266
+ sync = async (catalog) => {
1267
+ await this.initiateSync(catalog);
1268
+ logger.debug(`[TAP] All streams have finished syncing. We send the [complete] signal to the target.`);
1269
+ await this._target.complete();
1270
+ return Promise.resolve();
1271
+ };
1272
+ async generateMetadataFromStream(stream, parentStreamId) {
1273
+ const metadata = new StreamMetadata();
1274
+ metadata.write([], "table-key-properties", stream.primaryKey);
1275
+ const defaultReplicationConfig = stream.defaultReplicationConfig();
1276
+ metadata.write([], "replication-method", defaultReplicationConfig.replicationMethod);
1277
+ const validReplicationkeys = await stream.getValidReplicationKeys();
1278
+ metadata.write([], "valid-replication-keys", validReplicationkeys);
1279
+ if (defaultReplicationConfig.isForced) {
1280
+ metadata.write([], "forced-replication-method", defaultReplicationConfig.replicationMethod);
1281
+ if (stream.forcedReplicationKey) {
1282
+ metadata.write([], "forced-replication-key", stream.forcedReplicationKey);
1283
+ }
1284
+ }
1285
+ const rowCount = await stream.getRowCount();
1286
+ if (rowCount !== void 0) {
1287
+ metadata.write([], "row-count", rowCount);
1288
+ }
1289
+ if (stream.isSilent === true) {
1290
+ metadata.write([], "whaly-is-hidden", stream.isSilent);
1291
+ }
1292
+ if (stream.isSilent === false) {
1293
+ metadata.write([], "selected-by-default", stream.selectedByDefault);
1294
+ }
1295
+ if (stream.displayLabel) {
1296
+ metadata.write([], "whaly-display-label", stream.displayLabel);
1297
+ }
1298
+ if (stream.description) {
1299
+ metadata.write([], "whaly-description", stream.description);
1300
+ }
1301
+ if (parentStreamId !== void 0) {
1302
+ metadata.write([], "whaly-parent-stream", parentStreamId);
1303
+ }
1304
+ return { metadata: metadata.toList() };
1305
+ }
1306
+ async initiateSync(catalog) {
1307
+ logger.info(`\u{1F680} Start syncing`);
1308
+ const state = StateService.getInstance().get();
1309
+ logger.info(`\u{1F4CD} Received state: %j`, state);
1310
+ logger.debug(`\u{1F4DA} Received catalog: %j`, catalog);
1311
+ const selectedStreams = catalog.getSelectedStreams();
1312
+ if (selectedStreams.length === 0) {
1313
+ logger.info(`\u{1F631} There is no stream to sync.
1314
+
1315
+ Did you update the \`catalog.json\` to add \`"selected": true\` in the table metadata associated with the root breadcrumb (e.g. \`[]\`)`);
1316
+ return;
1317
+ }
1318
+ const selectedStreamIds = selectedStreams.map((stream) => stream.stream);
1319
+ logger.info(`\u{1F973} Syncing selected streams: %s`, selectedStreamIds.join(","));
1320
+ await import_bluebird.Promise.map(
1321
+ this._streams,
1322
+ (stream) => {
1323
+ if (selectedStreamIds.includes(stream.streamId)) {
1324
+ const selectedStream = selectedStreams.find(((selectedStream2) => selectedStream2.stream === stream.streamId));
1325
+ const rootMetadataEntry = selectedStream?.metadata.find((streamMdt) => streamMdt.breadcrumb.length === 0);
1326
+ if (!rootMetadataEntry) {
1327
+ throw new Error(`Can't find a catalog root metadata entry for StreamId=${stream.streamId}`);
1328
+ }
1329
+ stream.setRootMetadataEntry(rootMetadataEntry.metadata);
1330
+ return stream.sync();
1331
+ }
1332
+ },
1333
+ {
1334
+ concurrency: this._concurrency
1335
+ }
1336
+ );
1337
+ }
1338
+ // Used to clean up any ressource before exit
1339
+ end() {
1340
+ return Promise.resolve();
1341
+ }
1342
+ };
1343
+
1344
+ // src/sdk/models/tap/stream.ts
1345
+ var import_util2 = require("util");
1346
+ var StreamV2 = class {
1347
+ // Runtime values, can't be overriden
1348
+ _config;
1349
+ _tapName = "default";
1350
+ _tapState;
1351
+ _target;
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;
1358
+ selectedByDefault = true;
1359
+ STATE_MSG_FREQUENCY = 10;
1360
+ streamId = "default";
1361
+ schemaPath = void 0;
1362
+ displayLabel = void 0;
1363
+ description = void 0;
1364
+ primaryKey = [];
1365
+ forcedReplicationKey = void 0;
1366
+ // When set, use the state from another stream for this stream
1367
+ useStateFromStreamId = void 0;
1368
+ relationships = [];
1369
+ children = [];
1370
+ // If true, it means that the records in this stream are sorted by replicationKey.
1371
+ // If false, then the records are coming in an unsorted manner.
1372
+ isSorted = false;
1373
+ // If true, then no SCHEMA / RECORD messages will be sent for this Stream.
1374
+ // However, a STATE will still be emitted if the Stream is using an incremental sync type.
1375
+ isSilent = false;
1376
+ childConcurrency;
1377
+ // Metrics configuration
1378
+ rowsSyncedMetricsConf;
1379
+ executionTimeMetricsConf;
1380
+ constructor(tapName, config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
1381
+ this._tapName = tapName;
1382
+ this._config = config;
1383
+ this._tapState = _2.cloneDeep(state);
1384
+ this._target = target;
1385
+ this.childConcurrency = childConcurrency;
1386
+ this.rowsSyncedMetricsConf = {
1387
+ isEnabled: () => !this.isSilent,
1388
+ getStreamIds: () => [this.streamId]
1389
+ };
1390
+ this.executionTimeMetricsConf = {
1391
+ isEnabled: () => !this.isSilent,
1392
+ getStreamIds: () => [this.streamId]
1393
+ };
1394
+ }
1395
+ setRootMetadataEntry = (rootMetadataEntry) => {
1396
+ this.rootMetadataEntry = rootMetadataEntry;
1397
+ };
1398
+ /**
1399
+ * Sync this stream
1400
+ * Called only for streams with no parents, aka. "root tap streams"
1401
+ * @returns
1402
+ */
1403
+ sync = async () => {
1404
+ try {
1405
+ const replicationMethod = this.configuredReplicationConfig().replicationMethod;
1406
+ logger.info(
1407
+ `\u{1F3AC} Beginning ${replicationMethod} sync of stream '${this.streamId}'...`
1408
+ );
1409
+ await this.asyncInit();
1410
+ await this._writeSchemaMessage();
1411
+ await this._writeReplicationMethodMessage();
1412
+ await this._writeSignpostInState();
1413
+ await this._syncRecords();
1414
+ return this.flush();
1415
+ } catch (err) {
1416
+ if (err instanceof Error) {
1417
+ throw new Error((0, import_util2.format)(`got an error while syncing streamId=%s, err: %s`, this.streamId, err.message));
1418
+ }
1419
+ throw err;
1420
+ }
1421
+ };
1422
+ flush = async () => {
1423
+ const replicationMethod = this.configuredReplicationConfig().replicationMethod;
1424
+ logger.info(
1425
+ `\u{1F6BD} Flushing ${replicationMethod} sync of stream '${this.streamId}' + all of its children...`
1426
+ );
1427
+ await this._flush();
1428
+ await import_bluebird2.Promise.map(this.children, (child) => {
1429
+ return child.flush();
1430
+ }, { concurrency: 3 });
1431
+ };
1432
+ // Private message authoring methods:
1433
+ /**
1434
+ * Write out a STATE message with the latest state.
1435
+ */
1436
+ _writeStateMessage = () => {
1437
+ const message = new StateMessage(StateService.getInstance().get());
1438
+ return this._target.state(message);
1439
+ };
1440
+ /**
1441
+ * Write out a ACTIVATE_VERSION message.
1442
+ */
1443
+ _writeReplicationMethodMessage = async () => {
1444
+ const method = this.configuredReplicationConfig().replicationMethod;
1445
+ const message = new ReplicationMethodMessage(this.streamId, method);
1446
+ await import_bluebird2.Promise.map(this.children, async (c) => {
1447
+ return c._writeReplicationMethodMessage();
1448
+ }, { concurrency: 3 });
1449
+ await this._target.replicationMethod(message);
1450
+ };
1451
+ /**
1452
+ * Write out a SCHEMA message with the stream schema.
1453
+ */
1454
+ _writeSchemaMessage = async (skipChildrenSchema) => {
1455
+ if (!this.isSilent) {
1456
+ const schema = await this.getSchema();
1457
+ if (schema !== void 0) {
1458
+ const relatedRels = findRelatedRelationships(this.streamId, this.relationships);
1459
+ const replicationConfig = this.configuredReplicationConfig();
1460
+ const message = new SchemaMessage({
1461
+ keyProperties: this.primaryKey,
1462
+ stream: this.streamId,
1463
+ schema: schema.jsonSchema,
1464
+ relationships: relatedRels,
1465
+ ...replicationConfig.replicationKey ? { bookmarkProperties: [replicationConfig.replicationKey] } : {},
1466
+ ...this.displayLabel ? { displayLabel: this.displayLabel } : {},
1467
+ ...this.description ? { description: this.description } : {},
1468
+ ...schema.propertiesMetadata ? { propertiesMetadata: schema.propertiesMetadata } : {}
1469
+ });
1470
+ await this._target.schema(message);
1471
+ }
1472
+ }
1473
+ if (skipChildrenSchema !== true) {
1474
+ await import_bluebird2.Promise.map(this.children, async (child) => {
1475
+ await child._writeSchemaMessage();
1476
+ }, { concurrency: 3 });
1477
+ }
1478
+ };
1479
+ // Private bookmarking methods
1480
+ _writeSignpostInState = async () => {
1481
+ const signpostMoment = await this.getReplicationKeySignpost();
1482
+ const signpostValue = signpostMoment?.format(defaultDateTimeFormat);
1483
+ if (signpostValue) {
1484
+ StateService.getInstance().setBookmarkSignpostV2(this.streamId, signpostValue);
1485
+ }
1486
+ };
1487
+ /**
1488
+ * Update state of stream with data from the provided record.
1489
+ */
1490
+ _incrementStreamState = (latestRecord) => {
1491
+ if (latestRecord) {
1492
+ const replicationConfig = this.configuredReplicationConfig();
1493
+ if ([
1494
+ "INCREMENTAL",
1495
+ "LOG_BASED"
1496
+ ].includes(replicationConfig.replicationMethod)) {
1497
+ if (this.useStateFromStreamId) {
1498
+ return;
1499
+ }
1500
+ if (this.forcedReplicationMethod && !this.forcedReplicationKey) {
1501
+ return;
1502
+ }
1503
+ if (!replicationConfig.replicationKey) {
1504
+ throw new Error(
1505
+ `Could not detect replication key for '${this.streamId}' stream"
1506
+ replication method=${replicationConfig.replicationMethod})`
1507
+ );
1508
+ }
1509
+ const stateServiceInst = StateService.getInstance();
1510
+ const state = stateServiceInst.getBookmarkV2(this.streamId);
1511
+ const newState = incrementStreamState(
1512
+ state,
1513
+ replicationConfig.replicationKey,
1514
+ latestRecord,
1515
+ this.isSorted
1516
+ );
1517
+ StateService.getInstance().setBookmarkV2(this.streamId, newState);
1518
+ }
1519
+ }
1520
+ };
1521
+ /**
1522
+ * Return True if the stream uses a timestamp-based replication key.
1523
+ *
1524
+ * Developers can override with `is_timestamp_replication_key = True` in
1525
+ order to force this value.
1526
+ */
1527
+ isTimestampReplicationKey = async () => {
1528
+ const replicationConfig = this.configuredReplicationConfig();
1529
+ if (!replicationConfig.replicationKey) {
1530
+ return false;
1531
+ }
1532
+ const schema = await this.getSchema();
1533
+ if (!schema || !schema.jsonSchema || !schema.jsonSchema.properties) {
1534
+ return false;
1535
+ }
1536
+ const typeFromSchema = schema.jsonSchema["properties"][replicationConfig.replicationKey];
1537
+ if (!typeFromSchema) {
1538
+ return false;
1539
+ }
1540
+ return isDatetimeType(typeFromSchema);
1541
+ };
1542
+ /**
1543
+ * Return state timestamp if set for the stream,
1544
+ * or `start_date` config if set,
1545
+ * or the UNIX Epoch
1546
+ **/
1547
+ getStartingTimestamp() {
1548
+ const streamIdToReadFrom = this.useStateFromStreamId ? this.useStateFromStreamId : this.streamId;
1549
+ const state = extractStateForStream(this._tapState, streamIdToReadFrom);
1550
+ const startDate = this._config.start_date;
1551
+ const replicationConfig = this.configuredReplicationConfig();
1552
+ logger.debug(`${this.streamId}: state.replicationKeyValue: ${state.replicationKeyValue},
1553
+ state.replicationKey: ${state.replicationKey},
1554
+ this.replicationKey: ${replicationConfig.replicationKey}`);
1555
+ if (state.replicationKeyValue) {
1556
+ logger.debug(`${this.streamId} - getStartingTimestamp -> Returning replication key value: ${state.replicationKeyValue}`);
1557
+ return (0, import_moment2.default)(state.replicationKeyValue);
1558
+ } else if (startDate) {
1559
+ logger.debug(`${this.streamId} - getStartingTimestamp -> Returning config start date: ${startDate}`);
1560
+ return (0, import_moment2.default)(startDate);
1561
+ } else {
1562
+ logger.debug(`${this.streamId} - getStartingTimestamp -> Returning EPOCH (1970)`);
1563
+ return (0, import_moment2.default)(0);
1564
+ }
1565
+ }
1566
+ /**
1567
+ * Return the max allowable bookmark value for this stream's replication key.
1568
+ *
1569
+ * For timestamp-based replication keys, this defaults to `moment()`. For
1570
+ * non-timestamp replication keys, default to `undefined`.
1571
+ *
1572
+ * Override this value to prevent bookmarks from being advanced in cases where we
1573
+ * may only have a partial set of records.
1574
+ */
1575
+ getReplicationKeySignpost = async () => {
1576
+ if (await this.isTimestampReplicationKey()) {
1577
+ return (0, import_moment2.default)();
1578
+ }
1579
+ return void 0;
1580
+ };
1581
+ /**
1582
+ * Return the default replication method for the stream that will be used to write the catalog.
1583
+ */
1584
+ defaultReplicationConfig = () => {
1585
+ if (this.forcedReplicationMethod) {
1586
+ return {
1587
+ replicationMethod: this.forcedReplicationMethod,
1588
+ replicationKey: this.forcedReplicationKey,
1589
+ isForced: true
1590
+ };
1591
+ }
1592
+ if (this.forcedReplicationKey || this.useStateFromStreamId) {
1593
+ return {
1594
+ replicationMethod: "INCREMENTAL",
1595
+ replicationKey: this.forcedReplicationKey,
1596
+ isForced: true
1597
+ };
1598
+ }
1599
+ return {
1600
+ replicationMethod: "FULL_TABLE",
1601
+ replicationKey: void 0,
1602
+ isForced: false
1603
+ };
1604
+ };
1605
+ /**
1606
+ * Return the configured replication method that will be used for the sync.
1607
+ */
1608
+ configuredReplicationConfig = () => {
1609
+ const defaultReplicationMethod = this.defaultReplicationConfig();
1610
+ if (defaultReplicationMethod.isForced) {
1611
+ return {
1612
+ replicationMethod: defaultReplicationMethod.replicationMethod,
1613
+ replicationKey: defaultReplicationMethod.replicationKey
1614
+ };
1615
+ }
1616
+ return {
1617
+ replicationMethod: this.rootMetadataEntry?.["replication-method"] || defaultReplicationMethod.replicationMethod,
1618
+ replicationKey: this.rootMetadataEntry?.["replication-key"] || defaultReplicationMethod.replicationKey
1619
+ };
1620
+ };
1621
+ // Private sync methods:
1622
+ async _syncRecords(parent) {
1623
+ let rowsSent = 0;
1624
+ let recordSyncedMetrics = [];
1625
+ if (this.rowsSyncedMetricsConf.isEnabled() === true) {
1626
+ recordSyncedMetrics = getCounterMetrics(
1627
+ ROWS_SYNCED_METRIC_NAME,
1628
+ this.rowsSyncedMetricsConf.getStreamIds ? this.rowsSyncedMetricsConf.getStreamIds() : []
1629
+ );
1630
+ }
1631
+ let executionTimeMetrics = [];
1632
+ if (this.executionTimeMetricsConf.isEnabled() === true) {
1633
+ executionTimeMetrics = getCounterMetrics(
1634
+ EXECUTION_TIME_METRIC_NAME,
1635
+ this.executionTimeMetricsConf.getStreamIds ? this.executionTimeMetricsConf.getStreamIds() : []
1636
+ );
1637
+ }
1638
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
1639
+ for await (const row of this._getRecords(parent)) {
1640
+ if ((rowsSent - 1) % this.STATE_MSG_FREQUENCY == 0) {
1641
+ await this._writeStateMessage();
1642
+ }
1643
+ const recordMessage = new RecordMessage(
1644
+ { ...row },
1645
+ this.streamId
1646
+ );
1647
+ if (!this.isSilent) {
1648
+ await this._target.record(recordMessage);
1649
+ }
1650
+ await import_bluebird2.Promise.map(
1651
+ this.children,
1652
+ async (child) => {
1653
+ await child.asyncInit(row);
1654
+ return child._syncRecords(row);
1655
+ },
1656
+ {
1657
+ concurrency: this.childConcurrency
1658
+ }
1659
+ );
1660
+ this._incrementStreamState(row);
1661
+ recordSyncedMetrics.forEach((metric) => {
1662
+ metric.increment();
1663
+ });
1664
+ rowsSent += 1;
1665
+ }
1666
+ const stateServiceInst = StateService.getInstance();
1667
+ const state = stateServiceInst.getBookmarkV2(this.streamId);
1668
+ const newState = finalizeStateProgressMarkers(state);
1669
+ stateServiceInst.setBookmarkV2(this.streamId, newState);
1670
+ if (!parent) {
1671
+ logger.info(`\u2705 Completed sync for stream: ${this.streamId} (${rowsSent} records)`);
1672
+ } else {
1673
+ logger.debug(`\u2705 Completed sync for sub-stream: ${this.streamId} (${rowsSent} records)`);
1674
+ }
1675
+ await this._writeStateMessage();
1676
+ const endTime = (/* @__PURE__ */ new Date()).getTime();
1677
+ const executionTimeMs = endTime - startTime;
1678
+ executionTimeMetrics.forEach((metric) => {
1679
+ metric.increment(executionTimeMs);
1680
+ });
1681
+ }
1682
+ // Overridable Methods
1683
+ asyncInit(parent) {
1684
+ return Promise.resolve();
1685
+ }
1686
+ /**
1687
+ * Return the stream schema.
1688
+ *
1689
+ * Can be overriden to return a dynamic schema
1690
+ */
1691
+ getSchema() {
1692
+ logger.debug(`stream: ${this.streamId} - getSchema() is called`);
1693
+ if (this.schemaPath) {
1694
+ const schema = loadJson(`schemas/${this._tapName}/${this.schemaPath}`);
1695
+ return Promise.resolve({ jsonSchema: schema });
1696
+ } else {
1697
+ if (this.isSilent) {
1698
+ return Promise.resolve(void 0);
1699
+ }
1700
+ throw new Error(`Stream=${this.streamId} - No schema path was passed.
1701
+ Either you forgot to fill the the \`schemaPath\` property,
1702
+ or to override the getSchema() method.`);
1703
+ }
1704
+ }
1705
+ getRowCount() {
1706
+ return Promise.resolve(void 0);
1707
+ }
1708
+ /**
1709
+ * Abstract row generator function. Must be overridden by the child class.
1710
+ */
1711
+ _getRecords(parent) {
1712
+ throw new Error(`Stream._getRecords() - Should be implemented by child class`);
1713
+ }
1714
+ /**
1715
+ * Return the list of keys that can be configured in the UI by the end user as the replication key
1716
+ * @returns
1717
+ */
1718
+ getValidReplicationKeys() {
1719
+ return Promise.resolve([]);
1720
+ }
1721
+ /**
1722
+ * Called at the end of the sync. Use to flush all records before closing the Stream.
1723
+ * @returns
1724
+ */
1725
+ async _flush() {
1726
+ await Promise.resolve();
1727
+ }
1728
+ };
1729
+
1730
+ // src/sdk/helpers/qs-logger.ts
1731
+ var _3 = __toESM(require("lodash"));
1732
+ var MAX_QS_KEYS = 10;
1733
+ var MAX_QS_STRING_LENGTH = 200;
1734
+ var MAX_QS_ARRAY_ITEMS = 5;
1735
+ var MAX_QS_DEPTH = 2;
1736
+ function truncateQueryParams(value, depth = 0) {
1737
+ if (value == null) return value;
1738
+ if (depth >= MAX_QS_DEPTH) return "[\u2026]";
1739
+ if (typeof value === "string") {
1740
+ if (value.length <= MAX_QS_STRING_LENGTH) return value;
1741
+ const truncated = value.slice(0, MAX_QS_STRING_LENGTH);
1742
+ return `${truncated}\u2026(+${value.length - MAX_QS_STRING_LENGTH} chars)`;
1743
+ }
1744
+ if (Array.isArray(value)) {
1745
+ const items = value.slice(0, MAX_QS_ARRAY_ITEMS).map((v) => truncateQueryParams(v, depth + 1));
1746
+ const omittedCount = Math.max(0, value.length - MAX_QS_ARRAY_ITEMS);
1747
+ if (omittedCount > 0) {
1748
+ items.push(`[\u2026 ${omittedCount} more items]`);
1749
+ }
1750
+ return items;
1751
+ }
1752
+ if (_3.isPlainObject(value)) {
1753
+ const keys = Object.keys(value);
1754
+ const result = {};
1755
+ for (const key of keys.slice(0, MAX_QS_KEYS)) {
1756
+ result[key] = truncateQueryParams(value[key], depth + 1);
1757
+ }
1758
+ const omittedCount = Math.max(0, keys.length - MAX_QS_KEYS);
1759
+ if (omittedCount > 0) {
1760
+ result["__omittedKeys"] = omittedCount;
1761
+ }
1762
+ return result;
1763
+ }
1764
+ return value;
1765
+ }
1766
+ function getTruncatedParamsForLog(params) {
1767
+ try {
1768
+ return truncateQueryParams(params);
1769
+ } catch {
1770
+ return "[unserializable params]";
1771
+ }
1772
+ }
1773
+
1774
+ // src/sdk/models/tap/restStream.ts
1775
+ var _4 = __toESM(require("lodash"));
1776
+ var RESTStreamV2 = class extends StreamV2 {
1777
+ axiosInstance;
1778
+ constructor(tapName, config, state, target, childConcurrency = DEFAULT_MAX_CONCURRENT_STREAMS) {
1779
+ super(tapName, config, state, target, childConcurrency);
1780
+ this.apiCallsMetricsConf = {
1781
+ isEnabled: () => !this.isSilent,
1782
+ getStreamIds: () => [this.streamId]
1783
+ };
1784
+ }
1785
+ // Internal
1786
+ // Those functions shouldn't be overriden (or it means that we fuck'ed up the SDK during the design phase)
1787
+ /**
1788
+ * Prepare an Axios request object.
1789
+ * Pagination information can be parsed from `nextPageToken` if `nextPageToken` is not undefined.
1790
+ * @param nextPageToken
1791
+ * @returns
1792
+ */
1793
+ _prepareRequest = async (nextPageToken, parent) => {
1794
+ const method = this.httpMethod;
1795
+ const url = this.getUrl(parent);
1796
+ if (!url) {
1797
+ throw new Error(`StreamId: ${this.streamId} - URL is undefined. Did your properly define the URL for this stream?`);
1798
+ }
1799
+ const authenticator = this.authenticator;
1800
+ const params = {
1801
+ ...this.getNextUrlParams(nextPageToken, parent),
1802
+ ...await authenticator?.getAuthQS(this._config)
1803
+ };
1804
+ const requestBody = this.getRequestBodyForNextCall(nextPageToken, parent);
1805
+ let headers = this.getCustomHTTPHeaders(parent);
1806
+ if (!headers) {
1807
+ headers = {};
1808
+ }
1809
+ if (authenticator) {
1810
+ const authHeaders = await authenticator.getAuthHeaders(this._config);
1811
+ headers = {
1812
+ ...headers,
1813
+ ...authHeaders
1814
+ };
1815
+ }
1816
+ const paramsSerializer = (params2) => {
1817
+ return qs2.stringify(params2, { arrayFormat: "repeat" });
1818
+ };
1819
+ const request = {
1820
+ url,
1821
+ method,
1822
+ data: requestBody,
1823
+ params,
1824
+ paramsSerializer,
1825
+ headers
1826
+ };
1827
+ return request;
1828
+ };
1829
+ async _requestWithRetry(request, privateLogging) {
1830
+ try {
1831
+ logger.debug(
1832
+ `\u{1F916} ${request.method} ${request.url} with params: %j | body: %j`,
1833
+ request.params,
1834
+ request.data,
1835
+ { private: privateLogging }
1836
+ );
1837
+ logger.info(
1838
+ `\u{1F916} ${request.method} ${request.url} qs: %j`,
1839
+ getTruncatedParamsForLog(request.params),
1840
+ { private: privateLogging }
1841
+ );
1842
+ const axiosInstance = this.axiosInstance;
1843
+ if (!axiosInstance) {
1844
+ throw new Error(`Axios Instance is not initialized!`);
1845
+ }
1846
+ const response = await axiosInstance.request(request);
1847
+ return response.data;
1848
+ } catch (err) {
1849
+ if (import_axios2.default.isAxiosError(err)) {
1850
+ logger.error(
1851
+ `\u{1F916} Error on ${request.method} ${request.url} with params %j | body: %j | headers: %j - getting error %s - response body: %j`,
1852
+ request.params,
1853
+ request.data,
1854
+ request.headers,
1855
+ err.message,
1856
+ err.response?.data
1857
+ );
1858
+ const recoveredValue = await this.httpErrorHandler(request.url, err);
1859
+ if (recoveredValue === void 0) {
1860
+ return "STOP_SYNC";
1861
+ } else {
1862
+ return recoveredValue;
1863
+ }
1864
+ } else {
1865
+ throw err;
1866
+ }
1867
+ }
1868
+ }
1869
+ async *_requestRecords(parent) {
1870
+ let nextPageToken = void 0;
1871
+ let finished = false;
1872
+ let apiCallsMetrics = [];
1873
+ if (this.apiCallsMetricsConf.isEnabled() === true) {
1874
+ apiCallsMetrics = getCounterMetrics(
1875
+ API_CALLS_METRIC_NAME,
1876
+ this.apiCallsMetricsConf.getStreamIds ? this.apiCallsMetricsConf.getStreamIds() : []
1877
+ );
1878
+ }
1879
+ this.axiosInstance = getAxiosInstance(this.httpRetryCount);
1880
+ while (!finished) {
1881
+ const preparedRequest = await this._prepareRequest(nextPageToken, parent);
1882
+ const resp = await this._requestWithRetry(preparedRequest);
1883
+ apiCallsMetrics.forEach((metric) => {
1884
+ metric.increment();
1885
+ });
1886
+ if (resp === "STOP_SYNC") {
1887
+ return;
1888
+ }
1889
+ const rows = this.parseResponse(resp, parent, nextPageToken);
1890
+ for await (const row of rows) {
1891
+ yield row;
1892
+ }
1893
+ const previousToken = _4.cloneDeep(nextPageToken);
1894
+ nextPageToken = this.getNextPageToken(resp, previousToken);
1895
+ if (nextPageToken && _4.isEqual(nextPageToken, previousToken)) {
1896
+ throw new Error(
1897
+ `${this.streamId} - Loop detected in pagination. "
1898
+ Pagination token: \`${JSON.stringify(nextPageToken)}\` is identical to prior token \`${JSON.stringify(previousToken)}\`.`
1899
+ );
1900
+ }
1901
+ finished = !nextPageToken;
1902
+ }
1903
+ }
1904
+ async *_getRecords(parent) {
1905
+ for await (const row of this._requestRecords(parent)) {
1906
+ yield row;
1907
+ }
1908
+ }
1909
+ // Overridable
1910
+ // Even if the default impl. of some function can be used as-is, those functions
1911
+ // are meant to be overriden when necessary to match the API needs.
1912
+ httpMethod = "GET";
1913
+ baseUrl = "";
1914
+ path = "";
1915
+ httpRetryCount = 24;
1916
+ authenticator;
1917
+ apiCallsMetricsConf;
1918
+ // When undefined is returned, we should stop the sync without any error
1919
+ async httpErrorHandler(url, err) {
1920
+ if ((err.response?.status || 0) > 400) {
1921
+ throw new Error(`We got a \`${err.response?.status}\` status code from endpoint: \`${url}\`.
1922
+
1923
+ Response body from the server: ${JSON.stringify(err.response?.data)}`);
1924
+ }
1925
+ throw err;
1926
+ }
1927
+ /**
1928
+ * Return a dictionary of values to be used in URL parameterization.
1929
+ * If paging is supported, developers may override with specific paging logic.
1930
+ *
1931
+ * By default, no params are passed.
1932
+ */
1933
+ getNextUrlParams(nextPageToken, parent) {
1934
+ return {};
1935
+ }
1936
+ /**
1937
+ * Prepare the data payload for the REST API request.
1938
+ * Useful when working with POST APIs.
1939
+ *
1940
+ * By default, no payload will be sent (return undefined).
1941
+ */
1942
+ getRequestBodyForNextCall(nextPageToken, parent) {
1943
+ return void 0;
1944
+ }
1945
+ /**
1946
+ * Return token identifying next page or undefined if all records have been read.
1947
+ *
1948
+ * By default, no token will be returned and a single API call will be made.
1949
+ *
1950
+ * @param response
1951
+ * @param previousToken
1952
+ * @returns
1953
+ */
1954
+ getNextPageToken(response, previousToken) {
1955
+ return void 0;
1956
+ }
1957
+ /**
1958
+ * Return custom headers to be used for HTTP requests.
1959
+ *
1960
+ * By default, no additional header will be added
1961
+ */
1962
+ getCustomHTTPHeaders(parent) {
1963
+ return void 0;
1964
+ }
1965
+ /**
1966
+ * Return a URL, optionally targeted to a specific partition.
1967
+ * This can be overriden to perform dynamic URL generation.
1968
+ *
1969
+ * TODO: Make URL + Path templatisable and replace those with Extractor (or Tap) config
1970
+ * @returns
1971
+ */
1972
+ getUrl(parent) {
1973
+ const urlPattern = [this.baseUrl, this.path].join("");
1974
+ return urlPattern;
1975
+ }
1976
+ /**
1977
+ * Parse the response and return an iterator of result rows.
1978
+ *
1979
+ * Default impl assume that R = O or R = O[]
1980
+ * @param response
1981
+ */
1982
+ async *parseResponse(response, parent, nextPageToken) {
1983
+ if (Array.isArray(response)) {
1984
+ for (const row of response) {
1985
+ yield row;
1986
+ }
1987
+ } else {
1988
+ yield response;
1989
+ }
1990
+ }
1991
+ };
1992
+
1993
+ // src/sdk/models/target/dbSync.ts
1994
+ var import_async_retry = __toESM(require("async-retry"));
1995
+ var StreamWarehouseSyncService = class {
1996
+ streamId;
1997
+ database;
1998
+ schema;
1999
+ table;
2000
+ flattenedSchema;
2001
+ primaryKeys;
2002
+ maxRetryCount = 5;
2003
+ renamedColumnStore;
2004
+ constructor(streamId, database, schema, table, renameColumnStore) {
2005
+ this.streamId = streamId;
2006
+ this.database = database;
2007
+ this.schema = schema;
2008
+ this.table = table;
2009
+ this.primaryKeys = [];
2010
+ this.flattenedSchema = {};
2011
+ this.renamedColumnStore = renameColumnStore;
2012
+ }
2013
+ getcolumnNameWithTypeSuffix(colName, warehouseType) {
2014
+ return `${colName}__${warehouseType}`;
2015
+ }
2016
+ // Orchestration functions
2017
+ updateSchemaInWarehouse = async (flattenedSchema, primaryKeys) => {
2018
+ await this.createDatabaseAndSchemaIfNotExists(0);
2019
+ this.flattenedSchema = flattenedSchema;
2020
+ this.renamedColumnStore.computeColumnNameForStream(
2021
+ this.streamId,
2022
+ flattenedSchema
2023
+ );
2024
+ this.primaryKeys = primaryKeys.map((pk) => {
2025
+ return this.renamedColumnStore.getColumnTranslation(this.streamId, pk);
2026
+ });
2027
+ await this.syncTableSchemaWithWarehouse();
2028
+ };
2029
+ renameColumns(record) {
2030
+ const renamedColsRecord = {};
2031
+ Object.keys(record).forEach((originalColName) => {
2032
+ renamedColsRecord[this.renamedColumnStore.getColumnTranslation(this.streamId, originalColName)] = record[originalColName];
2033
+ });
2034
+ return renamedColsRecord;
2035
+ }
2036
+ // Function to execute queries on the warehouse
2037
+ runQueriesWithRetry = async (queries) => {
2038
+ await (0, import_async_retry.default)(
2039
+ async () => {
2040
+ await this.runQueries(queries);
2041
+ },
2042
+ {
2043
+ retries: 5
2044
+ }
2045
+ );
2046
+ };
2047
+ syncTableSchemaWithWarehouse = async () => {
2048
+ const tables = await this.getTablesInSchema();
2049
+ const foundTables = tables.filter((table) => this.table === table.tableName);
2050
+ if (foundTables.length === 0) {
2051
+ logger.info(`\u{1F423} Database: \`${this.database}\`, Schema: \`${this.schema}\`, Table: \`${this.table}\` does not exist in Warehouse. Creating it...`);
2052
+ await this.createTable();
2053
+ } else if (foundTables.length === 1) {
2054
+ logger.info(`\u{1F44B} Database: \`${this.database}\`, Schema: \`${this.schema}\`, Table: \`${this.table}\` already exists. Updating the columns if needed.`);
2055
+ await this.updateTableColumns();
2056
+ }
2057
+ };
2058
+ /**
2059
+ * When there is a conflict between the synced data type and the data type of an
2060
+ * already existing column in the Warehouse, this create a new column in Warehouse with the new
2061
+ * datatype as suffix.
2062
+ *
2063
+ * This refer the fact that data should now be synced with the new column.
2064
+ *
2065
+ * @param tableFields
2066
+ */
2067
+ addVersionedColumns = async (tableFields) => {
2068
+ if (tableFields.length > 0) {
2069
+ logger.info(`\u{1F6C2} Stream: ${this.streamId} - Versionning following columns in Warehouse schema: ${JSON.stringify(tableFields)}`);
2070
+ const columnsFromWarehouse = await this.getTableColumnsFromWarehouse();
2071
+ const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
2072
+ let columnsToBeAddedInWarehouse = [];
2073
+ tableFields.forEach((tblField) => {
2074
+ const safeName = tblField.name;
2075
+ const warehouseTypeToBeUsed = this.getWarehouseTypeFromJSONSchema(tblField.definition);
2076
+ const columnNameWithTypeSuffix = this.getcolumnNameWithTypeSuffix(safeName, warehouseTypeToBeUsed);
2077
+ this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId)[tblField.unsafeName] = columnNameWithTypeSuffix;
2078
+ if (!columnNamesFromWarehouse.includes(columnNameWithTypeSuffix)) {
2079
+ columnsToBeAddedInWarehouse.push({
2080
+ unsafeName: columnNameWithTypeSuffix,
2081
+ name: columnNameWithTypeSuffix,
2082
+ definition: tblField.definition
2083
+ });
2084
+ }
2085
+ });
2086
+ if (columnsToBeAddedInWarehouse.length > 0) {
2087
+ await this.addColumns(columnsToBeAddedInWarehouse);
2088
+ }
2089
+ }
2090
+ };
2091
+ updateTableColumns = async () => {
2092
+ const columnsFromWarehouse = await this.getTableColumnsFromWarehouse();
2093
+ const columnNamesFromWarehouse = columnsFromWarehouse.map((col) => col.name);
2094
+ const columnsMappingStore = this.renamedColumnStore.getUnsafeToSafeColumnMapping(this.streamId);
2095
+ const columnsToAdd = Object.keys(columnsMappingStore).filter((unsafeColumnKey) => {
2096
+ const safeColumnName = columnsMappingStore[unsafeColumnKey];
2097
+ if (!safeColumnName) {
2098
+ return false;
2099
+ }
2100
+ return !columnNamesFromWarehouse.includes(safeColumnName);
2101
+ }).map((unsafeColumnKey) => {
2102
+ const safeColumnName = columnsMappingStore[unsafeColumnKey];
2103
+ const definition = this.flattenedSchema[unsafeColumnKey];
2104
+ if (!safeColumnName || !definition) {
2105
+ return void 0;
2106
+ }
2107
+ return {
2108
+ unsafeName: unsafeColumnKey,
2109
+ name: safeColumnName,
2110
+ definition
2111
+ };
2112
+ }).filter((item) => !!item);
2113
+ await this.addColumns(columnsToAdd);
2114
+ const columnsToVersion = Object.keys(this.flattenedSchema).filter((unsafeColumnKey) => {
2115
+ const definition = this.flattenedSchema[unsafeColumnKey];
2116
+ if (!definition) {
2117
+ return false;
2118
+ }
2119
+ const warehouseTypeToBeUsed = this.getWarehouseTypeFromJSONSchema(definition);
2120
+ if (warehouseTypeToBeUsed) {
2121
+ const safeName = this.safeColumnName(unsafeColumnKey);
2122
+ if (columnNamesFromWarehouse.includes(safeName)) {
2123
+ const columnFromWarehouse = columnsFromWarehouse.find((col) => col.name === safeName);
2124
+ if (!columnFromWarehouse) {
2125
+ throw new Error(`We can't find in Warehouse schema any column definition for colName: ${safeName}`);
2126
+ }
2127
+ const { type } = columnFromWarehouse;
2128
+ if (warehouseTypeToBeUsed !== type) {
2129
+ return true;
2130
+ }
2131
+ }
2132
+ }
2133
+ return false;
2134
+ }).map((unsafeColumnKey) => {
2135
+ const definition = this.flattenedSchema[unsafeColumnKey];
2136
+ if (!definition) {
2137
+ return void 0;
2138
+ }
2139
+ return {
2140
+ unsafeName: unsafeColumnKey,
2141
+ name: this.safeColumnName(unsafeColumnKey),
2142
+ definition
2143
+ };
2144
+ }).filter((tableField) => !!tableField);
2145
+ await this.addVersionedColumns(columnsToVersion);
2146
+ };
2147
+ mergeTempTableWithDestination = async (replicationMethod) => {
2148
+ let mergeQueries = [];
2149
+ if (replicationMethod === "FULL_TABLE") {
2150
+ mergeQueries = this.getReplaceQueries();
2151
+ } else if (replicationMethod === "INCREMENTAL") {
2152
+ mergeQueries = this.getMergeQueries();
2153
+ } else if (replicationMethod === "LOG_BASED") {
2154
+ mergeQueries = this.getMergeQueries();
2155
+ }
2156
+ logger.info(`\u{1F346} Table=${this.table} - Merging staging data with method ${replicationMethod}`);
2157
+ logger.debug(`Table=${this.table} - Merge queries: %j`, mergeQueries);
2158
+ await this.runQueriesWithRetry(mergeQueries);
2159
+ };
2160
+ };
2161
+
2162
+ // src/sdk/models/target/error.ts
2163
+ var SchemaValidationError = class extends Error {
2164
+ streamId;
2165
+ validationErrors;
2166
+ constructor(message, streamId, errors2) {
2167
+ super(message);
2168
+ this.name = "SchemaValidationError";
2169
+ this.streamId = streamId;
2170
+ this.validationErrors = errors2;
2171
+ }
2172
+ };
2173
+ var MissingFieldInSchemaError = class extends Error {
2174
+ streamId;
2175
+ constructor(message, streamId) {
2176
+ super(message);
2177
+ this.name = "MissingFieldInSchemaError";
2178
+ this.streamId = streamId;
2179
+ }
2180
+ };
2181
+ var MissingSchemaError = class extends Error {
2182
+ streamId;
2183
+ constructor(message, streamId) {
2184
+ super(message);
2185
+ this.name = "MissingSchemaError";
2186
+ this.streamId = streamId;
2187
+ }
2188
+ };
2189
+
2190
+ // src/sdk/models/target/record.ts
2191
+ var removeParasiteProperties = (record, schema) => {
2192
+ const schemaKeys = Object.keys(schema);
2193
+ Object.keys(record).forEach((key) => {
2194
+ if (!schemaKeys.includes(key)) {
2195
+ delete record[key];
2196
+ }
2197
+ });
2198
+ return record;
2199
+ };
2200
+ var addWhalyFields = (record, batchDate) => {
2201
+ return {
2202
+ ...record,
2203
+ _whaly_synced: batchDate
2204
+ };
2205
+ };
2206
+
2207
+ // src/sdk/models/target/renameColumnStore.ts
2208
+ var RenameColumnStore = class {
2209
+ // Store all the mappings store for all streamId
2210
+ renamedColumns;
2211
+ safeColumnNameConverter;
2212
+ constructor() {
2213
+ this.renamedColumns = {};
2214
+ }
2215
+ setSafeColumnNameConverter = (fn) => {
2216
+ this.safeColumnNameConverter = fn;
2217
+ };
2218
+ // This function has a side effect on:
2219
+ // - `this.renamedColumns`
2220
+ // This isn't very clean and should be updated to write a pure version of this function
2221
+ computeColumnNameForStream = (streamId, flattenedSchema) => {
2222
+ const safeColNameConv = this.safeColumnNameConverter;
2223
+ if (!safeColNameConv) {
2224
+ throw new Error(`safeColumnNameConverter is not defined!`);
2225
+ }
2226
+ const colSafeToUnsafeMapping = Object.keys(flattenedSchema).sort().reduce((acc, unsafeColName) => {
2227
+ const safeColName = safeColNameConv(unsafeColName);
2228
+ if (!acc[safeColName]) {
2229
+ acc[safeColName] = unsafeColName;
2230
+ } else {
2231
+ logger.info(`\u{1F6A8} Stream: ${streamId} - We have a column name conflict after sanitization for column unsafe=${unsafeColName}. We'll rename the sanitized name to a non taken name.
2232
+ Details: unsafeColName=${unsafeColName} is colliding with: ${acc[safeColName]}`);
2233
+ for (let i = 1; ; i++) {
2234
+ if (!acc[`${safeColName}_${i}`]) {
2235
+ acc[`${safeColName}_${i}`] = unsafeColName;
2236
+ break;
2237
+ }
2238
+ }
2239
+ }
2240
+ return acc;
2241
+ }, {});
2242
+ const colUnsafeToSafeMapping = Object.keys(colSafeToUnsafeMapping).reduce((acc, safeColName) => {
2243
+ const unsafeColname = colSafeToUnsafeMapping[safeColName];
2244
+ if (unsafeColname) {
2245
+ acc[unsafeColname] = safeColName;
2246
+ }
2247
+ return acc;
2248
+ }, {});
2249
+ const mapping = this.renamedColumns[streamId] || {};
2250
+ this.renamedColumns[streamId] = mapping;
2251
+ Object.keys(colUnsafeToSafeMapping).forEach((unsafeColName) => {
2252
+ const safeName = colUnsafeToSafeMapping[unsafeColName];
2253
+ if (safeName) {
2254
+ mapping[unsafeColName] = safeName;
2255
+ }
2256
+ });
2257
+ };
2258
+ getColumnTranslation = (streamId, originalColumnName) => {
2259
+ if (!this.renamedColumns[streamId]) {
2260
+ throw new Error(
2261
+ `StreamId=${streamId} - Columns Mapping Store is not initialized!
2262
+ Trying to get renamed value for column '${originalColumnName}'`
2263
+ );
2264
+ }
2265
+ const value = this.renamedColumns[streamId][originalColumnName];
2266
+ if (!value) {
2267
+ throw new Error(`StreamId=${streamId} - No renamed value found for column '${originalColumnName}'`);
2268
+ }
2269
+ return value;
2270
+ };
2271
+ getUnsafeToSafeColumnMapping = (streamId) => {
2272
+ const mapping = this.renamedColumns[streamId];
2273
+ if (!mapping) {
2274
+ throw new Error(`StreamId=${streamId} - Columns Mapping Store is not initialized!`);
2275
+ }
2276
+ return mapping;
2277
+ };
2278
+ isReady = (streamId) => {
2279
+ if (!this.renamedColumns[streamId]) {
2280
+ return false;
2281
+ } else {
2282
+ return true;
2283
+ }
2284
+ };
2285
+ };
2286
+
2287
+ // src/sdk/models/target/schema.ts
2288
+ var flattenSchema = (streamId, schema) => {
2289
+ logger.debug(`Flattening schema for stream: ${streamId}`);
2290
+ try {
2291
+ const mSchema = schema;
2292
+ if (!mSchema["properties"]) {
2293
+ return {};
2294
+ }
2295
+ const properties = mSchema["properties"];
2296
+ const flattenedSchema = Object.keys(properties).reduce((acc, propertyName) => {
2297
+ const propertySchema = properties[propertyName];
2298
+ const columnName = propertyName;
2299
+ if (!propertySchema) {
2300
+ return acc;
2301
+ }
2302
+ if (!propertySchema["type"]) {
2303
+ throw new Error(`We don't have any \`type\` for the property: \`${propertyName}\`.
2304
+ Schema of the property is: ${JSON.stringify(propertySchema)}`);
2305
+ }
2306
+ acc[columnName] = {
2307
+ type: propertySchema["type"],
2308
+ format: propertySchema["format"],
2309
+ items: propertySchema["items"]
2310
+ };
2311
+ return acc;
2312
+ }, {});
2313
+ return flattenedSchema;
2314
+ } catch (err) {
2315
+ logger.error(`There was an error when processing the schema of stream:\`${streamId}\`: err: ${err.message} - ${err.stack}.
2316
+ Processed schema: ${JSON.stringify(schema)}`);
2317
+ gracefulExit(1);
2318
+ return {};
2319
+ }
2320
+ };
2321
+
2322
+ // src/sdk/models/target/target.ts
2323
+ var import_jsonschema = require("jsonschema");
2324
+ var import_moment4 = __toESM(require("moment"));
2325
+
2326
+ // src/sdk/models/target/streamDbState.ts
2327
+ var import_moment3 = __toESM(require("moment"));
2328
+
2329
+ // src/sdk/models/target/temporaryFile.ts
2330
+ var import_fs_extra2 = __toESM(require("fs-extra"));
2331
+ var import_lodash = require("lodash");
2332
+ var createTemporaryFileStream = (streamId) => {
2333
+ import_fs_extra2.default.ensureDirSync("./tmp");
2334
+ const path = `./tmp/${safePath(streamId)}-${(0, import_lodash.uniqueId)()}.tmp`;
2335
+ const writeStream = import_fs_extra2.default.createWriteStream(path, { flags: "w" });
2336
+ return {
2337
+ stream: writeStream,
2338
+ path
2339
+ };
2340
+ };
2341
+ function safePath(path) {
2342
+ let formattedPath = path;
2343
+ formattedPath = formattedPath.replace(/^The\s+/gi, "");
2344
+ formattedPath = formattedPath.replace(/\s+(?:\(|\[)?Dis(?:c|k) (?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d)+(?:\)|\])?/gi, "");
2345
+ formattedPath = formattedPath.replace(/\s+/gi, "_");
2346
+ formattedPath = formattedPath.replace(/(?:\'|`)/gi, "");
2347
+ formattedPath = replaceAccented(formattedPath);
2348
+ formattedPath = replaceSymbols(formattedPath);
2349
+ formattedPath = formattedPath.replace(/(?:[^a-z0-9])+/gi, "_");
2350
+ return formattedPath;
2351
+ }
2352
+ function replaceAccented(str) {
2353
+ str = str.replace(/(?:\xC0|\xC1|\xC2|\xC3|\xC4|\xC5)/g, "A");
2354
+ str = str.replace(/(?:\xC7)/g, "C");
2355
+ str = str.replace(/(?:\xC8|\xC9|\xCA|\xCB)/g, "E");
2356
+ str = str.replace(/(?:\xCC|\xCD|\xCE|\xCF)/g, "I");
2357
+ str = str.replace(/(?:\xD2|\xD3|\xD4|\xD6|\xD5)/g, "O");
2358
+ str = str.replace(/(?:\xD9|\xDA|\xDB|\xDC)/g, "U");
2359
+ str = str.replace(/(?:\xDD|\x9F)/g, "Y");
2360
+ str = str.replace(/(?:\xD1)/g, "N");
2361
+ str = str.replace(/(?:\xC6)/g, "Ae");
2362
+ str = str.replace(/(?:\xDF)/g, "ss");
2363
+ str = str.replace(/(?:\xE0|\xE1|\xE2|\xE4|\xE5|\xE3)/g, "a");
2364
+ str = str.replace(/(?:\xE7)/g, "c");
2365
+ str = str.replace(/(?:\xE8|\xE9|\xEA|\xEB)/g, "e");
2366
+ str = str.replace(/(?:\xEC|\xED|\xEE|\xEF)/g, "i");
2367
+ str = str.replace(/(?:\xF2|\xF3|\xF4|\xF6|\xF5)/g, "o");
2368
+ str = str.replace(/(?:\xF9|\xFA|\xFB|\xFC)/g, "u");
2369
+ str = str.replace(/(?:\xFD|\xFF)/g, "y");
2370
+ str = str.replace(/(?:\xF1)/g, "n");
2371
+ str = str.replace(/(?:\xE6)/g, "ae");
2372
+ return str;
2373
+ }
2374
+ function replaceSymbols(str) {
2375
+ str = str.replace(/(?:\(|\))/g, "");
2376
+ str = str.replace(/(?:\.|\?)/g, "");
2377
+ str = str.replace(/(?:_+)/g, "_");
2378
+ str = str.replace(/(?:\$+)/g, "s");
2379
+ return str;
2380
+ }
2381
+
2382
+ // src/sdk/models/target/streamDbState.ts
2383
+ var fs4 = __toESM(require("fs"));
2384
+ var StreamState = class {
2385
+ streamId;
2386
+ schema;
2387
+ dbSync;
2388
+ batchDate;
2389
+ // Count rows batched in the local file, waiting for Warehouse upload
2390
+ batchedRowCount;
2391
+ // Local CSV files written to be sent to Warehouse
2392
+ fileToLoad;
2393
+ // Count rows sent to Warehouse
2394
+ syncedRowCount;
2395
+ keyProperties;
2396
+ // When a FULL_TABLE stream is loaded multiple times, the query used for the 2nd+ loads
2397
+ // needs to be different than the 1st merge query -> 1st load is dropping the existing table, 2nd+ loads are merging with the existing table (otherwise the previous load data are dropped)
2398
+ // So we need to keep track of whether we are at the first load or not
2399
+ _hasBeenLoadedYetDuringThisSync;
2400
+ replicationMethod;
2401
+ constructor(streamId, dbSync, replicationMethod) {
2402
+ this.streamId = streamId;
2403
+ this.dbSync = dbSync;
2404
+ this.batchedRowCount = 0;
2405
+ this.fileToLoad = createTemporaryFileStream(streamId);
2406
+ this.syncedRowCount = 0;
2407
+ this.keyProperties = [];
2408
+ this.batchDate = (0, import_moment3.default)();
2409
+ this.replicationMethod = replicationMethod;
2410
+ this._hasBeenLoadedYetDuringThisSync = false;
2411
+ }
2412
+ setSchema(schema) {
2413
+ this.schema = schema;
2414
+ }
2415
+ getSchema() {
2416
+ return this.schema;
2417
+ }
2418
+ getBatchDate() {
2419
+ return this.batchDate.format(defaultDateTimeFormat);
2420
+ }
2421
+ getBatchedRowCount() {
2422
+ return this.batchedRowCount;
2423
+ }
2424
+ incrementBatchedRowCount() {
2425
+ this.batchedRowCount += 1;
2426
+ }
2427
+ getFileToLoad() {
2428
+ return this.fileToLoad;
2429
+ }
2430
+ resetFileToLoad() {
2431
+ this.fileToLoad.stream.close();
2432
+ const oldPath = this.fileToLoad.path;
2433
+ fs4.unlinkSync(oldPath);
2434
+ this.fileToLoad = createTemporaryFileStream(this.streamId);
2435
+ this.batchedRowCount = 0;
2436
+ }
2437
+ getKeyProperties() {
2438
+ return this.keyProperties;
2439
+ }
2440
+ setKeyProperties(keyProperties) {
2441
+ this.keyProperties = keyProperties;
2442
+ }
2443
+ getDbSync() {
2444
+ return this.dbSync;
2445
+ }
2446
+ getReplicationMethod() {
2447
+ return this.replicationMethod;
2448
+ }
2449
+ setReplicationMethod(r) {
2450
+ this.replicationMethod = r;
2451
+ }
2452
+ setHasBeenLoadedYet() {
2453
+ this._hasBeenLoadedYetDuringThisSync = true;
2454
+ }
2455
+ };
2456
+
2457
+ // src/sdk/models/target/target.ts
2458
+ var import_async_mutex = require("async-mutex");
2459
+ var import_bluebird3 = require("bluebird");
2460
+ var semaphore = new import_async_mutex.Semaphore(1);
2461
+ var ITarget = class _ITarget {
2462
+ config;
2463
+ schemaHooks;
2464
+ resolver;
2465
+ syncTime;
2466
+ // Latest state message received from the Tap
2467
+ // Not yet flushed as we didn't upload the stream data since receiving it
2468
+ batchedState;
2469
+ // Latest "flushed" state message
2470
+ // A state message is flushed when we do a stream upload.
2471
+ flushedState;
2472
+ renameColumnStore;
2473
+ streams;
2474
+ // JSON Schema validator
2475
+ validator;
2476
+ constructor(config, resolver) {
2477
+ this.config = config;
2478
+ this.resolver = resolver;
2479
+ this.schemaHooks = resolver.getSchemaHooks();
2480
+ this.streams = {};
2481
+ this.batchedState = {};
2482
+ this.flushedState = {};
2483
+ this.validator = new import_jsonschema.Validator();
2484
+ this.syncTime = (0, import_moment4.default)();
2485
+ this.renameColumnStore = new RenameColumnStore();
2486
+ }
2487
+ ///////////////////////////////////////////
2488
+ //// To be overriden by child classes /////
2489
+ ///////////////////////////////////////////
2490
+ static requiredConfigKeys = [];
2491
+ ////////////////////////////////////////////
2492
+ /////////// Implementation /////////////////
2493
+ ////////////////////////////////////////////
2494
+ // Analyze schema changes to determine if they are breaking or non-breaking
2495
+ analyzeSchemaChanges = (previousSchema, newSchema, streamId) => {
2496
+ const changes = [];
2497
+ let hasBreakingChanges = false;
2498
+ const prevColumns = new Set(Object.keys(previousSchema).filter((col) => !col.startsWith("_whaly")));
2499
+ const newColumns = new Set(Object.keys(newSchema).filter((col) => !col.startsWith("_whaly")));
2500
+ for (const columnName of newColumns) {
2501
+ if (!prevColumns.has(columnName)) {
2502
+ changes.push(`Added column '${columnName}'`);
2503
+ }
2504
+ }
2505
+ for (const columnName of prevColumns) {
2506
+ if (!newColumns.has(columnName)) {
2507
+ changes.push(`Removed column '${columnName}'`);
2508
+ hasBreakingChanges = true;
2509
+ }
2510
+ }
2511
+ for (const columnName of prevColumns) {
2512
+ if (newColumns.has(columnName)) {
2513
+ const prevType = previousSchema[columnName];
2514
+ const newType = newSchema[columnName];
2515
+ if (JSON.stringify(prevType) !== JSON.stringify(newType)) {
2516
+ changes.push(`Changed type of column '${columnName}'`);
2517
+ hasBreakingChanges = true;
2518
+ }
2519
+ }
2520
+ }
2521
+ if (changes.length === 0) {
2522
+ changes.push("No schema changes detected");
2523
+ }
2524
+ logger.debug(`StreamId=${streamId} - Schema analysis: ${hasBreakingChanges ? "BREAKING" : "NON-BREAKING"} changes found: ${changes.join(", ")}`);
2525
+ return { hasBreakingChanges, changes };
2526
+ };
2527
+ // Serialize a RECORD message into a line that will be uploaded in the staging area
2528
+ getSerializedRecord = (streamId, record) => {
2529
+ const stream = this.streams[streamId];
2530
+ if (!stream) {
2531
+ throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
2532
+ }
2533
+ return stream.getDbSync().getSerializedRecord(record);
2534
+ };
2535
+ record = async (message) => {
2536
+ try {
2537
+ const streamId = message.stream;
2538
+ const stream = this.streams[streamId];
2539
+ if (!stream) {
2540
+ throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
2541
+ }
2542
+ await semaphore.runExclusive(async () => {
2543
+ const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
2544
+ if (shouldUploadIntermediateBatch) {
2545
+ await this.loadAllStreamsInWarehouse({ isFinalLoad: false });
2546
+ }
2547
+ });
2548
+ const record = message.record;
2549
+ const schema = stream.getSchema();
2550
+ if (!schema) {
2551
+ logger.warn(`Stream: ${streamId} - Received a RECORD message while schema is undefined. Dropping message.`);
2552
+ return;
2553
+ }
2554
+ const recordWithoutParasiteProperties = removeParasiteProperties(record, schema);
2555
+ const recordWithWhalyFields = addWhalyFields(recordWithoutParasiteProperties, stream.getBatchDate());
2556
+ const validationResult = this.validator.validate(recordWithWhalyFields, schema);
2557
+ if (!validationResult.valid) {
2558
+ throw new SchemaValidationError(`Stream: ${streamId} - Record is not valid according to schema.
2559
+ Validation errors: ${JSON.stringify(validationResult.errors)}
2560
+
2561
+ Record: ${JSON.stringify(recordWithoutParasiteProperties)}
2562
+ Schema: ${JSON.stringify(stream.getSchema())}`, streamId, validationResult.errors);
2563
+ }
2564
+ const recordWithValidDate = this.validateDateRange(recordWithWhalyFields, schema);
2565
+ const recordWithDecimal = this.convertNumberIntoDecimal(recordWithValidDate, schema);
2566
+ const dbSyncInstance = stream.getDbSync();
2567
+ const recordWithrenamedColumns = dbSyncInstance.renameColumns(recordWithDecimal);
2568
+ const serializedRecord = this.getSerializedRecord(streamId, recordWithrenamedColumns);
2569
+ return new Promise((resolve, reject) => {
2570
+ stream.getFileToLoad().stream.write(serializedRecord, (error) => {
2571
+ if (error) {
2572
+ reject(error);
2573
+ }
2574
+ stream.incrementBatchedRowCount();
2575
+ resolve();
2576
+ });
2577
+ });
2578
+ } catch (err) {
2579
+ logger.error(`Error when handling "record" message`);
2580
+ throw err;
2581
+ }
2582
+ };
2583
+ initStreamStateAndSyncService(streamId, replicationMethod) {
2584
+ const dbSyncInstance = this.newDBSyncInstance(
2585
+ streamId,
2586
+ this.config.database,
2587
+ this.config.schema,
2588
+ this.genTableName(streamId)
2589
+ );
2590
+ this.streams[streamId] = new StreamState(
2591
+ streamId,
2592
+ dbSyncInstance,
2593
+ replicationMethod || "FULL_TABLE"
2594
+ );
2595
+ }
2596
+ replicationMethod = (message) => {
2597
+ try {
2598
+ logger.info(`\u{1F448} Received replicationMethod message: %j`, message);
2599
+ const streamId = message.stream;
2600
+ if (!this.streams[streamId]) {
2601
+ this.initStreamStateAndSyncService(streamId, message.replication);
2602
+ } else {
2603
+ this.streams[streamId].setReplicationMethod(message.replication);
2604
+ }
2605
+ } catch (err) {
2606
+ logger.error(`Error when handling "replicationMethod" event`);
2607
+ throw err;
2608
+ }
2609
+ };
2610
+ schema = async (message) => {
2611
+ try {
2612
+ const streamId = message.stream;
2613
+ logger.info(`Stream: ${streamId} - Got a SCHEMA message from the tap`);
2614
+ logger.debug(`Stream: ${streamId} - Got a SCHEMA message from the tap: %j`, message);
2615
+ if (!this.streams[streamId]) {
2616
+ this.initStreamStateAndSyncService(streamId);
2617
+ } else {
2618
+ const prevStreamState = this.streams[streamId];
2619
+ if (prevStreamState.batchedRowCount > 0) {
2620
+ const prevSchema = prevStreamState.getSchema();
2621
+ if (prevSchema) {
2622
+ const newFlattenedSchema = flattenSchema(streamId, message.schema);
2623
+ const { hasBreakingChanges, changes } = this.analyzeSchemaChanges(prevSchema, newFlattenedSchema, streamId);
2624
+ if (hasBreakingChanges) {
2625
+ logger.info(`\u{1F525} StreamId=${streamId} - Detected BREAKING schema changes: ${changes.join(", ")}. Loading batched records before applying new schema.`);
2626
+ await _ITarget.uploadSingleStreamToWarehouse(prevStreamState);
2627
+ } else {
2628
+ logger.info(`\u2705 StreamId=${streamId} - Detected NON-BREAKING schema changes: ${changes.join(", ")}. Continuing without uploading batched records.`);
2629
+ }
2630
+ }
2631
+ }
2632
+ }
2633
+ const streamState = this.streams[streamId];
2634
+ if (!streamState) {
2635
+ throw new MissingSchemaError(`Stream: ${streamId} - Received SCHEMA message before stream state was initialized.`, streamId);
2636
+ }
2637
+ const schema = flattenSchema(streamId, message.schema);
2638
+ schema["_whaly_synced"] = {
2639
+ format: "date-time",
2640
+ type: ["null", "string"]
2641
+ };
2642
+ const dbSync = streamState.getDbSync();
2643
+ Object.keys(schema).forEach((fieldUnsafeKey) => {
2644
+ const fieldDef = schema[fieldUnsafeKey];
2645
+ const targetWarehouseType = fieldDef ? dbSync.getWarehouseTypeFromJSONSchema(fieldDef) : void 0;
2646
+ if (!targetWarehouseType) {
2647
+ delete schema[fieldUnsafeKey];
2648
+ }
2649
+ });
2650
+ streamState.setSchema(schema);
2651
+ if (message.keyProperties.length === 0) {
2652
+ throw new Error(`StreamId: ${message.stream} - \`key_properties\` field is required and can't be empty in SCHEMA message.
2653
+ Received SCHEMA message: ${JSON.stringify(message)}`);
2654
+ }
2655
+ streamState.setKeyProperties(message.keyProperties);
2656
+ await dbSync.updateSchemaInWarehouse(schema, message.keyProperties);
2657
+ const {
2658
+ database: databaseName,
2659
+ schema: schemaName
2660
+ } = this.config;
2661
+ const tableName = this.genTableName(streamId);
2662
+ const formattedRelationshipMessage = {
2663
+ ...message,
2664
+ keyProperties: message.keyProperties.map((k) => {
2665
+ const translation = streamState.getDbSync().renamedColumnStore.getColumnTranslation(message.stream, k);
2666
+ if (translation) {
2667
+ return translation;
2668
+ }
2669
+ 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
+ }
2703
+ };
2704
+ await import_bluebird3.Promise.map(this.schemaHooks, async (hook) => {
2705
+ const input = {
2706
+ databaseName,
2707
+ schemaName,
2708
+ tableName,
2709
+ message: formattedRelationshipMessage
2710
+ };
2711
+ await hook.writeSchema(input);
2712
+ }, { concurrency: 3 });
2713
+ return Promise.resolve();
2714
+ } catch (err) {
2715
+ logger.error(`Error when handling schema event.`, err);
2716
+ throw err;
2717
+ }
2718
+ };
2719
+ state = (message) => {
2720
+ try {
2721
+ this.batchedState = message.value;
2722
+ if (!this.flushedState) {
2723
+ this.flushedState = Object.assign({}, this.state);
2724
+ }
2725
+ return Promise.resolve();
2726
+ } catch (err) {
2727
+ logger.error(`Error while handling state event.`);
2728
+ throw err;
2729
+ }
2730
+ };
2731
+ complete = async () => {
2732
+ try {
2733
+ logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
2734
+ await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
2735
+ const configResolver = loadResolver();
2736
+ await configResolver.markSyncComplete();
2737
+ await configResolver.flushMetrics();
2738
+ return Promise.resolve();
2739
+ } catch (err) {
2740
+ logger.error(`Error while handling complete event.`);
2741
+ throw err;
2742
+ }
2743
+ };
2744
+ shouldUploadIntermediateBatch = (streamId) => {
2745
+ const stream = this.streams[streamId];
2746
+ if (!stream) {
2747
+ return false;
2748
+ }
2749
+ const batchedRecords = stream.getBatchedRowCount();
2750
+ if (stream.getReplicationMethod() === "INCREMENTAL" && batchedRecords > 1e6) {
2751
+ logger.info(`\u{1F9CA} Stream=${streamId} has reached the maximum amount of batched records. We'll trigger a flush of ALL streams in the Warehouse.`);
2752
+ return true;
2753
+ } else {
2754
+ return false;
2755
+ }
2756
+ };
2757
+ loadAllStreamsInWarehouse = async (opts) => {
2758
+ logger.info(`\u{1F69B}\u{1F69B}\u{1F69B} Loading ALL Streams in Warehouse.`);
2759
+ const { isFinalLoad } = opts;
2760
+ const streamsWithRemainingRecords = Object.keys(this.streams).filter((streamId) => {
2761
+ const s = this.streams[streamId];
2762
+ if (!s) {
2763
+ return false;
2764
+ }
2765
+ if (!isFinalLoad && s.getReplicationMethod() === "FULL_TABLE") {
2766
+ return false;
2767
+ }
2768
+ return s.getBatchedRowCount() > 0;
2769
+ });
2770
+ if (streamsWithRemainingRecords.length > 0) {
2771
+ this.flushedState = await this.uploadStreamsToWarehouse(streamsWithRemainingRecords);
2772
+ }
2773
+ if (Object.keys(this.flushedState).length > 0) {
2774
+ await this.emitState(Object.assign({}, this.flushedState));
2775
+ } else {
2776
+ await this.emitState(Object.assign({}, this.state));
2777
+ }
2778
+ };
2779
+ emitState = (state) => {
2780
+ const configResolver = loadResolver();
2781
+ return writeStateFile(configResolver, JSON.stringify(state));
2782
+ };
2783
+ uploadStreamsToWarehouse = async (streamsToUpload) => {
2784
+ try {
2785
+ await import_bluebird3.Promise.map(streamsToUpload, async (streamId) => {
2786
+ logger.info(`\u{1F4E4} Upload stream: ${streamId} to Warehouse`);
2787
+ const streamState = this.streams[streamId];
2788
+ if (streamState) {
2789
+ await _ITarget.uploadSingleStreamToWarehouse(streamState);
2790
+ }
2791
+ }, { concurrency: 3 });
2792
+ return this.batchedState;
2793
+ } catch (err) {
2794
+ logger.error(`Error while uploading all streams + merging them.`);
2795
+ throw err;
2796
+ }
2797
+ };
2798
+ static uploadSingleStreamToWarehouse = async (streamState) => {
2799
+ const dbSync = streamState.getDbSync();
2800
+ try {
2801
+ const replicationMethod = streamState.getReplicationMethod();
2802
+ const fileToLoad = streamState.getFileToLoad();
2803
+ await dbSync.createStagingArea();
2804
+ await dbSync.loadStreamInStagingArea(fileToLoad.path);
2805
+ await dbSync.mergeTempTableWithDestination(replicationMethod);
2806
+ await dbSync.deleteStagingArea();
2807
+ streamState.setHasBeenLoadedYet();
2808
+ streamState.resetFileToLoad();
2809
+ } catch (err) {
2810
+ if (err instanceof Error) {
2811
+ logger.error(`StreamId=${dbSync.streamId} - Couldn't upload stream due to an error: %s`, err.message);
2812
+ throw err;
2813
+ } else {
2814
+ throw new Error("Unknown error");
2815
+ }
2816
+ }
2817
+ };
2818
+ };
2819
+ // Annotate the CommonJS export names for ESM import in node:
2820
+ 0 && (module.exports = {
2821
+ ALL_STREAM_ID_KEYWORD,
2822
+ API_CALLS_METRIC_NAME,
2823
+ Authenticator,
2824
+ BATCH_INTERVAL_MS,
2825
+ Catalog,
2826
+ CounterMetric,
2827
+ DEFAULT_MAX_CONCURRENT_STREAMS,
2828
+ EXECUTION_TIME_METRIC_NAME,
2829
+ ITarget,
2830
+ MissingFieldInSchemaError,
2831
+ MissingSchemaError,
2832
+ RESTStreamV2,
2833
+ ROWS_SYNCED_METRIC_NAME,
2834
+ RecordMessage,
2835
+ RenameColumnStore,
2836
+ ReplicationMethodMessage,
2837
+ Resolver,
2838
+ SchemaMessage,
2839
+ SchemaValidationError,
2840
+ StateMessage,
2841
+ StateService,
2842
+ StreamMetadata,
2843
+ StreamV2,
2844
+ StreamWarehouseSyncService,
2845
+ Tap,
2846
+ _greaterThan,
2847
+ addWhalyFields,
2848
+ checkRequiredConfigKeys,
2849
+ createTemporaryFileStream,
2850
+ extractStateForStream,
2851
+ finalizeStateProgressMarkers,
2852
+ findRelatedRelationships,
2853
+ flattenSchema,
2854
+ getAllMetrics,
2855
+ getAxiosInstance,
2856
+ getCounterMetric,
2857
+ getCounterMetrics,
2858
+ getDownloadFileApiCall,
2859
+ getJSONApiCall,
2860
+ getJSONApiCallWithFullResponse,
2861
+ getRawJSONApiCall,
2862
+ gracefulExit,
2863
+ haltAndCatchFire,
2864
+ incrementStreamState,
2865
+ loadJson,
2866
+ loadSchema,
2867
+ loadShoreConfig,
2868
+ logger,
2869
+ postFormDataApiCall,
2870
+ postJSONApiCall,
2871
+ postUrlEncodedApiCall,
2872
+ printMemoryFootprint,
2873
+ removeParasiteProperties,
2874
+ safePath,
2875
+ writeCatalogFile,
2876
+ writeStateFile
2877
+ });
2878
+ //# sourceMappingURL=index.js.map