@whaly/connector-sdk 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +297 -4
- package/dist/index.d.ts +297 -4
- package/dist/index.js +782 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +764 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -32,9 +32,14 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ALL_STREAM_ID_KEYWORD: () => ALL_STREAM_ID_KEYWORD,
|
|
34
34
|
API_CALLS_METRIC_NAME: () => API_CALLS_METRIC_NAME,
|
|
35
|
+
AssetStream: () => AssetStream,
|
|
36
|
+
AssetTap: () => AssetTap,
|
|
37
|
+
AssetTarget: () => AssetTarget,
|
|
35
38
|
Authenticator: () => Authenticator,
|
|
36
39
|
BATCH_INTERVAL_MS: () => BATCH_INTERVAL_MS,
|
|
37
40
|
BigQueryTarget: () => BigQueryTarget,
|
|
41
|
+
CdnAssetTarget: () => CdnAssetTarget,
|
|
42
|
+
CdnService: () => CdnService,
|
|
38
43
|
CloudStorageService: () => CloudStorageService,
|
|
39
44
|
CounterMetric: () => CounterMetric,
|
|
40
45
|
CsvExtractionConfigBuilder: () => CsvExtractionConfigBuilder,
|
|
@@ -48,6 +53,8 @@ __export(index_exports, {
|
|
|
48
53
|
FileTap: () => FileTap,
|
|
49
54
|
GCSStateProvider: () => GCSStateProvider,
|
|
50
55
|
ITarget: () => ITarget,
|
|
56
|
+
ImageTransform: () => ImageTransform,
|
|
57
|
+
MIME_TYPES: () => MIME_TYPES,
|
|
51
58
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
52
59
|
MissingSchemaError: () => MissingSchemaError,
|
|
53
60
|
RESTStream: () => RESTStream,
|
|
@@ -69,6 +76,7 @@ __export(index_exports, {
|
|
|
69
76
|
_greaterThan: () => _greaterThan,
|
|
70
77
|
addWhalyFields: () => addWhalyFields,
|
|
71
78
|
checkCsvHeaderRow: () => checkCsvHeaderRow,
|
|
79
|
+
collectPaginated: () => collectPaginated,
|
|
72
80
|
countCsvLines: () => countCsvLines,
|
|
73
81
|
createCellReference: () => createCellReference,
|
|
74
82
|
createCsvStreamConfig: () => createCsvStreamConfig,
|
|
@@ -76,6 +84,7 @@ __export(index_exports, {
|
|
|
76
84
|
createExcelStreamConfig: () => createExcelStreamConfig,
|
|
77
85
|
createTemporaryFileStream: () => createTemporaryFileStream,
|
|
78
86
|
csvFieldsToJsonSchema: () => csvFieldsToJsonSchema,
|
|
87
|
+
downloadFiles: () => downloadFiles,
|
|
79
88
|
excelColumnToIndex: () => excelColumnToIndex,
|
|
80
89
|
excelFieldsToJsonSchema: () => excelFieldsToJsonSchema,
|
|
81
90
|
excelRowToIndex: () => excelRowToIndex,
|
|
@@ -88,29 +97,40 @@ __export(index_exports, {
|
|
|
88
97
|
findAllMatchingConfigs: () => findAllMatchingConfigs,
|
|
89
98
|
flattenSchema: () => flattenSchema,
|
|
90
99
|
getAllMetrics: () => getAllMetrics,
|
|
100
|
+
getApiEndpoint: () => getApiEndpoint,
|
|
91
101
|
getAxiosInstance: () => getAxiosInstance,
|
|
102
|
+
getCdnId: () => getCdnId,
|
|
92
103
|
getCounterMetric: () => getCounterMetric,
|
|
93
104
|
getCounterMetrics: () => getCounterMetrics,
|
|
94
105
|
getDownloadFileApiCall: () => getDownloadFileApiCall,
|
|
106
|
+
getDryRunLimit: () => getDryRunLimit,
|
|
95
107
|
getJSONApiCall: () => getJSONApiCall,
|
|
96
108
|
getJSONApiCallWithFullResponse: () => getJSONApiCallWithFullResponse,
|
|
109
|
+
getMimeType: () => getMimeType,
|
|
97
110
|
getRawJSONApiCall: () => getRawJSONApiCall,
|
|
111
|
+
getServiceAccountKey: () => getServiceAccountKey,
|
|
98
112
|
gracefulExit: () => gracefulExit,
|
|
99
113
|
haltAndCatchFire: () => haltAndCatchFire,
|
|
100
114
|
incrementStreamState: () => incrementStreamState,
|
|
101
115
|
indexToExcelColumn: () => indexToExcelColumn,
|
|
102
116
|
indexToExcelRow: () => indexToExcelRow,
|
|
117
|
+
isDryRun: () => isDryRun,
|
|
118
|
+
listFilesRecursively: () => listFilesRecursively,
|
|
103
119
|
loadJson: () => loadJson,
|
|
104
120
|
loadSchema: () => loadSchema,
|
|
105
121
|
logger: () => logger,
|
|
122
|
+
optionalEnv: () => optionalEnv,
|
|
106
123
|
parseCellReference: () => parseCellReference,
|
|
107
124
|
postFormDataApiCall: () => postFormDataApiCall,
|
|
108
125
|
postJSONApiCall: () => postJSONApiCall,
|
|
109
126
|
postUrlEncodedApiCall: () => postUrlEncodedApiCall,
|
|
110
127
|
printMemoryFootprint: () => printMemoryFootprint,
|
|
111
128
|
processFileStreams: () => processFileStreams,
|
|
129
|
+
processFromAsyncIterable: () => processFromAsyncIterable,
|
|
112
130
|
removeParasiteProperties: () => removeParasiteProperties,
|
|
131
|
+
requireEnv: () => requireEnv,
|
|
113
132
|
rowGeneratorFromCsv: () => rowGeneratorFromCsv,
|
|
133
|
+
runWithConcurrency: () => runWithConcurrency,
|
|
114
134
|
safePath: () => safePath,
|
|
115
135
|
unzip: () => unzip,
|
|
116
136
|
validateExcelConfig: () => validateExcelConfig,
|
|
@@ -400,13 +420,26 @@ var postUrlEncodedApiCall = async (endpoint, config, payload) => {
|
|
|
400
420
|
return response.data;
|
|
401
421
|
} catch (err) {
|
|
402
422
|
if (err.response.status > 400) {
|
|
403
|
-
throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
|
|
404
|
-
|
|
423
|
+
throw new Error(`We got a \`${err.response.status}\` status code from endpoint: \`${endpoint}\`.
|
|
424
|
+
|
|
405
425
|
Response body from the server: ${JSON.stringify(err.response.data)}`);
|
|
406
426
|
}
|
|
407
427
|
throw err;
|
|
408
428
|
}
|
|
409
429
|
};
|
|
430
|
+
async function collectPaginated(options) {
|
|
431
|
+
const { initialUrl, fetchPage, getRecords, getNextUrl } = options;
|
|
432
|
+
const results = [];
|
|
433
|
+
let currentUrl = initialUrl;
|
|
434
|
+
while (true) {
|
|
435
|
+
const response = await fetchPage(currentUrl);
|
|
436
|
+
results.push(...getRecords(response));
|
|
437
|
+
const nextUrl = getNextUrl(response);
|
|
438
|
+
if (nextUrl === void 0) break;
|
|
439
|
+
currentUrl = nextUrl;
|
|
440
|
+
}
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
410
443
|
|
|
411
444
|
// src/sdk/service/metric.ts
|
|
412
445
|
var metricStore = {};
|
|
@@ -497,6 +530,157 @@ var haltAndCatchFire = async (errorType, errorText, errorDebugText) => {
|
|
|
497
530
|
await gracefulExit(0);
|
|
498
531
|
};
|
|
499
532
|
|
|
533
|
+
// src/sdk/service/dryRun.ts
|
|
534
|
+
function isDryRun() {
|
|
535
|
+
const val = process.env["DRY_RUN"];
|
|
536
|
+
return val !== void 0 && val !== "" && val !== "0" && val !== "false";
|
|
537
|
+
}
|
|
538
|
+
function getDryRunLimit() {
|
|
539
|
+
const val = process.env["DRY_RUN_LIMIT"];
|
|
540
|
+
if (!val) return void 0;
|
|
541
|
+
const n = parseInt(val, 10);
|
|
542
|
+
return isNaN(n) || n <= 0 ? void 0 : n;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/sdk/service/apiEndpoint.ts
|
|
546
|
+
var ENV_VAR = "WLY_API_ENDPOINT";
|
|
547
|
+
function getApiEndpoint(explicit) {
|
|
548
|
+
const endpoint = explicit ?? process.env[ENV_VAR];
|
|
549
|
+
if (!endpoint) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`No API endpoint provided. Either pass "apiEndpoint" in config or set the ${ENV_VAR} environment variable.`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return endpoint;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/sdk/service/serviceAccountKey.ts
|
|
558
|
+
var ENV_VAR2 = "WLY_SERVICE_ACCOUNT_KEY";
|
|
559
|
+
function getServiceAccountKey(explicit) {
|
|
560
|
+
const key = explicit ?? process.env[ENV_VAR2];
|
|
561
|
+
if (!key) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
`No service account key provided. Either pass "serviceAccountKey" in config or set the ${ENV_VAR2} environment variable.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return key;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/sdk/service/cdnId.ts
|
|
570
|
+
var ENV_VAR3 = "WLY_CDN_ID";
|
|
571
|
+
function getCdnId(explicit) {
|
|
572
|
+
const cdnId = explicit ?? process.env[ENV_VAR3];
|
|
573
|
+
if (!cdnId) {
|
|
574
|
+
throw new Error(
|
|
575
|
+
`No CDN ID provided. Either pass "cdnId" in config or set the ${ENV_VAR3} environment variable.`
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
return cdnId;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/sdk/service/env.ts
|
|
582
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
583
|
+
var dotenvLoaded = false;
|
|
584
|
+
function ensureDotenv() {
|
|
585
|
+
if (!dotenvLoaded) {
|
|
586
|
+
import_dotenv.default.config();
|
|
587
|
+
dotenvLoaded = true;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function requireEnv(name) {
|
|
591
|
+
ensureDotenv();
|
|
592
|
+
const value = process.env[name];
|
|
593
|
+
if (!value) {
|
|
594
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
595
|
+
}
|
|
596
|
+
return value;
|
|
597
|
+
}
|
|
598
|
+
function optionalEnv(name, defaultValue) {
|
|
599
|
+
ensureDotenv();
|
|
600
|
+
return process.env[name] ?? defaultValue;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/sdk/service/concurrency.ts
|
|
604
|
+
async function runWithConcurrency(tasks, concurrency) {
|
|
605
|
+
if (concurrency < 1) {
|
|
606
|
+
throw new Error(`concurrency must be >= 1, got ${concurrency}`);
|
|
607
|
+
}
|
|
608
|
+
const results = new Array(tasks.length);
|
|
609
|
+
let nextIndex = 0;
|
|
610
|
+
async function worker() {
|
|
611
|
+
while (nextIndex < tasks.length) {
|
|
612
|
+
const index = nextIndex++;
|
|
613
|
+
const task = tasks[index];
|
|
614
|
+
if (task) {
|
|
615
|
+
results[index] = await task();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const workers = Array.from(
|
|
620
|
+
{ length: Math.min(concurrency, tasks.length) },
|
|
621
|
+
() => worker()
|
|
622
|
+
);
|
|
623
|
+
await Promise.all(workers);
|
|
624
|
+
return results;
|
|
625
|
+
}
|
|
626
|
+
async function processFromAsyncIterable(iterable, processor, concurrency, onError) {
|
|
627
|
+
if (concurrency < 1) {
|
|
628
|
+
throw new Error(`concurrency must be >= 1, got ${concurrency}`);
|
|
629
|
+
}
|
|
630
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
631
|
+
let stopped = false;
|
|
632
|
+
async function worker() {
|
|
633
|
+
while (!stopped) {
|
|
634
|
+
const { value, done } = await iterator.next();
|
|
635
|
+
if (done) break;
|
|
636
|
+
try {
|
|
637
|
+
const result = await processor(value);
|
|
638
|
+
if (result === "stop") {
|
|
639
|
+
stopped = true;
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
} catch (err) {
|
|
643
|
+
if (onError) {
|
|
644
|
+
onError(value, err);
|
|
645
|
+
} else {
|
|
646
|
+
stopped = true;
|
|
647
|
+
throw err;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const workers = Array.from({ length: concurrency }, () => worker());
|
|
653
|
+
await Promise.all(workers);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/sdk/service/mime.ts
|
|
657
|
+
var import_node_path = __toESM(require("path"));
|
|
658
|
+
var MIME_TYPES = {
|
|
659
|
+
".webp": "image/webp",
|
|
660
|
+
".png": "image/png",
|
|
661
|
+
".jpg": "image/jpeg",
|
|
662
|
+
".jpeg": "image/jpeg",
|
|
663
|
+
".gif": "image/gif",
|
|
664
|
+
".svg": "image/svg+xml",
|
|
665
|
+
".avif": "image/avif",
|
|
666
|
+
".bmp": "image/bmp",
|
|
667
|
+
".tiff": "image/tiff",
|
|
668
|
+
".tif": "image/tiff",
|
|
669
|
+
".ico": "image/x-icon",
|
|
670
|
+
".pdf": "application/pdf",
|
|
671
|
+
".json": "application/json",
|
|
672
|
+
".csv": "text/csv",
|
|
673
|
+
".xml": "application/xml",
|
|
674
|
+
".zip": "application/zip",
|
|
675
|
+
".gz": "application/gzip",
|
|
676
|
+
".xls": "application/vnd.ms-excel",
|
|
677
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
678
|
+
};
|
|
679
|
+
function getMimeType(filePathOrExt) {
|
|
680
|
+
const ext = filePathOrExt.startsWith(".") ? filePathOrExt.toLowerCase() : import_node_path.default.extname(filePathOrExt).toLowerCase();
|
|
681
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
682
|
+
}
|
|
683
|
+
|
|
500
684
|
// src/sdk/models/replication.ts
|
|
501
685
|
var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
|
|
502
686
|
ReplicationMethod2["INCREMENTAL"] = "INCREMENTAL";
|
|
@@ -794,6 +978,9 @@ var Tap = class {
|
|
|
794
978
|
this.tapState = (0, import_cloneDeep.default)(initialState.state);
|
|
795
979
|
}
|
|
796
980
|
await this.init();
|
|
981
|
+
if (isDryRun()) {
|
|
982
|
+
logger.info(`[DRY_RUN] mode active \u2014 no external writes will occur`);
|
|
983
|
+
}
|
|
797
984
|
logger.info(`\u{1F680} Start syncing`);
|
|
798
985
|
const state = StateService.getInstance().get();
|
|
799
986
|
logger.info(`\u{1F4CD} Received state: %j`, state);
|
|
@@ -1087,6 +1274,7 @@ var Stream = class {
|
|
|
1087
1274
|
// Private sync methods:
|
|
1088
1275
|
async _syncRecords(parent) {
|
|
1089
1276
|
let rowsSent = 0;
|
|
1277
|
+
const dryRunLimit = isDryRun() ? getDryRunLimit() : void 0;
|
|
1090
1278
|
let recordSyncedMetrics = [];
|
|
1091
1279
|
if (this.rowsSyncedMetricsConf.isEnabled() === true) {
|
|
1092
1280
|
recordSyncedMetrics = getCounterMetrics(
|
|
@@ -1128,6 +1316,10 @@ var Stream = class {
|
|
|
1128
1316
|
metric.increment();
|
|
1129
1317
|
});
|
|
1130
1318
|
rowsSent += 1;
|
|
1319
|
+
if (dryRunLimit !== void 0 && rowsSent >= dryRunLimit) {
|
|
1320
|
+
logger.info(`\u{1F6D1} Stream: ${this.streamId} - DRY_RUN_LIMIT reached (${dryRunLimit} records), stopping.`);
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1131
1323
|
if (rowsSent % 1e3 === 0) {
|
|
1132
1324
|
const percentStr = this.totalRows ? ` (${Math.round(rowsSent / this.totalRows * 100)}%)` : "";
|
|
1133
1325
|
logger.info(`\u23F3 Stream: ${this.streamId} - ${rowsSent} records synced so far...${percentStr}`);
|
|
@@ -1819,15 +2011,15 @@ var import_fs_extra = __toESM(require("fs-extra"));
|
|
|
1819
2011
|
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
1820
2012
|
var createTemporaryFileStream = (streamId) => {
|
|
1821
2013
|
import_fs_extra.default.ensureDirSync("./tmp");
|
|
1822
|
-
const
|
|
1823
|
-
const writeStream = import_fs_extra.default.createWriteStream(
|
|
2014
|
+
const path7 = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
2015
|
+
const writeStream = import_fs_extra.default.createWriteStream(path7, { flags: "w" });
|
|
1824
2016
|
return {
|
|
1825
2017
|
stream: writeStream,
|
|
1826
|
-
path:
|
|
2018
|
+
path: path7
|
|
1827
2019
|
};
|
|
1828
2020
|
};
|
|
1829
|
-
function safePath(
|
|
1830
|
-
let formattedPath =
|
|
2021
|
+
function safePath(path7) {
|
|
2022
|
+
let formattedPath = path7;
|
|
1831
2023
|
formattedPath = formattedPath.replace(/^The\s+/gi, "");
|
|
1832
2024
|
formattedPath = formattedPath.replace(/\s+(?:\(|\[)?Dis(?:c|k) (?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d)+(?:\)|\])?/gi, "");
|
|
1833
2025
|
formattedPath = formattedPath.replace(/\s+/gi, "_");
|
|
@@ -1953,6 +2145,8 @@ var StreamState = class {
|
|
|
1953
2145
|
// src/sdk/models/target/target.ts
|
|
1954
2146
|
var import_async_mutex = require("async-mutex");
|
|
1955
2147
|
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2148
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
2149
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1956
2150
|
var semaphore = new import_async_mutex.Semaphore(1);
|
|
1957
2151
|
var ITarget = class _ITarget {
|
|
1958
2152
|
config;
|
|
@@ -2039,7 +2233,7 @@ var ITarget = class _ITarget {
|
|
|
2039
2233
|
if (!stream) {
|
|
2040
2234
|
throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
|
|
2041
2235
|
}
|
|
2042
|
-
if (stream.getBatchedRowCount() % 1e4 === 0) {
|
|
2236
|
+
if (stream.getBatchedRowCount() % 1e4 === 0 && !isDryRun()) {
|
|
2043
2237
|
await semaphore.runExclusive(async () => {
|
|
2044
2238
|
const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
|
|
2045
2239
|
if (shouldUploadIntermediateBatch) {
|
|
@@ -2125,7 +2319,9 @@ var ITarget = class _ITarget {
|
|
|
2125
2319
|
const { hasBreakingChanges, changes } = this.analyzeSchemaChanges(prevSchema, newFlattenedSchema, streamId);
|
|
2126
2320
|
if (hasBreakingChanges) {
|
|
2127
2321
|
logger.info(`\u{1F525} StreamId=${streamId} - Detected BREAKING schema changes: ${changes.join(", ")}. Loading batched records before applying new schema.`);
|
|
2128
|
-
|
|
2322
|
+
if (!isDryRun()) {
|
|
2323
|
+
await _ITarget.uploadSingleStreamToWarehouse(prevStreamState);
|
|
2324
|
+
}
|
|
2129
2325
|
} else {
|
|
2130
2326
|
logger.info(`\u2705 StreamId=${streamId} - Detected NON-BREAKING schema changes: ${changes.join(", ")}. Continuing without uploading batched records.`);
|
|
2131
2327
|
}
|
|
@@ -2149,36 +2345,41 @@ var ITarget = class _ITarget {
|
|
|
2149
2345
|
streamState.setSchema(schema);
|
|
2150
2346
|
streamState.setCompiledValidateFn(this.ajv.compile({ type: "object", properties: schema }));
|
|
2151
2347
|
const streamReplicationMethod = this.streams[streamId]?.getReplicationMethod();
|
|
2152
|
-
if (message.keyProperties.length === 0 && streamReplicationMethod
|
|
2348
|
+
if (message.keyProperties.length === 0 && streamReplicationMethod === "INCREMENTAL" /* INCREMENTAL */) {
|
|
2153
2349
|
throw new Error(`StreamId: ${message.stream} - \`key_properties\` field is required and can't be empty in SCHEMA message.
|
|
2154
2350
|
Received SCHEMA message: ${JSON.stringify(message)}`);
|
|
2155
2351
|
}
|
|
2156
2352
|
streamState.setKeyProperties(message.keyProperties);
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
const input = {
|
|
2175
|
-
databaseName,
|
|
2176
|
-
schemaName,
|
|
2177
|
-
tableName,
|
|
2178
|
-
message: formattedRelationshipMessage
|
|
2353
|
+
dbSync.renamedColumnStore.computeColumnNameForStream(streamId, schema);
|
|
2354
|
+
if (!isDryRun()) {
|
|
2355
|
+
await dbSync.updateSchemaInWarehouse(schema, message.keyProperties);
|
|
2356
|
+
const {
|
|
2357
|
+
database: databaseName,
|
|
2358
|
+
schema: schemaName
|
|
2359
|
+
} = this.config;
|
|
2360
|
+
const tableName = this.genTableName(streamId);
|
|
2361
|
+
const formattedRelationshipMessage = {
|
|
2362
|
+
...message,
|
|
2363
|
+
keyProperties: message.keyProperties.map((k) => {
|
|
2364
|
+
const translation = streamState.getDbSync().renamedColumnStore.getColumnTranslation(message.stream, k);
|
|
2365
|
+
if (translation) {
|
|
2366
|
+
return translation;
|
|
2367
|
+
}
|
|
2368
|
+
return k;
|
|
2369
|
+
})
|
|
2179
2370
|
};
|
|
2180
|
-
await
|
|
2181
|
-
|
|
2371
|
+
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2372
|
+
const input = {
|
|
2373
|
+
databaseName,
|
|
2374
|
+
schemaName,
|
|
2375
|
+
tableName,
|
|
2376
|
+
message: formattedRelationshipMessage
|
|
2377
|
+
};
|
|
2378
|
+
await hook.writeSchema(input);
|
|
2379
|
+
}, { concurrency: 3 });
|
|
2380
|
+
} else {
|
|
2381
|
+
logger.info(`[DRY_RUN] Stream: ${streamId} - Skipping warehouse schema update and schema hooks`);
|
|
2382
|
+
}
|
|
2182
2383
|
return Promise.resolve();
|
|
2183
2384
|
} catch (err) {
|
|
2184
2385
|
logger.error(`Error when handling schema event.`, err);
|
|
@@ -2199,6 +2400,29 @@ var ITarget = class _ITarget {
|
|
|
2199
2400
|
};
|
|
2200
2401
|
complete = async () => {
|
|
2201
2402
|
try {
|
|
2403
|
+
if (isDryRun()) {
|
|
2404
|
+
logger.info(`[DRY_RUN] Data Extraction is complete. Copying tmp files to out/ instead of loading to warehouse.`);
|
|
2405
|
+
const outDir = import_node_path2.default.resolve("out");
|
|
2406
|
+
await import_fs_extra2.default.ensureDir(outDir);
|
|
2407
|
+
for (const streamId of Object.keys(this.streams)) {
|
|
2408
|
+
const streamState = this.streams[streamId];
|
|
2409
|
+
if (!streamState || streamState.getBatchedRowCount() === 0) continue;
|
|
2410
|
+
const fileToLoad = streamState.getFileToLoad();
|
|
2411
|
+
await new Promise((resolve, reject) => {
|
|
2412
|
+
fileToLoad.stream.end(() => {
|
|
2413
|
+
fileToLoad.stream.close((err) => {
|
|
2414
|
+
if (err) reject(err);
|
|
2415
|
+
else resolve();
|
|
2416
|
+
});
|
|
2417
|
+
});
|
|
2418
|
+
});
|
|
2419
|
+
const destPath = import_node_path2.default.join(outDir, `${safePath(streamId)}.ndjson`);
|
|
2420
|
+
await import_fs_extra2.default.copy(fileToLoad.path, destPath);
|
|
2421
|
+
logger.info(`[DRY_RUN] Stream: ${streamId} - Copied ${streamState.getBatchedRowCount()} records to ${destPath}`);
|
|
2422
|
+
await import_fs_extra2.default.remove(fileToLoad.path);
|
|
2423
|
+
}
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2202
2426
|
logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
|
|
2203
2427
|
await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
|
|
2204
2428
|
return Promise.resolve();
|
|
@@ -2631,7 +2855,7 @@ var formatHexComparison = (expected, actual) => {
|
|
|
2631
2855
|
Expected: "${expected}" (${expectedInfo.length} chars) [${expectedInfo.hex}]
|
|
2632
2856
|
Actual: "${actual}" (${actualInfo.length} chars) [${actualInfo.hex}]`;
|
|
2633
2857
|
};
|
|
2634
|
-
async function* rowGeneratorFromCsv(
|
|
2858
|
+
async function* rowGeneratorFromCsv(path7, fileConfig) {
|
|
2635
2859
|
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2636
2860
|
const configKeys = isGeneratorConfigArray ? [] : Object.keys(fileConfig.fields);
|
|
2637
2861
|
const csvOptions = { separator: fileConfig.separator };
|
|
@@ -2643,10 +2867,10 @@ async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
|
2643
2867
|
let finalInputStream;
|
|
2644
2868
|
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2645
2869
|
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2646
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2870
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2647
2871
|
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2648
2872
|
} else {
|
|
2649
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2873
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2650
2874
|
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2651
2875
|
}
|
|
2652
2876
|
finalInputStream.pipe(csvStream);
|
|
@@ -2688,7 +2912,7 @@ async function countCsvLines(filePath) {
|
|
|
2688
2912
|
}).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
|
|
2689
2913
|
});
|
|
2690
2914
|
}
|
|
2691
|
-
var checkCsvHeaderRow = async (
|
|
2915
|
+
var checkCsvHeaderRow = async (path7, fileConfig) => {
|
|
2692
2916
|
return new Promise((resolve, reject) => {
|
|
2693
2917
|
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
2694
2918
|
console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
|
|
@@ -2705,10 +2929,10 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2705
2929
|
let finalInputStream;
|
|
2706
2930
|
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2707
2931
|
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2708
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2932
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2709
2933
|
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2710
2934
|
} else {
|
|
2711
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2935
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2712
2936
|
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2713
2937
|
}
|
|
2714
2938
|
let isFirstLine = true;
|
|
@@ -2734,7 +2958,7 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2734
2958
|
i,
|
|
2735
2959
|
col,
|
|
2736
2960
|
headers[i] || "<undefined>",
|
|
2737
|
-
|
|
2961
|
+
path7
|
|
2738
2962
|
));
|
|
2739
2963
|
}
|
|
2740
2964
|
});
|
|
@@ -2751,7 +2975,7 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2751
2975
|
throw new Error((0, import_util3.format)(
|
|
2752
2976
|
`CSV missing expected column '%s' in file: %s`,
|
|
2753
2977
|
col,
|
|
2754
|
-
|
|
2978
|
+
path7
|
|
2755
2979
|
));
|
|
2756
2980
|
}
|
|
2757
2981
|
});
|
|
@@ -2777,7 +3001,7 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2777
3001
|
try {
|
|
2778
3002
|
const firstRow = data[0];
|
|
2779
3003
|
if (!firstRow) {
|
|
2780
|
-
|
|
3004
|
+
logger.info(`${logPrefix2} No data to write to CSV file=%s`, fileName);
|
|
2781
3005
|
return;
|
|
2782
3006
|
}
|
|
2783
3007
|
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
@@ -2787,7 +3011,7 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2787
3011
|
})
|
|
2788
3012
|
});
|
|
2789
3013
|
await csvWriter.writeRecords(data);
|
|
2790
|
-
|
|
3014
|
+
logger.info(`${logPrefix2} CSV file=%s written successfully`, fileName);
|
|
2791
3015
|
} catch (err) {
|
|
2792
3016
|
if (err instanceof Error) {
|
|
2793
3017
|
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, fileName, err.message));
|
|
@@ -2795,15 +3019,17 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2795
3019
|
throw err;
|
|
2796
3020
|
}
|
|
2797
3021
|
};
|
|
2798
|
-
var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
3022
|
+
var writeGeneratorToCSV = async (generator, outputFileName, options) => {
|
|
3023
|
+
const batchSize = options?.batchSize ?? 1e4;
|
|
3024
|
+
const alwaysQuote = options?.alwaysQuote ?? true;
|
|
2799
3025
|
try {
|
|
2800
3026
|
let rowCount = 0;
|
|
2801
3027
|
const firstDataRow = (await generator.next()).value;
|
|
2802
3028
|
if (!firstDataRow) {
|
|
2803
|
-
|
|
3029
|
+
logger.info(`${logPrefix2} [%s] first data row of csv empty, skipping`, outputFileName);
|
|
2804
3030
|
return 0;
|
|
2805
3031
|
}
|
|
2806
|
-
|
|
3032
|
+
logger.info(`${logPrefix2} [%s] inferring headers from first data row`, outputFileName);
|
|
2807
3033
|
const headers = Object.keys(firstDataRow).map((col) => {
|
|
2808
3034
|
return {
|
|
2809
3035
|
id: col,
|
|
@@ -2813,14 +3039,14 @@ var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
|
2813
3039
|
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2814
3040
|
path: outputFileName,
|
|
2815
3041
|
header: headers,
|
|
2816
|
-
alwaysQuote
|
|
3042
|
+
alwaysQuote
|
|
2817
3043
|
});
|
|
2818
|
-
|
|
3044
|
+
logger.info(`${logPrefix2} [%s] writing csv`, outputFileName);
|
|
2819
3045
|
let batch = [firstDataRow];
|
|
2820
3046
|
for await (const data of generator) {
|
|
2821
3047
|
batch.push(data);
|
|
2822
3048
|
rowCount++;
|
|
2823
|
-
if (batch.length
|
|
3049
|
+
if (batch.length >= batchSize) {
|
|
2824
3050
|
try {
|
|
2825
3051
|
await csvWriter.writeRecords(batch);
|
|
2826
3052
|
} catch (err) {
|
|
@@ -2838,8 +3064,10 @@ var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
|
2838
3064
|
batch = [];
|
|
2839
3065
|
}
|
|
2840
3066
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
3067
|
+
if (batch.length > 0) {
|
|
3068
|
+
await csvWriter.writeRecords(batch);
|
|
3069
|
+
}
|
|
3070
|
+
logger.info(`${logPrefix2} [%s] csv written successfully with %d rows`, outputFileName, rowCount);
|
|
2843
3071
|
return rowCount;
|
|
2844
3072
|
} catch (err) {
|
|
2845
3073
|
if (err instanceof Error) {
|
|
@@ -3125,9 +3353,65 @@ var FilePatterns = class {
|
|
|
3125
3353
|
};
|
|
3126
3354
|
|
|
3127
3355
|
// src/services/sftp.ts
|
|
3356
|
+
var import_node_path3 = __toESM(require("path"));
|
|
3357
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
3128
3358
|
var import_ssh2_sftp_client = __toESM(require("ssh2-sftp-client"));
|
|
3129
3359
|
var import_ssh2_sftp_client2 = __toESM(require("ssh2-sftp-client"));
|
|
3130
3360
|
var SftpService = new import_ssh2_sftp_client.default();
|
|
3361
|
+
var logPrefix3 = "[sftp]";
|
|
3362
|
+
async function listFilesRecursively(client, remotePath, options) {
|
|
3363
|
+
const recursive = options?.recursive ?? true;
|
|
3364
|
+
const extensions = options?.extensions?.map((e) => e.toLowerCase());
|
|
3365
|
+
const maxIterations = options?.maxIterations ?? 1e3;
|
|
3366
|
+
const results = [];
|
|
3367
|
+
const dirs = [remotePath];
|
|
3368
|
+
let iterations = 0;
|
|
3369
|
+
while (dirs.length > 0) {
|
|
3370
|
+
if (iterations++ >= maxIterations) {
|
|
3371
|
+
logger.warn(`${logPrefix3} listFilesRecursively reached maxIterations (${maxIterations}), stopping.`);
|
|
3372
|
+
break;
|
|
3373
|
+
}
|
|
3374
|
+
const currentDir = dirs.shift();
|
|
3375
|
+
let entries;
|
|
3376
|
+
try {
|
|
3377
|
+
entries = await client.list(currentDir);
|
|
3378
|
+
} catch (err) {
|
|
3379
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3380
|
+
logger.warn(`${logPrefix3} Failed to list ${currentDir}: ${msg}`);
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
for (const entry of entries) {
|
|
3384
|
+
if (entry.type === "d") {
|
|
3385
|
+
if (recursive && entry.name !== "." && entry.name !== "..") {
|
|
3386
|
+
dirs.push(import_node_path3.default.posix.join(currentDir, entry.name));
|
|
3387
|
+
}
|
|
3388
|
+
continue;
|
|
3389
|
+
}
|
|
3390
|
+
if (entry.type !== "-") continue;
|
|
3391
|
+
if (extensions) {
|
|
3392
|
+
const ext = import_node_path3.default.extname(entry.name).toLowerCase();
|
|
3393
|
+
if (!extensions.includes(ext)) continue;
|
|
3394
|
+
}
|
|
3395
|
+
const fullPath = import_node_path3.default.posix.join(currentDir, entry.name);
|
|
3396
|
+
results.push({ ...entry, name: fullPath });
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
logger.info(`${logPrefix3} listFilesRecursively found ${results.length} files under ${remotePath}`);
|
|
3400
|
+
return results;
|
|
3401
|
+
}
|
|
3402
|
+
async function downloadFiles(client, files, localDir, options) {
|
|
3403
|
+
const concurrency = options?.concurrency ?? 5;
|
|
3404
|
+
await import_fs_extra3.default.ensureDir(localDir);
|
|
3405
|
+
const tasks = files.map((file, index) => () => {
|
|
3406
|
+
const localName = `${index}_${import_node_path3.default.basename(file.name)}`;
|
|
3407
|
+
const localPath = import_node_path3.default.join(localDir, localName);
|
|
3408
|
+
logger.debug(`${logPrefix3} Downloading ${file.name} \u2192 ${localPath}`);
|
|
3409
|
+
return client.fastGet(file.name, localPath).then(() => localPath);
|
|
3410
|
+
});
|
|
3411
|
+
const results = await runWithConcurrency(tasks, concurrency);
|
|
3412
|
+
logger.info(`${logPrefix3} Downloaded ${results.length} files to ${localDir}`);
|
|
3413
|
+
return results;
|
|
3414
|
+
}
|
|
3131
3415
|
|
|
3132
3416
|
// src/services/cloud-storage.ts
|
|
3133
3417
|
var import_storage = require("@google-cloud/storage");
|
|
@@ -3135,7 +3419,7 @@ var import_util5 = require("util");
|
|
|
3135
3419
|
var pathModule = __toESM(require("path"));
|
|
3136
3420
|
var import_fs3 = require("fs");
|
|
3137
3421
|
var import_crypto = require("crypto");
|
|
3138
|
-
var
|
|
3422
|
+
var logPrefix4 = "[CloudStorageService]";
|
|
3139
3423
|
var tmpDir = "tmp";
|
|
3140
3424
|
var CloudStorageService = class {
|
|
3141
3425
|
storage;
|
|
@@ -3143,10 +3427,10 @@ var CloudStorageService = class {
|
|
|
3143
3427
|
processedSuffix;
|
|
3144
3428
|
supportedExtensions;
|
|
3145
3429
|
path;
|
|
3146
|
-
constructor(bucketName,
|
|
3430
|
+
constructor(bucketName, path7, opts) {
|
|
3147
3431
|
this.storage = new import_storage.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
3148
3432
|
this.bucket = this.storage.bucket(bucketName);
|
|
3149
|
-
this.path =
|
|
3433
|
+
this.path = path7;
|
|
3150
3434
|
this.processedSuffix = opts?.processedSuffix ?? ".processed";
|
|
3151
3435
|
this.supportedExtensions = opts?.supportedExtensions ?? [];
|
|
3152
3436
|
}
|
|
@@ -3187,7 +3471,7 @@ var CloudStorageService = class {
|
|
|
3187
3471
|
try {
|
|
3188
3472
|
const file = this.bucket.file(markerFileName);
|
|
3189
3473
|
await file.save((0, import_util5.format)("Marked file %s as processed", fileName));
|
|
3190
|
-
logger.info(`${
|
|
3474
|
+
logger.info(`${logPrefix4} Marker file ${markerFileName} created successfully.`);
|
|
3191
3475
|
} catch (err) {
|
|
3192
3476
|
if (err instanceof Error) {
|
|
3193
3477
|
throw new Error((0, import_util5.format)(`error while writing marker file=%s, err:%s`, markerFileName, err.message));
|
|
@@ -3211,7 +3495,7 @@ var CloudStorageService = class {
|
|
|
3211
3495
|
(0, import_fs3.unlinkSync)(destFilename);
|
|
3212
3496
|
}
|
|
3213
3497
|
await file.download({ destination: destFilename });
|
|
3214
|
-
logger.info(`${
|
|
3498
|
+
logger.info(`${logPrefix4} Downloaded ${fileName} to ${destFilename}`);
|
|
3215
3499
|
return destFilename;
|
|
3216
3500
|
} catch (err) {
|
|
3217
3501
|
if (err instanceof Error) {
|
|
@@ -3229,9 +3513,9 @@ var CloudStorageService = class {
|
|
|
3229
3513
|
*/
|
|
3230
3514
|
async uploadFile(localPath, destPath) {
|
|
3231
3515
|
try {
|
|
3232
|
-
logger.info(`${
|
|
3516
|
+
logger.info(`${logPrefix4} preparing to upload '%s' to '%s'`, localPath, destPath);
|
|
3233
3517
|
const [file] = await this.bucket.upload(localPath, { destination: destPath });
|
|
3234
|
-
logger.info(`${
|
|
3518
|
+
logger.info(`${logPrefix4} file %s has been successfully uploaded into %s`, localPath, destPath);
|
|
3235
3519
|
return file;
|
|
3236
3520
|
} catch (err) {
|
|
3237
3521
|
if (err instanceof Error) {
|
|
@@ -3307,6 +3591,376 @@ var unzip = async (zipFilePath, extractedPath) => {
|
|
|
3307
3591
|
return extractedPath;
|
|
3308
3592
|
};
|
|
3309
3593
|
|
|
3594
|
+
// src/services/cdn.ts
|
|
3595
|
+
var import_axios3 = __toESM(require("axios"));
|
|
3596
|
+
var import_axios_retry2 = __toESM(require("axios-retry"));
|
|
3597
|
+
var logPrefix5 = "[CdnService]";
|
|
3598
|
+
var MAX_RETRIES = 10;
|
|
3599
|
+
var CdnService = class {
|
|
3600
|
+
axiosClient;
|
|
3601
|
+
constructor(config) {
|
|
3602
|
+
const resolvedKey = getServiceAccountKey(config.serviceAccountKey);
|
|
3603
|
+
const resolvedEndpoint = getApiEndpoint(config.apiEndpoint);
|
|
3604
|
+
this.axiosClient = import_axios3.default.create({
|
|
3605
|
+
baseURL: resolvedEndpoint,
|
|
3606
|
+
timeout: 12e4,
|
|
3607
|
+
headers: {
|
|
3608
|
+
Authorization: `Bearer ${resolvedKey}`,
|
|
3609
|
+
Accept: "application/json"
|
|
3610
|
+
}
|
|
3611
|
+
});
|
|
3612
|
+
(0, import_axios_retry2.default)(this.axiosClient, {
|
|
3613
|
+
retries: MAX_RETRIES,
|
|
3614
|
+
retryCondition: (error) => {
|
|
3615
|
+
const status = error.response?.status ?? 0;
|
|
3616
|
+
return !error.response || status === 429 || status >= 500 && status < 600;
|
|
3617
|
+
},
|
|
3618
|
+
retryDelay: (retryCount, error) => {
|
|
3619
|
+
const retryAfter = error.response?.headers?.["retry-after"];
|
|
3620
|
+
if (retryAfter) {
|
|
3621
|
+
const seconds = Number(retryAfter);
|
|
3622
|
+
if (!isNaN(seconds) && seconds > 0) {
|
|
3623
|
+
logger.info(`${logPrefix5} Rate limited \u2014 waiting ${seconds}s (Retry-After header), attempt ${retryCount}/${MAX_RETRIES}`);
|
|
3624
|
+
return seconds * 1e3;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
const delay = import_axios_retry2.default.exponentialDelay(retryCount);
|
|
3628
|
+
logger.info(`${logPrefix5} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
|
|
3629
|
+
return delay;
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Returns metadata for a CDN file via a HEAD request.
|
|
3635
|
+
* If the file does not exist, returns `{ exists: false, lastModified: null, contentType: null }`.
|
|
3636
|
+
* Falls back to not-existing if HEAD is not supported (405).
|
|
3637
|
+
*/
|
|
3638
|
+
async getFileMetadata(cdnId, fileName) {
|
|
3639
|
+
const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
|
|
3640
|
+
try {
|
|
3641
|
+
const response = await this.axiosClient.head(url);
|
|
3642
|
+
const lastModifiedHeader = response.headers["last-modified"];
|
|
3643
|
+
const contentTypeHeader = response.headers["content-type"];
|
|
3644
|
+
return {
|
|
3645
|
+
exists: true,
|
|
3646
|
+
lastModified: lastModifiedHeader ? new Date(lastModifiedHeader) : null,
|
|
3647
|
+
contentType: contentTypeHeader ?? null
|
|
3648
|
+
};
|
|
3649
|
+
} catch (err) {
|
|
3650
|
+
if (import_axios3.default.isAxiosError(err)) {
|
|
3651
|
+
const status = err.response?.status;
|
|
3652
|
+
if (status === 404) return { exists: false, lastModified: null, contentType: null };
|
|
3653
|
+
if (status === 405) {
|
|
3654
|
+
logger.warn(`${logPrefix5} HEAD not supported for ${url}, treating file as absent`);
|
|
3655
|
+
return { exists: false, lastModified: null, contentType: null };
|
|
3656
|
+
}
|
|
3657
|
+
this.throwMeaningfulError(err);
|
|
3658
|
+
}
|
|
3659
|
+
throw err;
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
/**
|
|
3663
|
+
* Checks whether a file already exists in the CDN via a HEAD request.
|
|
3664
|
+
* Returns true if the server responds 2xx, false on 404.
|
|
3665
|
+
* Falls back to false (treat as not existing) if HEAD is not supported (405).
|
|
3666
|
+
* No body is transferred.
|
|
3667
|
+
*/
|
|
3668
|
+
async fileExists(cdnId, fileName) {
|
|
3669
|
+
return (await this.getFileMetadata(cdnId, fileName)).exists;
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* Uploads a file buffer to the Whaly CDN.
|
|
3673
|
+
* The PUT is idempotent — uploading the same fileName overwrites the existing file.
|
|
3674
|
+
*/
|
|
3675
|
+
async uploadFile(cdnId, fileName, fileBuffer) {
|
|
3676
|
+
const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
|
|
3677
|
+
try {
|
|
3678
|
+
logger.info(`${logPrefix5} Uploading ${fileName} (${fileBuffer.length} bytes) to CDN ${cdnId}`);
|
|
3679
|
+
const form = new FormData();
|
|
3680
|
+
form.append("file", new Blob([fileBuffer]), fileName);
|
|
3681
|
+
const response = await this.axiosClient.put(url, form);
|
|
3682
|
+
logger.info(`${logPrefix5} Successfully uploaded ${fileName} (status ${response.status})`);
|
|
3683
|
+
return {
|
|
3684
|
+
filePath: response.data?.filePath ?? `/org/${cdnId}/file/${fileName}`
|
|
3685
|
+
};
|
|
3686
|
+
} catch (err) {
|
|
3687
|
+
if (import_axios3.default.isAxiosError(err)) {
|
|
3688
|
+
this.throwMeaningfulError(err);
|
|
3689
|
+
}
|
|
3690
|
+
throw err;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
throwMeaningfulError(err) {
|
|
3694
|
+
const status = err.response?.status;
|
|
3695
|
+
if (status === 401 || status === 403) {
|
|
3696
|
+
throw new Error("Unauthorized - invalid or expired service account key");
|
|
3697
|
+
} else if (status === 404) {
|
|
3698
|
+
throw new Error("Organization or CDN not found");
|
|
3699
|
+
} else if (status === 500) {
|
|
3700
|
+
throw new Error("API server error - check CDN ID and service account permissions");
|
|
3701
|
+
} else {
|
|
3702
|
+
throw new Error(`API error: ${err.response?.statusText ?? err.message}`);
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
};
|
|
3706
|
+
|
|
3707
|
+
// src/sdk/models/asset-tap/asset-stream.ts
|
|
3708
|
+
var AssetStream = class {
|
|
3709
|
+
streamId;
|
|
3710
|
+
config;
|
|
3711
|
+
replicationMode;
|
|
3712
|
+
constructor(streamId, config, replicationMode = "INCREMENTAL") {
|
|
3713
|
+
this.streamId = streamId;
|
|
3714
|
+
this.config = config;
|
|
3715
|
+
this.replicationMode = replicationMode;
|
|
3716
|
+
}
|
|
3717
|
+
async transformFile(downloadedPath, _entry) {
|
|
3718
|
+
return downloadedPath;
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
|
|
3722
|
+
// src/sdk/models/asset-tap/asset-tap.ts
|
|
3723
|
+
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
3724
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3725
|
+
var logPrefix6 = "[AssetTap]";
|
|
3726
|
+
function inferContentType(filePath, fallback) {
|
|
3727
|
+
const mime = getMimeType(filePath);
|
|
3728
|
+
return mime !== "application/octet-stream" ? mime : fallback;
|
|
3729
|
+
}
|
|
3730
|
+
function deriveManifestMode(streams) {
|
|
3731
|
+
const modes = new Set(streams.map((s) => s.replicationMode));
|
|
3732
|
+
if (modes.size > 1) {
|
|
3733
|
+
logger.warn(`${logPrefix6} Streams have mixed replication modes (${[...modes].join(", ")}). Manifest will report "FULL".`);
|
|
3734
|
+
return "FULL";
|
|
3735
|
+
}
|
|
3736
|
+
return streams[0]?.replicationMode ?? "INCREMENTAL";
|
|
3737
|
+
}
|
|
3738
|
+
var AssetTap = class {
|
|
3739
|
+
config;
|
|
3740
|
+
outputDir;
|
|
3741
|
+
streams = [];
|
|
3742
|
+
concurrency;
|
|
3743
|
+
target;
|
|
3744
|
+
constructor(target, config, outputDir = "out", concurrency = 5) {
|
|
3745
|
+
this.target = target;
|
|
3746
|
+
this.config = config;
|
|
3747
|
+
this.outputDir = outputDir;
|
|
3748
|
+
this.concurrency = concurrency;
|
|
3749
|
+
}
|
|
3750
|
+
async sync() {
|
|
3751
|
+
await this.init();
|
|
3752
|
+
const dryRun = isDryRun();
|
|
3753
|
+
const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
|
|
3754
|
+
if (dryRun) {
|
|
3755
|
+
logger.info(`${logPrefix6} [DRY_RUN] mode active \u2014 skipping CDN checks and uploads`);
|
|
3756
|
+
await import_fs_extra4.default.emptyDir(this.outputDir);
|
|
3757
|
+
if (dryRunLimit !== void 0) {
|
|
3758
|
+
logger.info(`${logPrefix6} [DRY_RUN] Limit: ${dryRunLimit} assets per stream`);
|
|
3759
|
+
}
|
|
3760
|
+
} else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
|
|
3761
|
+
logger.warn(`${logPrefix6} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
|
|
3762
|
+
}
|
|
3763
|
+
const tmpDir2 = import_node_path4.default.join(this.outputDir, "tmp");
|
|
3764
|
+
await import_fs_extra4.default.ensureDir(tmpDir2);
|
|
3765
|
+
const streamManifests = [];
|
|
3766
|
+
let entryIndex = 0;
|
|
3767
|
+
let totalSummary = { total: 0, uploaded: 0, skipped: 0, errors: 0 };
|
|
3768
|
+
for (const stream of this.streams) {
|
|
3769
|
+
logger.info(`${logPrefix6} Processing stream: ${stream.streamId} (mode=${stream.replicationMode}, concurrency=${this.concurrency})`);
|
|
3770
|
+
const assetEntries = [];
|
|
3771
|
+
let streamAssetCount = 0;
|
|
3772
|
+
await processFromAsyncIterable(
|
|
3773
|
+
stream.listAssets(),
|
|
3774
|
+
async (entry) => {
|
|
3775
|
+
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
3776
|
+
return "stop";
|
|
3777
|
+
}
|
|
3778
|
+
logger.debug(`${logPrefix6} Processing entry: ${entry.sourcePath}`);
|
|
3779
|
+
if (stream.replicationMode === "INCREMENTAL" && !dryRun) {
|
|
3780
|
+
const shouldSync = await this.target.shouldSync(entry);
|
|
3781
|
+
if (!shouldSync) {
|
|
3782
|
+
logger.debug(`${logPrefix6} Skipping ${entry.sourcePath} (up-to-date)`);
|
|
3783
|
+
assetEntries.push({
|
|
3784
|
+
sourcePath: entry.sourcePath,
|
|
3785
|
+
destinationPath: entry.destinationPath,
|
|
3786
|
+
downloadedPath: "",
|
|
3787
|
+
transformedPath: "",
|
|
3788
|
+
size: 0,
|
|
3789
|
+
contentType: entry.contentType,
|
|
3790
|
+
status: "skipped",
|
|
3791
|
+
transformed: false
|
|
3792
|
+
});
|
|
3793
|
+
return "continue";
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
const fileName = `${entryIndex}_${import_node_path4.default.basename(entry.sourcePath)}`;
|
|
3797
|
+
const downloadedPath = import_node_path4.default.join(tmpDir2, fileName);
|
|
3798
|
+
let uploadPath = downloadedPath;
|
|
3799
|
+
entryIndex++;
|
|
3800
|
+
streamAssetCount++;
|
|
3801
|
+
try {
|
|
3802
|
+
await stream.downloadEntry(entry, downloadedPath);
|
|
3803
|
+
uploadPath = await stream.transformFile(downloadedPath, entry);
|
|
3804
|
+
const wasTransformed = uploadPath !== downloadedPath;
|
|
3805
|
+
const stat = await import_fs_extra4.default.stat(uploadPath);
|
|
3806
|
+
const processed = {
|
|
3807
|
+
entry,
|
|
3808
|
+
downloadedPath,
|
|
3809
|
+
uploadPath,
|
|
3810
|
+
wasTransformed,
|
|
3811
|
+
size: stat.size,
|
|
3812
|
+
contentType: wasTransformed ? inferContentType(uploadPath, entry.contentType) : entry.contentType
|
|
3813
|
+
};
|
|
3814
|
+
if (!dryRun) {
|
|
3815
|
+
await this.target.uploadAsset(processed);
|
|
3816
|
+
} else {
|
|
3817
|
+
const inspectPath = import_node_path4.default.join(this.outputDir, stream.streamId, entry.destinationPath);
|
|
3818
|
+
await import_fs_extra4.default.ensureDir(import_node_path4.default.dirname(inspectPath));
|
|
3819
|
+
await import_fs_extra4.default.copy(uploadPath, inspectPath);
|
|
3820
|
+
}
|
|
3821
|
+
assetEntries.push({
|
|
3822
|
+
sourcePath: entry.sourcePath,
|
|
3823
|
+
destinationPath: entry.destinationPath,
|
|
3824
|
+
downloadedPath,
|
|
3825
|
+
transformedPath: wasTransformed ? uploadPath : "",
|
|
3826
|
+
size: processed.size,
|
|
3827
|
+
contentType: processed.contentType,
|
|
3828
|
+
status: "uploaded",
|
|
3829
|
+
transformed: wasTransformed
|
|
3830
|
+
});
|
|
3831
|
+
logger.info(`${logPrefix6} ${dryRun ? "[DRY_RUN] Processed" : "Uploaded"} ${entry.sourcePath} \u2192 ${entry.destinationPath}`);
|
|
3832
|
+
} catch (err) {
|
|
3833
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3834
|
+
logger.error(`${logPrefix6} Failed to process ${entry.sourcePath}: ${message}`);
|
|
3835
|
+
assetEntries.push({
|
|
3836
|
+
sourcePath: entry.sourcePath,
|
|
3837
|
+
destinationPath: entry.destinationPath,
|
|
3838
|
+
downloadedPath: "",
|
|
3839
|
+
transformedPath: "",
|
|
3840
|
+
size: 0,
|
|
3841
|
+
contentType: entry.contentType,
|
|
3842
|
+
status: "error",
|
|
3843
|
+
transformed: false,
|
|
3844
|
+
error: message
|
|
3845
|
+
});
|
|
3846
|
+
} finally {
|
|
3847
|
+
await import_fs_extra4.default.remove(downloadedPath).catch(() => void 0);
|
|
3848
|
+
if (uploadPath !== downloadedPath) {
|
|
3849
|
+
await import_fs_extra4.default.remove(uploadPath).catch(() => void 0);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
3853
|
+
logger.info(`${logPrefix6} [DRY_RUN] Reached limit of ${dryRunLimit} for stream "${stream.streamId}", stopping.`);
|
|
3854
|
+
return "stop";
|
|
3855
|
+
}
|
|
3856
|
+
return "continue";
|
|
3857
|
+
},
|
|
3858
|
+
this.concurrency
|
|
3859
|
+
);
|
|
3860
|
+
const streamSummary = {
|
|
3861
|
+
total: assetEntries.length,
|
|
3862
|
+
uploaded: assetEntries.filter((a) => a.status === "uploaded").length,
|
|
3863
|
+
skipped: assetEntries.filter((a) => a.status === "skipped").length,
|
|
3864
|
+
errors: assetEntries.filter((a) => a.status === "error").length
|
|
3865
|
+
};
|
|
3866
|
+
totalSummary.total += streamSummary.total;
|
|
3867
|
+
totalSummary.uploaded += streamSummary.uploaded;
|
|
3868
|
+
totalSummary.skipped += streamSummary.skipped;
|
|
3869
|
+
totalSummary.errors += streamSummary.errors;
|
|
3870
|
+
streamManifests.push({
|
|
3871
|
+
streamId: stream.streamId,
|
|
3872
|
+
mode: stream.replicationMode,
|
|
3873
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3874
|
+
assets: assetEntries,
|
|
3875
|
+
summary: streamSummary
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
const manifest = {
|
|
3879
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3880
|
+
mode: deriveManifestMode(this.streams),
|
|
3881
|
+
streams: streamManifests,
|
|
3882
|
+
summary: totalSummary
|
|
3883
|
+
};
|
|
3884
|
+
await import_fs_extra4.default.ensureDir(this.outputDir);
|
|
3885
|
+
await import_fs_extra4.default.writeJson(import_node_path4.default.join(this.outputDir, "manifest.json"), manifest, { spaces: 2 });
|
|
3886
|
+
if (!dryRun) {
|
|
3887
|
+
await this.target.complete();
|
|
3888
|
+
}
|
|
3889
|
+
logger.info(`${logPrefix6} Sync complete. Uploaded=${totalSummary.uploaded} Skipped=${totalSummary.skipped} Errors=${totalSummary.errors}`);
|
|
3890
|
+
return manifest;
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
|
|
3894
|
+
// src/sdk/models/asset-tap/image-transform.ts
|
|
3895
|
+
var import_sharp = __toESM(require("sharp"));
|
|
3896
|
+
var import_node_path5 = __toESM(require("path"));
|
|
3897
|
+
var SHARP_GRAVITY = {
|
|
3898
|
+
center: "centre",
|
|
3899
|
+
top: "north",
|
|
3900
|
+
bottom: "south",
|
|
3901
|
+
left: "west",
|
|
3902
|
+
right: "east",
|
|
3903
|
+
"top left": "northwest",
|
|
3904
|
+
"top right": "northeast",
|
|
3905
|
+
"bottom left": "southwest",
|
|
3906
|
+
"bottom right": "southeast"
|
|
3907
|
+
};
|
|
3908
|
+
function parseBackground(bg) {
|
|
3909
|
+
if (bg === void 0 || bg === "none" || bg === "transparent") {
|
|
3910
|
+
return { r: 0, g: 0, b: 0, alpha: 0 };
|
|
3911
|
+
}
|
|
3912
|
+
return bg;
|
|
3913
|
+
}
|
|
3914
|
+
var ImageTransform = class {
|
|
3915
|
+
/**
|
|
3916
|
+
* Converts an image to WebP format using sharp (libvips).
|
|
3917
|
+
*
|
|
3918
|
+
* The output file is written to the same directory as the input,
|
|
3919
|
+
* with the extension replaced by `.webp`.
|
|
3920
|
+
*
|
|
3921
|
+
* @param inputPath Path to the source image file.
|
|
3922
|
+
* @param options Optional resize / padding parameters.
|
|
3923
|
+
* @returns Path to the produced `.webp` file.
|
|
3924
|
+
*/
|
|
3925
|
+
static async toWebp(inputPath, options) {
|
|
3926
|
+
const dir = import_node_path5.default.dirname(inputPath);
|
|
3927
|
+
const basename = import_node_path5.default.basename(inputPath, import_node_path5.default.extname(inputPath));
|
|
3928
|
+
const outputPath = import_node_path5.default.join(dir, `${basename}.webp`);
|
|
3929
|
+
let pipeline = (0, import_sharp.default)(inputPath);
|
|
3930
|
+
const hasSize = options.width !== void 0 && options.height !== void 0;
|
|
3931
|
+
if (hasSize) {
|
|
3932
|
+
const position = options.gravity !== void 0 ? SHARP_GRAVITY[options.gravity] ?? options.gravity : void 0;
|
|
3933
|
+
if (options.extent === true) {
|
|
3934
|
+
pipeline = pipeline.resize(options.width, options.height, {
|
|
3935
|
+
fit: "contain",
|
|
3936
|
+
position,
|
|
3937
|
+
background: parseBackground(options.background)
|
|
3938
|
+
});
|
|
3939
|
+
} else {
|
|
3940
|
+
pipeline = pipeline.resize(options.width, options.height, {
|
|
3941
|
+
fit: "inside",
|
|
3942
|
+
position
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
pipeline = pipeline.webp(
|
|
3947
|
+
options.quality !== void 0 ? { quality: options.quality } : {}
|
|
3948
|
+
);
|
|
3949
|
+
await pipeline.toFile(outputPath);
|
|
3950
|
+
return outputPath;
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
|
|
3954
|
+
// src/sdk/models/asset-target/asset-target.ts
|
|
3955
|
+
var AssetTarget = class {
|
|
3956
|
+
config;
|
|
3957
|
+
constructor(config) {
|
|
3958
|
+
this.config = config;
|
|
3959
|
+
}
|
|
3960
|
+
async complete() {
|
|
3961
|
+
}
|
|
3962
|
+
};
|
|
3963
|
+
|
|
3310
3964
|
// src/targets/bigquery/helpers.ts
|
|
3311
3965
|
var safeColumnName = (key) => {
|
|
3312
3966
|
let returnString = key;
|
|
@@ -3343,7 +3997,9 @@ var import_decimal = __toESM(require("decimal.js"));
|
|
|
3343
3997
|
var import_dayjs5 = __toESM(require("dayjs"));
|
|
3344
3998
|
var validateDateRange = (record, schema) => {
|
|
3345
3999
|
Object.keys(schema).forEach((key) => {
|
|
3346
|
-
const
|
|
4000
|
+
const field = schema[key];
|
|
4001
|
+
if (!field) return;
|
|
4002
|
+
const { format: format8 } = field;
|
|
3347
4003
|
if (format8 === "date-time") {
|
|
3348
4004
|
if (record[key]) {
|
|
3349
4005
|
const formattedDate = (0, import_dayjs5.default)(record[key]).format(defaultDateTimeFormat);
|
|
@@ -3573,6 +4229,12 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
3573
4229
|
});
|
|
3574
4230
|
};
|
|
3575
4231
|
getReplaceQueries = () => {
|
|
4232
|
+
if (this.primaryKeys.length === 0) {
|
|
4233
|
+
return [
|
|
4234
|
+
`TRUNCATE TABLE ${this.sqlTableId};`,
|
|
4235
|
+
...this.getAppendQueries()
|
|
4236
|
+
];
|
|
4237
|
+
}
|
|
3576
4238
|
return [
|
|
3577
4239
|
`TRUNCATE TABLE ${this.sqlTableId};`,
|
|
3578
4240
|
...this.getMergeQueries()
|
|
@@ -3860,6 +4522,46 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3860
4522
|
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
3861
4523
|
};
|
|
3862
4524
|
|
|
4525
|
+
// src/targets/cdn/main.ts
|
|
4526
|
+
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
4527
|
+
var logPrefix7 = "[CdnAssetTarget]";
|
|
4528
|
+
var CdnAssetTarget = class extends AssetTarget {
|
|
4529
|
+
cdnService;
|
|
4530
|
+
constructor(config) {
|
|
4531
|
+
super(config);
|
|
4532
|
+
this.cdnService = new CdnService(config);
|
|
4533
|
+
}
|
|
4534
|
+
async shouldSync(entry) {
|
|
4535
|
+
const metadata = await this.cdnService.getFileMetadata(getCdnId(this.config.cdnId), entry.destinationPath);
|
|
4536
|
+
if (!metadata.exists) {
|
|
4537
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} not in CDN \u2192 will sync`);
|
|
4538
|
+
return true;
|
|
4539
|
+
}
|
|
4540
|
+
if (entry.lastModified === void 0) {
|
|
4541
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} source has no lastModified \u2192 will sync`);
|
|
4542
|
+
return true;
|
|
4543
|
+
}
|
|
4544
|
+
if (metadata.lastModified === null) {
|
|
4545
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} CDN has no lastModified \u2192 will sync`);
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
const sourceIsNewer = entry.lastModified > metadata.lastModified;
|
|
4549
|
+
if (!sourceIsNewer) {
|
|
4550
|
+
logger.debug(`${logPrefix7} Skipping ${entry.destinationPath} (CDN is up-to-date)`);
|
|
4551
|
+
}
|
|
4552
|
+
return sourceIsNewer;
|
|
4553
|
+
}
|
|
4554
|
+
async uploadAsset(asset) {
|
|
4555
|
+
const fileBuffer = await import_fs_extra5.default.readFile(asset.uploadPath);
|
|
4556
|
+
await this.cdnService.uploadFile(
|
|
4557
|
+
getCdnId(this.config.cdnId),
|
|
4558
|
+
asset.entry.destinationPath,
|
|
4559
|
+
fileBuffer
|
|
4560
|
+
);
|
|
4561
|
+
logger.info(`${logPrefix7} Uploaded ${asset.entry.destinationPath} (${fileBuffer.length} bytes)`);
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4564
|
+
|
|
3863
4565
|
// src/state-providers/gcs/main.ts
|
|
3864
4566
|
var import_util6 = require("util");
|
|
3865
4567
|
var GCSStateProvider = class {
|
|
@@ -3901,9 +4603,14 @@ var GCSStateProvider = class {
|
|
|
3901
4603
|
0 && (module.exports = {
|
|
3902
4604
|
ALL_STREAM_ID_KEYWORD,
|
|
3903
4605
|
API_CALLS_METRIC_NAME,
|
|
4606
|
+
AssetStream,
|
|
4607
|
+
AssetTap,
|
|
4608
|
+
AssetTarget,
|
|
3904
4609
|
Authenticator,
|
|
3905
4610
|
BATCH_INTERVAL_MS,
|
|
3906
4611
|
BigQueryTarget,
|
|
4612
|
+
CdnAssetTarget,
|
|
4613
|
+
CdnService,
|
|
3907
4614
|
CloudStorageService,
|
|
3908
4615
|
CounterMetric,
|
|
3909
4616
|
CsvExtractionConfigBuilder,
|
|
@@ -3917,6 +4624,8 @@ var GCSStateProvider = class {
|
|
|
3917
4624
|
FileTap,
|
|
3918
4625
|
GCSStateProvider,
|
|
3919
4626
|
ITarget,
|
|
4627
|
+
ImageTransform,
|
|
4628
|
+
MIME_TYPES,
|
|
3920
4629
|
MissingFieldInSchemaError,
|
|
3921
4630
|
MissingSchemaError,
|
|
3922
4631
|
RESTStream,
|
|
@@ -3938,6 +4647,7 @@ var GCSStateProvider = class {
|
|
|
3938
4647
|
_greaterThan,
|
|
3939
4648
|
addWhalyFields,
|
|
3940
4649
|
checkCsvHeaderRow,
|
|
4650
|
+
collectPaginated,
|
|
3941
4651
|
countCsvLines,
|
|
3942
4652
|
createCellReference,
|
|
3943
4653
|
createCsvStreamConfig,
|
|
@@ -3945,6 +4655,7 @@ var GCSStateProvider = class {
|
|
|
3945
4655
|
createExcelStreamConfig,
|
|
3946
4656
|
createTemporaryFileStream,
|
|
3947
4657
|
csvFieldsToJsonSchema,
|
|
4658
|
+
downloadFiles,
|
|
3948
4659
|
excelColumnToIndex,
|
|
3949
4660
|
excelFieldsToJsonSchema,
|
|
3950
4661
|
excelRowToIndex,
|
|
@@ -3957,29 +4668,40 @@ var GCSStateProvider = class {
|
|
|
3957
4668
|
findAllMatchingConfigs,
|
|
3958
4669
|
flattenSchema,
|
|
3959
4670
|
getAllMetrics,
|
|
4671
|
+
getApiEndpoint,
|
|
3960
4672
|
getAxiosInstance,
|
|
4673
|
+
getCdnId,
|
|
3961
4674
|
getCounterMetric,
|
|
3962
4675
|
getCounterMetrics,
|
|
3963
4676
|
getDownloadFileApiCall,
|
|
4677
|
+
getDryRunLimit,
|
|
3964
4678
|
getJSONApiCall,
|
|
3965
4679
|
getJSONApiCallWithFullResponse,
|
|
4680
|
+
getMimeType,
|
|
3966
4681
|
getRawJSONApiCall,
|
|
4682
|
+
getServiceAccountKey,
|
|
3967
4683
|
gracefulExit,
|
|
3968
4684
|
haltAndCatchFire,
|
|
3969
4685
|
incrementStreamState,
|
|
3970
4686
|
indexToExcelColumn,
|
|
3971
4687
|
indexToExcelRow,
|
|
4688
|
+
isDryRun,
|
|
4689
|
+
listFilesRecursively,
|
|
3972
4690
|
loadJson,
|
|
3973
4691
|
loadSchema,
|
|
3974
4692
|
logger,
|
|
4693
|
+
optionalEnv,
|
|
3975
4694
|
parseCellReference,
|
|
3976
4695
|
postFormDataApiCall,
|
|
3977
4696
|
postJSONApiCall,
|
|
3978
4697
|
postUrlEncodedApiCall,
|
|
3979
4698
|
printMemoryFootprint,
|
|
3980
4699
|
processFileStreams,
|
|
4700
|
+
processFromAsyncIterable,
|
|
3981
4701
|
removeParasiteProperties,
|
|
4702
|
+
requireEnv,
|
|
3982
4703
|
rowGeneratorFromCsv,
|
|
4704
|
+
runWithConcurrency,
|
|
3983
4705
|
safePath,
|
|
3984
4706
|
unzip,
|
|
3985
4707
|
validateExcelConfig,
|