dicom-curate 0.40.0 → 0.40.2

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/README.md CHANGED
@@ -436,6 +436,27 @@ When a file is excluded, `curateOne` / `curateMany` still returns a result objec
436
436
  result.excluded // => 'pre' | 'post' | undefined
437
437
  ```
438
438
 
439
+ ## Testing
440
+
441
+ Vitest is split into three projects in `vitest.config.ts`:
442
+
443
+ | Command | Project | Location | Purpose |
444
+ |---------|---------|----------|---------|
445
+ | `pnpm test` | `unit` | `src/**/*.test.ts` | Unit and integration tests co-located with source |
446
+ | `pnpm test:e2e` | `e2e` | `e2e/` | Pipeline smoke tests (runs `build:esm` first; uses `dist/`) |
447
+ | `pnpm test:conformance` | `conformance` | `conformance/` | `dciodvfy` regression (synthetic; optional public/local via env) |
448
+
449
+ Other scripts:
450
+
451
+ | Script | Purpose |
452
+ |--------|---------|
453
+ | `pnpm test:coverage` | Unit project with coverage |
454
+ | `pnpm update:conformance-baselines` | Regenerate committed `dciodvfy` baseline JSON |
455
+
456
+ Conformance tests require the external `dciodvfy` binary from [dicom3tools](https://www.dclunie.com/dicom3tools.html). See [conformance/README.md](conformance/README.md) for install, CI behaviour, baseline refresh, and optional `RUN_PUBLIC_CONFORMANCE` / `CONFORMANCE_LOCAL_*` env vars.
457
+
458
+ Shared test helpers: `testutils/` (minimal DICOM files, worker mocks). Fixture generation and public-case fetch: devDependency [`dicom-synth`](https://github.com/clintools/dicom-synth).
459
+
439
460
  ## DICOM Conformance Notes
440
461
 
441
462
  dicom-curate
@@ -86824,10 +86824,12 @@ async function curateOne({
86824
86824
  error3
86825
86825
  );
86826
86826
  const mapResults = {
86827
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
86828
- errors: [
86829
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`
86830
- ],
86827
+ // PHI-safe strings: the raw filename is intentionally omitted because
86828
+ // anomalies/errors are shared between the private (input) log and the
86829
+ // server-bound (output) log. The raw name/path is carried only in
86830
+ // fileInfo, keeping it in the private log. See #283.
86831
+ anomalies: [`Could not parse file as DICOM data`],
86832
+ errors: [`File is not a valid DICOM file or is corrupted`],
86831
86833
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
86832
86834
  fileInfo: {
86833
86835
  name: fileInfo.name,
@@ -80533,10 +80533,12 @@ async function curateOne({
80533
80533
  error3
80534
80534
  );
80535
80535
  const mapResults = {
80536
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
80537
- errors: [
80538
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`
80539
- ],
80536
+ // PHI-safe strings: the raw filename is intentionally omitted because
80537
+ // anomalies/errors are shared between the private (input) log and the
80538
+ // server-bound (output) log. The raw name/path is carried only in
80539
+ // fileInfo, keeping it in the private log. See #283.
80540
+ anomalies: [`Could not parse file as DICOM data`],
80541
+ errors: [`File is not a valid DICOM file or is corrupted`],
80540
80542
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
80541
80543
  fileInfo: {
80542
80544
  name: fileInfo.name,
package/dist/esm/index.js CHANGED
@@ -88427,10 +88427,12 @@ async function curateOne({
88427
88427
  error3
88428
88428
  );
88429
88429
  const mapResults = {
88430
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
88431
- errors: [
88432
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`
88433
- ],
88430
+ // PHI-safe strings: the raw filename is intentionally omitted because
88431
+ // anomalies/errors are shared between the private (input) log and the
88432
+ // server-bound (output) log. The raw name/path is carried only in
88433
+ // fileInfo, keeping it in the private log. See #283.
88434
+ anomalies: [`Could not parse file as DICOM data`],
88435
+ errors: [`File is not a valid DICOM file or is corrupted`],
88434
88436
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
88435
88437
  fileInfo: {
88436
88438
  name: fileInfo.name,
@@ -94704,8 +94706,24 @@ async function dispatchMappingJobs() {
94704
94706
  console.log("job is finished");
94705
94707
  if (!mapResultsList)
94706
94708
  mapResultsList = [];
94707
- if (!mappingWorkerOptions.skipWrite) {
94708
- scanAnomalies.forEach(({ fileInfo, anomalies }) => {
94709
+ scanAnomalies.forEach(({ fileInfo, anomalies, errors }) => {
94710
+ const hasReadErrors = !!errors && errors.length > 0;
94711
+ if (hasReadErrors) {
94712
+ const scanErrorResult = {
94713
+ sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
94714
+ // Intentionally no outputFilePath: an unread file has no
94715
+ // de-identified output path, and we must not place the raw input
94716
+ // path in the server-bound channel.
94717
+ mappings: {},
94718
+ anomalies: anomalies ?? [],
94719
+ errors,
94720
+ quarantine: {},
94721
+ fileInfo
94722
+ };
94723
+ mapResultsList.push(scanErrorResult);
94724
+ return;
94725
+ }
94726
+ if (!mappingWorkerOptions.skipWrite && anomalies.length > 0) {
94709
94727
  const scanAnomalyResult = {
94710
94728
  sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
94711
94729
  outputFilePath: `${fileInfo.path}/${fileInfo.name}`,
@@ -94717,8 +94735,8 @@ async function dispatchMappingJobs() {
94717
94735
  quarantine: {}
94718
94736
  };
94719
94737
  mapResultsList.push(scanAnomalyResult);
94720
- });
94721
- }
94738
+ }
94739
+ });
94722
94740
  progressCallback({
94723
94741
  response: "done",
94724
94742
  mapResultsList,
@@ -94945,8 +94963,8 @@ async function initializeFileListWorker(rejectCallback) {
94945
94963
  break;
94946
94964
  }
94947
94965
  case "scanAnomalies": {
94948
- const { fileInfo: anomalyFileInfo, anomalies } = event.data;
94949
- scanAnomalies.push({ fileInfo: anomalyFileInfo, anomalies });
94966
+ const { fileInfo: anomalyFileInfo, anomalies, errors } = event.data;
94967
+ scanAnomalies.push({ fileInfo: anomalyFileInfo, anomalies, errors });
94950
94968
  break;
94951
94969
  }
94952
94970
  case "count": {
@@ -86824,10 +86824,12 @@ async function curateOne({
86824
86824
  error3
86825
86825
  );
86826
86826
  const mapResults = {
86827
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
86828
- errors: [
86829
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`
86830
- ],
86827
+ // PHI-safe strings: the raw filename is intentionally omitted because
86828
+ // anomalies/errors are shared between the private (input) log and the
86829
+ // server-bound (output) log. The raw name/path is carried only in
86830
+ // fileInfo, keeping it in the private log. See #283.
86831
+ anomalies: [`Could not parse file as DICOM data`],
86832
+ errors: [`File is not a valid DICOM file or is corrupted`],
86831
86833
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
86832
86834
  fileInfo: {
86833
86835
  name: fileInfo.name,
@@ -93104,8 +93106,24 @@ async function dispatchMappingJobs() {
93104
93106
  console.log("job is finished");
93105
93107
  if (!mapResultsList)
93106
93108
  mapResultsList = [];
93107
- if (!mappingWorkerOptions.skipWrite) {
93108
- scanAnomalies.forEach(({ fileInfo, anomalies }) => {
93109
+ scanAnomalies.forEach(({ fileInfo, anomalies, errors }) => {
93110
+ const hasReadErrors = !!errors && errors.length > 0;
93111
+ if (hasReadErrors) {
93112
+ const scanErrorResult = {
93113
+ sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
93114
+ // Intentionally no outputFilePath: an unread file has no
93115
+ // de-identified output path, and we must not place the raw input
93116
+ // path in the server-bound channel.
93117
+ mappings: {},
93118
+ anomalies: anomalies ?? [],
93119
+ errors,
93120
+ quarantine: {},
93121
+ fileInfo
93122
+ };
93123
+ mapResultsList.push(scanErrorResult);
93124
+ return;
93125
+ }
93126
+ if (!mappingWorkerOptions.skipWrite && anomalies.length > 0) {
93109
93127
  const scanAnomalyResult = {
93110
93128
  sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
93111
93129
  outputFilePath: `${fileInfo.path}/${fileInfo.name}`,
@@ -93117,8 +93135,8 @@ async function dispatchMappingJobs() {
93117
93135
  quarantine: {}
93118
93136
  };
93119
93137
  mapResultsList.push(scanAnomalyResult);
93120
- });
93121
- }
93138
+ }
93139
+ });
93122
93140
  progressCallback({
93123
93141
  response: "done",
93124
93142
  mapResultsList,
@@ -37136,6 +37136,10 @@ var DEFAULT_EXCLUDED_FILETYPES = [
37136
37136
  "thumbs.db",
37137
37137
  ".ds_store"
37138
37138
  ];
37139
+ function safeReadErrorMessage(error2) {
37140
+ const detail = error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
37141
+ return `Unable to read file (filesystem error): ${detail}`;
37142
+ }
37139
37143
  var keepScanning = true;
37140
37144
  var pauseResolve = null;
37141
37145
  var pausePromise = null;
@@ -37445,9 +37449,36 @@ async function scanDirectory(dir) {
37445
37449
  if (!keepScanning)
37446
37450
  return;
37447
37451
  if (entry.kind === "file") {
37448
- const file = await entry.getFile();
37449
37452
  const key = `${prefix}/${entry.name}`;
37450
37453
  const prev = previousIndex ? previousIndex[key] : void 0;
37454
+ let file;
37455
+ try {
37456
+ file = await entry.getFile();
37457
+ } catch (readError) {
37458
+ if (cheapFilterNameOnly(entry.name, key)) {
37459
+ totalDiscovered--;
37460
+ globalThis.postMessage({
37461
+ response: "count",
37462
+ totalDiscovered
37463
+ });
37464
+ }
37465
+ globalThis.postMessage({
37466
+ response: "scanAnomalies",
37467
+ fileInfo: {
37468
+ path: prefix,
37469
+ name: entry.name,
37470
+ size: 0,
37471
+ kind: "handle",
37472
+ fileHandle: entry
37473
+ },
37474
+ anomalies: [],
37475
+ errors: [safeReadErrorMessage(readError)],
37476
+ previousFileInfo: prev
37477
+ });
37478
+ if (!await waitIfPaused())
37479
+ return;
37480
+ continue;
37481
+ }
37451
37482
  const fileAnomalies = [];
37452
37483
  if (await shouldProcessFile(file, fileAnomalies, key)) {
37453
37484
  globalThis.postMessage({
@@ -37545,9 +37576,36 @@ async function scanDirectoryNode(dirPath) {
37545
37576
  return;
37546
37577
  if (entry.isFile()) {
37547
37578
  const filePath = path.join(currentPath, entry.name);
37548
- const stats = await fs.stat(filePath);
37549
37579
  const key = `${prefix}/${entry.name}`;
37550
37580
  const prev = previousIndex ? previousIndex[key] : void 0;
37581
+ let stats;
37582
+ try {
37583
+ stats = await fs.stat(filePath);
37584
+ } catch (readError) {
37585
+ if (cheapFilterNameOnly(entry.name, key)) {
37586
+ totalDiscovered--;
37587
+ globalThis.postMessage({
37588
+ response: "count",
37589
+ totalDiscovered
37590
+ });
37591
+ }
37592
+ globalThis.postMessage({
37593
+ response: "scanAnomalies",
37594
+ fileInfo: {
37595
+ path: prefix,
37596
+ name: entry.name,
37597
+ size: 0,
37598
+ kind: "path",
37599
+ fullPath: filePath
37600
+ },
37601
+ anomalies: [],
37602
+ errors: [safeReadErrorMessage(readError)],
37603
+ previousFileInfo: prev
37604
+ });
37605
+ if (!await waitIfPaused())
37606
+ return;
37607
+ continue;
37608
+ }
37551
37609
  const fileAnomalies = [];
37552
37610
  if (await shouldProcessFileNode(
37553
37611
  filePath,
@@ -27,6 +27,7 @@ export declare function setDirectoryScanFinished(value: boolean): void;
27
27
  export declare let scanAnomalies: {
28
28
  fileInfo: TFileInfo;
29
29
  anomalies: string[];
30
+ errors?: string[];
30
31
  }[];
31
32
  export declare function setMappingWorkerOptions(opts: TMappingWorkerOptions): void;
32
33
  export declare function setCustomUploader(uploader: TCustomUploader | undefined): void;
@@ -11,6 +11,15 @@ export type FileScanMsg = {
11
11
  response: 'scanAnomalies';
12
12
  fileInfo: TFileInfo;
13
13
  anomalies: string[];
14
+ /**
15
+ * Hard errors discovered during scanning (e.g. a file that cannot be
16
+ * read at all via the FileSystem API / fs.stat). Unlike `anomalies`
17
+ * (benign findings such as non-DICOM or too-small files), these are
18
+ * surfaced as errors so they are visible regardless of pass. The string
19
+ * MUST NOT contain the raw filename/path — that is carried only in
20
+ * `fileInfo` so it stays in the private (input) log.
21
+ */
22
+ errors?: string[];
14
23
  previousFileInfo?: {
15
24
  size?: number;
16
25
  mtime?: string;
@@ -55131,10 +55131,12 @@
55131
55131
  catch (error) {
55132
55132
  console.warn(`[dicom-curate] Could not parse ${fileInfo.name} as DICOM data:`, error);
55133
55133
  const mapResults = {
55134
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
55135
- errors: [
55136
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`,
55137
- ],
55134
+ // PHI-safe strings: the raw filename is intentionally omitted because
55135
+ // anomalies/errors are shared between the private (input) log and the
55136
+ // server-bound (output) log. The raw name/path is carried only in
55137
+ // fileInfo, keeping it in the private log. See #283.
55138
+ anomalies: [`Could not parse file as DICOM data`],
55139
+ errors: [`File is not a valid DICOM file or is corrupted`],
55138
55140
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
55139
55141
  fileInfo: {
55140
55142
  name: fileInfo.name,
@@ -68860,6 +68862,17 @@
68860
68862
  'thumbs.db',
68861
68863
  '.ds_store',
68862
68864
  ];
68865
+ /**
68866
+ * Build a PHI-safe error message for a file that could not be read during
68867
+ * scanning. Intentionally omits the filename/path so the string is safe to
68868
+ * place in `errors` (which is shared between the private and server-bound
68869
+ * logs). The raw path/name is carried separately in `fileInfo` so it appears
68870
+ * only in the private (input) log.
68871
+ */
68872
+ function safeReadErrorMessage(error) {
68873
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
68874
+ return `Unable to read file (filesystem error): ${detail}`;
68875
+ }
68863
68876
  let keepScanning = true;
68864
68877
  // Backpressure gate: when the main thread signals 'stop', the feeder
68865
68878
  // awaits this promise before emitting the next file. 'resume' resolves it.
@@ -69242,9 +69255,42 @@
69242
69255
  if (!keepScanning)
69243
69256
  return;
69244
69257
  if (entry.kind === 'file') {
69245
- const file = await entry.getFile();
69246
69258
  const key = `${prefix}/${entry.name}`;
69247
69259
  const prev = previousIndex ? previousIndex[key] : undefined;
69260
+ let file;
69261
+ try {
69262
+ file = await entry.getFile();
69263
+ }
69264
+ catch (readError) {
69265
+ // A single file we cannot read (corrupted, locked, permission
69266
+ // revoked via the Chromium FileSystem API) must NOT abort the whole
69267
+ // scan. Report it as a hard error and continue. The error string is
69268
+ // PHI-safe (no filename); the raw path/name lives only in fileInfo,
69269
+ // which keeps it in the private (input) log.
69270
+ if (cheapFilterNameOnly(entry.name, key)) {
69271
+ totalDiscovered--;
69272
+ globalThis.postMessage({
69273
+ response: 'count',
69274
+ totalDiscovered,
69275
+ });
69276
+ }
69277
+ globalThis.postMessage({
69278
+ response: 'scanAnomalies',
69279
+ fileInfo: {
69280
+ path: prefix,
69281
+ name: entry.name,
69282
+ size: 0,
69283
+ kind: 'handle',
69284
+ fileHandle: entry,
69285
+ },
69286
+ anomalies: [],
69287
+ errors: [safeReadErrorMessage(readError)],
69288
+ previousFileInfo: prev,
69289
+ });
69290
+ if (!(await waitIfPaused()))
69291
+ return;
69292
+ continue;
69293
+ }
69248
69294
  const fileAnomalies = [];
69249
69295
  if (await shouldProcessFile(file, fileAnomalies, key)) {
69250
69296
  globalThis.postMessage({
@@ -69362,9 +69408,41 @@
69362
69408
  return;
69363
69409
  if (entry.isFile()) {
69364
69410
  const filePath = path.join(currentPath, entry.name);
69365
- const stats = await fs.stat(filePath);
69366
69411
  const key = `${prefix}/${entry.name}`;
69367
69412
  const prev = previousIndex ? previousIndex[key] : undefined;
69413
+ let stats;
69414
+ try {
69415
+ stats = await fs.stat(filePath);
69416
+ }
69417
+ catch (readError) {
69418
+ // A single file we cannot stat (vanished, permission denied) must
69419
+ // NOT abort the whole scan. Report it as a hard error and continue.
69420
+ // The error string is PHI-safe (no path); the raw path lives only
69421
+ // in fileInfo, keeping it in the private (input) log.
69422
+ if (cheapFilterNameOnly(entry.name, key)) {
69423
+ totalDiscovered--;
69424
+ globalThis.postMessage({
69425
+ response: 'count',
69426
+ totalDiscovered,
69427
+ });
69428
+ }
69429
+ globalThis.postMessage({
69430
+ response: 'scanAnomalies',
69431
+ fileInfo: {
69432
+ path: prefix,
69433
+ name: entry.name,
69434
+ size: 0,
69435
+ kind: 'path',
69436
+ fullPath: filePath,
69437
+ },
69438
+ anomalies: [],
69439
+ errors: [safeReadErrorMessage(readError)],
69440
+ previousFileInfo: prev,
69441
+ });
69442
+ if (!(await waitIfPaused()))
69443
+ return;
69444
+ continue;
69445
+ }
69368
69446
  const fileAnomalies = [];
69369
69447
  if (await shouldProcessFileNode(filePath, entry.name, stats.size, fileAnomalies, key)) {
69370
69448
  globalThis.postMessage({
@@ -126773,10 +126851,12 @@
126773
126851
  catch (error) {
126774
126852
  console.warn(`[dicom-curate] Could not parse ${fileInfo.name} as DICOM data:`, error);
126775
126853
  const mapResults = {
126776
- anomalies: [`Could not parse ${fileInfo.name} as DICOM data`],
126777
- errors: [
126778
- `File ${fileInfo.name} is not a valid DICOM file or is corrupted`,
126779
- ],
126854
+ // PHI-safe strings: the raw filename is intentionally omitted because
126855
+ // anomalies/errors are shared between the private (input) log and the
126856
+ // server-bound (output) log. The raw name/path is carried only in
126857
+ // fileInfo, keeping it in the private log. See #283.
126858
+ anomalies: [`Could not parse file as DICOM data`],
126859
+ errors: [`File is not a valid DICOM file or is corrupted`],
126780
126860
  sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
126781
126861
  fileInfo: {
126782
126862
  name: fileInfo.name,
@@ -140785,10 +140865,39 @@
140785
140865
  console.log('job is finished');
140786
140866
  if (!mapResultsList)
140787
140867
  mapResultsList = [];
140788
- // Create individual mapResults entries for each scan anomaly
140789
- // Only do this during actual processing (not first pass)
140790
- if (!mappingWorkerOptions.skipWrite) {
140791
- scanAnomalies.forEach(({ fileInfo, anomalies }) => {
140868
+ // Create individual mapResults entries for each scan finding.
140869
+ //
140870
+ // Two kinds of scan findings are surfaced differently:
140871
+ //
140872
+ // - Benign anomalies (non-DICOM, too-small, excluded filetypes): noise
140873
+ // during the first pass (form generation), so only emitted on the
140874
+ // write pass (!skipWrite), and they carry the input path in
140875
+ // `outputFilePath` as before.
140876
+ //
140877
+ // - Hard read errors (file could not be read at all): always surfaced,
140878
+ // in both passes, via `errors`. We deliberately OMIT `outputFilePath`
140879
+ // so the raw input path is not leaked into the server-bound log; the
140880
+ // raw path is retained in `fileInfo` for the private (input) log, and a
140881
+ // path-less trace is rendered in the server-bound log by the consumer.
140882
+ scanAnomalies.forEach(({ fileInfo, anomalies, errors }) => {
140883
+ const hasReadErrors = !!errors && errors.length > 0;
140884
+ if (hasReadErrors) {
140885
+ const scanErrorResult = {
140886
+ sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
140887
+ // Intentionally no outputFilePath: an unread file has no
140888
+ // de-identified output path, and we must not place the raw input
140889
+ // path in the server-bound channel.
140890
+ mappings: {},
140891
+ anomalies: anomalies ?? [],
140892
+ errors,
140893
+ quarantine: {},
140894
+ fileInfo,
140895
+ };
140896
+ mapResultsList.push(scanErrorResult);
140897
+ return;
140898
+ }
140899
+ // Benign anomalies: only on the write pass.
140900
+ if (!mappingWorkerOptions.skipWrite && anomalies.length > 0) {
140792
140901
  const scanAnomalyResult = {
140793
140902
  sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
140794
140903
  outputFilePath: `${fileInfo.path}/${fileInfo.name}`, // Use the actual file path
@@ -140797,10 +140906,9 @@
140797
140906
  errors: [],
140798
140907
  quarantine: {},
140799
140908
  };
140800
- // Add each scan anomaly result to the final results
140801
140909
  mapResultsList.push(scanAnomalyResult);
140802
- });
140803
- }
140910
+ }
140911
+ });
140804
140912
  progressCallback({
140805
140913
  response: 'done',
140806
140914
  mapResultsList: mapResultsList,
@@ -141086,8 +141194,8 @@
141086
141194
  }
141087
141195
  case 'scanAnomalies': {
141088
141196
  // Handle scan anomalies separately - they don't go to processing
141089
- const { fileInfo: anomalyFileInfo, anomalies } = event.data;
141090
- scanAnomalies.push({ fileInfo: anomalyFileInfo, anomalies });
141197
+ const { fileInfo: anomalyFileInfo, anomalies, errors } = event.data;
141198
+ scanAnomalies.push({ fileInfo: anomalyFileInfo, anomalies, errors });
141091
141199
  break;
141092
141200
  }
141093
141201
  case 'count': {