dicom-curate 0.26.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -77997,7 +77997,7 @@ function isPrivateTag(tagId) {
77997
77997
  return false;
77998
77998
  }
77999
77999
  function convertKeywordToTagId(keyword) {
78000
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
78000
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
78001
78001
  return tagId.replace(/[(),]/g, "").toLowerCase();
78002
78002
  }
78003
78003
  function convertKeywordPathToTagIdPath(keywordPath) {
@@ -78856,7 +78856,7 @@ function getCid7050Codes(options) {
78856
78856
  var import_lodash = __toESM(require_lodash(), 1);
78857
78857
  var nameMap = dcmjs2.data.DicomMetaDictionary.nameMap;
78858
78858
  function getVr(keyword) {
78859
- const element = nameMap[keyword] || nameMap[`RETIRED_${keyword}`];
78859
+ const element = nameMap[keyword] ?? nameMap[`RETIRED_${keyword}`];
78860
78860
  return element?.vr;
78861
78861
  }
78862
78862
  function temporalVr(vr) {
@@ -78928,7 +78928,7 @@ function deidentifyPS315E({
78928
78928
  }
78929
78929
  }
78930
78930
  }
78931
- return current2[tagName] || null;
78931
+ return current2[tagName] ?? null;
78932
78932
  }
78933
78933
  const {
78934
78934
  cleanDescriptorsOption,
@@ -79661,6 +79661,30 @@ function curateDict(inputFilePath, dicomData, mappingOptions) {
79661
79661
  return { dicomData: mappedDicomData, mapResults: (0, import_lodash4.cloneDeep)(mapResults) };
79662
79662
  }
79663
79663
 
79664
+ // src/fetchWithRetry.ts
79665
+ var MAX_ATTEMPTS = 5;
79666
+ var BASE_DELAY_MS = 1e3;
79667
+ var BACKOFF_MULTIPLIER = 3;
79668
+ async function fetchWithRetry(...args) {
79669
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
79670
+ try {
79671
+ return await fetch(...args);
79672
+ } catch (error2) {
79673
+ const isNetworkError = error2 instanceof TypeError;
79674
+ const isLastAttempt = attempt === MAX_ATTEMPTS;
79675
+ if (!isNetworkError || isLastAttempt) {
79676
+ throw error2;
79677
+ }
79678
+ const delayMs = BASE_DELAY_MS * BACKOFF_MULTIPLIER ** (attempt - 1);
79679
+ console.warn(
79680
+ `fetch attempt ${attempt}/${MAX_ATTEMPTS} failed: ${error2.message}. Retrying in ${delayMs}ms...`
79681
+ );
79682
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
79683
+ }
79684
+ }
79685
+ throw new Error("fetchWithRetry: unreachable");
79686
+ }
79687
+
79664
79688
  // src/hash.ts
79665
79689
  var import_md5 = __toESM(require_md5(), 1);
79666
79690
  var import_js_crc = __toESM(require_crc(), 1);
@@ -79671,9 +79695,9 @@ async function hash(buffer, hashMethod) {
79671
79695
  case "crc32":
79672
79696
  return crc32Hex(buffer);
79673
79697
  case "md5":
79698
+ default:
79674
79699
  return md5Hex(buffer);
79675
79700
  case "crc64":
79676
- default:
79677
79701
  return crc64Hex(buffer);
79678
79702
  }
79679
79703
  }
@@ -79748,7 +79772,7 @@ async function loadS3Client() {
79748
79772
  const { createRequire } = await import("module");
79749
79773
  const req = createRequire(import.meta.url);
79750
79774
  const mod = req("@aws-sdk/client-s3");
79751
- cachedS3Client = mod?.default || mod;
79775
+ cachedS3Client = mod?.default ?? mod;
79752
79776
  } else {
79753
79777
  cachedS3Client = await Promise.resolve().then(() => __toESM(require_dist_cjs71(), 1));
79754
79778
  }
