@ubercode/dcmtk 0.6.5 → 0.7.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.
@@ -1569,6 +1569,12 @@ interface DicomReceiverOptions {
1569
1569
  readonly dimseTimeout?: number | undefined;
1570
1570
  /** Maximum PDU receive size (passed through to Dcmrecv workers). */
1571
1571
  readonly maxPdu?: number | undefined;
1572
+ /** Filename generation mode for received files (passed through to Dcmrecv workers). */
1573
+ readonly filenameMode?: FilenameModeValue | undefined;
1574
+ /** Extension appended to received filenames, e.g. `'.dcm'` (passed through to Dcmrecv workers). */
1575
+ readonly filenameExtension?: string | undefined;
1576
+ /** Storage mode for received files (passed through to Dcmrecv workers). */
1577
+ readonly storageMode?: StorageModeValue | undefined;
1572
1578
  /** AbortSignal for external cancellation. */
1573
1579
  readonly signal?: AbortSignal | undefined;
1574
1580
  }
@@ -1569,6 +1569,12 @@ interface DicomReceiverOptions {
1569
1569
  readonly dimseTimeout?: number | undefined;
1570
1570
  /** Maximum PDU receive size (passed through to Dcmrecv workers). */
1571
1571
  readonly maxPdu?: number | undefined;
1572
+ /** Filename generation mode for received files (passed through to Dcmrecv workers). */
1573
+ readonly filenameMode?: FilenameModeValue | undefined;
1574
+ /** Extension appended to received filenames, e.g. `'.dcm'` (passed through to Dcmrecv workers). */
1575
+ readonly filenameExtension?: string | undefined;
1576
+ /** Storage mode for received files (passed through to Dcmrecv workers). */
1577
+ readonly storageMode?: StorageModeValue | undefined;
1572
1578
  /** AbortSignal for external cancellation. */
1573
1579
  readonly signal?: AbortSignal | undefined;
1574
1580
  }
package/dist/index.cjs CHANGED
@@ -33342,7 +33342,7 @@ async function storescu(options) {
33342
33342
  if (result.value.exitCode !== 0) {
33343
33343
  return err(createToolError("storescu", args, result.value.exitCode, result.value.stderr));
33344
33344
  }
33345
- return ok({ success: true, stderr: result.value.stderr });
33345
+ return ok({ success: true, stdout: result.value.stdout, stderr: result.value.stderr });
33346
33346
  }
33347
33347
  var QueryModel = {
33348
33348
  WORKLIST: "worklist",
@@ -36197,6 +36197,9 @@ var DicomReceiverOptionsSchema = zod.z.object({
36197
36197
  acseTimeout: zod.z.number().int().positive().optional(),
36198
36198
  dimseTimeout: zod.z.number().int().positive().optional(),
36199
36199
  maxPdu: zod.z.number().int().min(4096).max(131072).optional(),
36200
+ filenameMode: zod.z.enum(["default", "unique", "short-unique", "system-time"]).optional(),
36201
+ filenameExtension: zod.z.string().min(1).optional(),
36202
+ storageMode: zod.z.enum(["normal", "bit-preserving", "ignore"]).optional(),
36200
36203
  signal: zod.z.instanceof(AbortSignal).optional()
36201
36204
  }).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
36202
36205
  message: "minPoolSize must be <= maxPoolSize"
@@ -36518,7 +36521,10 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
36518
36521
  configProfile: this.options.configProfile,
36519
36522
  acseTimeout: this.options.acseTimeout,
36520
36523
  dimseTimeout: this.options.dimseTimeout,
36521
- maxPdu: this.options.maxPdu
36524
+ maxPdu: this.options.maxPdu,
36525
+ filenameMode: this.options.filenameMode,
36526
+ filenameExtension: this.options.filenameExtension,
36527
+ storageMode: this.options.storageMode
36522
36528
  });
36523
36529
  }
36524
36530
  /** Spawns a single Dcmrecv worker with an ephemeral port. */
@@ -36880,6 +36886,14 @@ var DicomSenderOptionsSchema = zod.z.object({
36880
36886
  retryDelayMs: zod.z.number().int().min(0).optional(),
36881
36887
  bucketFlushMs: zod.z.number().int().positive().optional(),
36882
36888
  maxBucketSize: zod.z.number().int().min(1).optional(),
36889
+ maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
36890
+ maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
36891
+ associationTimeout: zod.z.number().int().positive().optional(),
36892
+ acseTimeout: zod.z.number().int().positive().optional(),
36893
+ dimseTimeout: zod.z.number().int().positive().optional(),
36894
+ noHostnameLookup: zod.z.boolean().optional(),
36895
+ noUidChecks: zod.z.boolean().optional(),
36896
+ verbosity: zod.z.enum(["verbose", "debug"]).optional(),
36883
36897
  signal: zod.z.instanceof(AbortSignal).optional()
36884
36898
  }).strict();
