@ubercode/dcmtk 0.13.1 → 0.14.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.
package/dist/servers.cjs CHANGED
@@ -2396,15 +2396,17 @@ async function dcm2json(inputPath, options) {
2396
2396
  }
2397
2397
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2398
2398
  const signal = options?.signal;
2399
+ const startTime = Date.now();
2400
+ const remaining = () => Math.max(1e3, timeoutMs - (Date.now() - startTime));
2399
2401
  const verbosity = options?.verbosity;
2400
2402
  if (options?.directOnly === true) {
2401
- return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
2403
+ return tryDirectPath(inputPath, remaining(), signal, verbosity);
2402
2404
  }
2403
- const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal, buildXmlOpts(options));
2405
+ const xmlResult = await tryXmlPath(inputPath, remaining(), signal, buildXmlOpts(options));
2404
2406
  if (xmlResult.ok) {
2405
2407
  return xmlResult;
2406
2408
  }
2407
- return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
2409
+ return tryDirectPath(inputPath, remaining(), signal, verbosity);
2408
2410
  }
2409
2411
  var TAG_OR_PATH_PATTERN = /^\([0-9A-Fa-f]{4},[0-9A-Fa-f]{4}\)(\[\d+\]\.\([0-9A-Fa-f]{4},[0-9A-Fa-f]{4}\))*(\[\d+\])?$/;
2410
2412
  var TagModificationSchema = zod.z.object({
@@ -2888,7 +2890,7 @@ var Worker = class {
2888
2890
  this.port = port;
2889
2891
  this.tempDir = tempDir;
2890
2892
  }
2891
- /** Current worker state. */
2893
+ /** Current worker state: idle (ready), busy (handling association), finalizing (draining). */
2892
2894
  get state() {
2893
2895
  return this._state;
2894
2896
  }
@@ -2920,10 +2922,18 @@ var Worker = class {
2920
2922
  this._instancesReceived = 0;
2921
2923
  this._instanceErrors = 0;
2922
2924
  }
2923
- /** Returns the worker to idle and clears all association state. */
2925
+ /** Marks the worker as finalizing still has valid context but cannot accept new connections. */
2926
+ markFinalizing() {
2927
+ this._state = "finalizing";
2928
+ }
2929
+ /**
2930
+ * Returns the worker to idle. Context is intentionally preserved so that
2931
+ * late FILE_RECEIVED events (arriving in a subsequent pipe chunk after
2932
+ * ASSOCIATION_RELEASE) still have valid association info. Context is
2933
+ * cleared on the next {@link beginAssociation} call.
2934
+ */
2924
2935
  endAssociation() {
2925
2936
  this._state = "idle";
2926
- this._context = void 0;
2927
2937
  this._files.length = 0;
2928
2938
  this._fileSizes.length = 0;
2929
2939
  this._outputLines.length = 0;
@@ -3098,6 +3108,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3098
3108
  this.options.signal.removeEventListener("abort", this.abortHandler);
3099
3109
  }
3100
3110
  await this.closeTcpProxy();
3111
+ await this.drainAllWorkers();
3101
3112
  const stopPromises = [];
3102
3113
  for (const worker of this.workers.values()) {
3103
3114
  stopPromises.push(this.stopWorker(worker));
@@ -3266,6 +3277,18 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3266
3277
  this.tcpServer.close(() => resolve());
3267
3278
  });
3268
3279
  }
3280
+ /** Drains in-flight file and instance work on all non-idle workers, with a 5s safety timeout. */
3281
+ async drainAllWorkers() {
3282
+ const drainPromises = [];
3283
+ for (const worker of this.workers.values()) {
3284
+ if (worker.state !== "idle") {
3285
+ drainPromises.push(worker.drainPendingFiles());
3286
+ drainPromises.push(worker.drainInstancePending());
3287
+ }
3288
+ }
3289
+ if (drainPromises.length === 0) return;
3290
+ await Promise.race([Promise.all(drainPromises), promises.setTimeout(5e3)]);
3291
+ }
3269
3292
  // -----------------------------------------------------------------------
3270
3293
  // Connection routing
3271
3294
  // -----------------------------------------------------------------------
@@ -3294,7 +3317,9 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3294
3317
  };
3295
3318
  worker.beginAssociation(ctx);
3296
3319
  this.pipeConnection(worker, remoteSocket);
3297
- void this.replenishPool();
3320
+ void this.replenishPool().catch((e) => {
3321
+ this.emit("error", { error: e instanceof Error ? e : new Error(String(e)) });
3322
+ });
3298
3323
  }
3299
3324
  /** Finds an idle worker, retrying up to connectionTimeoutMs. */
3300
3325
  async findIdleWorker() {
@@ -3533,7 +3558,10 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3533
3558
  /** Returns worker to idle pool on association complete, emits summary. */
3534
3559
  wireAssociationComplete(worker) {
3535
3560
  worker.dcmrecv.onAssociationComplete((data) => {
3536
- void this.finalizeAssociation(worker, data);
3561
+ worker.markFinalizing();
3562
+ setImmediate(() => {
3563
+ void this.finalizeAssociation(worker, data);
3564
+ });
3537
3565
  });
3538
3566
  }
3539
3567
  /** Awaits file ops, emits ASSOCIATION_COMPLETE, awaits parsing, emits ASSOCIATION_FINALIZED. */