dicom-curate 0.40.2 → 0.40.4
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/esm/applyMappingsWorker.js +9 -2
- package/dist/esm/curateOne.js +9 -2
- package/dist/esm/hash.js +5 -1
- package/dist/esm/index.js +12 -3
- package/dist/esm/mappingWorkerPool.js +12 -3
- package/dist/esm/scanDirectoryWorker.js +9 -1
- package/dist/types/hash.d.ts +8 -0
- package/dist/umd/dicom-curate.umd.js +53 -10
- package/dist/umd/dicom-curate.umd.js.map +1 -1
- package/dist/umd/dicom-curate.umd.min.js +2 -2
- package/dist/umd/dicom-curate.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -86462,6 +86462,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
|
|
|
86462
86462
|
// src/hash.ts
|
|
86463
86463
|
var import_js_crc = __toESM(require_crc(), 1);
|
|
86464
86464
|
var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
|
|
86465
|
+
function phiSafeToken(input) {
|
|
86466
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
86467
|
+
}
|
|
86465
86468
|
async function hashStream(stream, hashMethod, hashPartSize) {
|
|
86466
86469
|
switch (hashMethod) {
|
|
86467
86470
|
case "sha256":
|
|
@@ -86830,7 +86833,7 @@ async function curateOne({
|
|
|
86830
86833
|
// fileInfo, keeping it in the private log. See #283.
|
|
86831
86834
|
anomalies: [`Could not parse file as DICOM data`],
|
|
86832
86835
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
86833
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
86836
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
86834
86837
|
fileInfo: {
|
|
86835
86838
|
name: fileInfo.name,
|
|
86836
86839
|
size: fileInfo.size,
|
|
@@ -86936,7 +86939,11 @@ async function curateOne({
|
|
|
86936
86939
|
create: true
|
|
86937
86940
|
});
|
|
86938
86941
|
const writable = await fileHandle.createWritable();
|
|
86939
|
-
await
|
|
86942
|
+
for await (const chunk of readableStreamToAsyncIterable(
|
|
86943
|
+
modifiedBlob.stream()
|
|
86944
|
+
)) {
|
|
86945
|
+
await writable.write(chunk);
|
|
86946
|
+
}
|
|
86940
86947
|
await writable.close();
|
|
86941
86948
|
}
|
|
86942
86949
|
} else if (typeof outputTarget?.directory === "string") {
|
package/dist/esm/curateOne.js
CHANGED
|
@@ -80171,6 +80171,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
|
|
|
80171
80171
|
// src/hash.ts
|
|
80172
80172
|
var import_js_crc = __toESM(require_crc(), 1);
|
|
80173
80173
|
var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
|
|
80174
|
+
function phiSafeToken(input) {
|
|
80175
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
80176
|
+
}
|
|
80174
80177
|
async function hashStream(stream, hashMethod, hashPartSize) {
|
|
80175
80178
|
switch (hashMethod) {
|
|
80176
80179
|
case "sha256":
|
|
@@ -80539,7 +80542,7 @@ async function curateOne({
|
|
|
80539
80542
|
// fileInfo, keeping it in the private log. See #283.
|
|
80540
80543
|
anomalies: [`Could not parse file as DICOM data`],
|
|
80541
80544
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
80542
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
80545
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
80543
80546
|
fileInfo: {
|
|
80544
80547
|
name: fileInfo.name,
|
|
80545
80548
|
size: fileInfo.size,
|
|
@@ -80645,7 +80648,11 @@ async function curateOne({
|
|
|
80645
80648
|
create: true
|
|
80646
80649
|
});
|
|
80647
80650
|
const writable = await fileHandle.createWritable();
|
|
80648
|
-
await
|
|
80651
|
+
for await (const chunk of readableStreamToAsyncIterable(
|
|
80652
|
+
modifiedBlob.stream()
|
|
80653
|
+
)) {
|
|
80654
|
+
await writable.write(chunk);
|
|
80655
|
+
}
|
|
80649
80656
|
await writable.close();
|
|
80650
80657
|
}
|
|
80651
80658
|
} else if (typeof outputTarget?.directory === "string") {
|
package/dist/esm/hash.js
CHANGED
|
@@ -800,6 +800,9 @@ var sha256 = /* @__PURE__ */ createHasher(
|
|
|
800
800
|
// src/hash.ts
|
|
801
801
|
var import_js_crc = __toESM(require_crc(), 1);
|
|
802
802
|
var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
|
|
803
|
+
function phiSafeToken(input) {
|
|
804
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
805
|
+
}
|
|
803
806
|
async function hash(buffer, hashMethod, hashPartSize) {
|
|
804
807
|
switch (hashMethod) {
|
|
805
808
|
case "sha256":
|
|
@@ -975,7 +978,8 @@ function crc64Hex(input) {
|
|
|
975
978
|
}
|
|
976
979
|
export {
|
|
977
980
|
hash,
|
|
978
|
-
hashStream
|
|
981
|
+
hashStream,
|
|
982
|
+
phiSafeToken
|
|
979
983
|
};
|
|
980
984
|
/*! Bundled license information:
|
|
981
985
|
|
package/dist/esm/index.js
CHANGED
|
@@ -88025,6 +88025,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
|
|
|
88025
88025
|
// src/hash.ts
|
|
88026
88026
|
var import_js_crc = __toESM(require_crc(), 1);
|
|
88027
88027
|
var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
|
|
88028
|
+
function phiSafeToken(input) {
|
|
88029
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
88030
|
+
}
|
|
88028
88031
|
async function hash(buffer, hashMethod, hashPartSize) {
|
|
88029
88032
|
switch (hashMethod) {
|
|
88030
88033
|
case "sha256":
|
|
@@ -88433,7 +88436,7 @@ async function curateOne({
|
|
|
88433
88436
|
// fileInfo, keeping it in the private log. See #283.
|
|
88434
88437
|
anomalies: [`Could not parse file as DICOM data`],
|
|
88435
88438
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
88436
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
88439
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
88437
88440
|
fileInfo: {
|
|
88438
88441
|
name: fileInfo.name,
|
|
88439
88442
|
size: fileInfo.size,
|
|
@@ -88539,7 +88542,11 @@ async function curateOne({
|
|
|
88539
88542
|
create: true
|
|
88540
88543
|
});
|
|
88541
88544
|
const writable = await fileHandle.createWritable();
|
|
88542
|
-
await
|
|
88545
|
+
for await (const chunk of readableStreamToAsyncIterable(
|
|
88546
|
+
modifiedBlob.stream()
|
|
88547
|
+
)) {
|
|
88548
|
+
await writable.write(chunk);
|
|
88549
|
+
}
|
|
88543
88550
|
await writable.close();
|
|
88544
88551
|
}
|
|
88545
88552
|
} else if (typeof outputTarget?.directory === "string") {
|
|
@@ -94710,7 +94717,9 @@ async function dispatchMappingJobs() {
|
|
|
94710
94717
|
const hasReadErrors = !!errors && errors.length > 0;
|
|
94711
94718
|
if (hasReadErrors) {
|
|
94712
94719
|
const scanErrorResult = {
|
|
94713
|
-
|
|
94720
|
+
// phiSafeToken rather than the sanitised name: this result reaches
|
|
94721
|
+
// the server-bound log, so the UID must not encode the filename.
|
|
94722
|
+
sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
94714
94723
|
// Intentionally no outputFilePath: an unread file has no
|
|
94715
94724
|
// de-identified output path, and we must not place the raw input
|
|
94716
94725
|
// path in the server-bound channel.
|
|
@@ -86462,6 +86462,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
|
|
|
86462
86462
|
// src/hash.ts
|
|
86463
86463
|
var import_js_crc = __toESM(require_crc(), 1);
|
|
86464
86464
|
var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
|
|
86465
|
+
function phiSafeToken(input) {
|
|
86466
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
86467
|
+
}
|
|
86465
86468
|
async function hashStream(stream, hashMethod, hashPartSize) {
|
|
86466
86469
|
switch (hashMethod) {
|
|
86467
86470
|
case "sha256":
|
|
@@ -86830,7 +86833,7 @@ async function curateOne({
|
|
|
86830
86833
|
// fileInfo, keeping it in the private log. See #283.
|
|
86831
86834
|
anomalies: [`Could not parse file as DICOM data`],
|
|
86832
86835
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
86833
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
86836
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
86834
86837
|
fileInfo: {
|
|
86835
86838
|
name: fileInfo.name,
|
|
86836
86839
|
size: fileInfo.size,
|
|
@@ -86936,7 +86939,11 @@ async function curateOne({
|
|
|
86936
86939
|
create: true
|
|
86937
86940
|
});
|
|
86938
86941
|
const writable = await fileHandle.createWritable();
|
|
86939
|
-
await
|
|
86942
|
+
for await (const chunk of readableStreamToAsyncIterable(
|
|
86943
|
+
modifiedBlob.stream()
|
|
86944
|
+
)) {
|
|
86945
|
+
await writable.write(chunk);
|
|
86946
|
+
}
|
|
86940
86947
|
await writable.close();
|
|
86941
86948
|
}
|
|
86942
86949
|
} else if (typeof outputTarget?.directory === "string") {
|
|
@@ -93110,7 +93117,9 @@ async function dispatchMappingJobs() {
|
|
|
93110
93117
|
const hasReadErrors = !!errors && errors.length > 0;
|
|
93111
93118
|
if (hasReadErrors) {
|
|
93112
93119
|
const scanErrorResult = {
|
|
93113
|
-
|
|
93120
|
+
// phiSafeToken rather than the sanitised name: this result reaches
|
|
93121
|
+
// the server-bound log, so the UID must not encode the filename.
|
|
93122
|
+
sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
93114
93123
|
// Intentionally no outputFilePath: an unread file has no
|
|
93115
93124
|
// de-identified output path, and we must not place the raw input
|
|
93116
93125
|
// path in the server-bound channel.
|
|
@@ -37137,7 +37137,15 @@ var DEFAULT_EXCLUDED_FILETYPES = [
|
|
|
37137
37137
|
".ds_store"
|
|
37138
37138
|
];
|
|
37139
37139
|
function safeReadErrorMessage(error2) {
|
|
37140
|
-
|
|
37140
|
+
let detail = "unknown error";
|
|
37141
|
+
if (error2 && typeof error2 === "object") {
|
|
37142
|
+
const { code, name } = error2;
|
|
37143
|
+
if (typeof code === "string" && code) {
|
|
37144
|
+
detail = code;
|
|
37145
|
+
} else if (typeof name === "string" && name) {
|
|
37146
|
+
detail = name;
|
|
37147
|
+
}
|
|
37148
|
+
}
|
|
37141
37149
|
return `Unable to read file (filesystem error): ${detail}`;
|
|
37142
37150
|
}
|
|
37143
37151
|
var keepScanning = true;
|
package/dist/types/hash.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { THashMethod } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Derive a short, stable, PHI-safe token from a string (e.g. a file path).
|
|
4
|
+
* Used to build synthetic sourceInstanceUIDs for files that failed to read or
|
|
5
|
+
* parse, where embedding the (sanitised) filename would leak PHI into the
|
|
6
|
+
* server-bound log. Same input always yields the same token, so results stay
|
|
7
|
+
* correlatable across passes and runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function phiSafeToken(input: string): string;
|
|
2
10
|
export declare function hash(buffer: ArrayBuffer, hashMethod: THashMethod, hashPartSize?: number): Promise<string>;
|
|
3
11
|
/**
|
|
4
12
|
* Computes a hash over a stream of data chunks without materializing the full
|
|
@@ -54699,6 +54699,16 @@
|
|
|
54699
54699
|
var crcExports = requireCrc();
|
|
54700
54700
|
|
|
54701
54701
|
const DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024; // 5 MB — matches @aws-sdk/lib-storage default
|
|
54702
|
+
/**
|
|
54703
|
+
* Derive a short, stable, PHI-safe token from a string (e.g. a file path).
|
|
54704
|
+
* Used to build synthetic sourceInstanceUIDs for files that failed to read or
|
|
54705
|
+
* parse, where embedding the (sanitised) filename would leak PHI into the
|
|
54706
|
+
* server-bound log. Same input always yields the same token, so results stay
|
|
54707
|
+
* correlatable across passes and runs.
|
|
54708
|
+
*/
|
|
54709
|
+
function phiSafeToken(input) {
|
|
54710
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
54711
|
+
}
|
|
54702
54712
|
/**
|
|
54703
54713
|
* Computes a hash over a stream of data chunks without materializing the full
|
|
54704
54714
|
* data into memory at once.
|
|
@@ -55137,7 +55147,7 @@
|
|
|
55137
55147
|
// fileInfo, keeping it in the private log. See #283.
|
|
55138
55148
|
anomalies: [`Could not parse file as DICOM data`],
|
|
55139
55149
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
55140
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
55150
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
55141
55151
|
fileInfo: {
|
|
55142
55152
|
name: fileInfo.name,
|
|
55143
55153
|
size: fileInfo.size,
|
|
@@ -55258,7 +55268,12 @@
|
|
|
55258
55268
|
create: true,
|
|
55259
55269
|
});
|
|
55260
55270
|
const writable = await fileHandle.createWritable();
|
|
55261
|
-
|
|
55271
|
+
// Write via stream() rather than write(modifiedBlob): LazyCompositeBlob
|
|
55272
|
+
// keeps an empty native Blob body and exposes its bytes only through the
|
|
55273
|
+
// overridden stream(), which write(blob) bypasses (yielding 0-byte files).
|
|
55274
|
+
for await (const chunk of readableStreamToAsyncIterable(modifiedBlob.stream())) {
|
|
55275
|
+
await writable.write(chunk);
|
|
55276
|
+
}
|
|
55262
55277
|
await writable.close();
|
|
55263
55278
|
}
|
|
55264
55279
|
}
|
|
@@ -68864,13 +68879,24 @@
|
|
|
68864
68879
|
];
|
|
68865
68880
|
/**
|
|
68866
68881
|
* Build a PHI-safe error message for a file that could not be read during
|
|
68867
|
-
* scanning.
|
|
68868
|
-
*
|
|
68869
|
-
*
|
|
68870
|
-
*
|
|
68882
|
+
* scanning. Only the error code/name is included — never `error.message`,
|
|
68883
|
+
* because node fs errors embed the full raw path in the message (e.g.
|
|
68884
|
+
* "ENOENT: no such file or directory, stat '/path/to/file.dcm'") and this
|
|
68885
|
+
* string goes into `errors`, which is shared between the private and
|
|
68886
|
+
* server-bound logs. The raw path/name is carried separately in `fileInfo`
|
|
68887
|
+
* so it appears only in the private (input) log.
|
|
68871
68888
|
*/
|
|
68872
68889
|
function safeReadErrorMessage(error) {
|
|
68873
|
-
|
|
68890
|
+
let detail = 'unknown error';
|
|
68891
|
+
if (error && typeof error === 'object') {
|
|
68892
|
+
const { code, name } = error;
|
|
68893
|
+
if (typeof code === 'string' && code) {
|
|
68894
|
+
detail = code;
|
|
68895
|
+
}
|
|
68896
|
+
else if (typeof name === 'string' && name) {
|
|
68897
|
+
detail = name;
|
|
68898
|
+
}
|
|
68899
|
+
}
|
|
68874
68900
|
return `Unable to read file (filesystem error): ${detail}`;
|
|
68875
68901
|
}
|
|
68876
68902
|
let keepScanning = true;
|
|
@@ -126358,6 +126384,16 @@
|
|
|
126358
126384
|
var crcExports = requireCrc();
|
|
126359
126385
|
|
|
126360
126386
|
const DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024; // 5 MB — matches @aws-sdk/lib-storage default
|
|
126387
|
+
/**
|
|
126388
|
+
* Derive a short, stable, PHI-safe token from a string (e.g. a file path).
|
|
126389
|
+
* Used to build synthetic sourceInstanceUIDs for files that failed to read or
|
|
126390
|
+
* parse, where embedding the (sanitised) filename would leak PHI into the
|
|
126391
|
+
* server-bound log. Same input always yields the same token, so results stay
|
|
126392
|
+
* correlatable across passes and runs.
|
|
126393
|
+
*/
|
|
126394
|
+
function phiSafeToken(input) {
|
|
126395
|
+
return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
|
|
126396
|
+
}
|
|
126361
126397
|
async function hash(buffer, hashMethod, hashPartSize) {
|
|
126362
126398
|
switch (hashMethod) {
|
|
126363
126399
|
case 'sha256':
|
|
@@ -126857,7 +126893,7 @@
|
|
|
126857
126893
|
// fileInfo, keeping it in the private log. See #283.
|
|
126858
126894
|
anomalies: [`Could not parse file as DICOM data`],
|
|
126859
126895
|
errors: [`File is not a valid DICOM file or is corrupted`],
|
|
126860
|
-
sourceInstanceUID: `invalid_${fileInfo.name
|
|
126896
|
+
sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
126861
126897
|
fileInfo: {
|
|
126862
126898
|
name: fileInfo.name,
|
|
126863
126899
|
size: fileInfo.size,
|
|
@@ -126978,7 +127014,12 @@
|
|
|
126978
127014
|
create: true,
|
|
126979
127015
|
});
|
|
126980
127016
|
const writable = await fileHandle.createWritable();
|
|
126981
|
-
|
|
127017
|
+
// Write via stream() rather than write(modifiedBlob): LazyCompositeBlob
|
|
127018
|
+
// keeps an empty native Blob body and exposes its bytes only through the
|
|
127019
|
+
// overridden stream(), which write(blob) bypasses (yielding 0-byte files).
|
|
127020
|
+
for await (const chunk of readableStreamToAsyncIterable(modifiedBlob.stream())) {
|
|
127021
|
+
await writable.write(chunk);
|
|
127022
|
+
}
|
|
126982
127023
|
await writable.close();
|
|
126983
127024
|
}
|
|
126984
127025
|
}
|
|
@@ -140883,7 +140924,9 @@
|
|
|
140883
140924
|
const hasReadErrors = !!errors && errors.length > 0;
|
|
140884
140925
|
if (hasReadErrors) {
|
|
140885
140926
|
const scanErrorResult = {
|
|
140886
|
-
|
|
140927
|
+
// phiSafeToken rather than the sanitised name: this result reaches
|
|
140928
|
+
// the server-bound log, so the UID must not encode the filename.
|
|
140929
|
+
sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
|
|
140887
140930
|
// Intentionally no outputFilePath: an unread file has no
|
|
140888
140931
|
// de-identified output path, and we must not place the raw input
|
|
140889
140932
|
// path in the server-bound channel.
|