@whaly/connector-sdk 0.3.10 → 0.3.11
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 +79 -3
- package/dist/index.d.ts +79 -3
- package/dist/index.js +186 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +218 -105
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8830,7 +8830,7 @@ var require_mime_types = __commonJS({
|
|
|
8830
8830
|
"node_modules/mime-types/index.js"(exports2) {
|
|
8831
8831
|
"use strict";
|
|
8832
8832
|
var db = require_mime_db();
|
|
8833
|
-
var
|
|
8833
|
+
var extname3 = require("path").extname;
|
|
8834
8834
|
var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
|
|
8835
8835
|
var TEXT_TYPE_REGEXP = /^text\//i;
|
|
8836
8836
|
exports2.charset = charset;
|
|
@@ -8884,7 +8884,7 @@ var require_mime_types = __commonJS({
|
|
|
8884
8884
|
if (!path8 || typeof path8 !== "string") {
|
|
8885
8885
|
return false;
|
|
8886
8886
|
}
|
|
8887
|
-
var extension2 =
|
|
8887
|
+
var extension2 = extname3("x." + path8).toLowerCase().substr(1);
|
|
8888
8888
|
if (!extension2) {
|
|
8889
8889
|
return false;
|
|
8890
8890
|
}
|
|
@@ -10003,7 +10003,7 @@ var require_form_data = __commonJS({
|
|
|
10003
10003
|
var http = require("http");
|
|
10004
10004
|
var https = require("https");
|
|
10005
10005
|
var parseUrl = require("url").parse;
|
|
10006
|
-
var
|
|
10006
|
+
var fs12 = require("fs");
|
|
10007
10007
|
var Stream2 = require("stream").Stream;
|
|
10008
10008
|
var crypto = require("crypto");
|
|
10009
10009
|
var mime = require_mime_types();
|
|
@@ -10070,7 +10070,7 @@ var require_form_data = __commonJS({
|
|
|
10070
10070
|
if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
|
|
10071
10071
|
callback(null, value.end + 1 - (value.start ? value.start : 0));
|
|
10072
10072
|
} else {
|
|
10073
|
-
|
|
10073
|
+
fs12.stat(value.path, function(err, stat) {
|
|
10074
10074
|
if (err) {
|
|
10075
10075
|
callback(err);
|
|
10076
10076
|
return;
|
|
@@ -10342,6 +10342,7 @@ __export(index_exports, {
|
|
|
10342
10342
|
GCSStateProvider: () => GCSStateProvider,
|
|
10343
10343
|
ITarget: () => ITarget,
|
|
10344
10344
|
ImageTransform: () => ImageTransform,
|
|
10345
|
+
LocalStorageService: () => LocalStorageService,
|
|
10345
10346
|
MIME_TYPES: () => MIME_TYPES,
|
|
10346
10347
|
MissingFieldInSchemaError: () => MissingFieldInSchemaError,
|
|
10347
10348
|
MissingSchemaError: () => MissingSchemaError,
|
|
@@ -10641,7 +10642,7 @@ var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) =>
|
|
|
10641
10642
|
...config.headers ? { headers: config.headers } : {},
|
|
10642
10643
|
paramsSerializer: { serialize: (params) => qs.stringify(params, { arrayFormat: "repeat" }) }
|
|
10643
10644
|
}).then((response) => {
|
|
10644
|
-
return new Promise((
|
|
10645
|
+
return new Promise((resolve2, reject) => {
|
|
10645
10646
|
response.data.pipe(writer);
|
|
10646
10647
|
let error = null;
|
|
10647
10648
|
writer.on("error", (err) => {
|
|
@@ -10651,7 +10652,7 @@ var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) =>
|
|
|
10651
10652
|
});
|
|
10652
10653
|
writer.on("close", () => {
|
|
10653
10654
|
if (!error) {
|
|
10654
|
-
|
|
10655
|
+
resolve2({ outputDir: output });
|
|
10655
10656
|
}
|
|
10656
10657
|
});
|
|
10657
10658
|
});
|
|
@@ -10802,8 +10803,8 @@ var printMemoryFootprint = (prefix) => {
|
|
|
10802
10803
|
|
|
10803
10804
|
// src/sdk/service/exit.ts
|
|
10804
10805
|
async function gracefulExit(exitCode) {
|
|
10805
|
-
const loggerFinish = new Promise((
|
|
10806
|
-
logger.closeWinston(
|
|
10806
|
+
const loggerFinish = new Promise((resolve2, reject) => {
|
|
10807
|
+
logger.closeWinston(resolve2);
|
|
10807
10808
|
});
|
|
10808
10809
|
await loggerFinish;
|
|
10809
10810
|
process.exit(exitCode);
|
|
@@ -12573,13 +12574,13 @@ var ITarget = class _ITarget {
|
|
|
12573
12574
|
const dbSyncInstance = stream.getDbSync();
|
|
12574
12575
|
const recordWithrenamedColumns = dbSyncInstance.renameColumns(recordWithDecimal);
|
|
12575
12576
|
const serializedRecord = this.getSerializedRecord(streamId, recordWithrenamedColumns);
|
|
12576
|
-
return new Promise((
|
|
12577
|
+
return new Promise((resolve2, reject) => {
|
|
12577
12578
|
stream.getFileToLoad().stream.write(serializedRecord, (error) => {
|
|
12578
12579
|
if (error) {
|
|
12579
12580
|
reject(error);
|
|
12580
12581
|
}
|
|
12581
12582
|
stream.incrementBatchedRowCount();
|
|
12582
|
-
|
|
12583
|
+
resolve2();
|
|
12583
12584
|
});
|
|
12584
12585
|
});
|
|
12585
12586
|
} catch (err) {
|
|
@@ -12720,11 +12721,11 @@ var ITarget = class _ITarget {
|
|
|
12720
12721
|
const streamState = this.streams[streamId];
|
|
12721
12722
|
if (!streamState || streamState.getBatchedRowCount() === 0) continue;
|
|
12722
12723
|
const fileToLoad = streamState.getFileToLoad();
|
|
12723
|
-
await new Promise((
|
|
12724
|
+
await new Promise((resolve2, reject) => {
|
|
12724
12725
|
fileToLoad.stream.end(() => {
|
|
12725
12726
|
fileToLoad.stream.close((err) => {
|
|
12726
12727
|
if (err) reject(err);
|
|
12727
|
-
else
|
|
12728
|
+
else resolve2();
|
|
12728
12729
|
});
|
|
12729
12730
|
});
|
|
12730
12731
|
});
|
|
@@ -13255,18 +13256,18 @@ async function* rowGeneratorFromCsv(path8, fileConfig) {
|
|
|
13255
13256
|
}
|
|
13256
13257
|
}
|
|
13257
13258
|
async function countCsvLines(filePath) {
|
|
13258
|
-
return new Promise((
|
|
13259
|
+
return new Promise((resolve2, reject) => {
|
|
13259
13260
|
let newlines = 0;
|
|
13260
13261
|
(0, import_fs2.createReadStream)(filePath).on("data", (chunk2) => {
|
|
13261
13262
|
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
|
|
13262
13263
|
for (let i = 0; i < buf.length; i++) {
|
|
13263
13264
|
if (buf[i] === 10) newlines++;
|
|
13264
13265
|
}
|
|
13265
|
-
}).on("end", () =>
|
|
13266
|
+
}).on("end", () => resolve2(Math.max(0, newlines - 1))).on("error", reject);
|
|
13266
13267
|
});
|
|
13267
13268
|
}
|
|
13268
13269
|
var checkCsvHeaderRow = async (path8, fileConfig) => {
|
|
13269
|
-
return new Promise((
|
|
13270
|
+
return new Promise((resolve2, reject) => {
|
|
13270
13271
|
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
13271
13272
|
console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
|
|
13272
13273
|
const info = getStringHexInfo(col);
|
|
@@ -13341,7 +13342,7 @@ var checkCsvHeaderRow = async (path8, fileConfig) => {
|
|
|
13341
13342
|
reject(new Error(`Error processing CSV file: ${error.message}`));
|
|
13342
13343
|
});
|
|
13343
13344
|
initialReadStream.on("close", () => {
|
|
13344
|
-
|
|
13345
|
+
resolve2();
|
|
13345
13346
|
});
|
|
13346
13347
|
});
|
|
13347
13348
|
};
|
|
@@ -13922,6 +13923,16 @@ var CloudStorageService = class {
|
|
|
13922
13923
|
throw new Error((0, import_util5.format)(`error writing GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
|
|
13923
13924
|
}
|
|
13924
13925
|
}
|
|
13926
|
+
/**
|
|
13927
|
+
* Resolves a file URI to a local file path by downloading it from the configured GCS bucket.
|
|
13928
|
+
*
|
|
13929
|
+
* @example
|
|
13930
|
+
* const storage = new CloudStorageService("my-bucket", "my-folder");
|
|
13931
|
+
* const localPath = await storage.resolveFileUri("folder/file.xlsx");
|
|
13932
|
+
*/
|
|
13933
|
+
async resolveFileUri(fileUri) {
|
|
13934
|
+
return this.downloadFile(fileUri, pathModule.basename(fileUri));
|
|
13935
|
+
}
|
|
13925
13936
|
/**
|
|
13926
13937
|
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
13927
13938
|
* Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
|
|
@@ -13950,6 +13961,108 @@ var CloudStorageService = class {
|
|
|
13950
13961
|
}
|
|
13951
13962
|
};
|
|
13952
13963
|
|
|
13964
|
+
// src/services/local-storage.ts
|
|
13965
|
+
var pathModule2 = __toESM(require("path"));
|
|
13966
|
+
var fs6 = __toESM(require("fs"));
|
|
13967
|
+
var import_crypto2 = require("crypto");
|
|
13968
|
+
var logPrefix5 = "[LocalStorageService]";
|
|
13969
|
+
var LocalStorageService = class {
|
|
13970
|
+
basePath;
|
|
13971
|
+
processedSuffix;
|
|
13972
|
+
supportedExtensions;
|
|
13973
|
+
constructor(basePath, opts) {
|
|
13974
|
+
this.basePath = pathModule2.resolve(basePath);
|
|
13975
|
+
this.processedSuffix = opts?.processedSuffix ?? ".processed";
|
|
13976
|
+
this.supportedExtensions = opts?.supportedExtensions ?? [];
|
|
13977
|
+
}
|
|
13978
|
+
async listFiles(prefix) {
|
|
13979
|
+
const dir = prefix ? pathModule2.resolve(this.basePath, prefix) : this.basePath;
|
|
13980
|
+
if (!fs6.existsSync(dir)) {
|
|
13981
|
+
return [];
|
|
13982
|
+
}
|
|
13983
|
+
const entries = fs6.readdirSync(dir, { recursive: true });
|
|
13984
|
+
const files = [];
|
|
13985
|
+
for (const entry of entries) {
|
|
13986
|
+
const rel = typeof entry === "string" ? entry : entry.toString();
|
|
13987
|
+
const full = pathModule2.join(dir, rel);
|
|
13988
|
+
if (fs6.statSync(full).isFile()) {
|
|
13989
|
+
files.push(pathModule2.relative(this.basePath, full));
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13992
|
+
return files;
|
|
13993
|
+
}
|
|
13994
|
+
async getUnprocessedFiles() {
|
|
13995
|
+
const allFiles = await this.listFiles();
|
|
13996
|
+
const markerFiles = new Set(
|
|
13997
|
+
allFiles.filter((f) => f.endsWith(this.processedSuffix)).map((f) => f.replace(this.processedSuffix, ""))
|
|
13998
|
+
);
|
|
13999
|
+
return allFiles.filter((f) => {
|
|
14000
|
+
if (f.endsWith(this.processedSuffix)) return false;
|
|
14001
|
+
if (this.supportedExtensions.length > 0) {
|
|
14002
|
+
const ext = pathModule2.extname(f).toLowerCase();
|
|
14003
|
+
if (!this.supportedExtensions.includes(ext)) return false;
|
|
14004
|
+
}
|
|
14005
|
+
return !markerFiles.has(f);
|
|
14006
|
+
});
|
|
14007
|
+
}
|
|
14008
|
+
async createMarkerFile(fileName) {
|
|
14009
|
+
const markerPath = pathModule2.join(this.basePath, `${fileName}${this.processedSuffix}`);
|
|
14010
|
+
const dir = pathModule2.dirname(markerPath);
|
|
14011
|
+
if (!fs6.existsSync(dir)) {
|
|
14012
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
14013
|
+
}
|
|
14014
|
+
fs6.writeFileSync(markerPath, `Marked file ${fileName} as processed`);
|
|
14015
|
+
logger.info(`${logPrefix5} Marker file ${markerPath} created successfully.`);
|
|
14016
|
+
}
|
|
14017
|
+
async downloadFile(filePath, _fileName) {
|
|
14018
|
+
const resolved = pathModule2.resolve(this.basePath, filePath);
|
|
14019
|
+
if (!fs6.existsSync(resolved)) {
|
|
14020
|
+
throw new Error(`Local file not found: ${resolved}`);
|
|
14021
|
+
}
|
|
14022
|
+
logger.info(`${logPrefix5} Resolved local file: ${resolved}`);
|
|
14023
|
+
return resolved;
|
|
14024
|
+
}
|
|
14025
|
+
async resolveFileUri(fileUri) {
|
|
14026
|
+
const resolved = pathModule2.resolve(fileUri);
|
|
14027
|
+
if (!fs6.existsSync(resolved)) {
|
|
14028
|
+
throw new Error(`Local file not found: ${resolved}`);
|
|
14029
|
+
}
|
|
14030
|
+
logger.info(`${logPrefix5} File URI resolved to local path: ${resolved}`);
|
|
14031
|
+
return resolved;
|
|
14032
|
+
}
|
|
14033
|
+
async uploadFile(localPath, destPath) {
|
|
14034
|
+
const dest = pathModule2.resolve(this.basePath, destPath);
|
|
14035
|
+
const dir = pathModule2.dirname(dest);
|
|
14036
|
+
if (!fs6.existsSync(dir)) {
|
|
14037
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
14038
|
+
}
|
|
14039
|
+
fs6.copyFileSync(localPath, dest);
|
|
14040
|
+
logger.info(`${logPrefix5} Copied '%s' to '%s'`, localPath, dest);
|
|
14041
|
+
return { name: destPath };
|
|
14042
|
+
}
|
|
14043
|
+
async readObjectAsString(objectPath) {
|
|
14044
|
+
const fullPath = pathModule2.resolve(this.basePath, objectPath);
|
|
14045
|
+
if (!fs6.existsSync(fullPath)) {
|
|
14046
|
+
throw new Error(`Local file not found: ${fullPath}`);
|
|
14047
|
+
}
|
|
14048
|
+
return fs6.readFileSync(fullPath, "utf8");
|
|
14049
|
+
}
|
|
14050
|
+
async writeStringObject(objectPath, contents) {
|
|
14051
|
+
const fullPath = pathModule2.resolve(this.basePath, objectPath);
|
|
14052
|
+
const dir = pathModule2.dirname(fullPath);
|
|
14053
|
+
if (!fs6.existsSync(dir)) {
|
|
14054
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
14055
|
+
}
|
|
14056
|
+
fs6.writeFileSync(fullPath, contents, "utf8");
|
|
14057
|
+
logger.info(`${logPrefix5} Wrote object to ${fullPath}`);
|
|
14058
|
+
}
|
|
14059
|
+
async uploadFileWithUniqueName(filePath, prefix, streamId) {
|
|
14060
|
+
const runFolder = process.env.RUN_ID ?? "default";
|
|
14061
|
+
const destName = `${prefix}/${runFolder}/${streamId}-${(0, import_crypto2.randomUUID)()}.jsonnl`;
|
|
14062
|
+
return this.uploadFile(filePath, destName);
|
|
14063
|
+
}
|
|
14064
|
+
};
|
|
14065
|
+
|
|
13953
14066
|
// src/services/zip.ts
|
|
13954
14067
|
var fse = __toESM(require("fs-extra"));
|
|
13955
14068
|
var import_decompress = __toESM(require("decompress"));
|
|
@@ -13962,7 +14075,7 @@ var unzip = async (zipFilePath, extractedPath) => {
|
|
|
13962
14075
|
// src/services/cdn.ts
|
|
13963
14076
|
var import_axios3 = __toESM(require("axios"));
|
|
13964
14077
|
var import_axios_retry2 = __toESM(require("axios-retry"));
|
|
13965
|
-
var
|
|
14078
|
+
var logPrefix6 = "[CdnService]";
|
|
13966
14079
|
var MAX_RETRIES = 10;
|
|
13967
14080
|
var CdnService = class {
|
|
13968
14081
|
axiosClient;
|
|
@@ -13988,12 +14101,12 @@ var CdnService = class {
|
|
|
13988
14101
|
if (retryAfter) {
|
|
13989
14102
|
const seconds = Number(retryAfter);
|
|
13990
14103
|
if (!isNaN(seconds) && seconds > 0) {
|
|
13991
|
-
logger.info(`${
|
|
14104
|
+
logger.info(`${logPrefix6} Rate limited \u2014 waiting ${seconds}s (Retry-After header), attempt ${retryCount}/${MAX_RETRIES}`);
|
|
13992
14105
|
return seconds * 1e3;
|
|
13993
14106
|
}
|
|
13994
14107
|
}
|
|
13995
14108
|
const delay = import_axios_retry2.default.exponentialDelay(retryCount);
|
|
13996
|
-
logger.info(`${
|
|
14109
|
+
logger.info(`${logPrefix6} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
|
|
13997
14110
|
return delay;
|
|
13998
14111
|
}
|
|
13999
14112
|
});
|
|
@@ -14019,7 +14132,7 @@ var CdnService = class {
|
|
|
14019
14132
|
const status = err.response?.status;
|
|
14020
14133
|
if (status === 404) return { exists: false, lastModified: null, contentType: null };
|
|
14021
14134
|
if (status === 405) {
|
|
14022
|
-
logger.warn(`${
|
|
14135
|
+
logger.warn(`${logPrefix6} HEAD not supported for ${url}, treating file as absent`);
|
|
14023
14136
|
return { exists: false, lastModified: null, contentType: null };
|
|
14024
14137
|
}
|
|
14025
14138
|
this.throwMeaningfulError(err);
|
|
@@ -14043,11 +14156,11 @@ var CdnService = class {
|
|
|
14043
14156
|
async uploadFile(cdnId, fileName, fileBuffer) {
|
|
14044
14157
|
const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
|
|
14045
14158
|
try {
|
|
14046
|
-
logger.info(`${
|
|
14159
|
+
logger.info(`${logPrefix6} Uploading ${fileName} (${fileBuffer.length} bytes) to CDN ${cdnId}`);
|
|
14047
14160
|
const form = new FormData();
|
|
14048
14161
|
form.append("file", new Blob([fileBuffer]), fileName);
|
|
14049
14162
|
const response = await this.axiosClient.put(url, form);
|
|
14050
|
-
logger.info(`${
|
|
14163
|
+
logger.info(`${logPrefix6} Successfully uploaded ${fileName} (status ${response.status})`);
|
|
14051
14164
|
return {
|
|
14052
14165
|
filePath: response.data?.filePath ?? `/org/${cdnId}/file/${fileName}`
|
|
14053
14166
|
};
|
|
@@ -14090,7 +14203,7 @@ var AssetStream = class {
|
|
|
14090
14203
|
// src/sdk/models/asset-tap/asset-tap.ts
|
|
14091
14204
|
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
14092
14205
|
var import_node_path4 = __toESM(require("path"));
|
|
14093
|
-
var
|
|
14206
|
+
var logPrefix7 = "[AssetTap]";
|
|
14094
14207
|
function inferContentType(filePath, fallback) {
|
|
14095
14208
|
const mime = getMimeType(filePath);
|
|
14096
14209
|
return mime !== "application/octet-stream" ? mime : fallback;
|
|
@@ -14098,7 +14211,7 @@ function inferContentType(filePath, fallback) {
|
|
|
14098
14211
|
function deriveManifestMode(streams) {
|
|
14099
14212
|
const modes = new Set(streams.map((s) => s.replicationMode));
|
|
14100
14213
|
if (modes.size > 1) {
|
|
14101
|
-
logger.warn(`${
|
|
14214
|
+
logger.warn(`${logPrefix7} Streams have mixed replication modes (${[...modes].join(", ")}). Manifest will report "FULL".`);
|
|
14102
14215
|
return "FULL";
|
|
14103
14216
|
}
|
|
14104
14217
|
return streams[0]?.replicationMode ?? "INCREMENTAL";
|
|
@@ -14120,13 +14233,13 @@ var AssetTap = class {
|
|
|
14120
14233
|
const dryRun = isDryRun();
|
|
14121
14234
|
const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
|
|
14122
14235
|
if (dryRun) {
|
|
14123
|
-
logger.info(`${
|
|
14236
|
+
logger.info(`${logPrefix7} [DRY_RUN] mode active \u2014 skipping CDN checks and uploads`);
|
|
14124
14237
|
await import_fs_extra4.default.emptyDir(this.outputDir);
|
|
14125
14238
|
if (dryRunLimit !== void 0) {
|
|
14126
|
-
logger.info(`${
|
|
14239
|
+
logger.info(`${logPrefix7} [DRY_RUN] Limit: ${dryRunLimit} assets per stream`);
|
|
14127
14240
|
}
|
|
14128
14241
|
} else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
|
|
14129
|
-
logger.warn(`${
|
|
14242
|
+
logger.warn(`${logPrefix7} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
|
|
14130
14243
|
}
|
|
14131
14244
|
const tmpDir2 = import_node_path4.default.join(this.outputDir, "tmp");
|
|
14132
14245
|
await import_fs_extra4.default.ensureDir(tmpDir2);
|
|
@@ -14134,7 +14247,7 @@ var AssetTap = class {
|
|
|
14134
14247
|
let entryIndex = 0;
|
|
14135
14248
|
let totalSummary = { total: 0, uploaded: 0, skipped: 0, errors: 0 };
|
|
14136
14249
|
for (const stream of this.streams) {
|
|
14137
|
-
logger.info(`${
|
|
14250
|
+
logger.info(`${logPrefix7} Processing stream: ${stream.streamId} (mode=${stream.replicationMode}, concurrency=${this.concurrency})`);
|
|
14138
14251
|
const assetEntries = [];
|
|
14139
14252
|
let streamAssetCount = 0;
|
|
14140
14253
|
await processFromAsyncIterable(
|
|
@@ -14143,11 +14256,11 @@ var AssetTap = class {
|
|
|
14143
14256
|
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
14144
14257
|
return "stop";
|
|
14145
14258
|
}
|
|
14146
|
-
logger.debug(`${
|
|
14259
|
+
logger.debug(`${logPrefix7} Processing entry: ${entry.sourcePath}`);
|
|
14147
14260
|
if (stream.replicationMode === "INCREMENTAL" && !dryRun) {
|
|
14148
14261
|
const shouldSync = await this.target.shouldSync(entry);
|
|
14149
14262
|
if (!shouldSync) {
|
|
14150
|
-
logger.debug(`${
|
|
14263
|
+
logger.debug(`${logPrefix7} Skipping ${entry.sourcePath} (up-to-date)`);
|
|
14151
14264
|
assetEntries.push({
|
|
14152
14265
|
sourcePath: entry.sourcePath,
|
|
14153
14266
|
destinationPath: entry.destinationPath,
|
|
@@ -14203,10 +14316,10 @@ var AssetTap = class {
|
|
|
14203
14316
|
status: "uploaded",
|
|
14204
14317
|
transformed: wasTransformed
|
|
14205
14318
|
});
|
|
14206
|
-
logger.info(`${
|
|
14319
|
+
logger.info(`${logPrefix7} ${dryRun ? "[DRY_RUN] Processed" : "Uploaded"} ${entry.sourcePath} \u2192 ${entry.destinationPath}`);
|
|
14207
14320
|
} catch (err) {
|
|
14208
14321
|
const message = err instanceof Error ? err.message : String(err);
|
|
14209
|
-
logger.error(`${
|
|
14322
|
+
logger.error(`${logPrefix7} Failed to process ${entry.sourcePath}: ${message}`);
|
|
14210
14323
|
assetEntries.push({
|
|
14211
14324
|
sourcePath: entry.sourcePath,
|
|
14212
14325
|
destinationPath: entry.destinationPath,
|
|
@@ -14225,7 +14338,7 @@ var AssetTap = class {
|
|
|
14225
14338
|
}
|
|
14226
14339
|
}
|
|
14227
14340
|
if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
|
|
14228
|
-
logger.info(`${
|
|
14341
|
+
logger.info(`${logPrefix7} [DRY_RUN] Reached limit of ${dryRunLimit} for stream "${stream.streamId}", stopping.`);
|
|
14229
14342
|
return "stop";
|
|
14230
14343
|
}
|
|
14231
14344
|
return "continue";
|
|
@@ -14261,7 +14374,7 @@ var AssetTap = class {
|
|
|
14261
14374
|
if (!dryRun) {
|
|
14262
14375
|
await this.target.complete();
|
|
14263
14376
|
}
|
|
14264
|
-
logger.info(`${
|
|
14377
|
+
logger.info(`${logPrefix7} Sync complete. Uploaded=${totalSummary.uploaded} Skipped=${totalSummary.skipped} Errors=${totalSummary.errors}`);
|
|
14265
14378
|
return manifest;
|
|
14266
14379
|
}
|
|
14267
14380
|
};
|
|
@@ -14299,8 +14412,8 @@ var ImageTransform = class {
|
|
|
14299
14412
|
*/
|
|
14300
14413
|
static async toWebp(inputPath, options) {
|
|
14301
14414
|
const dir = import_node_path5.default.dirname(inputPath);
|
|
14302
|
-
const
|
|
14303
|
-
const outputPath = import_node_path5.default.join(dir, `${
|
|
14415
|
+
const basename2 = import_node_path5.default.basename(inputPath, import_node_path5.default.extname(inputPath));
|
|
14416
|
+
const outputPath = import_node_path5.default.join(dir, `${basename2}.webp`);
|
|
14304
14417
|
let pipeline = (0, import_sharp.default)(inputPath);
|
|
14305
14418
|
const hasSize = options.width !== void 0 && options.height !== void 0;
|
|
14306
14419
|
if (hasSize) {
|
|
@@ -14424,7 +14537,7 @@ async function getFileSizeLabel(filePath) {
|
|
|
14424
14537
|
return "unknown size";
|
|
14425
14538
|
}
|
|
14426
14539
|
}
|
|
14427
|
-
var
|
|
14540
|
+
var logPrefix8 = "[DocumentTap]";
|
|
14428
14541
|
var DocumentTap = class {
|
|
14429
14542
|
config;
|
|
14430
14543
|
outputDir;
|
|
@@ -14443,32 +14556,32 @@ var DocumentTap = class {
|
|
|
14443
14556
|
}
|
|
14444
14557
|
async sync() {
|
|
14445
14558
|
if (!this.target) {
|
|
14446
|
-
throw new Error(`${
|
|
14559
|
+
throw new Error(`${logPrefix8} No target set. Assign a WhalyDocumentTarget before calling sync().`);
|
|
14447
14560
|
}
|
|
14448
14561
|
await this.init();
|
|
14449
14562
|
if (!this.stream) {
|
|
14450
|
-
throw new Error(`${
|
|
14563
|
+
throw new Error(`${logPrefix8} No stream set. Assign this.stream in init().`);
|
|
14451
14564
|
}
|
|
14452
14565
|
const dryRun = isDryRun();
|
|
14453
14566
|
const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
|
|
14454
14567
|
if (dryRun) {
|
|
14455
|
-
logger.info(`${
|
|
14568
|
+
logger.info(`${logPrefix8} [DRY_RUN] mode active \u2014 skipping API calls and uploads`);
|
|
14456
14569
|
await import_fs_extra5.default.emptyDir(this.outputDir);
|
|
14457
14570
|
if (dryRunLimit !== void 0) {
|
|
14458
|
-
logger.info(`${
|
|
14571
|
+
logger.info(`${logPrefix8} [DRY_RUN] Limit: ${dryRunLimit} documents per stream`);
|
|
14459
14572
|
}
|
|
14460
14573
|
} else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
|
|
14461
|
-
logger.warn(`${
|
|
14574
|
+
logger.warn(`${logPrefix8} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
|
|
14462
14575
|
}
|
|
14463
14576
|
const tmpDir2 = import_node_path6.default.join(this.outputDir, "tmp");
|
|
14464
14577
|
await import_fs_extra5.default.ensureDir(tmpDir2);
|
|
14465
14578
|
try {
|
|
14466
14579
|
const stream = this.stream;
|
|
14467
|
-
logger.info(`${
|
|
14580
|
+
logger.info(`${logPrefix8} Processing stream: ${stream.streamId}`);
|
|
14468
14581
|
const sourceEntries = await this.collectSourceEntries(stream, dryRunLimit);
|
|
14469
14582
|
const existingDocs = dryRun ? [] : await this.target.listExistingDocuments();
|
|
14470
14583
|
const diff = this.computeDiff(stream, sourceEntries, existingDocs);
|
|
14471
|
-
logger.info(`${
|
|
14584
|
+
logger.info(`${logPrefix8} Stream ${stream.streamId} diff: create=${diff.toCreate.length} reupload=${diff.toReupload.length} updateMeta=${diff.toUpdateMetadata.length} delete=${diff.toDelete.length} skip=${diff.skipped.length}`);
|
|
14472
14585
|
const entries = [];
|
|
14473
14586
|
const createTasks = diff.toCreate.map((entry) => async () => {
|
|
14474
14587
|
return this.executeCreate(stream, entry, tmpDir2, dryRun);
|
|
@@ -14502,7 +14615,7 @@ var DocumentTap = class {
|
|
|
14502
14615
|
};
|
|
14503
14616
|
await import_fs_extra5.default.ensureDir(this.outputDir);
|
|
14504
14617
|
await import_fs_extra5.default.writeJson(import_node_path6.default.join(this.outputDir, "manifest.json"), manifest, { spaces: 2 });
|
|
14505
|
-
logger.info(`${
|
|
14618
|
+
logger.info(`${logPrefix8} Sync complete. Created=${streamSummary.created} Updated=${streamSummary.updated} Reuploaded=${streamSummary.reuploaded} Deleted=${streamSummary.deleted} Skipped=${streamSummary.skipped} Errors=${streamSummary.errors}`);
|
|
14506
14619
|
return manifest;
|
|
14507
14620
|
} finally {
|
|
14508
14621
|
await import_fs_extra5.default.remove(tmpDir2).catch(() => void 0);
|
|
@@ -14570,12 +14683,12 @@ var DocumentTap = class {
|
|
|
14570
14683
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "created" };
|
|
14571
14684
|
} catch (err) {
|
|
14572
14685
|
if (err instanceof DocumentDownloadSkipError) {
|
|
14573
|
-
logger.warn(`${
|
|
14686
|
+
logger.warn(`${logPrefix8} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
|
|
14574
14687
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "skipped" };
|
|
14575
14688
|
}
|
|
14576
14689
|
const message = err instanceof Error ? err.message : String(err);
|
|
14577
14690
|
const sizeLabel = await getFileSizeLabel(downloadPath);
|
|
14578
|
-
logger.error(`${
|
|
14691
|
+
logger.error(`${logPrefix8} Failed to create ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
|
|
14579
14692
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
|
|
14580
14693
|
} finally {
|
|
14581
14694
|
await import_fs_extra5.default.remove(downloadPath).catch(() => void 0);
|
|
@@ -14596,12 +14709,12 @@ var DocumentTap = class {
|
|
|
14596
14709
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "reuploaded" };
|
|
14597
14710
|
} catch (err) {
|
|
14598
14711
|
if (err instanceof DocumentDownloadSkipError) {
|
|
14599
|
-
logger.warn(`${
|
|
14712
|
+
logger.warn(`${logPrefix8} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
|
|
14600
14713
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "skipped" };
|
|
14601
14714
|
}
|
|
14602
14715
|
const message = err instanceof Error ? err.message : String(err);
|
|
14603
14716
|
const sizeLabel = await getFileSizeLabel(downloadPath);
|
|
14604
|
-
logger.error(`${
|
|
14717
|
+
logger.error(`${logPrefix8} Failed to reupload ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
|
|
14605
14718
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
|
|
14606
14719
|
} finally {
|
|
14607
14720
|
await import_fs_extra5.default.remove(downloadPath).catch(() => void 0);
|
|
@@ -14615,7 +14728,7 @@ var DocumentTap = class {
|
|
|
14615
14728
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "updated" };
|
|
14616
14729
|
} catch (err) {
|
|
14617
14730
|
const message = err instanceof Error ? err.message : String(err);
|
|
14618
|
-
logger.error(`${
|
|
14731
|
+
logger.error(`${logPrefix8} Failed to update metadata for ${entry.externalId}: ${message}`);
|
|
14619
14732
|
return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
|
|
14620
14733
|
}
|
|
14621
14734
|
}
|
|
@@ -14627,7 +14740,7 @@ var DocumentTap = class {
|
|
|
14627
14740
|
return { externalId: doc.external_id, fileName: doc.file_name, status: "deleted" };
|
|
14628
14741
|
} catch (err) {
|
|
14629
14742
|
const message = err instanceof Error ? err.message : String(err);
|
|
14630
|
-
logger.error(`${
|
|
14743
|
+
logger.error(`${logPrefix8} Failed to delete ${doc.external_id}: ${message}`);
|
|
14631
14744
|
return { externalId: doc.external_id, fileName: doc.file_name, status: "error", error: message };
|
|
14632
14745
|
}
|
|
14633
14746
|
}
|
|
@@ -14664,7 +14777,7 @@ function enrichAxiosError(err) {
|
|
|
14664
14777
|
const detail = typeof body === "string" ? body : JSON.stringify(body ?? "");
|
|
14665
14778
|
return new Error(`${method} ${url} failed with status ${status}: ${detail}`, { cause: err });
|
|
14666
14779
|
}
|
|
14667
|
-
var
|
|
14780
|
+
var logPrefix9 = "[WhalyDocumentService]";
|
|
14668
14781
|
var MAX_RETRIES2 = 10;
|
|
14669
14782
|
var WhalyDocumentService = class {
|
|
14670
14783
|
axiosClient;
|
|
@@ -14677,7 +14790,7 @@ var WhalyDocumentService = class {
|
|
|
14677
14790
|
const gcsBucketName = process.env["WLY_GCS_BUCKET"];
|
|
14678
14791
|
if (gcsBucketName) {
|
|
14679
14792
|
this.gcsBucket = new import_storage2.Storage().bucket(gcsBucketName);
|
|
14680
|
-
logger.info(`${
|
|
14793
|
+
logger.info(`${logPrefix9} Using direct GCS upload to bucket: ${gcsBucketName}`);
|
|
14681
14794
|
}
|
|
14682
14795
|
this.axiosClient = import_axios4.default.create({
|
|
14683
14796
|
baseURL: resolvedEndpoint,
|
|
@@ -14698,12 +14811,12 @@ var WhalyDocumentService = class {
|
|
|
14698
14811
|
if (retryAfter) {
|
|
14699
14812
|
const seconds = Number(retryAfter);
|
|
14700
14813
|
if (!isNaN(seconds) && seconds > 0) {
|
|
14701
|
-
logger.info(`${
|
|
14814
|
+
logger.info(`${logPrefix9} Rate limited \u2014 waiting ${seconds}s, attempt ${retryCount}/${MAX_RETRIES2}`);
|
|
14702
14815
|
return seconds * 1e3;
|
|
14703
14816
|
}
|
|
14704
14817
|
}
|
|
14705
14818
|
const delay = import_axios_retry3.default.exponentialDelay(retryCount);
|
|
14706
|
-
logger.info(`${
|
|
14819
|
+
logger.info(`${logPrefix9} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES2}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
|
|
14707
14820
|
return delay;
|
|
14708
14821
|
}
|
|
14709
14822
|
});
|
|
@@ -14724,7 +14837,7 @@ var WhalyDocumentService = class {
|
|
|
14724
14837
|
if (!nextAfter) break;
|
|
14725
14838
|
after = nextAfter;
|
|
14726
14839
|
}
|
|
14727
|
-
logger.info(`${
|
|
14840
|
+
logger.info(`${logPrefix9} Fetched ${documents.length} existing documents from Whaly`);
|
|
14728
14841
|
return documents;
|
|
14729
14842
|
}
|
|
14730
14843
|
/** Upload a file to object storage. Returns storage path and size. */
|
|
@@ -14745,8 +14858,8 @@ var WhalyDocumentService = class {
|
|
|
14745
14858
|
} catch (err) {
|
|
14746
14859
|
if (attempt === maxAttempts) throw err;
|
|
14747
14860
|
const delay = Math.min(1e3 * 2 ** (attempt - 1), 3e4);
|
|
14748
|
-
logger.info(`${
|
|
14749
|
-
await new Promise((
|
|
14861
|
+
logger.info(`${logPrefix9} GCS upload retry ${attempt}/${MAX_RETRIES2} in ${delay}ms for ${destinationPath}`);
|
|
14862
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
14750
14863
|
}
|
|
14751
14864
|
}
|
|
14752
14865
|
const stat = await import_node_fs.default.promises.stat(localFilePath);
|
|
@@ -14802,7 +14915,7 @@ var WhalyDocumentService = class {
|
|
|
14802
14915
|
};
|
|
14803
14916
|
|
|
14804
14917
|
// src/sdk/models/document-target/whaly-document-target.ts
|
|
14805
|
-
var
|
|
14918
|
+
var logPrefix10 = "[WhalyDocumentTarget]";
|
|
14806
14919
|
var WhalyDocumentTarget = class {
|
|
14807
14920
|
config;
|
|
14808
14921
|
service;
|
|
@@ -14821,7 +14934,7 @@ var WhalyDocumentTarget = class {
|
|
|
14821
14934
|
async createDocument(streamId, entry, localFilePath) {
|
|
14822
14935
|
const destinationPath = `${streamId}/${entry.externalId}.${entry.extension}`;
|
|
14823
14936
|
const fileName = `${entry.externalId}.${entry.extension}`;
|
|
14824
|
-
logger.info(`${
|
|
14937
|
+
logger.info(`${logPrefix10} Uploading file for new document: ${entry.externalId}`);
|
|
14825
14938
|
const uploadResult = await this.service.uploadFile(destinationPath, localFilePath, fileName);
|
|
14826
14939
|
const stat = await import_fs_extra6.default.stat(localFilePath);
|
|
14827
14940
|
await this.service.createDocument({
|
|
@@ -14838,7 +14951,7 @@ var WhalyDocumentTarget = class {
|
|
|
14838
14951
|
storage: uploadResult.storage,
|
|
14839
14952
|
metadata: entry.metadata ?? {}
|
|
14840
14953
|
});
|
|
14841
|
-
logger.info(`${
|
|
14954
|
+
logger.info(`${logPrefix10} Created document: ${entry.externalId} (${entry.fileName})`);
|
|
14842
14955
|
}
|
|
14843
14956
|
/**
|
|
14844
14957
|
* Re-upload the file and update the document record.
|
|
@@ -14846,7 +14959,7 @@ var WhalyDocumentTarget = class {
|
|
|
14846
14959
|
async reuploadDocument(streamId, docId, entry, localFilePath) {
|
|
14847
14960
|
const destinationPath = `${streamId}/${entry.externalId}.${entry.extension}`;
|
|
14848
14961
|
const fileName = `${entry.externalId}.${entry.extension}`;
|
|
14849
|
-
logger.info(`${
|
|
14962
|
+
logger.info(`${logPrefix10} Re-uploading file for document: ${entry.externalId}`);
|
|
14850
14963
|
const uploadResult = await this.service.uploadFile(destinationPath, localFilePath, fileName);
|
|
14851
14964
|
const stat = await import_fs_extra6.default.stat(localFilePath);
|
|
14852
14965
|
await this.service.updateDocument(docId, {
|
|
@@ -14863,7 +14976,7 @@ var WhalyDocumentTarget = class {
|
|
|
14863
14976
|
storage: uploadResult.storage,
|
|
14864
14977
|
metadata: entry.metadata ?? {}
|
|
14865
14978
|
});
|
|
14866
|
-
logger.info(`${
|
|
14979
|
+
logger.info(`${logPrefix10} Re-uploaded document: ${entry.externalId} (${entry.fileName})`);
|
|
14867
14980
|
}
|
|
14868
14981
|
/** Update only the metadata fields (no file re-upload). */
|
|
14869
14982
|
async updateDocumentMetadata(docId, entry, existingDoc) {
|
|
@@ -14881,12 +14994,12 @@ var WhalyDocumentTarget = class {
|
|
|
14881
14994
|
storage: existingDoc.storage,
|
|
14882
14995
|
metadata: entry.metadata ?? {}
|
|
14883
14996
|
});
|
|
14884
|
-
logger.info(`${
|
|
14997
|
+
logger.info(`${logPrefix10} Updated metadata for document: ${entry.externalId} (${entry.fileName})`);
|
|
14885
14998
|
}
|
|
14886
14999
|
/** Delete a document record. */
|
|
14887
15000
|
async deleteDocument(docId, externalId) {
|
|
14888
15001
|
await this.service.deleteDocument(docId);
|
|
14889
|
-
logger.info(`${
|
|
15002
|
+
logger.info(`${logPrefix10} Deleted document: ${externalId} (id=${docId})`);
|
|
14890
15003
|
}
|
|
14891
15004
|
};
|
|
14892
15005
|
|
|
@@ -15225,7 +15338,7 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
|
|
|
15225
15338
|
gracefulExit(1);
|
|
15226
15339
|
} else {
|
|
15227
15340
|
logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
|
|
15228
|
-
await new Promise((
|
|
15341
|
+
await new Promise((resolve2, reject) => setTimeout(resolve2, 1e3 * retryCount));
|
|
15229
15342
|
await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
|
|
15230
15343
|
}
|
|
15231
15344
|
}
|
|
@@ -15453,7 +15566,7 @@ var BigQueryTarget = class extends ITarget {
|
|
|
15453
15566
|
|
|
15454
15567
|
// src/targets/cdn/main.ts
|
|
15455
15568
|
var import_fs_extra7 = __toESM(require("fs-extra"));
|
|
15456
|
-
var
|
|
15569
|
+
var logPrefix11 = "[CdnAssetTarget]";
|
|
15457
15570
|
var CdnAssetTarget = class extends AssetTarget {
|
|
15458
15571
|
cdnService;
|
|
15459
15572
|
constructor(config) {
|
|
@@ -15463,20 +15576,20 @@ var CdnAssetTarget = class extends AssetTarget {
|
|
|
15463
15576
|
async shouldSync(entry) {
|
|
15464
15577
|
const metadata = await this.cdnService.getFileMetadata(getCdnId(this.config.cdnId), entry.destinationPath);
|
|
15465
15578
|
if (!metadata.exists) {
|
|
15466
|
-
logger.debug(`${
|
|
15579
|
+
logger.debug(`${logPrefix11} ${entry.destinationPath} not in CDN \u2192 will sync`);
|
|
15467
15580
|
return true;
|
|
15468
15581
|
}
|
|
15469
15582
|
if (entry.lastModified === void 0) {
|
|
15470
|
-
logger.debug(`${
|
|
15583
|
+
logger.debug(`${logPrefix11} ${entry.destinationPath} source has no lastModified \u2192 will sync`);
|
|
15471
15584
|
return true;
|
|
15472
15585
|
}
|
|
15473
15586
|
if (metadata.lastModified === null) {
|
|
15474
|
-
logger.debug(`${
|
|
15587
|
+
logger.debug(`${logPrefix11} ${entry.destinationPath} CDN has no lastModified \u2192 will sync`);
|
|
15475
15588
|
return true;
|
|
15476
15589
|
}
|
|
15477
15590
|
const sourceIsNewer = entry.lastModified > metadata.lastModified;
|
|
15478
15591
|
if (!sourceIsNewer) {
|
|
15479
|
-
logger.debug(`${
|
|
15592
|
+
logger.debug(`${logPrefix11} Skipping ${entry.destinationPath} (CDN is up-to-date)`);
|
|
15480
15593
|
}
|
|
15481
15594
|
return sourceIsNewer;
|
|
15482
15595
|
}
|
|
@@ -15487,7 +15600,7 @@ var CdnAssetTarget = class extends AssetTarget {
|
|
|
15487
15600
|
asset.entry.destinationPath,
|
|
15488
15601
|
fileBuffer
|
|
15489
15602
|
);
|
|
15490
|
-
logger.info(`${
|
|
15603
|
+
logger.info(`${logPrefix11} Uploaded ${asset.entry.destinationPath} (${fileBuffer.length} bytes)`);
|
|
15491
15604
|
}
|
|
15492
15605
|
};
|
|
15493
15606
|
|
|
@@ -15557,6 +15670,7 @@ var GCSStateProvider = class {
|
|
|
15557
15670
|
GCSStateProvider,
|
|
15558
15671
|
ITarget,
|
|
15559
15672
|
ImageTransform,
|
|
15673
|
+
LocalStorageService,
|
|
15560
15674
|
MIME_TYPES,
|
|
15561
15675
|
MissingFieldInSchemaError,
|
|
15562
15676
|
MissingSchemaError,
|