36885
36899
  function resolveConfig(options) {
@@ -36978,14 +36992,22 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
36978
36992
  if (files.length === 0) {
36979
36993
  return Promise.resolve(err(new Error("DicomSender: no files provided")));
36980
36994
  }
36981
- const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
36982
- const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
36995
+ const params = {
36996
+ timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
36997
+ maxRetries: options?.maxRetries ?? this.defaultMaxRetries,
36998
+ calledAETitle: options?.calledAETitle,
36999
+ callingAETitle: options?.callingAETitle
37000
+ };
37001
+ return this.dispatchSend(files, params);
37002
+ }
37003
+ /** Dispatches a send to the appropriate mode handler. */
37004
+ dispatchSend(files, params) {
36983
37005
  switch (this.mode) {
36984
37006
  case "single":
36985
37007
  case "multiple":
36986
- return this.enqueueSend(files, timeoutMs, maxRetries);
37008
+ return this.enqueueSend(files, params);
36987
37009
  case "bucket":
36988
- return this.enqueueBucket(files, timeoutMs, maxRetries);
37010
+ return this.enqueueBucket(files, params);
36989
37011
  default:
36990
37012
  assertUnreachable(this.mode);
36991
37013
  }
@@ -37080,14 +37102,21 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37080
37102
  // Single/Multiple mode: queue-based dispatch
37081
37103
  // -----------------------------------------------------------------------
37082
37104
  /** Enqueues a send and dispatches immediately if capacity allows. */
37083
- enqueueSend(files, timeoutMs, maxRetries) {
37105
+ enqueueSend(files, params) {
37084
37106
  return new Promise((resolve) => {
37085
37107
  const totalQueued = this.queue.length + this.currentBucket.length;
37086
37108
  if (totalQueued >= this.maxQueueLength) {
37087
37109
  resolve(err(new Error("DicomSender: queue full")));
37088
37110
  return;
37089
37111
  }
37090
- const entry = { files, timeoutMs, maxRetries, resolve };
37112
+ const entry = {
37113
+ files,
37114
+ timeoutMs: params.timeoutMs,
37115
+ maxRetries: params.maxRetries,
37116
+ calledAETitle: params.calledAETitle,
37117
+ callingAETitle: params.callingAETitle,
37118
+ resolve
37119
+ };
37091
37120
  if (this.activeAssociations < this.effectiveMaxAssociations) {
37092
37121
  void this.executeEntry(entry);
37093
37122
  } else {
@@ -37107,14 +37136,15 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37107
37136
  // Bucket mode: accumulate-then-flush
37108
37137
  // -----------------------------------------------------------------------
37109
37138
  /** Adds files to the current bucket and triggers flush if full. */
37110
- enqueueBucket(files, timeoutMs, maxRetries) {
37139
+ enqueueBucket(files, params) {
37111
37140
  return new Promise((resolve) => {
37112
37141
  const totalQueued = this.queue.length + this.currentBucket.length;
37113
37142
  if (totalQueued >= this.maxQueueLength) {
37114
37143
  resolve(err(new Error("DicomSender: queue full")));
37115
37144
  return;
37116
37145
  }
37117
- this.currentBucket.push({ files, resolve, timeoutMs, maxRetries });
37146
+ const { timeoutMs, maxRetries, calledAETitle, callingAETitle } = params;
37147
+ this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, calledAETitle, callingAETitle });
37118
37148
  const totalFiles = this.countBucketFiles();
37119
37149
  if (totalFiles >= this.maxBucketSize) {
37120
37150
  this.clearBucketTimer();
@@ -37154,6 +37184,8 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37154
37184
  files: allFiles,
37155
37185
  timeoutMs,
37156
37186
  maxRetries,
37187
+ calledAETitle: entries[0]?.calledAETitle,
37188
+ callingAETitle: entries[0]?.callingAETitle,
37157
37189
  resolve: (result) => {
37158
37190
  for (let i = 0; i < entries.length; i++) {
37159
37191
  entries[i].resolve(result);
@@ -37189,17 +37221,24 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37189
37221
  this.activeAssociations++;
37190
37222
  const startMs = Date.now();
37191
37223
  const maxAttempts = entry.maxRetries + 1;
37192
- const lastError = await this.attemptSend(entry, maxAttempts, startMs);
37193
- if (lastError === void 0) return;
37224
+ const failure = await this.attemptSend(entry, maxAttempts, startMs);
37225
+ if (failure === void 0) return;
37194
37226
  this.activeAssociations--;
37195
37227
  this.recordFailure();
37196
- this.emit("SEND_FAILED", { files: entry.files, error: lastError, attempts: maxAttempts });
37197
- entry.resolve(err(lastError));
37228
+ this.emit("SEND_FAILED", {
37229
+ files: entry.files,
37230
+ error: failure.error,
37231
+ attempts: maxAttempts,
37232
+ stdout: failure.output.stdout,
37233
+ stderr: failure.output.stderr
37234
+ });
37235
+ entry.resolve(err(failure.error));
37198
37236
  this.drainQueue();
37199
37237
  }
37200
- /** Attempts storescu up to maxAttempts times. Returns undefined on success, or the last error. */
37238
+ /** Attempts storescu up to maxAttempts times. Returns undefined on success, or failure info. */
37201
37239
  async attemptSend(entry, maxAttempts, startMs) {
37202
37240
  let lastError;
37241
+ const lastOutput = { stdout: "", stderr: "" };
37203
37242
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
37204
37243
  if (this.isStopped) {
37205
37244
  this.activeAssociations--;
@@ -37208,7 +37247,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37208
37247
  }
37209
37248
  const result = await this.callStorescu(entry);
37210
37249
  if (result.ok) {
37211
- this.handleSendSuccess(entry, startMs);
37250
+ this.handleSendSuccess(entry, startMs, result.value);
37212
37251
  return void 0;
37213
37252
  }
37214
37253
  lastError = result.error;
@@ -37216,7 +37255,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37216
37255
  await delay2(this.retryDelayMs * (attempt + 1));
37217
37256
  }
37218
37257
  }
37219
- return lastError ?? new Error("DicomSender: send failed");
37258
+ return { error: lastError ?? new Error("DicomSender: send failed"), output: lastOutput };
37220
37259
  }
37221
37260
  /** Calls storescu with the configured options. */
37222
37261
  callStorescu(entry) {
@@ -37224,20 +37263,29 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37224
37263
  host: this.options.host,
37225
37264
  port: this.options.port,
37226
37265
  files: [...entry.files],
37227
- calledAETitle: this.options.calledAETitle,
37228
- callingAETitle: this.options.callingAETitle,
37266
+ calledAETitle: entry.calledAETitle ?? this.options.calledAETitle,
37267
+ callingAETitle: entry.callingAETitle ?? this.options.callingAETitle,
37229
37268
  proposedTransferSyntax: this.options.proposedTransferSyntax,
37269
+ maxPduReceive: this.options.maxPduReceive,
37270
+ maxPduSend: this.options.maxPduSend,
37271
+ associationTimeout: this.options.associationTimeout,
37272
+ acseTimeout: this.options.acseTimeout,
37273
+ dimseTimeout: this.options.dimseTimeout,
37274
+ noHostnameLookup: this.options.noHostnameLookup,
37275
+ noUidChecks: this.options.noUidChecks,
37276
+ verbosity: this.options.verbosity,
37230
37277
  timeoutMs: entry.timeoutMs,
37231
37278
  signal: this.options.signal
37232
37279
  });
37233
37280
  }
37234
37281
  /** Handles a successful send: updates state, emits event, resolves promise. */
37235
- handleSendSuccess(entry, startMs) {
37282
+ handleSendSuccess(entry, startMs, output) {
37236
37283
  this.activeAssociations--;
37237
37284
  const durationMs = Date.now() - startMs;
37238
37285
  this.recordSuccess();
37239
- this.emit("SEND_COMPLETE", { files: entry.files, fileCount: entry.files.length, durationMs });
37240
- entry.resolve(ok({ files: entry.files, fileCount: entry.files.length, durationMs }));
37286
+ const data = { files: entry.files, fileCount: entry.files.length, durationMs, stdout: output.stdout, stderr: output.stderr };
37287
+ this.emit("SEND_COMPLETE", data);
37288
+ entry.resolve(ok(data));
37241
37289
  this.drainQueue();
37242
37290
  }
37243
37291
  // -----------------------------------------------------------------------