@@ -79787,7 +79811,7 @@ async function curateOne({
79787
79811
  );
79788
79812
  }
79789
79813
  file = await resp.blob();
79790
- const lastModifiedHeader = resp.headers.get("last-modified") || void 0;
79814
+ const lastModifiedHeader = resp.headers.get("last-modified");
79791
79815
  if (lastModifiedHeader) {
79792
79816
  mtime = new Date(lastModifiedHeader).toISOString();
79793
79817
  }
@@ -79853,14 +79877,14 @@ async function curateOne({
79853
79877
  } catch (e4) {
79854
79878
  }
79855
79879
  }
79856
- const fileArrayBuffer = await file.arrayBuffer();
79880
+ let fileArrayBuffer = await file.arrayBuffer();
79857
79881
  let preMappedHash;
79858
79882
  let postMappedHash;
79859
79883
  const postMappedHashHeader = "x-source-file-hash";
79860
79884
  let canSkip = false;
79861
79885
  if (previousSourceFileInfo?.preMappedHash !== void 0) {
79862
79886
  try {
79863
- preMappedHash = await hash(fileArrayBuffer, hashMethod || "crc64");
79887
+ preMappedHash = await hash(fileArrayBuffer, hashMethod ?? "md5");
79864
79888
  } catch (e4) {
79865
79889
  console.warn(`Failed to compute preMappedHash for ${fileInfo.name}`, e4);
79866
79890
  }
@@ -79965,7 +79989,7 @@ async function curateOne({
79965
79989
  }
79966
79990
  if (!preMappedHash) {
79967
79991
  try {
79968
- preMappedHash = await hash(fileArrayBuffer, hashMethod || "crc64");
79992
+ preMappedHash = await hash(fileArrayBuffer, hashMethod ?? "md5");
79969
79993
  } catch (e4) {
79970
79994
  console.warn(`Failed to compute preMappedHash for ${fileInfo.name}`, e4);
79971
79995
  }
@@ -79976,7 +80000,8 @@ async function curateOne({
79976
80000
  const modifiedArrayBuffer = mappedDicomData.write({
79977
80001
  allowInvalidVRLength: true
79978
80002
  });
79979
- postMappedHash = await hash(modifiedArrayBuffer, hashMethod || "crc64");
80003
+ postMappedHash = await hash(modifiedArrayBuffer, hashMethod ?? "md5");
80004
+ fileArrayBuffer = null;
79980
80005
  const previousPostMappedHash = previousMappedFileInfo ? previousMappedFileInfo(clonedMapResults.outputFilePath)?.postMappedHash : void 0;
79981
80006
  if (previousPostMappedHash !== void 0 && previousPostMappedHash === postMappedHash) {
79982
80007
  return noMapResult(clonedMapResults.outputFilePath);
@@ -80008,54 +80033,53 @@ async function curateOne({
80008
80033
  }
80009
80034
  const fullFilePath = path.join(fullDirPath, fileName);
80010
80035
  await fs.writeFile(fullFilePath, new DataView(modifiedArrayBuffer));
80011
- } else {
80036
+ } else if (!outputTarget?.http && !outputTarget?.s3) {
80012
80037
  clonedMapResults.mappedBlob = new Blob([modifiedArrayBuffer], {
80013
80038
  type: "application/octet-stream"
80014
80039
  });
80015
80040
  }
80016
- clonedMapResults.mappedBlob = new Blob([modifiedArrayBuffer], {
80017
- type: "application/octet-stream"
80018
- });
80019
80041
  if (outputTarget?.http) {
80020
80042
  try {
80021
80043
  const key = clonedMapResults.outputFilePath.split("/").map(encodeURIComponent).join("/");
80022
80044
  const uploadUrl = `${outputTarget.http.url}/${key}`;
80023
80045
  const headers = {
80024
- "Content-Type": clonedMapResults.mappedBlob.type || "application/octet-stream",
80046
+ "Content-Type": "application/octet-stream",
80025
80047
  "X-File-Name": fileName,
80026
- "X-File-Type": clonedMapResults.mappedBlob.type || "application/octet-stream",
80048
+ "X-File-Type": "application/octet-stream",
80027
80049
  "X-File-Size": String(modifiedArrayBuffer.byteLength),
80028
- "X-Source-File-Size": String(clonedMapResults.fileInfo?.size || ""),
80029
- "X-Source-File-Modified-Time": mtime || "",
80030
- "X-Source-File-Hash": preMappedHash || ""
80050
+ "X-Source-File-Size": String(clonedMapResults.fileInfo?.size ?? ""),
80051
+ "X-Source-File-Modified-Time": mtime ?? "",
80052
+ "X-Source-File-Hash": preMappedHash ?? ""
80031
80053
  };
80032
80054
  if (outputTarget.http.headers) {
80033
80055
  Object.assign(headers, outputTarget.http.headers);
80034
80056
  }
80035
80057
  if (postMappedHashHeader && postMappedHash)
80036
80058
  headers[postMappedHashHeader] = postMappedHash;
80037
- const resp = await fetch(uploadUrl, {
80059
+ const resp = await fetchWithRetry(uploadUrl, {
80038
80060
  method: "PUT",
80039
80061
  headers,
80040
- body: clonedMapResults.mappedBlob
80062
+ body: new Blob([modifiedArrayBuffer], {
80063
+ type: "application/octet-stream"
80064
+ })
80041
80065
  });
80042
80066
  if (!resp.ok) {
80043
80067
  console.error(
80044
80068
  `Upload failed for ${uploadUrl}: ${resp.status} ${resp.statusText}`
80045
80069
  );
80046
- clonedMapResults.errors = clonedMapResults.errors || [];
80070
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
80047
80071
  clonedMapResults.errors.push(
80048
80072
  `Upload failed: ${resp.status} ${resp.statusText}`
80049
80073
  );
80050
80074
  } else {
80051
- clonedMapResults.outputUpload = clonedMapResults.outputUpload || {
80075
+ clonedMapResults.outputUpload = clonedMapResults.outputUpload ?? {
80052
80076
  url: uploadUrl,
80053
80077
  status: resp.status
80054
80078
  };
80055
80079
  }
80056
80080
  } catch (e4) {
80057
80081
  console.error("Upload error", e4);
80058
- clonedMapResults.errors = clonedMapResults.errors || [];
80082
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
80059
80083
  clonedMapResults.errors.push(
80060
80084
  `Upload error: ${e4 instanceof Error ? e4.message : String(e4)}`
80061
80085
  );
@@ -80075,12 +80099,14 @@ async function curateOne({
80075
80099
  new s32.PutObjectCommand({
80076
80100
  Bucket: outputTarget.s3.bucketName,
80077
80101
  Key: key,
80078
- Body: await clonedMapResults.mappedBlob.arrayBuffer(),
80079
- ContentType: clonedMapResults.mappedBlob.type || "application/octet-stream",
80102
+ // Use the ArrayBuffer directly — going through Blob.arrayBuffer()
80103
+ // would create yet another copy of the data in memory.
80104
+ Body: new Uint8Array(modifiedArrayBuffer),
80105
+ ContentType: "application/octet-stream",
80080
80106
  Metadata: {
80081
- "source-file-size": String(clonedMapResults.fileInfo?.size || ""),
80082
- "source-file-modified-time": mtime || "",
80083
- "source-file-hash": preMappedHash || "",
80107
+ "source-file-size": String(clonedMapResults.fileInfo?.size ?? ""),
80108
+ "source-file-modified-time": mtime ?? "",
80109
+ "source-file-hash": preMappedHash ?? "",
80084
80110
  ...postMappedHash ? { "source-file-post-mapped-hash": postMappedHash } : {}
80085
80111
  }
80086
80112
  })
@@ -80092,7 +80118,7 @@ async function curateOne({
80092
80118
  };
80093
80119
  } catch (e4) {
80094
80120
  console.error("S3 Upload error", e4);
80095
- clonedMapResults.errors = clonedMapResults.errors || [];
80121
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
80096
80122
  clonedMapResults.errors.push(
80097
80123
  `S3 Upload error: ${e4 instanceof Error ? e4.message : String(e4)}`
80098
80124
  );
@@ -34005,7 +34005,7 @@ function isPrivateTag(tagId) {
34005
34005
  return false;
34006
34006
  }
34007
34007
  function convertKeywordToTagId(keyword) {
34008
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
34008
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
34009
34009
  return tagId.replace(/[(),]/g, "").toLowerCase();
34010
34010
  }
34011
34011
 
@@ -34850,7 +34850,7 @@ function getCid7050Codes(options) {
34850
34850
  var import_lodash = __toESM(require_lodash(), 1);
34851
34851
  var nameMap = dcmjs2.data.DicomMetaDictionary.nameMap;
34852
34852
  function getVr(keyword) {
34853
- const element = nameMap[keyword] || nameMap[`RETIRED_${keyword}`];
34853
+ const element = nameMap[keyword] ?? nameMap[`RETIRED_${keyword}`];
34854
34854
  return element?.vr;
34855
34855
  }
34856
34856
  function temporalVr(vr) {
@@ -34922,7 +34922,7 @@ function deidentifyPS315E({
34922
34922
  }
34923
34923
  }
34924
34924
  }
34925
- return current[tagName] || null;
34925
+ return current[tagName] ?? null;
34926
34926
  }
34927
34927
  const {
34928
34928
  cleanDescriptorsOption,
@@ -17966,7 +17966,7 @@ function isPrivateTag(tagId) {
17966
17966
  return false;
17967
17967
  }
17968
17968
  function convertKeywordToTagId(keyword) {
17969
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
17969
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
17970
17970
  return tagId.replace(/[(),]/g, "").toLowerCase();
17971
17971
  }
17972
17972
  function convertKeywordPathToTagIdPath(keywordPath) {
@@ -97,7 +97,7 @@ function composedSpec() {
97
97
  ctxIn.hostProps.activityProviderName,
98
98
  ctxIn.centerSubjectId(parser),
99
99
  ctxIn.timepointName(parser),
100
- ctxIn.scanName(parser) + "=" + parser.getDicom("SeriesNumber") || "UNKNOWN",
100
+ ctxIn.scanName(parser) + "=" + (parser.getDicom("SeriesNumber") ?? "UNKNOWN"),
101
101
  parser.getFilePathComp(parser.FILEBASENAME) + ".dcm"
102
102
  ];
103
103
  },
@@ -34008,7 +34008,7 @@ function isPrivateTag(tagId) {
34008
34008
  return false;
34009
34009
  }
34010
34010
  function convertKeywordToTagId(keyword) {
34011
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
34011
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
34012
34012
  return tagId.replace(/[(),]/g, "").toLowerCase();
34013
34013
  }
34014
34014
  function convertKeywordPathToTagIdPath(keywordPath) {
@@ -34867,7 +34867,7 @@ function getCid7050Codes(options) {
34867
34867
  var import_lodash = __toESM(require_lodash(), 1);
34868
34868
  var nameMap = dcmjs2.data.DicomMetaDictionary.nameMap;
34869
34869
  function getVr(keyword) {
34870
- const element = nameMap[keyword] || nameMap[`RETIRED_${keyword}`];
34870
+ const element = nameMap[keyword] ?? nameMap[`RETIRED_${keyword}`];
34871
34871
  return element?.vr;
34872
34872
  }
34873
34873
  function temporalVr(vr) {
@@ -34939,7 +34939,7 @@ function deidentifyPS315E({
34939
34939
  }
34940
34940
  }
34941
34941
  }
34942
- return current[tagName] || null;
34942
+ return current[tagName] ?? null;
34943
34943
  }
34944
34944
  const {
34945
34945
  cleanDescriptorsOption,
@@ -71706,7 +71706,7 @@ function isPrivateTag(tagId) {
71706
71706
  return false;
71707
71707
  }
71708
71708
  function convertKeywordToTagId(keyword) {
71709
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
71709
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
71710
71710
  return tagId.replace(/[(),]/g, "").toLowerCase();
71711
71711
  }
71712
71712
  function convertKeywordPathToTagIdPath(keywordPath) {
@@ -72565,7 +72565,7 @@ function getCid7050Codes(options) {
72565
72565
  var import_lodash = __toESM(require_lodash(), 1);
72566
72566
  var nameMap = dcmjs2.data.DicomMetaDictionary.nameMap;
72567
72567
  function getVr(keyword) {
72568
- const element = nameMap[keyword] || nameMap[`RETIRED_${keyword}`];
72568
+ const element = nameMap[keyword] ?? nameMap[`RETIRED_${keyword}`];
72569
72569
  return element?.vr;
72570
72570
  }
72571
72571
  function temporalVr(vr) {
@@ -72637,7 +72637,7 @@ function deidentifyPS315E({
72637
72637
  }
72638
72638
  }
72639
72639
  }
72640
- return current[tagName] || null;
72640
+ return current[tagName] ?? null;
72641
72641
  }
72642
72642
  const {
72643
72643
  cleanDescriptorsOption,
@@ -73370,6 +73370,30 @@ function curateDict(inputFilePath, dicomData, mappingOptions) {
73370
73370
  return { dicomData: mappedDicomData, mapResults: (0, import_lodash4.cloneDeep)(mapResults) };
73371
73371
  }
73372
73372
 
73373
+ // src/fetchWithRetry.ts
73374
+ var MAX_ATTEMPTS = 5;
73375
+ var BASE_DELAY_MS = 1e3;
73376
+ var BACKOFF_MULTIPLIER = 3;
73377
+ async function fetchWithRetry(...args) {
73378
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
73379
+ try {
73380
+ return await fetch(...args);
73381
+ } catch (error2) {
73382
+ const isNetworkError = error2 instanceof TypeError;
73383
+ const isLastAttempt = attempt === MAX_ATTEMPTS;
73384
+ if (!isNetworkError || isLastAttempt) {
73385
+ throw error2;
73386
+ }
73387
+ const delayMs = BASE_DELAY_MS * BACKOFF_MULTIPLIER ** (attempt - 1);
73388
+ console.warn(
73389
+ `fetch attempt ${attempt}/${MAX_ATTEMPTS} failed: ${error2.message}. Retrying in ${delayMs}ms...`
73390
+ );
73391
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
73392
+ }
73393
+ }
73394
+ throw new Error("fetchWithRetry: unreachable");
73395
+ }
73396
+
73373
73397
  // src/hash.ts
73374
73398
  var import_md5 = __toESM(require_md5(), 1);
73375
73399
  var import_js_crc = __toESM(require_crc(), 1);
@@ -73380,9 +73404,9 @@ async function hash(buffer, hashMethod) {
73380
73404
  case "crc32":
73381
73405
  return crc32Hex(buffer);
73382
73406
  case "md5":
73407
+ default:
73383
73408
  return md5Hex(buffer);
73384
73409
  case "crc64":
73385
- default:
73386
73410
  return crc64Hex(buffer);
73387
73411
  }
73388
73412
  }
@@ -73457,7 +73481,7 @@ async function loadS3Client() {
73457
73481
  const { createRequire } = await import("module");
73458
73482
  const req = createRequire(import.meta.url);
73459
73483
  const mod = req("@aws-sdk/client-s3");
73460
- cachedS3Client = mod?.default || mod;
73484
+ cachedS3Client = mod?.default ?? mod;
73461
73485
  } else {
73462
73486
  cachedS3Client = await Promise.resolve().then(() => __toESM(require_dist_cjs71(), 1));
73463
73487
  }
@@ -73496,7 +73520,7 @@ async function curateOne({
73496
73520
  );
73497
73521
  }
73498
73522
  file = await resp.blob();
73499
- const lastModifiedHeader = resp.headers.get("last-modified") || void 0;
73523
+ const lastModifiedHeader = resp.headers.get("last-modified");
73500
73524
  if (lastModifiedHeader) {
73501
73525
  mtime = new Date(lastModifiedHeader).toISOString();
73502
73526
  }
@@ -73562,14 +73586,14 @@ async function curateOne({
73562
73586
  } catch (e4) {
73563
73587
  }
73564
73588
  }
73565
- const fileArrayBuffer = await file.arrayBuffer();
73589
+ let fileArrayBuffer = await file.arrayBuffer();
73566
73590
  let preMappedHash;
73567
73591
  let postMappedHash;
73568
73592
  const postMappedHashHeader = "x-source-file-hash";
73569
73593
  let canSkip = false;
73570
73594
  if (previousSourceFileInfo?.preMappedHash !== void 0) {
73571
73595
  try {
73572
- preMappedHash = await hash(fileArrayBuffer, hashMethod || "crc64");
73596
+ preMappedHash = await hash(fileArrayBuffer, hashMethod ?? "md5");
73573
73597
  } catch (e4) {
73574
73598
  console.warn(`Failed to compute preMappedHash for ${fileInfo.name}`, e4);
73575
73599
  }
@@ -73674,7 +73698,7 @@ async function curateOne({
73674
73698
  }
73675
73699
  if (!preMappedHash) {
73676
73700
  try {
73677
- preMappedHash = await hash(fileArrayBuffer, hashMethod || "crc64");
73701
+ preMappedHash = await hash(fileArrayBuffer, hashMethod ?? "md5");
73678
73702
  } catch (e4) {
73679
73703
  console.warn(`Failed to compute preMappedHash for ${fileInfo.name}`, e4);
73680
73704
  }
@@ -73685,7 +73709,8 @@ async function curateOne({
73685
73709
  const modifiedArrayBuffer = mappedDicomData.write({
73686
73710
  allowInvalidVRLength: true
73687
73711
  });
73688
- postMappedHash = await hash(modifiedArrayBuffer, hashMethod || "crc64");
73712
+ postMappedHash = await hash(modifiedArrayBuffer, hashMethod ?? "md5");
73713
+ fileArrayBuffer = null;
73689
73714
  const previousPostMappedHash = previousMappedFileInfo ? previousMappedFileInfo(clonedMapResults.outputFilePath)?.postMappedHash : void 0;
73690
73715
  if (previousPostMappedHash !== void 0 && previousPostMappedHash === postMappedHash) {
73691
73716
  return noMapResult(clonedMapResults.outputFilePath);
@@ -73717,54 +73742,53 @@ async function curateOne({
73717
73742
  }
73718
73743
  const fullFilePath = path.join(fullDirPath, fileName);
73719
73744
  await fs.writeFile(fullFilePath, new DataView(modifiedArrayBuffer));
73720
- } else {
73745
+ } else if (!outputTarget?.http && !outputTarget?.s3) {
73721
73746
  clonedMapResults.mappedBlob = new Blob([modifiedArrayBuffer], {
73722
73747
  type: "application/octet-stream"
73723
73748
  });
73724
73749
  }
73725
- clonedMapResults.mappedBlob = new Blob([modifiedArrayBuffer], {
73726
- type: "application/octet-stream"
73727
- });
73728
73750
  if (outputTarget?.http) {
73729
73751
  try {
73730
73752
  const key = clonedMapResults.outputFilePath.split("/").map(encodeURIComponent).join("/");
73731
73753
  const uploadUrl = `${outputTarget.http.url}/${key}`;
73732
73754
  const headers = {
73733
- "Content-Type": clonedMapResults.mappedBlob.type || "application/octet-stream",
73755
+ "Content-Type": "application/octet-stream",
73734
73756
  "X-File-Name": fileName,
73735
- "X-File-Type": clonedMapResults.mappedBlob.type || "application/octet-stream",
73757
+ "X-File-Type": "application/octet-stream",
73736
73758
  "X-File-Size": String(modifiedArrayBuffer.byteLength),
73737
- "X-Source-File-Size": String(clonedMapResults.fileInfo?.size || ""),
73738
- "X-Source-File-Modified-Time": mtime || "",
73739
- "X-Source-File-Hash": preMappedHash || ""
73759
+ "X-Source-File-Size": String(clonedMapResults.fileInfo?.size ?? ""),
73760
+ "X-Source-File-Modified-Time": mtime ?? "",
73761
+ "X-Source-File-Hash": preMappedHash ?? ""
73740
73762
  };
73741
73763
  if (outputTarget.http.headers) {
73742
73764
  Object.assign(headers, outputTarget.http.headers);
73743
73765
  }
73744
73766
  if (postMappedHashHeader && postMappedHash)
73745
73767
  headers[postMappedHashHeader] = postMappedHash;
73746
- const resp = await fetch(uploadUrl, {
73768
+ const resp = await fetchWithRetry(uploadUrl, {
73747
73769
  method: "PUT",
73748
73770
  headers,
73749
- body: clonedMapResults.mappedBlob
73771
+ body: new Blob([modifiedArrayBuffer], {
73772
+ type: "application/octet-stream"
73773
+ })
73750
73774
  });
73751
73775
  if (!resp.ok) {
73752
73776
  console.error(
73753
73777
  `Upload failed for ${uploadUrl}: ${resp.status} ${resp.statusText}`
73754
73778
  );
73755
- clonedMapResults.errors = clonedMapResults.errors || [];
73779
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
73756
73780
  clonedMapResults.errors.push(
73757
73781
  `Upload failed: ${resp.status} ${resp.statusText}`
73758
73782
  );
73759
73783
  } else {
73760
- clonedMapResults.outputUpload = clonedMapResults.outputUpload || {
73784
+ clonedMapResults.outputUpload = clonedMapResults.outputUpload ?? {
73761
73785
  url: uploadUrl,
73762
73786
  status: resp.status
73763
73787
  };
73764
73788
  }
73765
73789
  } catch (e4) {
73766
73790
  console.error("Upload error", e4);
73767
- clonedMapResults.errors = clonedMapResults.errors || [];
73791
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
73768
73792
  clonedMapResults.errors.push(
73769
73793
  `Upload error: ${e4 instanceof Error ? e4.message : String(e4)}`
73770
73794
  );
@@ -73784,12 +73808,14 @@ async function curateOne({
73784
73808
  new s32.PutObjectCommand({
73785
73809
  Bucket: outputTarget.s3.bucketName,
73786
73810
  Key: key,
73787
- Body: await clonedMapResults.mappedBlob.arrayBuffer(),
73788
- ContentType: clonedMapResults.mappedBlob.type || "application/octet-stream",
73811
+ // Use the ArrayBuffer directly — going through Blob.arrayBuffer()
73812
+ // would create yet another copy of the data in memory.
73813
+ Body: new Uint8Array(modifiedArrayBuffer),
73814
+ ContentType: "application/octet-stream",
73789
73815
  Metadata: {
73790
- "source-file-size": String(clonedMapResults.fileInfo?.size || ""),
73791
- "source-file-modified-time": mtime || "",
73792
- "source-file-hash": preMappedHash || "",
73816
+ "source-file-size": String(clonedMapResults.fileInfo?.size ?? ""),
73817
+ "source-file-modified-time": mtime ?? "",
73818
+ "source-file-hash": preMappedHash ?? "",
73793
73819
  ...postMappedHash ? { "source-file-post-mapped-hash": postMappedHash } : {}
73794
73820
  }
73795
73821
  })
@@ -73801,7 +73827,7 @@ async function curateOne({
73801
73827
  };
73802
73828
  } catch (e4) {
73803
73829
  console.error("S3 Upload error", e4);
73804
- clonedMapResults.errors = clonedMapResults.errors || [];
73830
+ clonedMapResults.errors = clonedMapResults.errors ?? [];
73805
73831
  clonedMapResults.errors.push(
73806
73832
  `S3 Upload error: ${e4 instanceof Error ? e4.message : String(e4)}`
73807
73833
  );
@@ -33999,7 +33999,7 @@ function isPrivateTag(tagId) {
33999
33999
  return false;
34000
34000
  }
34001
34001
  function convertKeywordToTagId(keyword) {
34002
- const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag || keyword;
34002
+ const tagId = isPrivateTag(keyword) ? keyword : dcmjs.data.DicomMetaDictionary.nameMap[keyword]?.tag ?? keyword;
34003
34003
  return tagId.replace(/[(),]/g, "").toLowerCase();
34004
34004
  }
34005
34005
 
@@ -34844,7 +34844,7 @@ function getCid7050Codes(options) {
34844
34844
  var import_lodash = __toESM(require_lodash(), 1);
34845
34845
  var nameMap = dcmjs2.data.DicomMetaDictionary.nameMap;
34846
34846
  function getVr(keyword) {
34847
- const element = nameMap[keyword] || nameMap[`RETIRED_${keyword}`];
34847
+ const element = nameMap[keyword] ?? nameMap[`RETIRED_${keyword}`];
34848
34848
  return element?.vr;
34849
34849
  }
34850
34850
  function temporalVr(vr) {
@@ -34916,7 +34916,7 @@ function deidentifyPS315E({
34916
34916
  }
34917
34917
  }
34918
34918
  }
34919
- return current[tagName] || null;
34919
+ return current[tagName] ?? null;
34920
34920
  }
34921
34921
  const {
34922
34922
  cleanDescriptorsOption,
@@ -0,0 +1,26 @@
1
+ // src/fetchWithRetry.ts
2
+ var MAX_ATTEMPTS = 5;
3
+ var BASE_DELAY_MS = 1e3;
4
+ var BACKOFF_MULTIPLIER = 3;
5
+ async function fetchWithRetry(...args) {
6
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
7
+ try {
8
+ return await fetch(...args);
9
+ } catch (error) {
10
+ const isNetworkError = error instanceof TypeError;
11
+ const isLastAttempt = attempt === MAX_ATTEMPTS;
12
+ if (!isNetworkError || isLastAttempt) {
13
+ throw error;
14
+ }
15
+ const delayMs = BASE_DELAY_MS * BACKOFF_MULTIPLIER ** (attempt - 1);
16
+ console.warn(
17
+ `fetch attempt ${attempt}/${MAX_ATTEMPTS} failed: ${error.message}. Retrying in ${delayMs}ms...`
18
+ );
19
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
20
+ }
21
+ }
22
+ throw new Error("fetchWithRetry: unreachable");
23
+ }
24
+ export {
25
+ fetchWithRetry
26
+ };
package/dist/esm/hash.js CHANGED
@@ -647,9 +647,9 @@ async function hash(buffer, hashMethod) {
647
647
  case "crc32":
648
648
  return crc32Hex(buffer);
649
649
  case "md5":
650
+ default:
650
651
  return md5Hex(buffer);
651
652
  case "crc64":
652
- default:
653
653
  return crc64Hex(buffer);
654
654
  }
655
655
  }