@whaly/connector-sdk 0.3.3 → 0.3.5
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 +299 -4
- package/dist/index.d.ts +299 -4
- package/dist/index.js +772 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +754 -65
- 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);
|
|
@@ -1819,15 +2006,15 @@ var import_fs_extra = __toESM(require("fs-extra"));
|
|
|
1819
2006
|
var import_uniqueId = __toESM(require("lodash/uniqueId.js"));
|
|
1820
2007
|
var createTemporaryFileStream = (streamId) => {
|
|
1821
2008
|
import_fs_extra.default.ensureDirSync("./tmp");
|
|
1822
|
-
const
|
|
1823
|
-
const writeStream = import_fs_extra.default.createWriteStream(
|
|
2009
|
+
const path7 = `./tmp/${safePath(streamId)}-${(0, import_uniqueId.default)()}.tmp`;
|
|
2010
|
+
const writeStream = import_fs_extra.default.createWriteStream(path7, { flags: "w" });
|
|
1824
2011
|
return {
|
|
1825
2012
|
stream: writeStream,
|
|
1826
|
-
path:
|
|
2013
|
+
path: path7
|
|
1827
2014
|
};
|
|
1828
2015
|
};
|
|
1829
|
-
function safePath(
|
|
1830
|
-
let formattedPath =
|
|
2016
|
+
function safePath(path7) {
|
|
2017
|
+
let formattedPath = path7;
|
|
1831
2018
|
formattedPath = formattedPath.replace(/^The\s+/gi, "");
|
|
1832
2019
|
formattedPath = formattedPath.replace(/\s+(?:\(|\[)?Dis(?:c|k) (?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d)+(?:\)|\])?/gi, "");
|
|
1833
2020
|
formattedPath = formattedPath.replace(/\s+/gi, "_");
|
|
@@ -1953,10 +2140,13 @@ var StreamState = class {
|
|
|
1953
2140
|
// src/sdk/models/target/target.ts
|
|
1954
2141
|
var import_async_mutex = require("async-mutex");
|
|
1955
2142
|
var import_bluebird3 = __toESM(require("bluebird"));
|
|
2143
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
2144
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1956
2145
|
var semaphore = new import_async_mutex.Semaphore(1);
|
|
1957
2146
|
var ITarget = class _ITarget {
|
|
1958
2147
|
config;
|
|
1959
2148
|
syncedAtColumnName;
|
|
2149
|
+
syncedAtColumnUseLegacyStringType;
|
|
1960
2150
|
schemaHooks;
|
|
1961
2151
|
syncTime;
|
|
1962
2152
|
stateProvider;
|
|
@@ -1973,6 +2163,7 @@ var ITarget = class _ITarget {
|
|
|
1973
2163
|
constructor(config, stateProvider) {
|
|
1974
2164
|
this.config = config;
|
|
1975
2165
|
this.syncedAtColumnName = config.syncedAtColumnName ?? DEFAULT_SYNCED_AT_COLUMN;
|
|
2166
|
+
this.syncedAtColumnUseLegacyStringType = config.syncedAtColumnUseLegacyStringType ?? false;
|
|
1976
2167
|
this.stateProvider = stateProvider;
|
|
1977
2168
|
this.schemaHooks = [];
|
|
1978
2169
|
this.streams = {};
|
|
@@ -2037,7 +2228,7 @@ var ITarget = class _ITarget {
|
|
|
2037
2228
|
if (!stream) {
|
|
2038
2229
|
throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
|
|
2039
2230
|
}
|
|
2040
|
-
if (stream.getBatchedRowCount() % 1e4 === 0) {
|
|
2231
|
+
if (stream.getBatchedRowCount() % 1e4 === 0 && !isDryRun()) {
|
|
2041
2232
|
await semaphore.runExclusive(async () => {
|
|
2042
2233
|
const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
|
|
2043
2234
|
if (shouldUploadIntermediateBatch) {
|
|
@@ -2123,7 +2314,9 @@ var ITarget = class _ITarget {
|
|
|
2123
2314
|
const { hasBreakingChanges, changes } = this.analyzeSchemaChanges(prevSchema, newFlattenedSchema, streamId);
|
|
2124
2315
|
if (hasBreakingChanges) {
|
|
2125
2316
|
logger.info(`\u{1F525} StreamId=${streamId} - Detected BREAKING schema changes: ${changes.join(", ")}. Loading batched records before applying new schema.`);
|
|
2126
|
-
|
|
2317
|
+
if (!isDryRun()) {
|
|
2318
|
+
await _ITarget.uploadSingleStreamToWarehouse(prevStreamState);
|
|
2319
|
+
}
|
|
2127
2320
|
} else {
|
|
2128
2321
|
logger.info(`\u2705 StreamId=${streamId} - Detected NON-BREAKING schema changes: ${changes.join(", ")}. Continuing without uploading batched records.`);
|
|
2129
2322
|
}
|
|
@@ -2135,10 +2328,7 @@ var ITarget = class _ITarget {
|
|
|
2135
2328
|
throw new MissingSchemaError(`Stream: ${streamId} - Received SCHEMA message before stream state was initialized.`, streamId);
|
|
2136
2329
|
}
|
|
2137
2330
|
const schema = flattenSchema(streamId, message.schema);
|
|
2138
|
-
schema[this.syncedAtColumnName] = {
|
|
2139
|
-
format: "date-time",
|
|
2140
|
-
type: ["null", "string"]
|
|
2141
|
-
};
|
|
2331
|
+
schema[this.syncedAtColumnName] = this.syncedAtColumnUseLegacyStringType ? { type: ["null", "string"] } : { format: "date-time", type: ["null", "string"] };
|
|
2142
2332
|
const dbSync = streamState.getDbSync();
|
|
2143
2333
|
Object.keys(schema).forEach((fieldUnsafeKey) => {
|
|
2144
2334
|
const fieldDef = schema[fieldUnsafeKey];
|
|
@@ -2155,31 +2345,35 @@ var ITarget = class _ITarget {
|
|
|
2155
2345
|
Received SCHEMA message: ${JSON.stringify(message)}`);
|
|
2156
2346
|
}
|
|
2157
2347
|
streamState.setKeyProperties(message.keyProperties);
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2175
|
-
const input = {
|
|
2176
|
-
databaseName,
|
|
2177
|
-
schemaName,
|
|
2178
|
-
tableName,
|
|
2179
|
-
message: formattedRelationshipMessage
|
|
2348
|
+
if (!isDryRun()) {
|
|
2349
|
+
await dbSync.updateSchemaInWarehouse(schema, message.keyProperties);
|
|
2350
|
+
const {
|
|
2351
|
+
database: databaseName,
|
|
2352
|
+
schema: schemaName
|
|
2353
|
+
} = this.config;
|
|
2354
|
+
const tableName = this.genTableName(streamId);
|
|
2355
|
+
const formattedRelationshipMessage = {
|
|
2356
|
+
...message,
|
|
2357
|
+
keyProperties: message.keyProperties.map((k) => {
|
|
2358
|
+
const translation = streamState.getDbSync().renamedColumnStore.getColumnTranslation(message.stream, k);
|
|
2359
|
+
if (translation) {
|
|
2360
|
+
return translation;
|
|
2361
|
+
}
|
|
2362
|
+
return k;
|
|
2363
|
+
})
|
|
2180
2364
|
};
|
|
2181
|
-
await
|
|
2182
|
-
|
|
2365
|
+
await import_bluebird3.default.map(this.schemaHooks, async (hook) => {
|
|
2366
|
+
const input = {
|
|
2367
|
+
databaseName,
|
|
2368
|
+
schemaName,
|
|
2369
|
+
tableName,
|
|
2370
|
+
message: formattedRelationshipMessage
|
|
2371
|
+
};
|
|
2372
|
+
await hook.writeSchema(input);
|
|
2373
|
+
}, { concurrency: 3 });
|
|
2374
|
+
} else {
|
|
2375
|
+
logger.info(`[DRY_RUN] Stream: ${streamId} - Skipping warehouse schema update and schema hooks`);
|
|
2376
|
+
}
|
|
2183
2377
|
return Promise.resolve();
|
|
2184
2378
|
} catch (err) {
|
|
2185
2379
|
logger.error(`Error when handling schema event.`, err);
|
|
@@ -2200,6 +2394,29 @@ var ITarget = class _ITarget {
|
|
|
2200
2394
|
};
|
|
2201
2395
|
complete = async () => {
|
|
2202
2396
|
try {
|
|
2397
|
+
if (isDryRun()) {
|
|
2398
|
+
logger.info(`[DRY_RUN] Data Extraction is complete. Copying tmp files to out/ instead of loading to warehouse.`);
|
|
2399
|
+
const outDir = import_node_path2.default.resolve("out");
|
|
2400
|
+
await import_fs_extra2.default.ensureDir(outDir);
|
|
2401
|
+
for (const streamId of Object.keys(this.streams)) {
|
|
2402
|
+
const streamState = this.streams[streamId];
|
|
2403
|
+
if (!streamState || streamState.getBatchedRowCount() === 0) continue;
|
|
2404
|
+
const fileToLoad = streamState.getFileToLoad();
|
|
2405
|
+
await new Promise((resolve, reject) => {
|
|
2406
|
+
fileToLoad.stream.end(() => {
|
|
2407
|
+
fileToLoad.stream.close((err) => {
|
|
2408
|
+
if (err) reject(err);
|
|
2409
|
+
else resolve();
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
});
|
|
2413
|
+
const destPath = import_node_path2.default.join(outDir, `${safePath(streamId)}.ndjson`);
|
|
2414
|
+
await import_fs_extra2.default.copy(fileToLoad.path, destPath);
|
|
2415
|
+
logger.info(`[DRY_RUN] Stream: ${streamId} - Copied ${streamState.getBatchedRowCount()} records to ${destPath}`);
|
|
2416
|
+
await import_fs_extra2.default.remove(fileToLoad.path);
|
|
2417
|
+
}
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2203
2420
|
logger.info(`\u{1F44D} Data Extraction is complete. Will load remaining batched stream records.`);
|
|
2204
2421
|
await this.loadAllStreamsInWarehouse({ isFinalLoad: true });
|
|
2205
2422
|
return Promise.resolve();
|
|
@@ -2632,7 +2849,7 @@ var formatHexComparison = (expected, actual) => {
|
|
|
2632
2849
|
Expected: "${expected}" (${expectedInfo.length} chars) [${expectedInfo.hex}]
|
|
2633
2850
|
Actual: "${actual}" (${actualInfo.length} chars) [${actualInfo.hex}]`;
|
|
2634
2851
|
};
|
|
2635
|
-
async function* rowGeneratorFromCsv(
|
|
2852
|
+
async function* rowGeneratorFromCsv(path7, fileConfig) {
|
|
2636
2853
|
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2637
2854
|
const configKeys = isGeneratorConfigArray ? [] : Object.keys(fileConfig.fields);
|
|
2638
2855
|
const csvOptions = { separator: fileConfig.separator };
|
|
@@ -2644,10 +2861,10 @@ async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
|
2644
2861
|
let finalInputStream;
|
|
2645
2862
|
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2646
2863
|
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2647
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2864
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2648
2865
|
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2649
2866
|
} else {
|
|
2650
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2867
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2651
2868
|
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2652
2869
|
}
|
|
2653
2870
|
finalInputStream.pipe(csvStream);
|
|
@@ -2689,7 +2906,7 @@ async function countCsvLines(filePath) {
|
|
|
2689
2906
|
}).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
|
|
2690
2907
|
});
|
|
2691
2908
|
}
|
|
2692
|
-
var checkCsvHeaderRow = async (
|
|
2909
|
+
var checkCsvHeaderRow = async (path7, fileConfig) => {
|
|
2693
2910
|
return new Promise((resolve, reject) => {
|
|
2694
2911
|
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
2695
2912
|
console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
|
|
@@ -2706,10 +2923,10 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2706
2923
|
let finalInputStream;
|
|
2707
2924
|
const encodingToUse = fileConfig.encoding ? fileConfig.encoding.toLowerCase() : null;
|
|
2708
2925
|
if (encodingToUse && encodingToUse !== "utf-8" && encodingToUse !== "utf8" && iconv.encodingExists(encodingToUse)) {
|
|
2709
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2926
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2710
2927
|
finalInputStream = initialReadStream.pipe(iconv.decodeStream(encodingToUse));
|
|
2711
2928
|
} else {
|
|
2712
|
-
initialReadStream = (0, import_fs2.createReadStream)(
|
|
2929
|
+
initialReadStream = (0, import_fs2.createReadStream)(path7);
|
|
2713
2930
|
finalInputStream = initialReadStream.pipe(createStripBomTransform());
|
|
2714
2931
|
}
|
|
2715
2932
|
let isFirstLine = true;
|
|
@@ -2735,7 +2952,7 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2735
2952
|
i,
|
|
2736
2953
|
col,
|
|
2737
2954
|
headers[i] || "<undefined>",
|
|
2738
|
-
|
|
2955
|
+
path7
|
|
2739
2956
|
));
|
|
2740
2957
|
}
|
|
2741
2958
|
});
|
|
@@ -2752,7 +2969,7 @@ var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
|
2752
2969
|
throw new Error((0, import_util3.format)(
|
|
2753
2970
|
`CSV missing expected column '%s' in file: %s`,
|
|
2754
2971
|
col,
|
|
2755
|
-
|
|
2972
|
+
path7
|
|
2756
2973
|
));
|
|
2757
2974
|
}
|
|
2758
2975
|
});
|
|
@@ -2778,7 +2995,7 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2778
2995
|
try {
|
|
2779
2996
|
const firstRow = data[0];
|
|
2780
2997
|
if (!firstRow) {
|
|
2781
|
-
|
|
2998
|
+
logger.info(`${logPrefix2} No data to write to CSV file=%s`, fileName);
|
|
2782
2999
|
return;
|
|
2783
3000
|
}
|
|
2784
3001
|
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
@@ -2788,7 +3005,7 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2788
3005
|
})
|
|
2789
3006
|
});
|
|
2790
3007
|
await csvWriter.writeRecords(data);
|
|
2791
|
-
|
|
3008
|
+
logger.info(`${logPrefix2} CSV file=%s written successfully`, fileName);
|
|
2792
3009
|
} catch (err) {
|
|
2793
3010
|
if (err instanceof Error) {
|
|
2794
3011
|
throw new Error((0, import_util4.format)(`error while writing csv file=%s, err: %s`, fileName, err.message));
|
|
@@ -2796,15 +3013,17 @@ var writeDataToCsv = async (fileName, data) => {
|
|
|
2796
3013
|
throw err;
|
|
2797
3014
|
}
|
|
2798
3015
|
};
|
|
2799
|
-
var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
3016
|
+
var writeGeneratorToCSV = async (generator, outputFileName, options) => {
|
|
3017
|
+
const batchSize = options?.batchSize ?? 1e4;
|
|
3018
|
+
const alwaysQuote = options?.alwaysQuote ?? true;
|
|
2800
3019
|
try {
|
|
2801
3020
|
let rowCount = 0;
|
|
2802
3021
|
const firstDataRow = (await generator.next()).value;
|
|
2803
3022
|
if (!firstDataRow) {
|
|
2804
|
-
|
|
3023
|
+
logger.info(`${logPrefix2} [%s] first data row of csv empty, skipping`, outputFileName);
|
|
2805
3024
|
return 0;
|
|
2806
3025
|
}
|
|
2807
|
-
|
|
3026
|
+
logger.info(`${logPrefix2} [%s] inferring headers from first data row`, outputFileName);
|
|
2808
3027
|
const headers = Object.keys(firstDataRow).map((col) => {
|
|
2809
3028
|
return {
|
|
2810
3029
|
id: col,
|
|
@@ -2814,14 +3033,14 @@ var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
|
2814
3033
|
const csvWriter = (0, import_csv_writer.createObjectCsvWriter)({
|
|
2815
3034
|
path: outputFileName,
|
|
2816
3035
|
header: headers,
|
|
2817
|
-
alwaysQuote
|
|
3036
|
+
alwaysQuote
|
|
2818
3037
|
});
|
|
2819
|
-
|
|
3038
|
+
logger.info(`${logPrefix2} [%s] writing csv`, outputFileName);
|
|
2820
3039
|
let batch = [firstDataRow];
|
|
2821
3040
|
for await (const data of generator) {
|
|
2822
3041
|
batch.push(data);
|
|
2823
3042
|
rowCount++;
|
|
2824
|
-
if (batch.length
|
|
3043
|
+
if (batch.length >= batchSize) {
|
|
2825
3044
|
try {
|
|
2826
3045
|
await csvWriter.writeRecords(batch);
|
|
2827
3046
|
} catch (err) {
|
|
@@ -2839,8 +3058,10 @@ var writeGeneratorToCSV = async (generator, outputFileName) => {
|
|
|
2839
3058
|
batch = [];
|
|
2840
3059
|
}
|
|
2841
3060
|
}
|
|
2842
|
-
|
|
2843
|
-
|
|
3061
|
+
if (batch.length > 0) {
|
|
3062
|
+
await csvWriter.writeRecords(batch);
|
|
3063
|
+
}
|
|
3064
|
+
logger.info(`${logPrefix2} [%s] csv written successfully with %d rows`, outputFileName, rowCount);
|
|
2844
3065
|
return rowCount;
|
|
2845
3066
|
} catch (err) {
|
|
2846
3067
|
if (err instanceof Error) {
|
|
@@ -3126,9 +3347,65 @@ var FilePatterns = class {
|
|
|
3126
3347
|
};
|
|
3127
3348
|
|
|
3128
3349
|
// src/services/sftp.ts
|
|
3350
|
+
var import_node_path3 = __toESM(require("path"));
|
|
3351
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
3129
3352
|
var import_ssh2_sftp_client = __toESM(require("ssh2-sftp-client"));
|
|
3130
3353
|
var import_ssh2_sftp_client2 = __toESM(require("ssh2-sftp-client"));
|
|
3131
3354
|
var SftpService = new import_ssh2_sftp_client.default();
|
|
3355
|
+
var logPrefix3 = "[sftp]";
|
|
3356
|
+
async function listFilesRecursively(client, remotePath, options) {
|
|
3357
|
+
const recursive = options?.recursive ?? true;
|
|
3358
|
+
const extensions = options?.extensions?.map((e) => e.toLowerCase());
|
|
3359
|
+
const maxIterations = options?.maxIterations ?? 1e3;
|
|
3360
|
+
const results = [];
|
|
3361
|
+
const dirs = [remotePath];
|
|
3362
|
+
let iterations = 0;
|
|
3363
|
+
while (dirs.length > 0) {
|
|
3364
|
+
if (iterations++ >= maxIterations) {
|
|
3365
|
+
logger.warn(`${logPrefix3} listFilesRecursively reached maxIterations (${maxIterations}), stopping.`);
|
|
3366
|
+
break;
|
|
3367
|
+
}
|
|
3368
|
+
const currentDir = dirs.shift();
|
|
3369
|
+
let entries;
|
|
3370
|
+
try {
|
|
3371
|
+
entries = await client.list(currentDir);
|
|
3372
|
+
} catch (err) {
|
|
3373
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3374
|
+
logger.warn(`${logPrefix3} Failed to list ${currentDir}: ${msg}`);
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
for (const entry of entries) {
|
|
3378
|
+
if (entry.type === "d") {
|
|
3379
|
+
if (recursive && entry.name !== "." && entry.name !== "..") {
|
|
3380
|
+
dirs.push(import_node_path3.default.posix.join(currentDir, entry.name));
|
|
3381
|
+
}
|
|
3382
|
+
continue;
|
|
3383
|
+
}
|
|
3384
|
+
if (entry.type !== "-") continue;
|
|
3385
|
+
if (extensions) {
|
|
3386
|
+
const ext = import_node_path3.default.extname(entry.name).toLowerCase();
|
|
3387
|
+
if (!extensions.includes(ext)) continue;
|
|
3388
|
+
}
|
|
3389
|
+
const fullPath = import_node_path3.default.posix.join(currentDir, entry.name);
|
|
3390
|
+
results.push({ ...entry, name: fullPath });
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
logger.info(`${logPrefix3} listFilesRecursively found ${results.length} files under ${remotePath}`);
|
|
3394
|
+
return results;
|
|
3395
|
+
}
|
|
3396
|
+
async function downloadFiles(client, files, localDir, options) {
|
|
3397
|
+
const concurrency = options?.concurrency ?? 5;
|
|
3398
|
+
await import_fs_extra3.default.ensureDir(localDir);
|
|
3399
|
+
const tasks = files.map((file, index) => () => {
|
|
3400
|
+
const localName = `${index}_${import_node_path3.default.basename(file.name)}`;
|
|
3401
|
+
const localPath = import_node_path3.default.join(localDir, localName);
|
|
3402
|
+
logger.debug(`${logPrefix3} Downloading ${file.name} \u2192 ${localPath}`);
|
|
3403
|
+
return client.fastGet(file.name, localPath).then(() => localPath);
|
|
3404
|
+
});
|
|
3405
|
+
const results = await runWithConcurrency(tasks, concurrency);
|
|
3406
|
+
logger.info(`${logPrefix3} Downloaded ${results.length} files to ${localDir}`);
|
|
3407
|
+
return results;
|
|
3408
|
+
}
|
|
3132
3409
|
|
|
3133
3410
|
// src/services/cloud-storage.ts
|
|
3134
3411
|
var import_storage = require("@google-cloud/storage");
|
|
@@ -3136,7 +3413,7 @@ var import_util5 = require("util");
|
|
|
3136
3413
|
var pathModule = __toESM(require("path"));
|
|
3137
3414
|
var import_fs3 = require("fs");
|
|
3138
3415
|
var import_crypto = require("crypto");
|
|
3139
|
-
var
|
|
3416
|
+
var logPrefix4 = "[CloudStorageService]";
|
|
3140
3417
|
var tmpDir = "tmp";
|
|
3141
3418
|
var CloudStorageService = class {
|
|
3142
3419
|
storage;
|
|
@@ -3144,10 +3421,10 @@ var CloudStorageService = class {
|
|
|
3144
3421
|
processedSuffix;
|
|
3145
3422
|
supportedExtensions;
|
|
3146
3423
|
path;
|
|
3147
|
-
constructor(bucketName,
|
|
3424
|
+
constructor(bucketName, path7, opts) {
|
|
3148
3425
|
this.storage = new import_storage.Storage({ retryOptions: { autoRetry: true, maxRetries: 20 } });
|
|
3149
3426
|
this.bucket = this.storage.bucket(bucketName);
|
|
3150
|
-
this.path =
|
|
3427
|
+
this.path = path7;
|
|
3151
3428
|
this.processedSuffix = opts?.processedSuffix ?? ".processed";
|
|
3152
3429
|
this.supportedExtensions = opts?.supportedExtensions ?? [];
|
|
3153
3430
|
}
|
|
@@ -3188,7 +3465,7 @@ var CloudStorageService = class {
|
|
|
3188
3465
|
try {
|
|
3189
3466
|
const file = this.bucket.file(markerFileName);
|
|
3190
3467
|
await file.save((0, import_util5.format)("Marked file %s as processed", fileName));
|
|
3191
|
-
logger.info(`${
|
|
3468
|
+
logger.info(`${logPrefix4} Marker file ${markerFileName} created successfully.`);
|
|
3192
3469
|
} catch (err) {
|
|
3193
3470
|
if (err instanceof Error) {
|
|
3194
3471
|
throw new Error((0, import_util5.format)(`error while writing marker file=%s, err:%s`, markerFileName, err.message));
|
|
@@ -3212,7 +3489,7 @@ var CloudStorageService = class {
|
|
|
3212
3489
|
(0, import_fs3.unlinkSync)(destFilename);
|
|
3213
3490
|
}
|
|
3214
3491
|
await file.download({ destination: destFilename });
|
|
3215
|
-
logger.info(`${
|
|
3492
|
+
logger.info(`${logPrefix4} Downloaded ${fileName} to ${destFilename}`);
|
|
3216
3493
|
return destFilename;
|
|
3217
3494
|
} catch (err) {
|
|
3218
3495
|
if (err instanceof Error) {
|
|
@@ -3230,9 +3507,9 @@ var CloudStorageService = class {
|
|
|
3230
3507
|
*/
|
|
3231
3508
|
async uploadFile(localPath, destPath) {
|
|
3232
3509
|
try {
|
|
3233
|
-
logger.info(`${
|
|
3510
|
+
logger.info(`${logPrefix4} preparing to upload '%s' to '%s'`, localPath, destPath);
|
|
3234
3511
|
const [file] = await this.bucket.upload(localPath, { destination: destPath });
|
|
3235
|
-
logger.info(`${
|
|
3512
|
+
logger.info(`${logPrefix4} file %s has been successfully uploaded into %s`, localPath, destPath);
|
|
3236
3513
|
return file;
|
|
3237
3514
|
} catch (err) {
|
|
3238
3515
|
if (err instanceof Error) {
|
|
@@ -3308,6 +3585,376 @@ var unzip = async (zipFilePath, extractedPath) => {
|
|
|
3308
3585
|
return extractedPath;
|
|
3309
3586
|
};
|
|
3310
3587
|
|
|
3588
|
+
// src/services/cdn.ts
|
|
3589
|
+
var import_axios3 = __toESM(require("axios"));
|
|
3590
|
+
var import_axios_retry2 = __toESM(require("axios-retry"));
|
|
3591
|
+
var logPrefix5 = "[CdnService]";
|
|
3592
|
+
var MAX_RETRIES = 10;
|
|
3593
|
+
var CdnService = class {
|
|
3594
|
+
axiosClient;
|
|
3595
|
+
constructor(config) {
|
|
3596
|
+
const resolvedKey = getServiceAccountKey(config.serviceAccountKey);
|
|
3597
|
+
const resolvedEndpoint = getApiEndpoint(config.apiEndpoint);
|
|
3598
|
+
this.axiosClient = import_axios3.default.create({
|
|
3599
|
+
baseURL: resolvedEndpoint,
|
|
3600
|
+
timeout: 12e4,
|
|
3601
|
+
headers: {
|
|
3602
|
+
Authorization: `Bearer ${resolvedKey}`,
|
|
3603
|
+
Accept: "application/json"
|
|
3604
|
+
}
|
|
3605
|
+
});
|
|
3606
|
+
(0, import_axios_retry2.default)(this.axiosClient, {
|
|
3607
|
+
retries: MAX_RETRIES,
|
|
3608
|
+
retryCondition: (error) => {
|
|
3609
|
+
const status = error.response?.status ?? 0;
|
|
3610
|
+
return !error.response || status === 429 || status >= 500 && status < 600;
|
|
3611
|
+
},
|
|
3612
|
+
retryDelay: (retryCount, error) => {
|
|
3613
|
+
const retryAfter = error.response?.headers?.["retry-after"];
|
|
3614
|
+
if (retryAfter) {
|
|
3615
|
+
const seconds = Number(retryAfter);
|
|
3616
|
+
if (!isNaN(seconds) && seconds > 0) {
|
|
3617
|
+
logger.info(`${logPrefix5} Rate limited \u2014 waiting ${seconds}s (Retry-After header), attempt ${retryCount}/${MAX_RETRIES}`);
|
|
3618
|
+
return seconds * 1e3;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
const delay = import_axios_retry2.default.exponentialDelay(retryCount);
|
|
3622
|
+
logger.info(`${logPrefix5} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
|
|
3623
|
+
return delay;
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Returns metadata for a CDN file via a HEAD request.
|
|
3629
|
+
* If the file does not exist, returns `{ exists: false, lastModified: null, contentType: null }`.
|
|
3630
|
+
* Falls back to not-existing if HEAD is not supported (405).
|
|
3631
|
+
*/
|
|
3632
|
+
async getFileMetadata(cdnId, fileName) {
|
|
3633
|
+
const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
|
|
3634
|
+
try {
|
|
3635
|
+
const response = await this.axiosClient.head(url);
|
|
3636
|
+
const lastModifiedHeader = response.headers["last-modified"];
|
|
3637
|
+
const contentTypeHeader = response.headers["content-type"];
|
|
3638
|
+
return {
|
|
3639
|
+
exists: true,
|
|
3640
|
+
lastModified: lastModifiedHeader ? new Date(lastModifiedHeader) : null,
|
|
3641
|
+
contentType: contentTypeHeader ?? null
|
|
3642
|
+
};
|
|
3643
|
+
} catch (err) {
|
|
3644
|
+
if (import_axios3.default.isAxiosError(err)) {
|
|
3645
|
+
const status = err.response?.status;
|
|
3646
|
+
if (status === 404) return { exists: false, lastModified: null, contentType: null };
|
|
3647
|
+
if (status === 405) {
|
|
3648
|
+
logger.warn(`${logPrefix5} HEAD not supported for ${url}, treating file as absent`);
|
|
3649
|
+
return { exists: false, lastModified: null, contentType: null };
|
|
3650
|
+
}
|
|
3651
|
+
this.throwMeaningfulError(err);
|
|
3652
|
+
}
|
|
3653
|
+
throw err;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Checks whether a file already exists in the CDN via a HEAD request.
|
|
3658
|
+
* Returns true if the server responds 2xx, false on 404.
|
|
3659
|
+
* Falls back to false (treat as not existing) if HEAD is not supported (405).
|
|
3660
|
+
* No body is transferred.
|
|
3661
|
+
*/
|
|
3662
|
+
async fileExists(cdnId, fileName) {
|
|
3663
|
+
return (await this.getFileMetadata(cdnId, fileName)).exists;
|
|
3664
|
+
}
|
|
3665
|
+
/**
|
|
3666
|
+
* Uploads a file buffer to the Whaly CDN.
|
|
3667
|
+
* The PUT is idempotent — uploading the same fileName overwrites the existing file.
|
|
3668
|
+
*/
|
|
3669
|
+
async uploadFile(cdnId, fileName, fileBuffer) {
|
|
3670
|
+
const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
|
|
3671
|
+
try {
|
|
3672
|
+
logger.info(`${logPrefix5} Uploading ${fileName} (${fileBuffer.length} bytes) to CDN ${cdnId}`);
|
|
3673
|
+
const form = new FormData();
|
|
3674
|
+
form.append("file", new Blob([fileBuffer]), fileName);
|
|
3675
|
+
const response = await this.axiosClient.put(url, form);
|
|
3676
|
+
logger.info(`${logPrefix5} Successfully uploaded ${fileName} (status ${response.status})`);
|
|
3677
|
+
return {
|
|
3678
|
+
filePath: response.data?.filePath ?? `/org/${cdnId}/file/${fileName}`
|
|
3679
|
+
};
|
|
3680
|
+
} catch (err) {
|
|
3681
|
+
if (import_axios3.default.isAxiosError(err)) {
|
|
3682
|
+
this.throwMeaningfulError(err);
|
|
3683
|
+
}
|
|
3684
|
+
throw err;
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
throwMeaningfulError(err) {
|
|
3688
|
+
const status = err.response?.status;
|
|
3689
|
+
if (status === 401 || status === 403) {
|
|
3690
|
+
throw new Error("Unauthorized - invalid or expired service account key");
|
|
3691
|
+
} else if (status === 404) {
|
|
3692
|
+
throw new Error("Organization or CDN not found");
|
|
3693
|
+
} else if (status === 500) {
|
|
3694
|
+
throw new Error("API server error - check CDN ID and service account permissions");
|
|
3695
|
+
} else {
|
|
3696
|
+
throw new Error(`API error: ${err.response?.statusText ?? err.message}`);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
};
|
|
3700
|
+
|
|
3701
|
+
// src/sdk/models/asset-tap/asset-stream.ts
|
|
3702
|
+
var AssetStream = class {
|
|
3703
|
+
streamId;
|
|
3704
|
+
config;
|
|
3705
|
+
replicationMode;
|
|
3706
|
+
constructor(streamId, config, replicationMode = "INCREMENTAL") {
|
|
3707
|
+
this.streamId = streamId;
|
|
3708
|
+
this.config = config;
|
|
3709
|
+
this.replicationMode = replicationMode;
|
|
3710
|
+
}
|
|
3711
|
+
async transformFile(downloadedPath, _entry) {
|
|
3712
|
+
return downloadedPath;
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3715
|
+
|
|
3716
|
+
// src/sdk/models/asset-tap/asset-tap.ts
|
|
3717
|
+
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
3718
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3719
|
+
var logPrefix6 = "[AssetTap]";
|
|
3720
|
+
function inferContentType(filePath, fallback) {
|
|
3721
|
+
const mime = getMimeType(filePath);
|
|
3722
|
+
return mime !== "application/octet-stream" ? mime : fallback;
|
|
3723
|
+
}
|
|
3724
|
+
function deriveManifestMode(streams) {
|
|
3725
|
+
const modes = new Set(streams.map((s) => s.replicationMode));
|
|
3726
|
+
if (modes.size > 1) {
|
|
3727
|
+
logger.warn(`${logPrefix6} Streams have mixed replication modes (${[...modes].join(", ")}). Manifest will report "FULL".`);
|
|
3728
|
+
return "FULL";
|
|
3729
|
+
}
|
|
3730
|
+
return streams[0]?.replicationMode ?? "INCREMENTAL";
|
|
3731
|
+
}
|
|
3732
|
+
var AssetTap = class {
|
|
3733
|
+
config;
|
|
3734
|
+
outputDir;
|
|
3735
|
+
streams = [];
|
|
3736
|
+
concurrency;
|
|
3737
|
+
target;
|
|
3738
|
+
constructor(target, config, outputDir = "out", concurrency = 5) {
|
|
3739
|
+
this.target = target;
|
|
3740
|
+
this.config = config;
|
|
3741
|
+
this.outputDir = outputDir;
|
|
3742
|
+
this.concurrency = concurrency;
|
|
3743
|
+
}
|
|
3744
|
+
async sync() {
|
|
3745
|
+
await this.init();
|
|
3746
|
+
const dryRun = isDryRun();
|
|
3747
|
+
const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
|
|
3748
|
+
if (dryRun) {
|
|
3749
|
+
logger.info(`${logPrefix6} [DRY_RUN] mode active \u2014 skipping CDN checks and uploads`);
|
|
3750
|
+
await import_fs_extra4.default.emptyDir(this.outputDir);
|
|
3751
|
+
if (dryRunLimit !== void 0) {
|
|
3752
|
+
logger.info(`${logPrefix6} [DRY_RUN] Limit: ${dryRunLimit} assets per stream`);
|
|
3753
|
+
}
|
|
3754
|
+
} else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
|
|
3755
|
+
logger.warn(`${logPrefix6} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
|
|
3756
|
+
}
|
|
3757
|
+
const tmpDir2 = import_node_path4.default.join(this.outputDir, "tmp");
|
|
3758
|
+
await import_fs_extra4.default.ensureDir(tmpDir2);
|
|
3759
|
+
const streamManifests = [];
|
|
3760
|
+
let entryIndex = 0;
|
|
3761
|
+
let totalSummary = { total: 0, uploaded: 0, skipped: 0, errors: 0 };
|
|
3762
|
+
for (const stream of this.streams) {
|
|
3763
|
+
logger.info(`${logPrefix6} Processing stream: ${stream.streamId} (mode=${stream.replicationMode}, concurrency=${this.concurrency})`);
|
|
3764
|
+
const assetEntries = [];
|
|
3765
|
+
let streamAssetCount = 0;
|
|
3766
|
+
await processFromAsyncIterable(
|
|
3767
|
+
stream.listAssets(),
|
|
3768
|
+
async (entry) => {
|
|
3769
|
+
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
3770
|
+
return "stop";
|
|
3771
|
+
}
|
|
3772
|
+
logger.debug(`${logPrefix6} Processing entry: ${entry.sourcePath}`);
|
|
3773
|
+
if (stream.replicationMode === "INCREMENTAL" && !dryRun) {
|
|
3774
|
+
const shouldSync = await this.target.shouldSync(entry);
|
|
3775
|
+
if (!shouldSync) {
|
|
3776
|
+
logger.debug(`${logPrefix6} Skipping ${entry.sourcePath} (up-to-date)`);
|
|
3777
|
+
assetEntries.push({
|
|
3778
|
+
sourcePath: entry.sourcePath,
|
|
3779
|
+
destinationPath: entry.destinationPath,
|
|
3780
|
+
downloadedPath: "",
|
|
3781
|
+
transformedPath: "",
|
|
3782
|
+
size: 0,
|
|
3783
|
+
contentType: entry.contentType,
|
|
3784
|
+
status: "skipped",
|
|
3785
|
+
transformed: false
|
|
3786
|
+
});
|
|
3787
|
+
return "continue";
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
const fileName = `${entryIndex}_${import_node_path4.default.basename(entry.sourcePath)}`;
|
|
3791
|
+
const downloadedPath = import_node_path4.default.join(tmpDir2, fileName);
|
|
3792
|
+
let uploadPath = downloadedPath;
|
|
3793
|
+
entryIndex++;
|
|
3794
|
+
streamAssetCount++;
|
|
3795
|
+
try {
|
|
3796
|
+
await stream.downloadEntry(entry, downloadedPath);
|
|
3797
|
+
uploadPath = await stream.transformFile(downloadedPath, entry);
|
|
3798
|
+
const wasTransformed = uploadPath !== downloadedPath;
|
|
3799
|
+
const stat = await import_fs_extra4.default.stat(uploadPath);
|
|
3800
|
+
const processed = {
|
|
3801
|
+
entry,
|
|
3802
|
+
downloadedPath,
|
|
3803
|
+
uploadPath,
|
|
3804
|
+
wasTransformed,
|
|
3805
|
+
size: stat.size,
|
|
3806
|
+
contentType: wasTransformed ? inferContentType(uploadPath, entry.contentType) : entry.contentType
|
|
3807
|
+
};
|
|
3808
|
+
if (!dryRun) {
|
|
3809
|
+
await this.target.uploadAsset(processed);
|
|
3810
|
+
} else {
|
|
3811
|
+
const inspectPath = import_node_path4.default.join(this.outputDir, stream.streamId, entry.destinationPath);
|
|
3812
|
+
await import_fs_extra4.default.ensureDir(import_node_path4.default.dirname(inspectPath));
|
|
3813
|
+
await import_fs_extra4.default.copy(uploadPath, inspectPath);
|
|
3814
|
+
}
|
|
3815
|
+
assetEntries.push({
|
|
3816
|
+
sourcePath: entry.sourcePath,
|
|
3817
|
+
destinationPath: entry.destinationPath,
|
|
3818
|
+
downloadedPath,
|
|
3819
|
+
transformedPath: wasTransformed ? uploadPath : "",
|
|
3820
|
+
size: processed.size,
|
|
3821
|
+
contentType: processed.contentType,
|
|
3822
|
+
status: "uploaded",
|
|
3823
|
+
transformed: wasTransformed
|
|
3824
|
+
});
|
|
3825
|
+
logger.info(`${logPrefix6} ${dryRun ? "[DRY_RUN] Processed" : "Uploaded"} ${entry.sourcePath} \u2192 ${entry.destinationPath}`);
|
|
3826
|
+
} catch (err) {
|
|
3827
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3828
|
+
logger.error(`${logPrefix6} Failed to process ${entry.sourcePath}: ${message}`);
|
|
3829
|
+
assetEntries.push({
|
|
3830
|
+
sourcePath: entry.sourcePath,
|
|
3831
|
+
destinationPath: entry.destinationPath,
|
|
3832
|
+
downloadedPath: "",
|
|
3833
|
+
transformedPath: "",
|
|
3834
|
+
size: 0,
|
|
3835
|
+
contentType: entry.contentType,
|
|
3836
|
+
status: "error",
|
|
3837
|
+
transformed: false,
|
|
3838
|
+
error: message
|
|
3839
|
+
});
|
|
3840
|
+
} finally {
|
|
3841
|
+
await import_fs_extra4.default.remove(downloadedPath).catch(() => void 0);
|
|
3842
|
+
if (uploadPath !== downloadedPath) {
|
|
3843
|
+
await import_fs_extra4.default.remove(uploadPath).catch(() => void 0);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
3847
|
+
logger.info(`${logPrefix6} [DRY_RUN] Reached limit of ${dryRunLimit} for stream "${stream.streamId}", stopping.`);
|
|
3848
|
+
return "stop";
|
|
3849
|
+
}
|
|
3850
|
+
return "continue";
|
|
3851
|
+
},
|
|
3852
|
+
this.concurrency
|
|
3853
|
+
);
|
|
3854
|
+
const streamSummary = {
|
|
3855
|
+
total: assetEntries.length,
|
|
3856
|
+
uploaded: assetEntries.filter((a) => a.status === "uploaded").length,
|
|
3857
|
+
skipped: assetEntries.filter((a) => a.status === "skipped").length,
|
|
3858
|
+
errors: assetEntries.filter((a) => a.status === "error").length
|
|
3859
|
+
};
|
|
3860
|
+
totalSummary.total += streamSummary.total;
|
|
3861
|
+
totalSummary.uploaded += streamSummary.uploaded;
|
|
3862
|
+
totalSummary.skipped += streamSummary.skipped;
|
|
3863
|
+
totalSummary.errors += streamSummary.errors;
|
|
3864
|
+
streamManifests.push({
|
|
3865
|
+
streamId: stream.streamId,
|
|
3866
|
+
mode: stream.replicationMode,
|
|
3867
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3868
|
+
assets: assetEntries,
|
|
3869
|
+
summary: streamSummary
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
const manifest = {
|
|
3873
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3874
|
+
mode: deriveManifestMode(this.streams),
|
|
3875
|
+
streams: streamManifests,
|
|
3876
|
+
summary: totalSummary
|
|
3877
|
+
};
|
|
3878
|
+
await import_fs_extra4.default.ensureDir(this.outputDir);
|
|
3879
|
+
await import_fs_extra4.default.writeJson(import_node_path4.default.join(this.outputDir, "manifest.json"), manifest, { spaces: 2 });
|
|
3880
|
+
if (!dryRun) {
|
|
3881
|
+
await this.target.complete();
|
|
3882
|
+
}
|
|
3883
|
+
logger.info(`${logPrefix6} Sync complete. Uploaded=${totalSummary.uploaded} Skipped=${totalSummary.skipped} Errors=${totalSummary.errors}`);
|
|
3884
|
+
return manifest;
|
|
3885
|
+
}
|
|
3886
|
+
};
|
|
3887
|
+
|
|
3888
|
+
// src/sdk/models/asset-tap/image-transform.ts
|
|
3889
|
+
var import_sharp = __toESM(require("sharp"));
|
|
3890
|
+
var import_node_path5 = __toESM(require("path"));
|
|
3891
|
+
var SHARP_GRAVITY = {
|
|
3892
|
+
center: "centre",
|
|
3893
|
+
top: "north",
|
|
3894
|
+
bottom: "south",
|
|
3895
|
+
left: "west",
|
|
3896
|
+
right: "east",
|
|
3897
|
+
"top left": "northwest",
|
|
3898
|
+
"top right": "northeast",
|
|
3899
|
+
"bottom left": "southwest",
|
|
3900
|
+
"bottom right": "southeast"
|
|
3901
|
+
};
|
|
3902
|
+
function parseBackground(bg) {
|
|
3903
|
+
if (bg === void 0 || bg === "none" || bg === "transparent") {
|
|
3904
|
+
return { r: 0, g: 0, b: 0, alpha: 0 };
|
|
3905
|
+
}
|
|
3906
|
+
return bg;
|
|
3907
|
+
}
|
|
3908
|
+
var ImageTransform = class {
|
|
3909
|
+
/**
|
|
3910
|
+
* Converts an image to WebP format using sharp (libvips).
|
|
3911
|
+
*
|
|
3912
|
+
* The output file is written to the same directory as the input,
|
|
3913
|
+
* with the extension replaced by `.webp`.
|
|
3914
|
+
*
|
|
3915
|
+
* @param inputPath Path to the source image file.
|
|
3916
|
+
* @param options Optional resize / padding parameters.
|
|
3917
|
+
* @returns Path to the produced `.webp` file.
|
|
3918
|
+
*/
|
|
3919
|
+
static async toWebp(inputPath, options) {
|
|
3920
|
+
const dir = import_node_path5.default.dirname(inputPath);
|
|
3921
|
+
const basename = import_node_path5.default.basename(inputPath, import_node_path5.default.extname(inputPath));
|
|
3922
|
+
const outputPath = import_node_path5.default.join(dir, `${basename}.webp`);
|
|
3923
|
+
let pipeline = (0, import_sharp.default)(inputPath);
|
|
3924
|
+
const hasSize = options.width !== void 0 && options.height !== void 0;
|
|
3925
|
+
if (hasSize) {
|
|
3926
|
+
const position = options.gravity !== void 0 ? SHARP_GRAVITY[options.gravity] ?? options.gravity : void 0;
|
|
3927
|
+
if (options.extent === true) {
|
|
3928
|
+
pipeline = pipeline.resize(options.width, options.height, {
|
|
3929
|
+
fit: "contain",
|
|
3930
|
+
position,
|
|
3931
|
+
background: parseBackground(options.background)
|
|
3932
|
+
});
|
|
3933
|
+
} else {
|
|
3934
|
+
pipeline = pipeline.resize(options.width, options.height, {
|
|
3935
|
+
fit: "inside",
|
|
3936
|
+
position
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
pipeline = pipeline.webp(
|
|
3941
|
+
options.quality !== void 0 ? { quality: options.quality } : {}
|
|
3942
|
+
);
|
|
3943
|
+
await pipeline.toFile(outputPath);
|
|
3944
|
+
return outputPath;
|
|
3945
|
+
}
|
|
3946
|
+
};
|
|
3947
|
+
|
|
3948
|
+
// src/sdk/models/asset-target/asset-target.ts
|
|
3949
|
+
var AssetTarget = class {
|
|
3950
|
+
config;
|
|
3951
|
+
constructor(config) {
|
|
3952
|
+
this.config = config;
|
|
3953
|
+
}
|
|
3954
|
+
async complete() {
|
|
3955
|
+
}
|
|
3956
|
+
};
|
|
3957
|
+
|
|
3311
3958
|
// src/targets/bigquery/helpers.ts
|
|
3312
3959
|
var safeColumnName = (key) => {
|
|
3313
3960
|
let returnString = key;
|
|
@@ -3344,7 +3991,9 @@ var import_decimal = __toESM(require("decimal.js"));
|
|
|
3344
3991
|
var import_dayjs5 = __toESM(require("dayjs"));
|
|
3345
3992
|
var validateDateRange = (record, schema) => {
|
|
3346
3993
|
Object.keys(schema).forEach((key) => {
|
|
3347
|
-
const
|
|
3994
|
+
const field = schema[key];
|
|
3995
|
+
if (!field) return;
|
|
3996
|
+
const { format: format8 } = field;
|
|
3348
3997
|
if (format8 === "date-time") {
|
|
3349
3998
|
if (record[key]) {
|
|
3350
3999
|
const formattedDate = (0, import_dayjs5.default)(record[key]).format(defaultDateTimeFormat);
|
|
@@ -3861,6 +4510,46 @@ var BigQueryTarget = class extends ITarget {
|
|
|
3861
4510
|
convertNumberIntoDecimal = convertNumberIntoDecimal;
|
|
3862
4511
|
};
|
|
3863
4512
|
|
|
4513
|
+
// src/targets/cdn/main.ts
|
|
4514
|
+
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
4515
|
+
var logPrefix7 = "[CdnAssetTarget]";
|
|
4516
|
+
var CdnAssetTarget = class extends AssetTarget {
|
|
4517
|
+
cdnService;
|
|
4518
|
+
constructor(config) {
|
|
4519
|
+
super(config);
|
|
4520
|
+
this.cdnService = new CdnService(config);
|
|
4521
|
+
}
|
|
4522
|
+
async shouldSync(entry) {
|
|
4523
|
+
const metadata = await this.cdnService.getFileMetadata(getCdnId(this.config.cdnId), entry.destinationPath);
|
|
4524
|
+
if (!metadata.exists) {
|
|
4525
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} not in CDN \u2192 will sync`);
|
|
4526
|
+
return true;
|
|
4527
|
+
}
|
|
4528
|
+
if (entry.lastModified === void 0) {
|
|
4529
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} source has no lastModified \u2192 will sync`);
|
|
4530
|
+
return true;
|
|
4531
|
+
}
|
|
4532
|
+
if (metadata.lastModified === null) {
|
|
4533
|
+
logger.debug(`${logPrefix7} ${entry.destinationPath} CDN has no lastModified \u2192 will sync`);
|
|
4534
|
+
return true;
|
|
4535
|
+
}
|
|
4536
|
+
const sourceIsNewer = entry.lastModified > metadata.lastModified;
|
|
4537
|
+
if (!sourceIsNewer) {
|
|
4538
|
+
logger.debug(`${logPrefix7} Skipping ${entry.destinationPath} (CDN is up-to-date)`);
|
|
4539
|
+
}
|
|
4540
|
+
return sourceIsNewer;
|
|
4541
|
+
}
|
|
4542
|
+
async uploadAsset(asset) {
|
|
4543
|
+
const fileBuffer = await import_fs_extra5.default.readFile(asset.uploadPath);
|
|
4544
|
+
await this.cdnService.uploadFile(
|
|
4545
|
+
getCdnId(this.config.cdnId),
|
|
4546
|
+
asset.entry.destinationPath,
|
|
4547
|
+
fileBuffer
|
|
4548
|
+
);
|
|
4549
|
+
logger.info(`${logPrefix7} Uploaded ${asset.entry.destinationPath} (${fileBuffer.length} bytes)`);
|
|
4550
|
+
}
|
|
4551
|
+
};
|
|
4552
|
+
|
|
3864
4553
|
// src/state-providers/gcs/main.ts
|
|
3865
4554
|
var import_util6 = require("util");
|
|
3866
4555
|
var GCSStateProvider = class {
|
|
@@ -3902,9 +4591,14 @@ var GCSStateProvider = class {
|
|
|
3902
4591
|
0 && (module.exports = {
|
|
3903
4592
|
ALL_STREAM_ID_KEYWORD,
|
|
3904
4593
|
API_CALLS_METRIC_NAME,
|
|
4594
|
+
AssetStream,
|
|
4595
|
+
AssetTap,
|
|
4596
|
+
AssetTarget,
|
|
3905
4597
|
Authenticator,
|
|
3906
4598
|
BATCH_INTERVAL_MS,
|
|
3907
4599
|
BigQueryTarget,
|
|
4600
|
+
CdnAssetTarget,
|
|
4601
|
+
CdnService,
|
|
3908
4602
|
CloudStorageService,
|
|
3909
4603
|
CounterMetric,
|
|
3910
4604
|
CsvExtractionConfigBuilder,
|
|
@@ -3918,6 +4612,8 @@ var GCSStateProvider = class {
|
|
|
3918
4612
|
FileTap,
|
|
3919
4613
|
GCSStateProvider,
|
|
3920
4614
|
ITarget,
|
|
4615
|
+
ImageTransform,
|
|
4616
|
+
MIME_TYPES,
|
|
3921
4617
|
MissingFieldInSchemaError,
|
|
3922
4618
|
MissingSchemaError,
|
|
3923
4619
|
RESTStream,
|
|
@@ -3939,6 +4635,7 @@ var GCSStateProvider = class {
|
|
|
3939
4635
|
_greaterThan,
|
|
3940
4636
|
addWhalyFields,
|
|
3941
4637
|
checkCsvHeaderRow,
|
|
4638
|
+
collectPaginated,
|
|
3942
4639
|
countCsvLines,
|
|
3943
4640
|
createCellReference,
|
|
3944
4641
|
createCsvStreamConfig,
|
|
@@ -3946,6 +4643,7 @@ var GCSStateProvider = class {
|
|
|
3946
4643
|
createExcelStreamConfig,
|
|
3947
4644
|
createTemporaryFileStream,
|
|
3948
4645
|
csvFieldsToJsonSchema,
|
|
4646
|
+
downloadFiles,
|
|
3949
4647
|
excelColumnToIndex,
|
|
3950
4648
|
excelFieldsToJsonSchema,
|
|
3951
4649
|
excelRowToIndex,
|
|
@@ -3958,29 +4656,40 @@ var GCSStateProvider = class {
|
|
|
3958
4656
|
findAllMatchingConfigs,
|
|
3959
4657
|
flattenSchema,
|
|
3960
4658
|
getAllMetrics,
|
|
4659
|
+
getApiEndpoint,
|
|
3961
4660
|
getAxiosInstance,
|
|
4661
|
+
getCdnId,
|
|
3962
4662
|
getCounterMetric,
|
|
3963
4663
|
getCounterMetrics,
|
|
3964
4664
|
getDownloadFileApiCall,
|
|
4665
|
+
getDryRunLimit,
|
|
3965
4666
|
getJSONApiCall,
|
|
3966
4667
|
getJSONApiCallWithFullResponse,
|
|
4668
|
+
getMimeType,
|
|
3967
4669
|
getRawJSONApiCall,
|
|
4670
|
+
getServiceAccountKey,
|
|
3968
4671
|
gracefulExit,
|
|
3969
4672
|
haltAndCatchFire,
|
|
3970
4673
|
incrementStreamState,
|
|
3971
4674
|
indexToExcelColumn,
|
|
3972
4675
|
indexToExcelRow,
|
|
4676
|
+
isDryRun,
|
|
4677
|
+
listFilesRecursively,
|
|
3973
4678
|
loadJson,
|
|
3974
4679
|
loadSchema,
|
|
3975
4680
|
logger,
|
|
4681
|
+
optionalEnv,
|
|
3976
4682
|
parseCellReference,
|
|
3977
4683
|
postFormDataApiCall,
|
|
3978
4684
|
postJSONApiCall,
|
|
3979
4685
|
postUrlEncodedApiCall,
|
|
3980
4686
|
printMemoryFootprint,
|
|
3981
4687
|
processFileStreams,
|
|
4688
|
+
processFromAsyncIterable,
|
|
3982
4689
|
removeParasiteProperties,
|
|
4690
|
+
requireEnv,
|
|
3983
4691
|
rowGeneratorFromCsv,
|
|
4692
|
+
runWithConcurrency,
|
|
3984
4693
|
safePath,
|
|
3985
4694
|
unzip,
|
|
3986
4695
|
validateExcelConfig,
|