@whaly/connector-sdk 0.3.9 → 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.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 extname2 = require("path").extname;
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 = extname2("x." + path8).toLowerCase().substr(1);
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 fs11 = require("fs");
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
- fs11.stat(value.path, function(err, stat) {
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,
@@ -10397,6 +10398,7 @@ __export(index_exports, {
10397
10398
  getCounterMetrics: () => getCounterMetrics,
10398
10399
  getDownloadFileApiCall: () => getDownloadFileApiCall,
10399
10400
  getDryRunLimit: () => getDryRunLimit,
10401
+ getExtension: () => getExtension,
10400
10402
  getJSONApiCall: () => getJSONApiCall,
10401
10403
  getJSONApiCallWithFullResponse: () => getJSONApiCallWithFullResponse,
10402
10404
  getMimeType: () => getMimeType,
@@ -10640,7 +10642,7 @@ var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) =>
10640
10642
  ...config.headers ? { headers: config.headers } : {},
10641
10643
  paramsSerializer: { serialize: (params) => qs.stringify(params, { arrayFormat: "repeat" }) }
10642
10644
  }).then((response) => {
10643
- return new Promise((resolve, reject) => {
10645
+ return new Promise((resolve2, reject) => {
10644
10646
  response.data.pipe(writer);
10645
10647
  let error = null;
10646
10648
  writer.on("error", (err) => {
@@ -10650,7 +10652,7 @@ var getDownloadFileApiCall = async (endpoint, config, output, privateLogging) =>
10650
10652
  });
10651
10653
  writer.on("close", () => {
10652
10654
  if (!error) {
10653
- resolve({ outputDir: output });
10655
+ resolve2({ outputDir: output });
10654
10656
  }
10655
10657
  });
10656
10658
  });
@@ -10801,8 +10803,8 @@ var printMemoryFootprint = (prefix) => {
10801
10803
 
10802
10804
  // src/sdk/service/exit.ts
10803
10805
  async function gracefulExit(exitCode) {
10804
- const loggerFinish = new Promise((resolve, reject) => {
10805
- logger.closeWinston(resolve);
10806
+ const loggerFinish = new Promise((resolve2, reject) => {
10807
+ logger.closeWinston(resolve2);
10806
10808
  });
10807
10809
  await loggerFinish;
10808
10810
  process.exit(exitCode);
@@ -10973,6 +10975,16 @@ function getMimeType(filePathOrExt) {
10973
10975
  const ext = filePathOrExt.startsWith(".") ? filePathOrExt.toLowerCase() : import_node_path.default.extname(filePathOrExt).toLowerCase();
10974
10976
  return MIME_TYPES[ext] ?? "application/octet-stream";
10975
10977
  }
10978
+ var EXTENSION_BY_MIME = Object.entries(MIME_TYPES).reduce(
10979
+ (acc, [ext, mime]) => {
10980
+ if (!acc[mime]) acc[mime] = ext;
10981
+ return acc;
10982
+ },
10983
+ {}
10984
+ );
10985
+ function getExtension(mimeType) {
10986
+ return EXTENSION_BY_MIME[mimeType] ?? ".bin";
10987
+ }
10976
10988
 
10977
10989
  // src/sdk/models/replication.ts
10978
10990
  var ReplicationMethod = /* @__PURE__ */ ((ReplicationMethod2) => {
@@ -12562,13 +12574,13 @@ var ITarget = class _ITarget {
12562
12574
  const dbSyncInstance = stream.getDbSync();
12563
12575
  const recordWithrenamedColumns = dbSyncInstance.renameColumns(recordWithDecimal);
12564
12576
  const serializedRecord = this.getSerializedRecord(streamId, recordWithrenamedColumns);
12565
- return new Promise((resolve, reject) => {
12577
+ return new Promise((resolve2, reject) => {
12566
12578
  stream.getFileToLoad().stream.write(serializedRecord, (error) => {
12567
12579
  if (error) {
12568
12580
  reject(error);
12569
12581
  }
12570
12582
  stream.incrementBatchedRowCount();
12571
- resolve();
12583
+ resolve2();
12572
12584
  });
12573
12585
  });
12574
12586
  } catch (err) {
@@ -12709,11 +12721,11 @@ var ITarget = class _ITarget {
12709
12721
  const streamState = this.streams[streamId];
12710
12722
  if (!streamState || streamState.getBatchedRowCount() === 0) continue;
12711
12723
  const fileToLoad = streamState.getFileToLoad();
12712
- await new Promise((resolve, reject) => {
12724
+ await new Promise((resolve2, reject) => {
12713
12725
  fileToLoad.stream.end(() => {
12714
12726
  fileToLoad.stream.close((err) => {
12715
12727
  if (err) reject(err);
12716
- else resolve();
12728
+ else resolve2();
12717
12729
  });
12718
12730
  });
12719
12731
  });
@@ -13244,18 +13256,18 @@ async function* rowGeneratorFromCsv(path8, fileConfig) {
13244
13256
  }
13245
13257
  }
13246
13258
  async function countCsvLines(filePath) {
13247
- return new Promise((resolve, reject) => {
13259
+ return new Promise((resolve2, reject) => {
13248
13260
  let newlines = 0;
13249
13261
  (0, import_fs2.createReadStream)(filePath).on("data", (chunk2) => {
13250
13262
  const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
13251
13263
  for (let i = 0; i < buf.length; i++) {
13252
13264
  if (buf[i] === 10) newlines++;
13253
13265
  }
13254
- }).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
13266
+ }).on("end", () => resolve2(Math.max(0, newlines - 1))).on("error", reject);
13255
13267
  });
13256
13268
  }
13257
13269
  var checkCsvHeaderRow = async (path8, fileConfig) => {
13258
- return new Promise((resolve, reject) => {
13270
+ return new Promise((resolve2, reject) => {
13259
13271
  const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
13260
13272
  console.debug(`${logPrefix} CSV Expected columns:`, colsWithParsingConfig.map((col) => {
13261
13273
  const info = getStringHexInfo(col);
@@ -13330,7 +13342,7 @@ var checkCsvHeaderRow = async (path8, fileConfig) => {
13330
13342
  reject(new Error(`Error processing CSV file: ${error.message}`));
13331
13343
  });
13332
13344
  initialReadStream.on("close", () => {
13333
- resolve();
13345
+ resolve2();
13334
13346
  });
13335
13347
  });
13336
13348
  };
@@ -13911,6 +13923,16 @@ var CloudStorageService = class {
13911
13923
  throw new Error((0, import_util5.format)(`error writing GCS object gs://${this.bucket.name}/${objectPath}, err: %s`, err?.message));
13912
13924
  }
13913
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
+ }
13914
13936
  /**
13915
13937
  * Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
13916
13938
  * Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
@@ -13939,6 +13961,108 @@ var CloudStorageService = class {
13939
13961
  }
13940
13962
  };
13941
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
+
13942
14066
  // src/services/zip.ts
13943
14067
  var fse = __toESM(require("fs-extra"));
13944
14068
  var import_decompress = __toESM(require("decompress"));
@@ -13951,7 +14075,7 @@ var unzip = async (zipFilePath, extractedPath) => {
13951
14075
  // src/services/cdn.ts
13952
14076
  var import_axios3 = __toESM(require("axios"));
13953
14077
  var import_axios_retry2 = __toESM(require("axios-retry"));
13954
- var logPrefix5 = "[CdnService]";
14078
+ var logPrefix6 = "[CdnService]";
13955
14079
  var MAX_RETRIES = 10;
13956
14080
  var CdnService = class {
13957
14081
  axiosClient;
@@ -13977,12 +14101,12 @@ var CdnService = class {
13977
14101
  if (retryAfter) {
13978
14102
  const seconds = Number(retryAfter);
13979
14103
  if (!isNaN(seconds) && seconds > 0) {
13980
- logger.info(`${logPrefix5} Rate limited \u2014 waiting ${seconds}s (Retry-After header), attempt ${retryCount}/${MAX_RETRIES}`);
14104
+ logger.info(`${logPrefix6} Rate limited \u2014 waiting ${seconds}s (Retry-After header), attempt ${retryCount}/${MAX_RETRIES}`);
13981
14105
  return seconds * 1e3;
13982
14106
  }
13983
14107
  }
13984
14108
  const delay = import_axios_retry2.default.exponentialDelay(retryCount);
13985
- logger.info(`${logPrefix5} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
14109
+ logger.info(`${logPrefix6} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
13986
14110
  return delay;
13987
14111
  }
13988
14112
  });
@@ -14008,7 +14132,7 @@ var CdnService = class {
14008
14132
  const status = err.response?.status;
14009
14133
  if (status === 404) return { exists: false, lastModified: null, contentType: null };
14010
14134
  if (status === 405) {
14011
- logger.warn(`${logPrefix5} HEAD not supported for ${url}, treating file as absent`);
14135
+ logger.warn(`${logPrefix6} HEAD not supported for ${url}, treating file as absent`);
14012
14136
  return { exists: false, lastModified: null, contentType: null };
14013
14137
  }
14014
14138
  this.throwMeaningfulError(err);
@@ -14032,11 +14156,11 @@ var CdnService = class {
14032
14156
  async uploadFile(cdnId, fileName, fileBuffer) {
14033
14157
  const url = `/v1/cdn/${cdnId}/files/${encodeURIComponent(fileName)}`;
14034
14158
  try {
14035
- logger.info(`${logPrefix5} Uploading ${fileName} (${fileBuffer.length} bytes) to CDN ${cdnId}`);
14159
+ logger.info(`${logPrefix6} Uploading ${fileName} (${fileBuffer.length} bytes) to CDN ${cdnId}`);
14036
14160
  const form = new FormData();
14037
14161
  form.append("file", new Blob([fileBuffer]), fileName);
14038
14162
  const response = await this.axiosClient.put(url, form);
14039
- logger.info(`${logPrefix5} Successfully uploaded ${fileName} (status ${response.status})`);
14163
+ logger.info(`${logPrefix6} Successfully uploaded ${fileName} (status ${response.status})`);
14040
14164
  return {
14041
14165
  filePath: response.data?.filePath ?? `/org/${cdnId}/file/${fileName}`
14042
14166
  };
@@ -14079,7 +14203,7 @@ var AssetStream = class {
14079
14203
  // src/sdk/models/asset-tap/asset-tap.ts
14080
14204
  var import_fs_extra4 = __toESM(require("fs-extra"));
14081
14205
  var import_node_path4 = __toESM(require("path"));
14082
- var logPrefix6 = "[AssetTap]";
14206
+ var logPrefix7 = "[AssetTap]";
14083
14207
  function inferContentType(filePath, fallback) {
14084
14208
  const mime = getMimeType(filePath);
14085
14209
  return mime !== "application/octet-stream" ? mime : fallback;
@@ -14087,7 +14211,7 @@ function inferContentType(filePath, fallback) {
14087
14211
  function deriveManifestMode(streams) {
14088
14212
  const modes = new Set(streams.map((s) => s.replicationMode));
14089
14213
  if (modes.size > 1) {
14090
- logger.warn(`${logPrefix6} Streams have mixed replication modes (${[...modes].join(", ")}). Manifest will report "FULL".`);
14214
+ logger.warn(`${logPrefix7} Streams have mixed replication modes (${[...modes].join(", ")}). Manifest will report "FULL".`);
14091
14215
  return "FULL";
14092
14216
  }
14093
14217
  return streams[0]?.replicationMode ?? "INCREMENTAL";
@@ -14109,13 +14233,13 @@ var AssetTap = class {
14109
14233
  const dryRun = isDryRun();
14110
14234
  const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
14111
14235
  if (dryRun) {
14112
- logger.info(`${logPrefix6} [DRY_RUN] mode active \u2014 skipping CDN checks and uploads`);
14236
+ logger.info(`${logPrefix7} [DRY_RUN] mode active \u2014 skipping CDN checks and uploads`);
14113
14237
  await import_fs_extra4.default.emptyDir(this.outputDir);
14114
14238
  if (dryRunLimit !== void 0) {
14115
- logger.info(`${logPrefix6} [DRY_RUN] Limit: ${dryRunLimit} assets per stream`);
14239
+ logger.info(`${logPrefix7} [DRY_RUN] Limit: ${dryRunLimit} assets per stream`);
14116
14240
  }
14117
14241
  } else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
14118
- logger.warn(`${logPrefix6} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
14242
+ logger.warn(`${logPrefix7} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
14119
14243
  }
14120
14244
  const tmpDir2 = import_node_path4.default.join(this.outputDir, "tmp");
14121
14245
  await import_fs_extra4.default.ensureDir(tmpDir2);
@@ -14123,7 +14247,7 @@ var AssetTap = class {
14123
14247
  let entryIndex = 0;
14124
14248
  let totalSummary = { total: 0, uploaded: 0, skipped: 0, errors: 0 };
14125
14249
  for (const stream of this.streams) {
14126
- logger.info(`${logPrefix6} Processing stream: ${stream.streamId} (mode=${stream.replicationMode}, concurrency=${this.concurrency})`);
14250
+ logger.info(`${logPrefix7} Processing stream: ${stream.streamId} (mode=${stream.replicationMode}, concurrency=${this.concurrency})`);
14127
14251
  const assetEntries = [];
14128
14252
  let streamAssetCount = 0;
14129
14253
  await processFromAsyncIterable(
@@ -14132,11 +14256,11 @@ var AssetTap = class {
14132
14256
  if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
14133
14257
  return "stop";
14134
14258
  }
14135
- logger.debug(`${logPrefix6} Processing entry: ${entry.sourcePath}`);
14259
+ logger.debug(`${logPrefix7} Processing entry: ${entry.sourcePath}`);
14136
14260
  if (stream.replicationMode === "INCREMENTAL" && !dryRun) {
14137
14261
  const shouldSync = await this.target.shouldSync(entry);
14138
14262
  if (!shouldSync) {
14139
- logger.debug(`${logPrefix6} Skipping ${entry.sourcePath} (up-to-date)`);
14263
+ logger.debug(`${logPrefix7} Skipping ${entry.sourcePath} (up-to-date)`);
14140
14264
  assetEntries.push({
14141
14265
  sourcePath: entry.sourcePath,
14142
14266
  destinationPath: entry.destinationPath,
@@ -14174,6 +14298,13 @@ var AssetTap = class {
14174
14298
  const inspectPath = import_node_path4.default.join(this.outputDir, stream.streamId, entry.destinationPath);
14175
14299
  await import_fs_extra4.default.ensureDir(import_node_path4.default.dirname(inspectPath));
14176
14300
  await import_fs_extra4.default.copy(uploadPath, inspectPath);
14301
+ if (wasTransformed) {
14302
+ const baseName = import_node_path4.default.basename(entry.destinationPath, import_node_path4.default.extname(entry.destinationPath));
14303
+ const origExt = getExtension(entry.contentType);
14304
+ const originalPath = import_node_path4.default.join(this.outputDir, "originals", `${baseName}${origExt}`);
14305
+ await import_fs_extra4.default.ensureDir(import_node_path4.default.dirname(originalPath));
14306
+ await import_fs_extra4.default.copy(downloadedPath, originalPath);
14307
+ }
14177
14308
  }
14178
14309
  assetEntries.push({
14179
14310
  sourcePath: entry.sourcePath,
@@ -14185,10 +14316,10 @@ var AssetTap = class {
14185
14316
  status: "uploaded",
14186
14317
  transformed: wasTransformed
14187
14318
  });
14188
- logger.info(`${logPrefix6} ${dryRun ? "[DRY_RUN] Processed" : "Uploaded"} ${entry.sourcePath} \u2192 ${entry.destinationPath}`);
14319
+ logger.info(`${logPrefix7} ${dryRun ? "[DRY_RUN] Processed" : "Uploaded"} ${entry.sourcePath} \u2192 ${entry.destinationPath}`);
14189
14320
  } catch (err) {
14190
14321
  const message = err instanceof Error ? err.message : String(err);
14191
- logger.error(`${logPrefix6} Failed to process ${entry.sourcePath}: ${message}`);
14322
+ logger.error(`${logPrefix7} Failed to process ${entry.sourcePath}: ${message}`);
14192
14323
  assetEntries.push({
14193
14324
  sourcePath: entry.sourcePath,
14194
14325
  destinationPath: entry.destinationPath,
@@ -14207,7 +14338,7 @@ var AssetTap = class {
14207
14338
  }
14208
14339
  }
14209
14340
  if (dryRunLimit !== void 0 && streamAssetCount >= dryRunLimit) {
14210
- logger.info(`${logPrefix6} [DRY_RUN] Reached limit of ${dryRunLimit} for stream "${stream.streamId}", stopping.`);
14341
+ logger.info(`${logPrefix7} [DRY_RUN] Reached limit of ${dryRunLimit} for stream "${stream.streamId}", stopping.`);
14211
14342
  return "stop";
14212
14343
  }
14213
14344
  return "continue";
@@ -14243,7 +14374,7 @@ var AssetTap = class {
14243
14374
  if (!dryRun) {
14244
14375
  await this.target.complete();
14245
14376
  }
14246
- logger.info(`${logPrefix6} Sync complete. Uploaded=${totalSummary.uploaded} Skipped=${totalSummary.skipped} Errors=${totalSummary.errors}`);
14377
+ logger.info(`${logPrefix7} Sync complete. Uploaded=${totalSummary.uploaded} Skipped=${totalSummary.skipped} Errors=${totalSummary.errors}`);
14247
14378
  return manifest;
14248
14379
  }
14249
14380
  };
@@ -14281,8 +14412,8 @@ var ImageTransform = class {
14281
14412
  */
14282
14413
  static async toWebp(inputPath, options) {
14283
14414
  const dir = import_node_path5.default.dirname(inputPath);
14284
- const basename = import_node_path5.default.basename(inputPath, import_node_path5.default.extname(inputPath));
14285
- const outputPath = import_node_path5.default.join(dir, `${basename}.webp`);
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`);
14286
14417
  let pipeline = (0, import_sharp.default)(inputPath);
14287
14418
  const hasSize = options.width !== void 0 && options.height !== void 0;
14288
14419
  if (hasSize) {
@@ -14406,7 +14537,7 @@ async function getFileSizeLabel(filePath) {
14406
14537
  return "unknown size";
14407
14538
  }
14408
14539
  }
14409
- var logPrefix7 = "[DocumentTap]";
14540
+ var logPrefix8 = "[DocumentTap]";
14410
14541
  var DocumentTap = class {
14411
14542
  config;
14412
14543
  outputDir;
@@ -14425,32 +14556,32 @@ var DocumentTap = class {
14425
14556
  }
14426
14557
  async sync() {
14427
14558
  if (!this.target) {
14428
- throw new Error(`${logPrefix7} No target set. Assign a WhalyDocumentTarget before calling sync().`);
14559
+ throw new Error(`${logPrefix8} No target set. Assign a WhalyDocumentTarget before calling sync().`);
14429
14560
  }
14430
14561
  await this.init();
14431
14562
  if (!this.stream) {
14432
- throw new Error(`${logPrefix7} No stream set. Assign this.stream in init().`);
14563
+ throw new Error(`${logPrefix8} No stream set. Assign this.stream in init().`);
14433
14564
  }
14434
14565
  const dryRun = isDryRun();
14435
14566
  const dryRunLimit = dryRun ? getDryRunLimit() : void 0;
14436
14567
  if (dryRun) {
14437
- logger.info(`${logPrefix7} [DRY_RUN] mode active \u2014 skipping API calls and uploads`);
14568
+ logger.info(`${logPrefix8} [DRY_RUN] mode active \u2014 skipping API calls and uploads`);
14438
14569
  await import_fs_extra5.default.emptyDir(this.outputDir);
14439
14570
  if (dryRunLimit !== void 0) {
14440
- logger.info(`${logPrefix7} [DRY_RUN] Limit: ${dryRunLimit} documents per stream`);
14571
+ logger.info(`${logPrefix8} [DRY_RUN] Limit: ${dryRunLimit} documents per stream`);
14441
14572
  }
14442
14573
  } else if (process.env["DRY_RUN_LIMIT"] !== void 0) {
14443
- logger.warn(`${logPrefix7} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
14574
+ logger.warn(`${logPrefix8} DRY_RUN_LIMIT is set but DRY_RUN is not active \u2014 limit will be ignored`);
14444
14575
  }
14445
14576
  const tmpDir2 = import_node_path6.default.join(this.outputDir, "tmp");
14446
14577
  await import_fs_extra5.default.ensureDir(tmpDir2);
14447
14578
  try {
14448
14579
  const stream = this.stream;
14449
- logger.info(`${logPrefix7} Processing stream: ${stream.streamId}`);
14580
+ logger.info(`${logPrefix8} Processing stream: ${stream.streamId}`);
14450
14581
  const sourceEntries = await this.collectSourceEntries(stream, dryRunLimit);
14451
14582
  const existingDocs = dryRun ? [] : await this.target.listExistingDocuments();
14452
14583
  const diff = this.computeDiff(stream, sourceEntries, existingDocs);
14453
- logger.info(`${logPrefix7} Stream ${stream.streamId} diff: create=${diff.toCreate.length} reupload=${diff.toReupload.length} updateMeta=${diff.toUpdateMetadata.length} delete=${diff.toDelete.length} skip=${diff.skipped.length}`);
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}`);
14454
14585
  const entries = [];
14455
14586
  const createTasks = diff.toCreate.map((entry) => async () => {
14456
14587
  return this.executeCreate(stream, entry, tmpDir2, dryRun);
@@ -14484,7 +14615,7 @@ var DocumentTap = class {
14484
14615
  };
14485
14616
  await import_fs_extra5.default.ensureDir(this.outputDir);
14486
14617
  await import_fs_extra5.default.writeJson(import_node_path6.default.join(this.outputDir, "manifest.json"), manifest, { spaces: 2 });
14487
- logger.info(`${logPrefix7} Sync complete. Created=${streamSummary.created} Updated=${streamSummary.updated} Reuploaded=${streamSummary.reuploaded} Deleted=${streamSummary.deleted} Skipped=${streamSummary.skipped} Errors=${streamSummary.errors}`);
14618
+ logger.info(`${logPrefix8} Sync complete. Created=${streamSummary.created} Updated=${streamSummary.updated} Reuploaded=${streamSummary.reuploaded} Deleted=${streamSummary.deleted} Skipped=${streamSummary.skipped} Errors=${streamSummary.errors}`);
14488
14619
  return manifest;
14489
14620
  } finally {
14490
14621
  await import_fs_extra5.default.remove(tmpDir2).catch(() => void 0);
@@ -14552,12 +14683,12 @@ var DocumentTap = class {
14552
14683
  return { externalId: entry.externalId, fileName: entry.fileName, status: "created" };
14553
14684
  } catch (err) {
14554
14685
  if (err instanceof DocumentDownloadSkipError) {
14555
- logger.warn(`${logPrefix7} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
14686
+ logger.warn(`${logPrefix8} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
14556
14687
  return { externalId: entry.externalId, fileName: entry.fileName, status: "skipped" };
14557
14688
  }
14558
14689
  const message = err instanceof Error ? err.message : String(err);
14559
14690
  const sizeLabel = await getFileSizeLabel(downloadPath);
14560
- logger.error(`${logPrefix7} Failed to create ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
14691
+ logger.error(`${logPrefix8} Failed to create ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
14561
14692
  return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
14562
14693
  } finally {
14563
14694
  await import_fs_extra5.default.remove(downloadPath).catch(() => void 0);
@@ -14578,12 +14709,12 @@ var DocumentTap = class {
14578
14709
  return { externalId: entry.externalId, fileName: entry.fileName, status: "reuploaded" };
14579
14710
  } catch (err) {
14580
14711
  if (err instanceof DocumentDownloadSkipError) {
14581
- logger.warn(`${logPrefix7} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
14712
+ logger.warn(`${logPrefix8} Skipped ${entry.externalId} (${entry.fileName}): ${err.message}`);
14582
14713
  return { externalId: entry.externalId, fileName: entry.fileName, status: "skipped" };
14583
14714
  }
14584
14715
  const message = err instanceof Error ? err.message : String(err);
14585
14716
  const sizeLabel = await getFileSizeLabel(downloadPath);
14586
- logger.error(`${logPrefix7} Failed to reupload ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
14717
+ logger.error(`${logPrefix8} Failed to reupload ${entry.externalId} (${entry.fileName}, ${sizeLabel}): ${message}`);
14587
14718
  return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
14588
14719
  } finally {
14589
14720
  await import_fs_extra5.default.remove(downloadPath).catch(() => void 0);
@@ -14597,7 +14728,7 @@ var DocumentTap = class {
14597
14728
  return { externalId: entry.externalId, fileName: entry.fileName, status: "updated" };
14598
14729
  } catch (err) {
14599
14730
  const message = err instanceof Error ? err.message : String(err);
14600
- logger.error(`${logPrefix7} Failed to update metadata for ${entry.externalId}: ${message}`);
14731
+ logger.error(`${logPrefix8} Failed to update metadata for ${entry.externalId}: ${message}`);
14601
14732
  return { externalId: entry.externalId, fileName: entry.fileName, status: "error", error: message };
14602
14733
  }
14603
14734
  }
@@ -14609,7 +14740,7 @@ var DocumentTap = class {
14609
14740
  return { externalId: doc.external_id, fileName: doc.file_name, status: "deleted" };
14610
14741
  } catch (err) {
14611
14742
  const message = err instanceof Error ? err.message : String(err);
14612
- logger.error(`${logPrefix7} Failed to delete ${doc.external_id}: ${message}`);
14743
+ logger.error(`${logPrefix8} Failed to delete ${doc.external_id}: ${message}`);
14613
14744
  return { externalId: doc.external_id, fileName: doc.file_name, status: "error", error: message };
14614
14745
  }
14615
14746
  }
@@ -14646,7 +14777,7 @@ function enrichAxiosError(err) {
14646
14777
  const detail = typeof body === "string" ? body : JSON.stringify(body ?? "");
14647
14778
  return new Error(`${method} ${url} failed with status ${status}: ${detail}`, { cause: err });
14648
14779
  }
14649
- var logPrefix8 = "[WhalyDocumentService]";
14780
+ var logPrefix9 = "[WhalyDocumentService]";
14650
14781
  var MAX_RETRIES2 = 10;
14651
14782
  var WhalyDocumentService = class {
14652
14783
  axiosClient;
@@ -14659,7 +14790,7 @@ var WhalyDocumentService = class {
14659
14790
  const gcsBucketName = process.env["WLY_GCS_BUCKET"];
14660
14791
  if (gcsBucketName) {
14661
14792
  this.gcsBucket = new import_storage2.Storage().bucket(gcsBucketName);
14662
- logger.info(`${logPrefix8} Using direct GCS upload to bucket: ${gcsBucketName}`);
14793
+ logger.info(`${logPrefix9} Using direct GCS upload to bucket: ${gcsBucketName}`);
14663
14794
  }
14664
14795
  this.axiosClient = import_axios4.default.create({
14665
14796
  baseURL: resolvedEndpoint,
@@ -14680,12 +14811,12 @@ var WhalyDocumentService = class {
14680
14811
  if (retryAfter) {
14681
14812
  const seconds = Number(retryAfter);
14682
14813
  if (!isNaN(seconds) && seconds > 0) {
14683
- logger.info(`${logPrefix8} Rate limited \u2014 waiting ${seconds}s, attempt ${retryCount}/${MAX_RETRIES2}`);
14814
+ logger.info(`${logPrefix9} Rate limited \u2014 waiting ${seconds}s, attempt ${retryCount}/${MAX_RETRIES2}`);
14684
14815
  return seconds * 1e3;
14685
14816
  }
14686
14817
  }
14687
14818
  const delay = import_axios_retry3.default.exponentialDelay(retryCount);
14688
- logger.info(`${logPrefix8} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES2}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
14819
+ logger.info(`${logPrefix9} Retrying in ${delay}ms (attempt ${retryCount}/${MAX_RETRIES2}) for ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
14689
14820
  return delay;
14690
14821
  }
14691
14822
  });
@@ -14706,7 +14837,7 @@ var WhalyDocumentService = class {
14706
14837
  if (!nextAfter) break;
14707
14838
  after = nextAfter;
14708
14839
  }
14709
- logger.info(`${logPrefix8} Fetched ${documents.length} existing documents from Whaly`);
14840
+ logger.info(`${logPrefix9} Fetched ${documents.length} existing documents from Whaly`);
14710
14841
  return documents;
14711
14842
  }
14712
14843
  /** Upload a file to object storage. Returns storage path and size. */
@@ -14727,8 +14858,8 @@ var WhalyDocumentService = class {
14727
14858
  } catch (err) {
14728
14859
  if (attempt === maxAttempts) throw err;
14729
14860
  const delay = Math.min(1e3 * 2 ** (attempt - 1), 3e4);
14730
- logger.info(`${logPrefix8} GCS upload retry ${attempt}/${MAX_RETRIES2} in ${delay}ms for ${destinationPath}`);
14731
- await new Promise((resolve) => setTimeout(resolve, delay));
14861
+ logger.info(`${logPrefix9} GCS upload retry ${attempt}/${MAX_RETRIES2} in ${delay}ms for ${destinationPath}`);
14862
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
14732
14863
  }
14733
14864
  }
14734
14865
  const stat = await import_node_fs.default.promises.stat(localFilePath);
@@ -14784,7 +14915,7 @@ var WhalyDocumentService = class {
14784
14915
  };
14785
14916
 
14786
14917
  // src/sdk/models/document-target/whaly-document-target.ts
14787
- var logPrefix9 = "[WhalyDocumentTarget]";
14918
+ var logPrefix10 = "[WhalyDocumentTarget]";
14788
14919
  var WhalyDocumentTarget = class {
14789
14920
  config;
14790
14921
  service;
@@ -14803,7 +14934,7 @@ var WhalyDocumentTarget = class {
14803
14934
  async createDocument(streamId, entry, localFilePath) {
14804
14935
  const destinationPath = `${streamId}/${entry.externalId}.${entry.extension}`;
14805
14936
  const fileName = `${entry.externalId}.${entry.extension}`;
14806
- logger.info(`${logPrefix9} Uploading file for new document: ${entry.externalId}`);
14937
+ logger.info(`${logPrefix10} Uploading file for new document: ${entry.externalId}`);
14807
14938
  const uploadResult = await this.service.uploadFile(destinationPath, localFilePath, fileName);
14808
14939
  const stat = await import_fs_extra6.default.stat(localFilePath);
14809
14940
  await this.service.createDocument({
@@ -14820,7 +14951,7 @@ var WhalyDocumentTarget = class {
14820
14951
  storage: uploadResult.storage,
14821
14952
  metadata: entry.metadata ?? {}
14822
14953
  });
14823
- logger.info(`${logPrefix9} Created document: ${entry.externalId} (${entry.fileName})`);
14954
+ logger.info(`${logPrefix10} Created document: ${entry.externalId} (${entry.fileName})`);
14824
14955
  }
14825
14956
  /**
14826
14957
  * Re-upload the file and update the document record.
@@ -14828,7 +14959,7 @@ var WhalyDocumentTarget = class {
14828
14959
  async reuploadDocument(streamId, docId, entry, localFilePath) {
14829
14960
  const destinationPath = `${streamId}/${entry.externalId}.${entry.extension}`;
14830
14961
  const fileName = `${entry.externalId}.${entry.extension}`;
14831
- logger.info(`${logPrefix9} Re-uploading file for document: ${entry.externalId}`);
14962
+ logger.info(`${logPrefix10} Re-uploading file for document: ${entry.externalId}`);
14832
14963
  const uploadResult = await this.service.uploadFile(destinationPath, localFilePath, fileName);
14833
14964
  const stat = await import_fs_extra6.default.stat(localFilePath);
14834
14965
  await this.service.updateDocument(docId, {
@@ -14845,7 +14976,7 @@ var WhalyDocumentTarget = class {
14845
14976
  storage: uploadResult.storage,
14846
14977
  metadata: entry.metadata ?? {}
14847
14978
  });
14848
- logger.info(`${logPrefix9} Re-uploaded document: ${entry.externalId} (${entry.fileName})`);
14979
+ logger.info(`${logPrefix10} Re-uploaded document: ${entry.externalId} (${entry.fileName})`);
14849
14980
  }
14850
14981
  /** Update only the metadata fields (no file re-upload). */
14851
14982
  async updateDocumentMetadata(docId, entry, existingDoc) {
@@ -14863,12 +14994,12 @@ var WhalyDocumentTarget = class {
14863
14994
  storage: existingDoc.storage,
14864
14995
  metadata: entry.metadata ?? {}
14865
14996
  });
14866
- logger.info(`${logPrefix9} Updated metadata for document: ${entry.externalId} (${entry.fileName})`);
14997
+ logger.info(`${logPrefix10} Updated metadata for document: ${entry.externalId} (${entry.fileName})`);
14867
14998
  }
14868
14999
  /** Delete a document record. */
14869
15000
  async deleteDocument(docId, externalId) {
14870
15001
  await this.service.deleteDocument(docId);
14871
- logger.info(`${logPrefix9} Deleted document: ${externalId} (id=${docId})`);
15002
+ logger.info(`${logPrefix10} Deleted document: ${externalId} (id=${docId})`);
14872
15003
  }
14873
15004
  };
14874
15005
 
@@ -15207,7 +15338,7 @@ var BigQueryDBSync = class extends StreamWarehouseSyncService {
15207
15338
  gracefulExit(1);
15208
15339
  } else {
15209
15340
  logger.error(`StreamId: ${this.streamId} - Error when checking if dataset already exists. Retrying... ${err.message} - ${err.code}`);
15210
- await new Promise((resolve, reject) => setTimeout(resolve, 1e3 * retryCount));
15341
+ await new Promise((resolve2, reject) => setTimeout(resolve2, 1e3 * retryCount));
15211
15342
  await this.createDatabaseAndSchemaIfNotExists(retryCount + 1);
15212
15343
  }
15213
15344
  }
@@ -15435,7 +15566,7 @@ var BigQueryTarget = class extends ITarget {
15435
15566
 
15436
15567
  // src/targets/cdn/main.ts
15437
15568
  var import_fs_extra7 = __toESM(require("fs-extra"));
15438
- var logPrefix10 = "[CdnAssetTarget]";
15569
+ var logPrefix11 = "[CdnAssetTarget]";
15439
15570
  var CdnAssetTarget = class extends AssetTarget {
15440
15571
  cdnService;
15441
15572
  constructor(config) {
@@ -15445,20 +15576,20 @@ var CdnAssetTarget = class extends AssetTarget {
15445
15576
  async shouldSync(entry) {
15446
15577
  const metadata = await this.cdnService.getFileMetadata(getCdnId(this.config.cdnId), entry.destinationPath);
15447
15578
  if (!metadata.exists) {
15448
- logger.debug(`${logPrefix10} ${entry.destinationPath} not in CDN \u2192 will sync`);
15579
+ logger.debug(`${logPrefix11} ${entry.destinationPath} not in CDN \u2192 will sync`);
15449
15580
  return true;
15450
15581
  }
15451
15582
  if (entry.lastModified === void 0) {
15452
- logger.debug(`${logPrefix10} ${entry.destinationPath} source has no lastModified \u2192 will sync`);
15583
+ logger.debug(`${logPrefix11} ${entry.destinationPath} source has no lastModified \u2192 will sync`);
15453
15584
  return true;
15454
15585
  }
15455
15586
  if (metadata.lastModified === null) {
15456
- logger.debug(`${logPrefix10} ${entry.destinationPath} CDN has no lastModified \u2192 will sync`);
15587
+ logger.debug(`${logPrefix11} ${entry.destinationPath} CDN has no lastModified \u2192 will sync`);
15457
15588
  return true;
15458
15589
  }
15459
15590
  const sourceIsNewer = entry.lastModified > metadata.lastModified;
15460
15591
  if (!sourceIsNewer) {
15461
- logger.debug(`${logPrefix10} Skipping ${entry.destinationPath} (CDN is up-to-date)`);
15592
+ logger.debug(`${logPrefix11} Skipping ${entry.destinationPath} (CDN is up-to-date)`);
15462
15593
  }
15463
15594
  return sourceIsNewer;
15464
15595
  }
@@ -15469,7 +15600,7 @@ var CdnAssetTarget = class extends AssetTarget {
15469
15600
  asset.entry.destinationPath,
15470
15601
  fileBuffer
15471
15602
  );
15472
- logger.info(`${logPrefix10} Uploaded ${asset.entry.destinationPath} (${fileBuffer.length} bytes)`);
15603
+ logger.info(`${logPrefix11} Uploaded ${asset.entry.destinationPath} (${fileBuffer.length} bytes)`);
15473
15604
  }
15474
15605
  };
15475
15606
 
@@ -15539,6 +15670,7 @@ var GCSStateProvider = class {
15539
15670
  GCSStateProvider,
15540
15671
  ITarget,
15541
15672
  ImageTransform,
15673
+ LocalStorageService,
15542
15674
  MIME_TYPES,
15543
15675
  MissingFieldInSchemaError,
15544
15676
  MissingSchemaError,
@@ -15594,6 +15726,7 @@ var GCSStateProvider = class {
15594
15726
  getCounterMetrics,
15595
15727
  getDownloadFileApiCall,
15596
15728
  getDryRunLimit,
15729
+ getExtension,
15597
15730
  getJSONApiCall,
15598
15731
  getJSONApiCallWithFullResponse,
15599
15732
  getMimeType,