@ubercode/dcmtk 0.14.0 → 0.15.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/dicom.cjs +45 -33
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.js +45 -33
- package/dist/dicom.js.map +1 -1
- package/dist/index.cjs +166 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +166 -61
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +14 -2
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.js +14 -2
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +52 -37
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +18 -1
- package/dist/tools.d.ts +18 -1
- package/dist/tools.js +52 -38
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -713,6 +713,51 @@ var LineParser = class extends events.EventEmitter {
|
|
|
713
713
|
}
|
|
714
714
|
};
|
|
715
715
|
|
|
716
|
+
// src/tools/_toolError.ts
|
|
717
|
+
var MAX_ARGS_LENGTH = 200;
|
|
718
|
+
var MAX_STDERR_LENGTH = 500;
|
|
719
|
+
function truncate(value, maxLength) {
|
|
720
|
+
if (value.length <= maxLength) {
|
|
721
|
+
return value;
|
|
722
|
+
}
|
|
723
|
+
return `${value.substring(0, maxLength)}...`;
|
|
724
|
+
}
|
|
725
|
+
var ToolExecutionError = class extends Error {
|
|
726
|
+
constructor(message, details) {
|
|
727
|
+
super(message);
|
|
728
|
+
__publicField(this, "stdout");
|
|
729
|
+
__publicField(this, "stderr");
|
|
730
|
+
__publicField(this, "exitCode");
|
|
731
|
+
this.name = "ToolExecutionError";
|
|
732
|
+
this.stdout = details.stdout;
|
|
733
|
+
this.stderr = details.stderr;
|
|
734
|
+
this.exitCode = details.exitCode;
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
function createToolError(toolName, args, exitCode, stderr8, stdout = "") {
|
|
738
|
+
const argsStr = truncate(args.join(" "), MAX_ARGS_LENGTH);
|
|
739
|
+
const stderrStr = truncate(stderr8.trim(), MAX_STDERR_LENGTH);
|
|
740
|
+
const parts = [`${toolName} failed (exit code ${String(exitCode)})`];
|
|
741
|
+
if (argsStr.length > 0) {
|
|
742
|
+
parts.push(`args: ${argsStr}`);
|
|
743
|
+
}
|
|
744
|
+
if (stderrStr.length > 0) {
|
|
745
|
+
parts.push(`stderr: ${stderrStr}`);
|
|
746
|
+
}
|
|
747
|
+
return new ToolExecutionError(parts.join(" | "), { stdout, stderr: stderr8, exitCode });
|
|
748
|
+
}
|
|
749
|
+
function createValidationError(toolName, zodError) {
|
|
750
|
+
const parts = [];
|
|
751
|
+
for (let i = 0; i < zodError.issues.length; i++) {
|
|
752
|
+
const issue = zodError.issues[i];
|
|
753
|
+
if (issue === void 0) continue;
|
|
754
|
+
const path2 = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
|
|
755
|
+
parts.push(`${path2}: ${issue.message}`);
|
|
756
|
+
}
|
|
757
|
+
const detail = parts.length > 0 ? parts.join("; ") : "unknown validation error";
|
|
758
|
+
return new Error(`${toolName}: invalid options \u2014 ${detail}`);
|
|
759
|
+
}
|
|
760
|
+
|
|
716
761
|
// src/dicom/vr.ts
|
|
717
762
|
var VR = {
|
|
718
763
|
/** Application Entity — 16 bytes max, leading/trailing spaces significant */
|
|
@@ -31136,39 +31181,6 @@ function resolveBinary(toolName) {
|
|
|
31136
31181
|
return ok(path.join(pathResult.value, binaryName2));
|
|
31137
31182
|
}
|
|
31138
31183
|
|
|
31139
|
-
// src/tools/_toolError.ts
|
|
31140
|
-
var MAX_ARGS_LENGTH = 200;
|
|
31141
|
-
var MAX_STDERR_LENGTH = 500;
|
|
31142
|
-
function truncate(value, maxLength) {
|
|
31143
|
-
if (value.length <= maxLength) {
|
|
31144
|
-
return value;
|
|
31145
|
-
}
|
|
31146
|
-
return `${value.substring(0, maxLength)}...`;
|
|
31147
|
-
}
|
|
31148
|
-
function createToolError(toolName, args, exitCode, stderr8) {
|
|
31149
|
-
const argsStr = truncate(args.join(" "), MAX_ARGS_LENGTH);
|
|
31150
|
-
const stderrStr = truncate(stderr8.trim(), MAX_STDERR_LENGTH);
|
|
31151
|
-
const parts = [`${toolName} failed (exit code ${String(exitCode)})`];
|
|
31152
|
-
if (argsStr.length > 0) {
|
|
31153
|
-
parts.push(`args: ${argsStr}`);
|
|
31154
|
-
}
|
|
31155
|
-
if (stderrStr.length > 0) {
|
|
31156
|
-
parts.push(`stderr: ${stderrStr}`);
|
|
31157
|
-
}
|
|
31158
|
-
return new Error(parts.join(" | "));
|
|
31159
|
-
}
|
|
31160
|
-
function createValidationError(toolName, zodError) {
|
|
31161
|
-
const parts = [];
|
|
31162
|
-
for (let i = 0; i < zodError.issues.length; i++) {
|
|
31163
|
-
const issue = zodError.issues[i];
|
|
31164
|
-
if (issue === void 0) continue;
|
|
31165
|
-
const path2 = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
|
|
31166
|
-
parts.push(`${path2}: ${issue.message}`);
|
|
31167
|
-
}
|
|
31168
|
-
const detail = parts.length > 0 ? parts.join("; ") : "unknown validation error";
|
|
31169
|
-
return new Error(`${toolName}: invalid options \u2014 ${detail}`);
|
|
31170
|
-
}
|
|
31171
|
-
|
|
31172
31184
|
// src/tools/dcm2xml.ts
|
|
31173
31185
|
var Dcm2xmlCharset = {
|
|
31174
31186
|
/** Use UTF-8 encoding (default). */
|
|
@@ -33290,7 +33302,7 @@ async function dcmsend(options) {
|
|
|
33290
33302
|
return err(result.error);
|
|
33291
33303
|
}
|
|
33292
33304
|
if (result.value.exitCode !== 0) {
|
|
33293
|
-
return err(createToolError("dcmsend", args, result.value.exitCode, result.value.stderr));
|
|
33305
|
+
return err(createToolError("dcmsend", args, result.value.exitCode, result.value.stderr, result.value.stdout));
|
|
33294
33306
|
}
|
|
33295
33307
|
return ok({ success: true, stdout: result.value.stdout, stderr: result.value.stderr });
|
|
33296
33308
|
}
|
|
@@ -33445,10 +33457,10 @@ async function storescu(options) {
|
|
|
33445
33457
|
return err(result.error);
|
|
33446
33458
|
}
|
|
33447
33459
|
if (result.value.exitCode !== 0) {
|
|
33448
|
-
return err(createToolError("storescu", args, result.value.exitCode, result.value.stderr));
|
|
33460
|
+
return err(createToolError("storescu", args, result.value.exitCode, result.value.stderr, result.value.stdout));
|
|
33449
33461
|
}
|
|
33450
33462
|
if (DIMSE_ERROR_PATTERN.test(result.value.stderr)) {
|
|
33451
|
-
return err(createToolError("storescu", args, 0, result.value.stderr));
|
|
33463
|
+
return err(createToolError("storescu", args, 0, result.value.stderr, result.value.stdout));
|
|
33452
33464
|
}
|
|
33453
33465
|
return ok({ success: true, stdout: result.value.stdout, stderr: result.value.stderr });
|
|
33454
33466
|
}
|
|
@@ -37341,8 +37353,14 @@ var SenderEngine = class {
|
|
|
37341
37353
|
// Bucket state (bucket mode only)
|
|
37342
37354
|
__publicField(this, "currentBucket", []);
|
|
37343
37355
|
__publicField(this, "bucketTimer");
|
|
37356
|
+
// Abort wiring: stopController fires on stop(); combinedSignal also fires
|
|
37357
|
+
// when the externally provided config.signal aborts. Passed into the
|
|
37358
|
+
// executor and into delayInterruptible so stop() unblocks promptly.
|
|
37359
|
+
__publicField(this, "stopController", new AbortController());
|
|
37360
|
+
__publicField(this, "combinedSignal");
|
|
37344
37361
|
this.config = config;
|
|
37345
37362
|
this.effectiveMaxAssociations = config.configuredMaxAssociations;
|
|
37363
|
+
this.combinedSignal = combineSignals(this.stopController.signal, config.signal);
|
|
37346
37364
|
}
|
|
37347
37365
|
// -----------------------------------------------------------------------
|
|
37348
37366
|
// Public API
|
|
@@ -37368,6 +37386,7 @@ var SenderEngine = class {
|
|
|
37368
37386
|
async stop() {
|
|
37369
37387
|
if (this.isStopped) return;
|
|
37370
37388
|
this.isStopped = true;
|
|
37389
|
+
this.stopController.abort();
|
|
37371
37390
|
this.clearBucketTimer();
|
|
37372
37391
|
this.rejectBucket(`${this.config.senderName}: sender stopped`);
|
|
37373
37392
|
this.rejectQueue(`${this.config.senderName}: sender stopped`);
|
|
@@ -37484,18 +37503,48 @@ var SenderEngine = class {
|
|
|
37484
37503
|
}
|
|
37485
37504
|
};
|
|
37486
37505
|
}
|
|
37487
|
-
/**
|
|
37506
|
+
/**
|
|
37507
|
+
* Flushes the current bucket. Entries with the same bucketParamsKey merge
|
|
37508
|
+
* into one underlying call; differing keys split into separate calls,
|
|
37509
|
+
* preserving first-occurrence order. Each group emits its own
|
|
37510
|
+
* BUCKET_FLUSHED. If the number of groups exceeds available capacity,
|
|
37511
|
+
* the surplus is queued and drains as capacity frees.
|
|
37512
|
+
*/
|
|
37488
37513
|
flushBucketInternal(reason) {
|
|
37489
37514
|
if (this.currentBucket.length === 0) return;
|
|
37490
37515
|
const entries = [...this.currentBucket];
|
|
37491
37516
|
this.currentBucket = [];
|
|
37492
|
-
const
|
|
37493
|
-
|
|
37494
|
-
|
|
37495
|
-
|
|
37496
|
-
|
|
37497
|
-
|
|
37517
|
+
const groups = this.groupBucketByParams(entries);
|
|
37518
|
+
for (const group of groups) {
|
|
37519
|
+
const bucketEntry = this.mergeBucketEntries(group);
|
|
37520
|
+
this.config.emitters.emitBucketFlushed({ fileCount: bucketEntry.files.length, reason });
|
|
37521
|
+
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37522
|
+
void this.executeEntry(bucketEntry);
|
|
37523
|
+
} else {
|
|
37524
|
+
this.queue.push(bucketEntry);
|
|
37525
|
+
}
|
|
37526
|
+
}
|
|
37527
|
+
}
|
|
37528
|
+
/**
|
|
37529
|
+
* Groups bucket entries by bucketParamsKey while preserving first-occurrence
|
|
37530
|
+
* order. Without a key function, returns a single group containing every
|
|
37531
|
+
* entry (current behavior for callers that don't vary binaryParams).
|
|
37532
|
+
*/
|
|
37533
|
+
groupBucketByParams(entries) {
|
|
37534
|
+
const keyFn = this.config.bucketParamsKey;
|
|
37535
|
+
if (keyFn === void 0) return [entries.slice()];
|
|
37536
|
+
const groups = /* @__PURE__ */ new Map();
|
|
37537
|
+
for (let i = 0; i < entries.length; i++) {
|
|
37538
|
+
const entry = entries[i];
|
|
37539
|
+
const key = keyFn(entry.binaryParams);
|
|
37540
|
+
let group = groups.get(key);
|
|
37541
|
+
if (group === void 0) {
|
|
37542
|
+
group = [];
|
|
37543
|
+
groups.set(key, group);
|
|
37544
|
+
}
|
|
37545
|
+
group.push(entry);
|
|
37498
37546
|
}
|
|
37547
|
+
return Array.from(groups.values());
|
|
37499
37548
|
}
|
|
37500
37549
|
/** Resets the bucket flush timer. */
|
|
37501
37550
|
resetBucketTimer() {
|
|
@@ -37537,25 +37586,33 @@ var SenderEngine = class {
|
|
|
37537
37586
|
/** Attempts the binary call up to maxAttempts times. Returns undefined on success. */
|
|
37538
37587
|
async attemptSend(entry, maxAttempts, startMs) {
|
|
37539
37588
|
let lastError;
|
|
37540
|
-
|
|
37589
|
+
let lastOutput = { stdout: "", stderr: "" };
|
|
37541
37590
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
37542
37591
|
if (this.isStopped) {
|
|
37543
|
-
this.
|
|
37544
|
-
entry.resolve(err(new Error(`${this.config.senderName}: sender stopped`)));
|
|
37545
|
-
return void 0;
|
|
37592
|
+
return this.resolveStopped(entry);
|
|
37546
37593
|
}
|
|
37547
|
-
const
|
|
37548
|
-
|
|
37549
|
-
|
|
37594
|
+
const outcome = await this.config.executor(entry.files, entry.timeoutMs, entry.binaryParams, this.combinedSignal);
|
|
37595
|
+
lastOutput = { stdout: outcome.stdout, stderr: outcome.stderr };
|
|
37596
|
+
if (outcome.error === void 0) {
|
|
37597
|
+
this.handleSendSuccess(entry, startMs, lastOutput);
|
|
37550
37598
|
return void 0;
|
|
37551
37599
|
}
|
|
37552
|
-
|
|
37600
|
+
if (this.isStopped) {
|
|
37601
|
+
return this.resolveStopped(entry);
|
|
37602
|
+
}
|
|
37603
|
+
lastError = outcome.error;
|
|
37553
37604
|
if (attempt < maxAttempts - 1) {
|
|
37554
|
-
await
|
|
37605
|
+
await delayInterruptible(this.config.retryDelayMs * (attempt + 1), this.combinedSignal);
|
|
37555
37606
|
}
|
|
37556
37607
|
}
|
|
37557
37608
|
return { error: lastError ?? new Error(`${this.config.senderName}: send failed`), output: lastOutput };
|
|
37558
37609
|
}
|
|
37610
|
+
/** Resolves the entry as stopped and decrements activeAssociations. Used from attemptSend. */
|
|
37611
|
+
resolveStopped(entry) {
|
|
37612
|
+
this.activeAssociations--;
|
|
37613
|
+
entry.resolve(err(new Error(`${this.config.senderName}: sender stopped`)));
|
|
37614
|
+
return void 0;
|
|
37615
|
+
}
|
|
37559
37616
|
/** Handles a successful send: updates state, emits event, resolves promise. */
|
|
37560
37617
|
handleSendSuccess(entry, startMs, output) {
|
|
37561
37618
|
this.activeAssociations--;
|
|
@@ -37661,11 +37718,41 @@ var SenderEngine = class {
|
|
|
37661
37718
|
});
|
|
37662
37719
|
}
|
|
37663
37720
|
};
|
|
37664
|
-
function
|
|
37721
|
+
function delayInterruptible(ms, signal) {
|
|
37722
|
+
if (signal.aborted) return Promise.resolve();
|
|
37665
37723
|
return new Promise((resolve) => {
|
|
37666
|
-
|
|
37724
|
+
const onAbort = () => {
|
|
37725
|
+
clearTimeout(timer);
|
|
37726
|
+
resolve();
|
|
37727
|
+
};
|
|
37728
|
+
const timer = setTimeout(() => {
|
|
37729
|
+
signal.removeEventListener("abort", onAbort);
|
|
37730
|
+
resolve();
|
|
37731
|
+
}, ms);
|
|
37732
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
37667
37733
|
});
|
|
37668
37734
|
}
|
|
37735
|
+
function combineSignals(primary, external) {
|
|
37736
|
+
if (external === void 0) return primary;
|
|
37737
|
+
if (primary.aborted) return primary;
|
|
37738
|
+
if (external.aborted) {
|
|
37739
|
+
const c = new AbortController();
|
|
37740
|
+
c.abort(external.reason);
|
|
37741
|
+
return c.signal;
|
|
37742
|
+
}
|
|
37743
|
+
const merged = new AbortController();
|
|
37744
|
+
const onPrimary = () => {
|
|
37745
|
+
merged.abort(primary.reason);
|
|
37746
|
+
external.removeEventListener("abort", onExternal);
|
|
37747
|
+
};
|
|
37748
|
+
const onExternal = () => {
|
|
37749
|
+
merged.abort(external.reason);
|
|
37750
|
+
primary.removeEventListener("abort", onPrimary);
|
|
37751
|
+
};
|
|
37752
|
+
primary.addEventListener("abort", onPrimary, { once: true });
|
|
37753
|
+
external.addEventListener("abort", onExternal, { once: true });
|
|
37754
|
+
return merged.signal;
|
|
37755
|
+
}
|
|
37669
37756
|
|
|
37670
37757
|
// src/senders/DicomSender.ts
|
|
37671
37758
|
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
@@ -37700,6 +37787,9 @@ var DicomSenderOptionsSchema = zod.z.object({
|
|
|
37700
37787
|
required: zod.z.boolean().optional(),
|
|
37701
37788
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37702
37789
|
}).strict();
|
|
37790
|
+
function storescuBucketKey(p) {
|
|
37791
|
+
return JSON.stringify(p);
|
|
37792
|
+
}
|
|
37703
37793
|
function createStorescuExecutor(options) {
|
|
37704
37794
|
return async (files, timeoutMs, params, signal) => {
|
|
37705
37795
|
const result = await storescu({
|
|
@@ -37721,8 +37811,13 @@ function createStorescuExecutor(options) {
|
|
|
37721
37811
|
timeoutMs,
|
|
37722
37812
|
signal
|
|
37723
37813
|
});
|
|
37724
|
-
if (!result.ok)
|
|
37725
|
-
|
|
37814
|
+
if (!result.ok) {
|
|
37815
|
+
const e = result.error;
|
|
37816
|
+
const stdout = e instanceof ToolExecutionError ? e.stdout : "";
|
|
37817
|
+
const stderr8 = e instanceof ToolExecutionError ? e.stderr : "";
|
|
37818
|
+
return { stdout, stderr: stderr8, error: e };
|
|
37819
|
+
}
|
|
37820
|
+
return { stdout: result.value.stdout, stderr: result.value.stderr };
|
|
37726
37821
|
};
|
|
37727
37822
|
}
|
|
37728
37823
|
function resolveConfig2(options) {
|
|
@@ -37779,6 +37874,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37779
37874
|
executor: createStorescuExecutor(options),
|
|
37780
37875
|
signal: options.signal,
|
|
37781
37876
|
senderName: "DicomSender",
|
|
37877
|
+
bucketParamsKey: storescuBucketKey,
|
|
37782
37878
|
emitters: {
|
|
37783
37879
|
emitSendComplete: (data) => {
|
|
37784
37880
|
senderRef.current.emit("SEND_COMPLETE", data);
|
|
@@ -37947,6 +38043,9 @@ var DicomSendOptionsSchema = zod.z.object({
|
|
|
37947
38043
|
noUidChecks: zod.z.boolean().optional(),
|
|
37948
38044
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37949
38045
|
}).strict();
|
|
38046
|
+
function dcmsendBucketKey(p) {
|
|
38047
|
+
return JSON.stringify(p);
|
|
38048
|
+
}
|
|
37950
38049
|
function createDcmsendExecutor(options) {
|
|
37951
38050
|
return async (files, timeoutMs, params, signal) => {
|
|
37952
38051
|
const result = await dcmsend({
|
|
@@ -37970,8 +38069,13 @@ function createDcmsendExecutor(options) {
|
|
|
37970
38069
|
timeoutMs,
|
|
37971
38070
|
signal
|
|
37972
38071
|
});
|
|
37973
|
-
if (!result.ok)
|
|
37974
|
-
|
|
38072
|
+
if (!result.ok) {
|
|
38073
|
+
const e = result.error;
|
|
38074
|
+
const stdout = e instanceof ToolExecutionError ? e.stdout : "";
|
|
38075
|
+
const stderr8 = e instanceof ToolExecutionError ? e.stderr : "";
|
|
38076
|
+
return { stdout, stderr: stderr8, error: e };
|
|
38077
|
+
}
|
|
38078
|
+
return { stdout: result.value.stdout, stderr: result.value.stderr };
|
|
37975
38079
|
};
|
|
37976
38080
|
}
|
|
37977
38081
|
function resolveConfig3(options) {
|
|
@@ -38028,6 +38132,7 @@ var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
|
38028
38132
|
executor: createDcmsendExecutor(options),
|
|
38029
38133
|
signal: options.signal,
|
|
38030
38134
|
senderName: "DicomSend",
|
|
38135
|
+
bucketParamsKey: dcmsendBucketKey,
|
|
38031
38136
|
emitters: {
|
|
38032
38137
|
emitSendComplete: (data) => {
|
|
38033
38138
|
senderRef.current.emit("SEND_COMPLETE", data);
|
|
@@ -38715,7 +38820,7 @@ function computeThroughput(succeeded, totalBytes, durationMs) {
|
|
|
38715
38820
|
bytesPerSec: durationSec > 0 ? totalBytes / durationSec : 0
|
|
38716
38821
|
};
|
|
38717
38822
|
}
|
|
38718
|
-
function
|
|
38823
|
+
function delay2(ms) {
|
|
38719
38824
|
return new Promise((resolve) => {
|
|
38720
38825
|
setTimeout(resolve, ms);
|
|
38721
38826
|
});
|
|
@@ -38862,7 +38967,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38862
38967
|
/** Sends a single file, with optional delay. */
|
|
38863
38968
|
async sendOneFile(file) {
|
|
38864
38969
|
if (this.opts.delayMs > 0) {
|
|
38865
|
-
await
|
|
38970
|
+
await delay2(this.opts.delayMs);
|
|
38866
38971
|
}
|
|
38867
38972
|
const result = await dcmsend({
|
|
38868
38973
|
host: this.opts.host,
|
|
@@ -39029,6 +39134,7 @@ exports.StoreSCP = StoreSCP;
|
|
|
39029
39134
|
exports.StoreSCPPreset = StoreSCPPreset;
|
|
39030
39135
|
exports.StorescpEvent = StorescpEvent;
|
|
39031
39136
|
exports.SubdirectoryMode = SubdirectoryMode;
|
|
39137
|
+
exports.ToolExecutionError = ToolExecutionError;
|
|
39032
39138
|
exports.TransferSyntax = TransferSyntax;
|
|
39033
39139
|
exports.UIDSchema = UIDSchema;
|
|
39034
39140
|
exports.UNIX_SEARCH_PATHS = UNIX_SEARCH_PATHS;
|