@ubercode/dcmtk 0.6.5 → 0.7.1

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
@@ -33257,7 +33257,6 @@ var StorescuOptionsSchema = zod.z.object({
33257
33257
  acseTimeout: zod.z.number().int().positive().optional(),
33258
33258
  dimseTimeout: zod.z.number().int().positive().optional(),
33259
33259
  noHostnameLookup: zod.z.boolean().optional(),
33260
- noUidChecks: zod.z.boolean().optional(),
33261
33260
  proposedTransferSyntax: zod.z.enum([
33262
33261
  "uncompressed",
33263
33262
  "littleEndian",
@@ -33299,9 +33298,6 @@ function pushNetworkArgs3(args, options) {
33299
33298
  function buildArgs34(options) {
33300
33299
  const args = [];
33301
33300
  pushNetworkArgs3(args, options);
33302
- if (options.noUidChecks === true) {
33303
- args.push("--no-uid-checks");
33304
- }
33305
33301
  if (options.callingAETitle !== void 0) {
33306
33302
  args.push("-aet", options.callingAETitle);
33307
33303
  }
@@ -33342,7 +33338,7 @@ async function storescu(options) {
33342
33338
  if (result.value.exitCode !== 0) {
33343
33339
  return err(createToolError("storescu", args, result.value.exitCode, result.value.stderr));
33344
33340
  }
33345
- return ok({ success: true, stderr: result.value.stderr });
33341
+ return ok({ success: true, stdout: result.value.stdout, stderr: result.value.stderr });
33346
33342
  }
33347
33343
  var QueryModel = {
33348
33344
  WORKLIST: "worklist",
@@ -36197,6 +36193,9 @@ var DicomReceiverOptionsSchema = zod.z.object({
36197
36193
  acseTimeout: zod.z.number().int().positive().optional(),
36198
36194
  dimseTimeout: zod.z.number().int().positive().optional(),
36199
36195
  maxPdu: zod.z.number().int().min(4096).max(131072).optional(),
36196
+ filenameMode: zod.z.enum(["default", "unique", "short-unique", "system-time"]).optional(),
36197
+ filenameExtension: zod.z.string().min(1).optional(),
36198
+ storageMode: zod.z.enum(["normal", "bit-preserving", "ignore"]).optional(),
36200
36199
  signal: zod.z.instanceof(AbortSignal).optional()
36201
36200
  }).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
36202
36201
  message: "minPoolSize must be <= maxPoolSize"
@@ -36518,7 +36517,10 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
36518
36517
  configProfile: this.options.configProfile,
36519
36518
  acseTimeout: this.options.acseTimeout,
36520
36519
  dimseTimeout: this.options.dimseTimeout,
36521
- maxPdu: this.options.maxPdu
36520
+ maxPdu: this.options.maxPdu,
36521
+ filenameMode: this.options.filenameMode,
36522
+ filenameExtension: this.options.filenameExtension,
36523
+ storageMode: this.options.storageMode
36522
36524
  });
36523
36525
  }
36524
36526
  /** Spawns a single Dcmrecv worker with an ephemeral port. */
@@ -36880,6 +36882,13 @@ var DicomSenderOptionsSchema = zod.z.object({
36880
36882
  retryDelayMs: zod.z.number().int().min(0).optional(),
36881
36883
  bucketFlushMs: zod.z.number().int().positive().optional(),
36882
36884
  maxBucketSize: zod.z.number().int().min(1).optional(),
36885
+ maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
36886
+ maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
36887
+ associationTimeout: zod.z.number().int().positive().optional(),
36888
+ acseTimeout: zod.z.number().int().positive().optional(),
36889
+ dimseTimeout: zod.z.number().int().positive().optional(),
36890
+ noHostnameLookup: zod.z.boolean().optional(),
36891
+ verbosity: zod.z.enum(["verbose", "debug"]).optional(),
36883
36892
  signal: zod.z.instanceof(AbortSignal).optional()
36884
36893
  }).strict();
36885
36894
  function resolveConfig(options) {
@@ -36978,14 +36987,22 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
36978
36987
  if (files.length === 0) {
36979
36988
  return Promise.resolve(err(new Error("DicomSender: no files provided")));
36980
36989
  }
36981
- const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
36982
- const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
36990
+ const params = {
36991
+ timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
36992
+ maxRetries: options?.maxRetries ?? this.defaultMaxRetries,
36993
+ calledAETitle: options?.calledAETitle,
36994
+ callingAETitle: options?.callingAETitle
36995
+ };
36996
+ return this.dispatchSend(files, params);
36997
+ }
36998
+ /** Dispatches a send to the appropriate mode handler. */
36999
+ dispatchSend(files, params) {
36983
37000
  switch (this.mode) {
36984
37001
  case "single":
36985
37002
  case "multiple":
36986
- return this.enqueueSend(files, timeoutMs, maxRetries);
37003
+ return this.enqueueSend(files, params);
36987
37004
  case "bucket":
36988
- return this.enqueueBucket(files, timeoutMs, maxRetries);
37005
+ return this.enqueueBucket(files, params);
36989
37006
  default:
36990
37007
  assertUnreachable(this.mode);
36991
37008
  }
@@ -37080,14 +37097,21 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37080
37097
  // Single/Multiple mode: queue-based dispatch
37081
37098
  // -----------------------------------------------------------------------
37082
37099
  /** Enqueues a send and dispatches immediately if capacity allows. */
37083
- enqueueSend(files, timeoutMs, maxRetries) {
37100
+ enqueueSend(files, params) {
37084
37101
  return new Promise((resolve) => {
37085
37102
  const totalQueued = this.queue.length + this.currentBucket.length;
37086
37103
  if (totalQueued >= this.maxQueueLength) {
37087
37104
  resolve(err(new Error("DicomSender: queue full")));
37088
37105
  return;
37089
37106
  }
37090
- const entry = { files, timeoutMs, maxRetries, resolve };
37107
+ const entry = {
37108
+ files,
37109
+ timeoutMs: params.timeoutMs,
37110
+ maxRetries: params.maxRetries,
37111
+ calledAETitle: params.calledAETitle,
37112
+ callingAETitle: params.callingAETitle,
37113
+ resolve
37114
+ };
37091
37115
  if (this.activeAssociations < this.effectiveMaxAssociations) {
37092
37116
  void this.executeEntry(entry);
37093
37117
  } else {
@@ -37107,14 +37131,15 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37107
37131
  // Bucket mode: accumulate-then-flush
37108
37132
  // -----------------------------------------------------------------------
37109
37133
  /** Adds files to the current bucket and triggers flush if full. */
37110
- enqueueBucket(files, timeoutMs, maxRetries) {
37134
+ enqueueBucket(files, params) {
37111
37135
  return new Promise((resolve) => {
37112
37136
  const totalQueued = this.queue.length + this.currentBucket.length;
37113
37137
  if (totalQueued >= this.maxQueueLength) {
37114
37138
  resolve(err(new Error("DicomSender: queue full")));
37115
37139
  return;
37116
37140
  }
37117
- this.currentBucket.push({ files, resolve, timeoutMs, maxRetries });
37141
+ const { timeoutMs, maxRetries, calledAETitle, callingAETitle } = params;
37142
+ this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, calledAETitle, callingAETitle });
37118
37143
  const totalFiles = this.countBucketFiles();
37119
37144
  if (totalFiles >= this.maxBucketSize) {
37120
37145
  this.clearBucketTimer();
@@ -37154,6 +37179,8 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37154
37179
  files: allFiles,
37155
37180
  timeoutMs,
37156
37181
  maxRetries,
37182
+ calledAETitle: entries[0]?.calledAETitle,
37183
+ callingAETitle: entries[0]?.callingAETitle,
37157
37184
  resolve: (result) => {
37158
37185
  for (let i = 0; i < entries.length; i++) {
37159
37186
  entries[i].resolve(result);
@@ -37189,17 +37216,24 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37189
37216
  this.activeAssociations++;
37190
37217
  const startMs = Date.now();
37191
37218
  const maxAttempts = entry.maxRetries + 1;
37192
- const lastError = await this.attemptSend(entry, maxAttempts, startMs);
37193
- if (lastError === void 0) return;
37219
+ const failure = await this.attemptSend(entry, maxAttempts, startMs);
37220
+ if (failure === void 0) return;
37194
37221
  this.activeAssociations--;
37195
37222
  this.recordFailure();
37196
- this.emit("SEND_FAILED", { files: entry.files, error: lastError, attempts: maxAttempts });
37197
- entry.resolve(err(lastError));
37223
+ this.emit("SEND_FAILED", {
37224
+ files: entry.files,
37225
+ error: failure.error,
37226
+ attempts: maxAttempts,
37227
+ stdout: failure.output.stdout,
37228
+ stderr: failure.output.stderr
37229
+ });
37230
+ entry.resolve(err(failure.error));
37198
37231
  this.drainQueue();
37199
37232
  }
37200
- /** Attempts storescu up to maxAttempts times. Returns undefined on success, or the last error. */
37233
+ /** Attempts storescu up to maxAttempts times. Returns undefined on success, or failure info. */
37201
37234
  async attemptSend(entry, maxAttempts, startMs) {
37202
37235
  let lastError;
37236
+ const lastOutput = { stdout: "", stderr: "" };
37203
37237
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
37204
37238
  if (this.isStopped) {
37205
37239
  this.activeAssociations--;
@@ -37208,7 +37242,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37208
37242
  }
37209
37243
  const result = await this.callStorescu(entry);
37210
37244
  if (result.ok) {
37211
- this.handleSendSuccess(entry, startMs);
37245
+ this.handleSendSuccess(entry, startMs, result.value);
37212
37246
  return void 0;
37213
37247
  }
37214
37248
  lastError = result.error;
@@ -37216,7 +37250,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37216
37250
  await delay2(this.retryDelayMs * (attempt + 1));
37217
37251
  }
37218
37252
  }
37219
- return lastError ?? new Error("DicomSender: send failed");
37253
+ return { error: lastError ?? new Error("DicomSender: send failed"), output: lastOutput };
37220
37254
  }
37221
37255
  /** Calls storescu with the configured options. */
37222
37256
  callStorescu(entry) {
@@ -37224,20 +37258,28 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
37224
37258
  host: this.options.host,
37225
37259
  port: this.options.port,
37226
37260
  files: [...entry.files],
37227
- calledAETitle: this.options.calledAETitle,
37228
- callingAETitle: this.options.callingAETitle,
37261
+ calledAETitle: entry.calledAETitle ?? this.options.calledAETitle,
37262
+ callingAETitle: entry.callingAETitle ?? this.options.callingAETitle,
37229
37263
  proposedTransferSyntax: this.options.proposedTransferSyntax,
37264
+ maxPduReceive: this.options.maxPduReceive,
37265
+ maxPduSend: this.options.maxPduSend,
37266
+ associationTimeout: this.options.associationTimeout,
37267
+ acseTimeout: this.options.acseTimeout,
37268
+ dimseTimeout: this.options.dimseTimeout,
37269
+ noHostnameLookup: this.options.noHostnameLookup,
37270
+ verbosity: this.options.verbosity,
37230
37271
  timeoutMs: entry.timeoutMs,
37231
37272
  signal: this.options.signal
37232
37273
  });
37233
37274
  }
37234
37275
  /** Handles a successful send: updates state, emits event, resolves promise. */
37235
- handleSendSuccess(entry, startMs) {
37276
+ handleSendSuccess(entry, startMs, output) {
37236
37277
  this.activeAssociations--;
37237
37278
  const durationMs = Date.now() - startMs;
37238
37279
  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 }));
37280
+ const data = { files: entry.files, fileCount: entry.files.length, durationMs, stdout: output.stdout, stderr: output.stderr };
37281
+ this.emit("SEND_COMPLETE", data);
37282
+ entry.resolve(ok(data));
37241
37283
  this.drainQueue();
37242
37284
  }
37243
37285
  // -----------------------------------------------------------------------