@ubercode/dcmtk 0.7.3 → 0.8.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.
- package/dist/{DicomInstance-BjrSIEGN.d.ts → DicomInstance-BKUiir0G.d.ts} +8 -3
- package/dist/{DicomInstance-zsmyd7fs.d.cts → DicomInstance-CxLKC-oM.d.cts} +8 -3
- package/dist/dicom.cjs +14 -5
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.d.cts +2 -2
- package/dist/dicom.d.ts +2 -2
- package/dist/dicom.js +14 -5
- package/dist/dicom.js.map +1 -1
- package/dist/{index-AYvUunlI.d.cts → index-B13DmBRs.d.cts} +1 -1
- package/dist/{index-C20fLU5U.d.ts → index-CCgNgve7.d.ts} +1 -1
- package/dist/index.cjs +614 -295
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +201 -60
- package/dist/index.d.ts +201 -60
- package/dist/index.js +614 -296
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +14 -5
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +2 -2
- package/dist/servers.d.ts +2 -2
- package/dist/servers.js +14 -5
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +53 -3
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +18 -0
- package/dist/tools.d.ts +18 -0
- package/dist/tools.js +53 -3
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -31159,6 +31159,7 @@ var Dcm2xmlOptionsSchema = z.object({
|
|
|
31159
31159
|
charset: z.enum(["utf8", "latin1", "ascii"]).optional(),
|
|
31160
31160
|
writeBinaryData: z.boolean().optional(),
|
|
31161
31161
|
encodeBinaryBase64: z.boolean().optional(),
|
|
31162
|
+
charsetAssume: z.string().min(1).optional(),
|
|
31162
31163
|
verbosity: z.enum(["verbose", "debug"]).optional()
|
|
31163
31164
|
}).strict().optional();
|
|
31164
31165
|
function pushEncodingArgs(args, options) {
|
|
@@ -31182,6 +31183,9 @@ function buildArgs(inputPath, options) {
|
|
|
31182
31183
|
if (options?.namespace === true) {
|
|
31183
31184
|
args.push("+Xn");
|
|
31184
31185
|
}
|
|
31186
|
+
if (options?.charsetAssume !== void 0) {
|
|
31187
|
+
args.push("+Ca", options.charsetAssume);
|
|
31188
|
+
}
|
|
31185
31189
|
pushEncodingArgs(args, options);
|
|
31186
31190
|
args.push(inputPath);
|
|
31187
31191
|
return args;
|
|
@@ -31431,6 +31435,7 @@ var Dcm2jsonOptionsSchema = z.object({
|
|
|
31431
31435
|
timeoutMs: z.number().int().positive().optional(),
|
|
31432
31436
|
signal: z.instanceof(AbortSignal).optional(),
|
|
31433
31437
|
directOnly: z.boolean().optional(),
|
|
31438
|
+
charsetAssume: z.string().min(1).optional(),
|
|
31434
31439
|
verbosity: z.enum(["verbose", "debug"]).optional()
|
|
31435
31440
|
}).strict().optional();
|
|
31436
31441
|
var VERBOSITY_FLAGS2 = { verbose: "-v", debug: "-d" };
|
|
@@ -31440,12 +31445,19 @@ function buildVerbosityArgs(verbosity) {
|
|
|
31440
31445
|
}
|
|
31441
31446
|
return [];
|
|
31442
31447
|
}
|
|
31443
|
-
|
|
31448
|
+
function buildXmlOpts(options) {
|
|
31449
|
+
const result = {};
|
|
31450
|
+
if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
|
|
31451
|
+
if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
|
|
31452
|
+
return result;
|
|
31453
|
+
}
|
|
31454
|
+
async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
|
|
31444
31455
|
const xmlBinary = resolveBinary("dcm2xml");
|
|
31445
31456
|
if (!xmlBinary.ok) {
|
|
31446
31457
|
return err(xmlBinary.error);
|
|
31447
31458
|
}
|
|
31448
|
-
const
|
|
31459
|
+
const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
|
|
31460
|
+
const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
|
|
31449
31461
|
const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
|
|
31450
31462
|
if (!xmlResult.ok) {
|
|
31451
31463
|
return err(xmlResult.error);
|
|
@@ -31491,7 +31503,7 @@ async function dcm2json(inputPath, options) {
|
|
|
31491
31503
|
if (options?.directOnly === true) {
|
|
31492
31504
|
return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
|
|
31493
31505
|
}
|
|
31494
|
-
const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal,
|
|
31506
|
+
const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal, buildXmlOpts(options));
|
|
31495
31507
|
if (xmlResult.ok) {
|
|
31496
31508
|
return xmlResult;
|
|
31497
31509
|
}
|
|
@@ -33106,6 +33118,30 @@ async function echoscu(options) {
|
|
|
33106
33118
|
return ok({ success: true, stderr: result.value.stderr });
|
|
33107
33119
|
}
|
|
33108
33120
|
var VERBOSITY_FLAGS19 = { verbose: "-v", debug: "-d" };
|
|
33121
|
+
var DECOMPRESS_FLAGS = {
|
|
33122
|
+
never: "--decompress-never",
|
|
33123
|
+
lossless: "--decompress-lossless",
|
|
33124
|
+
lossy: "--decompress-lossy"
|
|
33125
|
+
};
|
|
33126
|
+
function pushSendModeArgs(args, options) {
|
|
33127
|
+
if (options.noHalt === true) {
|
|
33128
|
+
args.push("--no-halt");
|
|
33129
|
+
}
|
|
33130
|
+
if (options.noIllegalProposal === true) {
|
|
33131
|
+
args.push("--no-illegal-proposal");
|
|
33132
|
+
}
|
|
33133
|
+
if (options.decompress !== void 0) {
|
|
33134
|
+
args.push(DECOMPRESS_FLAGS[options.decompress]);
|
|
33135
|
+
}
|
|
33136
|
+
if (options.multiAssociations === true) {
|
|
33137
|
+
args.push("+ma");
|
|
33138
|
+
} else if (options.multiAssociations === false) {
|
|
33139
|
+
args.push("-ma");
|
|
33140
|
+
}
|
|
33141
|
+
if (options.createReportFile !== void 0) {
|
|
33142
|
+
args.push("--create-report-file", options.createReportFile);
|
|
33143
|
+
}
|
|
33144
|
+
}
|
|
33109
33145
|
var DcmsendOptionsSchema = z.object({
|
|
33110
33146
|
timeoutMs: z.number().int().positive().optional(),
|
|
33111
33147
|
signal: z.instanceof(AbortSignal).optional(),
|
|
@@ -33115,8 +33151,15 @@ var DcmsendOptionsSchema = z.object({
|
|
|
33115
33151
|
callingAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33116
33152
|
calledAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33117
33153
|
scanDirectory: z.boolean().optional(),
|
|
33154
|
+
recurse: z.boolean().optional(),
|
|
33155
|
+
scanPattern: z.string().min(1).optional(),
|
|
33118
33156
|
verbosity: z.enum(["verbose", "debug"]).optional(),
|
|
33119
33157
|
noUidChecks: z.boolean().optional(),
|
|
33158
|
+
noHalt: z.boolean().optional(),
|
|
33159
|
+
noIllegalProposal: z.boolean().optional(),
|
|
33160
|
+
decompress: z.enum(["never", "lossless", "lossy"]).optional(),
|
|
33161
|
+
multiAssociations: z.boolean().optional(),
|
|
33162
|
+
createReportFile: z.string().min(1).optional(),
|
|
33120
33163
|
maxPduReceive: z.number().int().min(4096).max(131072).optional(),
|
|
33121
33164
|
maxPduSend: z.number().int().min(4096).max(131072).optional(),
|
|
33122
33165
|
noHostnameLookup: z.boolean().optional(),
|
|
@@ -33162,6 +33205,13 @@ function buildArgs33(options) {
|
|
|
33162
33205
|
if (options.scanDirectory === true) {
|
|
33163
33206
|
args.push("--scan-directories");
|
|
33164
33207
|
}
|
|
33208
|
+
if (options.recurse === true) {
|
|
33209
|
+
args.push("+r");
|
|
33210
|
+
}
|
|
33211
|
+
if (options.scanPattern !== void 0) {
|
|
33212
|
+
args.push("--scan-pattern", options.scanPattern);
|
|
33213
|
+
}
|
|
33214
|
+
pushSendModeArgs(args, options);
|
|
33165
33215
|
args.push(options.host, String(options.port));
|
|
33166
33216
|
args.push(...options.files);
|
|
33167
33217
|
return args;
|
|
@@ -34288,7 +34338,7 @@ var DicomInstance = class _DicomInstance {
|
|
|
34288
34338
|
* Opens a DICOM file and creates a DicomInstance.
|
|
34289
34339
|
*
|
|
34290
34340
|
* @param path - Filesystem path to the DICOM file
|
|
34291
|
-
* @param options - Timeout and
|
|
34341
|
+
* @param options - Timeout, abort, and charset options
|
|
34292
34342
|
* @returns A Result containing the DicomInstance or an error
|
|
34293
34343
|
*/
|
|
34294
34344
|
static async open(path2, options) {
|
|
@@ -34296,7 +34346,8 @@ var DicomInstance = class _DicomInstance {
|
|
|
34296
34346
|
if (!filePathResult.ok) return err(filePathResult.error);
|
|
34297
34347
|
const jsonResult = await dcm2json(path2, {
|
|
34298
34348
|
timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
34299
|
-
signal: options?.signal
|
|
34349
|
+
signal: options?.signal,
|
|
34350
|
+
charsetAssume: options?.charsetAssume
|
|
34300
34351
|
});
|
|
34301
34352
|
if (!jsonResult.ok) return err(jsonResult.error);
|
|
34302
34353
|
const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
|
|
@@ -36852,68 +36903,13 @@ var SenderHealth = {
|
|
|
36852
36903
|
DOWN: "down"
|
|
36853
36904
|
};
|
|
36854
36905
|
|
|
36855
|
-
// src/senders/
|
|
36856
|
-
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
36857
|
-
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
36858
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
36859
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
36860
|
-
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
36861
|
-
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
36862
|
-
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
36906
|
+
// src/senders/SenderEngine.ts
|
|
36863
36907
|
var DEGRADE_THRESHOLD = 3;
|
|
36864
36908
|
var DOWN_THRESHOLD = 10;
|
|
36865
36909
|
var RECOVERY_THRESHOLD = 3;
|
|
36866
|
-
var
|
|
36867
|
-
|
|
36868
|
-
|
|
36869
|
-
calledAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36870
|
-
callingAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36871
|
-
mode: z.enum(["single", "multiple", "bucket"]).optional(),
|
|
36872
|
-
maxAssociations: z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
36873
|
-
proposedTransferSyntax: z.union([z.enum(PROPOSED_TS_VALUES), z.array(z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
36874
|
-
combineProposedTransferSyntaxes: z.boolean().optional(),
|
|
36875
|
-
maxQueueLength: z.number().int().min(1).optional(),
|
|
36876
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
36877
|
-
maxRetries: z.number().int().min(0).optional(),
|
|
36878
|
-
retryDelayMs: z.number().int().min(0).optional(),
|
|
36879
|
-
bucketFlushMs: z.number().int().positive().optional(),
|
|
36880
|
-
maxBucketSize: z.number().int().min(1).optional(),
|
|
36881
|
-
maxPduReceive: z.number().int().min(4096).max(131072).optional(),
|
|
36882
|
-
maxPduSend: z.number().int().min(4096).max(131072).optional(),
|
|
36883
|
-
associationTimeout: z.number().int().positive().optional(),
|
|
36884
|
-
acseTimeout: z.number().int().positive().optional(),
|
|
36885
|
-
dimseTimeout: z.number().int().positive().optional(),
|
|
36886
|
-
noHostnameLookup: z.boolean().optional(),
|
|
36887
|
-
verbosity: z.enum(["verbose", "debug"]).optional(),
|
|
36888
|
-
required: z.boolean().optional(),
|
|
36889
|
-
signal: z.instanceof(AbortSignal).optional()
|
|
36890
|
-
}).strict();
|
|
36891
|
-
function resolveConfig(options) {
|
|
36892
|
-
const mode = options.mode ?? "multiple";
|
|
36893
|
-
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
36894
|
-
return {
|
|
36895
|
-
mode,
|
|
36896
|
-
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
36897
|
-
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
36898
|
-
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
36899
|
-
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
36900
|
-
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
36901
|
-
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
36902
|
-
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
36903
|
-
};
|
|
36904
|
-
}
|
|
36905
|
-
var DicomSender = class _DicomSender extends EventEmitter {
|
|
36906
|
-
constructor(options) {
|
|
36907
|
-
super();
|
|
36908
|
-
__publicField(this, "options");
|
|
36909
|
-
__publicField(this, "mode");
|
|
36910
|
-
__publicField(this, "configuredMaxAssociations");
|
|
36911
|
-
__publicField(this, "maxQueueLength");
|
|
36912
|
-
__publicField(this, "defaultTimeoutMs");
|
|
36913
|
-
__publicField(this, "defaultMaxRetries");
|
|
36914
|
-
__publicField(this, "retryDelayMs");
|
|
36915
|
-
__publicField(this, "bucketFlushMs");
|
|
36916
|
-
__publicField(this, "maxBucketSize");
|
|
36910
|
+
var SenderEngine = class {
|
|
36911
|
+
constructor(config) {
|
|
36912
|
+
__publicField(this, "config");
|
|
36917
36913
|
// Queue and concurrency state
|
|
36918
36914
|
__publicField(this, "queue", []);
|
|
36919
36915
|
__publicField(this, "activeAssociations", 0);
|
|
@@ -36926,113 +36922,36 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
36926
36922
|
// Bucket state (bucket mode only)
|
|
36927
36923
|
__publicField(this, "currentBucket", []);
|
|
36928
36924
|
__publicField(this, "bucketTimer");
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
this.setMaxListeners(20);
|
|
36932
|
-
this.on("error", () => {
|
|
36933
|
-
});
|
|
36934
|
-
this.options = options;
|
|
36935
|
-
const cfg = resolveConfig(options);
|
|
36936
|
-
this.mode = cfg.mode;
|
|
36937
|
-
this.configuredMaxAssociations = cfg.configuredMaxAssociations;
|
|
36938
|
-
this.effectiveMaxAssociations = cfg.configuredMaxAssociations;
|
|
36939
|
-
this.maxQueueLength = cfg.maxQueueLength;
|
|
36940
|
-
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
36941
|
-
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
36942
|
-
this.retryDelayMs = cfg.retryDelayMs;
|
|
36943
|
-
this.bucketFlushMs = cfg.bucketFlushMs;
|
|
36944
|
-
this.maxBucketSize = cfg.maxBucketSize;
|
|
36945
|
-
if (options.signal !== void 0) {
|
|
36946
|
-
this.wireAbortSignal(options.signal);
|
|
36947
|
-
}
|
|
36925
|
+
this.config = config;
|
|
36926
|
+
this.effectiveMaxAssociations = config.configuredMaxAssociations;
|
|
36948
36927
|
}
|
|
36949
36928
|
// -----------------------------------------------------------------------
|
|
36950
36929
|
// Public API
|
|
36951
36930
|
// -----------------------------------------------------------------------
|
|
36952
|
-
/**
|
|
36953
|
-
|
|
36954
|
-
*
|
|
36955
|
-
* @param options - Configuration options
|
|
36956
|
-
* @returns A Result containing the instance or a validation error
|
|
36957
|
-
*/
|
|
36958
|
-
static create(options) {
|
|
36959
|
-
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
36960
|
-
if (!validation.success) {
|
|
36961
|
-
return err(createValidationError("DicomSender", validation.error));
|
|
36962
|
-
}
|
|
36963
|
-
return ok(new _DicomSender(options));
|
|
36964
|
-
}
|
|
36965
|
-
/**
|
|
36966
|
-
* Sends one or more DICOM files to the remote endpoint.
|
|
36967
|
-
*
|
|
36968
|
-
* In single/multiple mode, files are sent as one storescu call.
|
|
36969
|
-
* In bucket mode, files are accumulated into a bucket and flushed
|
|
36970
|
-
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
36971
|
-
*
|
|
36972
|
-
* The returned promise resolves when the files are actually sent
|
|
36973
|
-
* (not just queued). Callers can await for confirmation or
|
|
36974
|
-
* fire-and-forget with `void sender.send(files)`.
|
|
36975
|
-
*
|
|
36976
|
-
* @param files - One or more DICOM file paths
|
|
36977
|
-
* @param options - Per-send overrides
|
|
36978
|
-
* @returns A Result containing the send result or an error
|
|
36979
|
-
*/
|
|
36980
|
-
send(files, options) {
|
|
36931
|
+
/** Sends files through the engine's queue/bucket system. */
|
|
36932
|
+
send(files, timeoutMs, maxRetries, binaryParams) {
|
|
36981
36933
|
if (this.isStopped) {
|
|
36982
|
-
return Promise.resolve(err(new Error(
|
|
36934
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: sender is stopped`)));
|
|
36983
36935
|
}
|
|
36984
36936
|
if (files.length === 0) {
|
|
36985
|
-
return Promise.resolve(err(new Error(
|
|
36986
|
-
}
|
|
36987
|
-
return this.dispatchSend(files, this.buildSendParams(options));
|
|
36988
|
-
}
|
|
36989
|
-
/** Builds SendParams from per-send options, applying instance defaults. */
|
|
36990
|
-
buildSendParams(options) {
|
|
36991
|
-
return {
|
|
36992
|
-
timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
|
|
36993
|
-
maxRetries: options?.maxRetries ?? this.defaultMaxRetries,
|
|
36994
|
-
calledAETitle: options?.calledAETitle,
|
|
36995
|
-
callingAETitle: options?.callingAETitle,
|
|
36996
|
-
required: options?.required,
|
|
36997
|
-
proposedTransferSyntax: options?.proposedTransferSyntax,
|
|
36998
|
-
combineProposedTransferSyntaxes: options?.combineProposedTransferSyntaxes
|
|
36999
|
-
};
|
|
37000
|
-
}
|
|
37001
|
-
/** Dispatches a send to the appropriate mode handler. */
|
|
37002
|
-
dispatchSend(files, params) {
|
|
37003
|
-
switch (this.mode) {
|
|
37004
|
-
case "single":
|
|
37005
|
-
case "multiple":
|
|
37006
|
-
return this.enqueueSend(files, params);
|
|
37007
|
-
case "bucket":
|
|
37008
|
-
return this.enqueueBucket(files, params);
|
|
37009
|
-
default:
|
|
37010
|
-
assertUnreachable(this.mode);
|
|
36937
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: no files provided`)));
|
|
37011
36938
|
}
|
|
36939
|
+
return this.dispatchSend(files, timeoutMs, maxRetries, binaryParams);
|
|
37012
36940
|
}
|
|
37013
|
-
/**
|
|
37014
|
-
* Flushes the current bucket immediately (bucket mode only).
|
|
37015
|
-
* In single/multiple mode this is a no-op.
|
|
37016
|
-
*/
|
|
36941
|
+
/** Flushes the current bucket immediately (bucket mode only). */
|
|
37017
36942
|
flush() {
|
|
37018
|
-
if (this.mode !== "bucket") return;
|
|
36943
|
+
if (this.config.mode !== "bucket") return;
|
|
37019
36944
|
if (this.currentBucket.length === 0) return;
|
|
37020
36945
|
this.clearBucketTimer();
|
|
37021
36946
|
this.flushBucketInternal("timer");
|
|
37022
36947
|
}
|
|
37023
|
-
/**
|
|
37024
|
-
* Gracefully stops the sender. Rejects all queued items and
|
|
37025
|
-
* waits for active associations to complete.
|
|
37026
|
-
*/
|
|
36948
|
+
/** Gracefully stops the engine. Rejects all queued items and waits for active associations. */
|
|
37027
36949
|
async stop() {
|
|
37028
36950
|
if (this.isStopped) return;
|
|
37029
36951
|
this.isStopped = true;
|
|
37030
|
-
if (this.options.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37031
|
-
this.options.signal.removeEventListener("abort", this.abortHandler);
|
|
37032
|
-
}
|
|
37033
36952
|
this.clearBucketTimer();
|
|
37034
|
-
this.rejectBucket(
|
|
37035
|
-
this.rejectQueue(
|
|
36953
|
+
this.rejectBucket(`${this.config.senderName}: sender stopped`);
|
|
36954
|
+
this.rejectQueue(`${this.config.senderName}: sender stopped`);
|
|
37036
36955
|
await this.waitForActive();
|
|
37037
36956
|
}
|
|
37038
36957
|
/** Current sender status. */
|
|
@@ -37047,77 +36966,37 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37047
36966
|
stopped: this.isStopped
|
|
37048
36967
|
};
|
|
37049
36968
|
}
|
|
36969
|
+
/** Whether the engine has been stopped. */
|
|
36970
|
+
get stopped() {
|
|
36971
|
+
return this.isStopped;
|
|
36972
|
+
}
|
|
37050
36973
|
// -----------------------------------------------------------------------
|
|
37051
|
-
//
|
|
36974
|
+
// Dispatch
|
|
37052
36975
|
// -----------------------------------------------------------------------
|
|
37053
|
-
/**
|
|
37054
|
-
|
|
37055
|
-
|
|
37056
|
-
|
|
37057
|
-
|
|
37058
|
-
|
|
37059
|
-
|
|
37060
|
-
|
|
37061
|
-
|
|
37062
|
-
|
|
37063
|
-
|
|
37064
|
-
* Registers a listener for successful sends.
|
|
37065
|
-
*
|
|
37066
|
-
* @param listener - Callback receiving send complete data
|
|
37067
|
-
* @returns this for chaining
|
|
37068
|
-
*/
|
|
37069
|
-
onSendComplete(listener) {
|
|
37070
|
-
return this.on("SEND_COMPLETE", listener);
|
|
37071
|
-
}
|
|
37072
|
-
/**
|
|
37073
|
-
* Registers a listener for failed sends.
|
|
37074
|
-
*
|
|
37075
|
-
* @param listener - Callback receiving send failed data
|
|
37076
|
-
* @returns this for chaining
|
|
37077
|
-
*/
|
|
37078
|
-
onSendFailed(listener) {
|
|
37079
|
-
return this.on("SEND_FAILED", listener);
|
|
37080
|
-
}
|
|
37081
|
-
/**
|
|
37082
|
-
* Registers a listener for health state changes.
|
|
37083
|
-
*
|
|
37084
|
-
* @param listener - Callback receiving health change data
|
|
37085
|
-
* @returns this for chaining
|
|
37086
|
-
*/
|
|
37087
|
-
onHealthChanged(listener) {
|
|
37088
|
-
return this.on("HEALTH_CHANGED", listener);
|
|
37089
|
-
}
|
|
37090
|
-
/**
|
|
37091
|
-
* Registers a listener for bucket flushes (bucket mode only).
|
|
37092
|
-
*
|
|
37093
|
-
* @param listener - Callback receiving bucket flush data
|
|
37094
|
-
* @returns this for chaining
|
|
37095
|
-
*/
|
|
37096
|
-
onBucketFlushed(listener) {
|
|
37097
|
-
return this.on("BUCKET_FLUSHED", listener);
|
|
36976
|
+
/** Dispatches a send to the appropriate mode handler. */
|
|
36977
|
+
dispatchSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
36978
|
+
switch (this.config.mode) {
|
|
36979
|
+
case "single":
|
|
36980
|
+
case "multiple":
|
|
36981
|
+
return this.enqueueSend(files, timeoutMs, maxRetries, binaryParams);
|
|
36982
|
+
case "bucket":
|
|
36983
|
+
return this.enqueueBucket(files, timeoutMs, maxRetries, binaryParams);
|
|
36984
|
+
default:
|
|
36985
|
+
assertUnreachable(this.config.mode);
|
|
36986
|
+
}
|
|
37098
36987
|
}
|
|
37099
36988
|
// -----------------------------------------------------------------------
|
|
37100
36989
|
// Single/Multiple mode: queue-based dispatch
|
|
37101
36990
|
// -----------------------------------------------------------------------
|
|
37102
36991
|
/** Enqueues a send and dispatches immediately if capacity allows. */
|
|
37103
|
-
enqueueSend(files,
|
|
36992
|
+
enqueueSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
37104
36993
|
return new Promise((resolve) => {
|
|
37105
36994
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37106
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37107
|
-
resolve(err(new Error(
|
|
36995
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
36996
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37108
36997
|
return;
|
|
37109
36998
|
}
|
|
37110
|
-
const entry = {
|
|
37111
|
-
files,
|
|
37112
|
-
timeoutMs: params.timeoutMs,
|
|
37113
|
-
maxRetries: params.maxRetries,
|
|
37114
|
-
calledAETitle: params.calledAETitle,
|
|
37115
|
-
callingAETitle: params.callingAETitle,
|
|
37116
|
-
required: params.required,
|
|
37117
|
-
proposedTransferSyntax: params.proposedTransferSyntax,
|
|
37118
|
-
combineProposedTransferSyntaxes: params.combineProposedTransferSyntaxes,
|
|
37119
|
-
resolve
|
|
37120
|
-
};
|
|
36999
|
+
const entry = { files, timeoutMs, maxRetries, binaryParams, resolve };
|
|
37121
37000
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37122
37001
|
void this.executeEntry(entry);
|
|
37123
37002
|
} else {
|
|
@@ -37137,27 +37016,16 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37137
37016
|
// Bucket mode: accumulate-then-flush
|
|
37138
37017
|
// -----------------------------------------------------------------------
|
|
37139
37018
|
/** Adds files to the current bucket and triggers flush if full. */
|
|
37140
|
-
enqueueBucket(files,
|
|
37019
|
+
enqueueBucket(files, timeoutMs, maxRetries, binaryParams) {
|
|
37141
37020
|
return new Promise((resolve) => {
|
|
37142
37021
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37143
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37144
|
-
resolve(err(new Error(
|
|
37022
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
37023
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37145
37024
|
return;
|
|
37146
37025
|
}
|
|
37147
|
-
|
|
37148
|
-
this.currentBucket.push({
|
|
37149
|
-
files,
|
|
37150
|
-
resolve,
|
|
37151
|
-
timeoutMs,
|
|
37152
|
-
maxRetries,
|
|
37153
|
-
calledAETitle,
|
|
37154
|
-
callingAETitle,
|
|
37155
|
-
required,
|
|
37156
|
-
proposedTransferSyntax,
|
|
37157
|
-
combineProposedTransferSyntaxes
|
|
37158
|
-
});
|
|
37026
|
+
this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, binaryParams });
|
|
37159
37027
|
const totalFiles = this.countBucketFiles();
|
|
37160
|
-
if (totalFiles >= this.maxBucketSize) {
|
|
37028
|
+
if (totalFiles >= this.config.maxBucketSize) {
|
|
37161
37029
|
this.clearBucketTimer();
|
|
37162
37030
|
void this.flushBucketInternal("maxSize");
|
|
37163
37031
|
} else {
|
|
@@ -37189,11 +37057,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37189
37057
|
files: allFiles,
|
|
37190
37058
|
timeoutMs,
|
|
37191
37059
|
maxRetries,
|
|
37192
|
-
|
|
37193
|
-
callingAETitle: entries[0]?.callingAETitle,
|
|
37194
|
-
required: entries[0]?.required,
|
|
37195
|
-
proposedTransferSyntax: entries[0]?.proposedTransferSyntax,
|
|
37196
|
-
combineProposedTransferSyntaxes: entries[0]?.combineProposedTransferSyntaxes,
|
|
37060
|
+
binaryParams: entries[0].binaryParams,
|
|
37197
37061
|
resolve: (result) => {
|
|
37198
37062
|
for (let i = 0; i < entries.length; i++) {
|
|
37199
37063
|
entries[i].resolve(result);
|
|
@@ -37207,7 +37071,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37207
37071
|
const entries = [...this.currentBucket];
|
|
37208
37072
|
this.currentBucket = [];
|
|
37209
37073
|
const bucketEntry = this.mergeBucketEntries(entries);
|
|
37210
|
-
this.
|
|
37074
|
+
this.config.emitters.emitBucketFlushed({ fileCount: bucketEntry.files.length, reason });
|
|
37211
37075
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37212
37076
|
void this.executeEntry(bucketEntry);
|
|
37213
37077
|
} else {
|
|
@@ -37220,7 +37084,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37220
37084
|
this.bucketTimer = setTimeout(() => {
|
|
37221
37085
|
this.bucketTimer = void 0;
|
|
37222
37086
|
void this.flushBucketInternal("timer");
|
|
37223
|
-
}, this.bucketFlushMs);
|
|
37087
|
+
}, this.config.bucketFlushMs);
|
|
37224
37088
|
}
|
|
37225
37089
|
/** Clears the bucket flush timer. */
|
|
37226
37090
|
clearBucketTimer() {
|
|
@@ -37232,7 +37096,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37232
37096
|
// -----------------------------------------------------------------------
|
|
37233
37097
|
// Core send execution with retry
|
|
37234
37098
|
// -----------------------------------------------------------------------
|
|
37235
|
-
/** Executes a single queue entry: calls
|
|
37099
|
+
/** Executes a single queue entry: calls the binary with retry. */
|
|
37236
37100
|
async executeEntry(entry) {
|
|
37237
37101
|
this.activeAssociations++;
|
|
37238
37102
|
const startMs = Date.now();
|
|
@@ -37241,7 +37105,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37241
37105
|
if (failure === void 0) return;
|
|
37242
37106
|
this.activeAssociations--;
|
|
37243
37107
|
this.recordFailure();
|
|
37244
|
-
this.
|
|
37108
|
+
this.config.emitters.emitSendFailed({
|
|
37245
37109
|
files: entry.files,
|
|
37246
37110
|
error: failure.error,
|
|
37247
37111
|
attempts: maxAttempts,
|
|
@@ -37251,49 +37115,27 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37251
37115
|
entry.resolve(err(failure.error));
|
|
37252
37116
|
this.drainQueue();
|
|
37253
37117
|
}
|
|
37254
|
-
/** Attempts
|
|
37118
|
+
/** Attempts the binary call up to maxAttempts times. Returns undefined on success. */
|
|
37255
37119
|
async attemptSend(entry, maxAttempts, startMs) {
|
|
37256
37120
|
let lastError;
|
|
37257
37121
|
const lastOutput = { stdout: "", stderr: "" };
|
|
37258
37122
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
37259
37123
|
if (this.isStopped) {
|
|
37260
37124
|
this.activeAssociations--;
|
|
37261
|
-
entry.resolve(err(new Error(
|
|
37125
|
+
entry.resolve(err(new Error(`${this.config.senderName}: sender stopped`)));
|
|
37262
37126
|
return void 0;
|
|
37263
37127
|
}
|
|
37264
|
-
const result = await this.
|
|
37128
|
+
const result = await this.config.executor(entry.files, entry.timeoutMs, entry.binaryParams, this.config.signal);
|
|
37265
37129
|
if (result.ok) {
|
|
37266
37130
|
this.handleSendSuccess(entry, startMs, result.value);
|
|
37267
37131
|
return void 0;
|
|
37268
37132
|
}
|
|
37269
37133
|
lastError = result.error;
|
|
37270
37134
|
if (attempt < maxAttempts - 1) {
|
|
37271
|
-
await delay2(this.retryDelayMs * (attempt + 1));
|
|
37135
|
+
await delay2(this.config.retryDelayMs * (attempt + 1));
|
|
37272
37136
|
}
|
|
37273
37137
|
}
|
|
37274
|
-
return { error: lastError ?? new Error(
|
|
37275
|
-
}
|
|
37276
|
-
/** Calls storescu with the configured options. */
|
|
37277
|
-
callStorescu(entry) {
|
|
37278
|
-
return storescu({
|
|
37279
|
-
host: this.options.host,
|
|
37280
|
-
port: this.options.port,
|
|
37281
|
-
files: [...entry.files],
|
|
37282
|
-
calledAETitle: entry.calledAETitle ?? this.options.calledAETitle,
|
|
37283
|
-
callingAETitle: entry.callingAETitle ?? this.options.callingAETitle,
|
|
37284
|
-
proposedTransferSyntax: entry.proposedTransferSyntax ?? this.options.proposedTransferSyntax,
|
|
37285
|
-
combineProposedTransferSyntaxes: entry.combineProposedTransferSyntaxes ?? this.options.combineProposedTransferSyntaxes,
|
|
37286
|
-
maxPduReceive: this.options.maxPduReceive,
|
|
37287
|
-
maxPduSend: this.options.maxPduSend,
|
|
37288
|
-
associationTimeout: this.options.associationTimeout,
|
|
37289
|
-
acseTimeout: this.options.acseTimeout,
|
|
37290
|
-
dimseTimeout: this.options.dimseTimeout,
|
|
37291
|
-
noHostnameLookup: this.options.noHostnameLookup,
|
|
37292
|
-
verbosity: this.options.verbosity,
|
|
37293
|
-
required: entry.required ?? this.options.required,
|
|
37294
|
-
timeoutMs: entry.timeoutMs,
|
|
37295
|
-
signal: this.options.signal
|
|
37296
|
-
});
|
|
37138
|
+
return { error: lastError ?? new Error(`${this.config.senderName}: send failed`), output: lastOutput };
|
|
37297
37139
|
}
|
|
37298
37140
|
/** Handles a successful send: updates state, emits event, resolves promise. */
|
|
37299
37141
|
handleSendSuccess(entry, startMs, output) {
|
|
@@ -37301,7 +37143,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37301
37143
|
const durationMs = Date.now() - startMs;
|
|
37302
37144
|
this.recordSuccess();
|
|
37303
37145
|
const data = { files: entry.files, fileCount: entry.files.length, durationMs, stdout: output.stdout, stderr: output.stderr };
|
|
37304
|
-
this.
|
|
37146
|
+
this.config.emitters.emitSendComplete(data);
|
|
37305
37147
|
entry.resolve(ok(data));
|
|
37306
37148
|
this.drainQueue();
|
|
37307
37149
|
}
|
|
@@ -37319,8 +37161,8 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37319
37161
|
if (this.health === SenderHealth.DOWN) {
|
|
37320
37162
|
this.health = SenderHealth.DEGRADED;
|
|
37321
37163
|
} else {
|
|
37322
|
-
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.configuredMaxAssociations);
|
|
37323
|
-
if (this.effectiveMaxAssociations >= this.configuredMaxAssociations) {
|
|
37164
|
+
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.config.configuredMaxAssociations);
|
|
37165
|
+
if (this.effectiveMaxAssociations >= this.config.configuredMaxAssociations) {
|
|
37324
37166
|
this.health = SenderHealth.HEALTHY;
|
|
37325
37167
|
}
|
|
37326
37168
|
}
|
|
@@ -37340,22 +37182,26 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37340
37182
|
this.emitHealthChanged(previousHealth);
|
|
37341
37183
|
}
|
|
37342
37184
|
} else if (this.consecutiveFailures >= DEGRADE_THRESHOLD && this.consecutiveFailures % DEGRADE_THRESHOLD === 0) {
|
|
37343
|
-
|
|
37344
|
-
|
|
37345
|
-
|
|
37185
|
+
this.degradeHealth(previousHealth);
|
|
37186
|
+
}
|
|
37187
|
+
}
|
|
37188
|
+
/** Handles HEALTHY→DEGRADED or DEGRADED→DEGRADED transitions. */
|
|
37189
|
+
degradeHealth(previousHealth) {
|
|
37190
|
+
if (this.health === SenderHealth.HEALTHY) {
|
|
37191
|
+
this.health = SenderHealth.DEGRADED;
|
|
37192
|
+
this.effectiveMaxAssociations = Math.max(1, Math.floor(this.config.configuredMaxAssociations / 2));
|
|
37193
|
+
this.emitHealthChanged(previousHealth);
|
|
37194
|
+
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37195
|
+
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37196
|
+
if (newMax !== this.effectiveMaxAssociations) {
|
|
37197
|
+
this.effectiveMaxAssociations = newMax;
|
|
37346
37198
|
this.emitHealthChanged(previousHealth);
|
|
37347
|
-
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37348
|
-
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37349
|
-
if (newMax !== this.effectiveMaxAssociations) {
|
|
37350
|
-
this.effectiveMaxAssociations = newMax;
|
|
37351
|
-
this.emitHealthChanged(previousHealth);
|
|
37352
|
-
}
|
|
37353
37199
|
}
|
|
37354
37200
|
}
|
|
37355
37201
|
}
|
|
37356
|
-
/** Emits a HEALTH_CHANGED event. */
|
|
37202
|
+
/** Emits a HEALTH_CHANGED event via the wrapper's emitter. */
|
|
37357
37203
|
emitHealthChanged(previousHealth) {
|
|
37358
|
-
this.
|
|
37204
|
+
this.config.emitters.emitHealthChanged({
|
|
37359
37205
|
previousHealth,
|
|
37360
37206
|
newHealth: this.health,
|
|
37361
37207
|
effectiveMaxAssociations: this.effectiveMaxAssociations,
|
|
@@ -37395,20 +37241,6 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37395
37241
|
check();
|
|
37396
37242
|
});
|
|
37397
37243
|
}
|
|
37398
|
-
// -----------------------------------------------------------------------
|
|
37399
|
-
// Abort signal
|
|
37400
|
-
// -----------------------------------------------------------------------
|
|
37401
|
-
/** Wires an AbortSignal to stop the sender. */
|
|
37402
|
-
wireAbortSignal(signal) {
|
|
37403
|
-
if (signal.aborted) {
|
|
37404
|
-
void this.stop();
|
|
37405
|
-
return;
|
|
37406
|
-
}
|
|
37407
|
-
this.abortHandler = () => {
|
|
37408
|
-
void this.stop();
|
|
37409
|
-
};
|
|
37410
|
-
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37411
|
-
}
|
|
37412
37244
|
};
|
|
37413
37245
|
function delay2(ms) {
|
|
37414
37246
|
return new Promise((resolve) => {
|
|
@@ -37416,6 +37248,492 @@ function delay2(ms) {
|
|
|
37416
37248
|
});
|
|
37417
37249
|
}
|
|
37418
37250
|
|
|
37251
|
+
// src/senders/DicomSender.ts
|
|
37252
|
+
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
37253
|
+
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
37254
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
37255
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
37256
|
+
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
37257
|
+
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
37258
|
+
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
37259
|
+
var DicomSenderOptionsSchema = z.object({
|
|
37260
|
+
host: z.string().min(1),
|
|
37261
|
+
port: z.number().int().min(1).max(65535),
|
|
37262
|
+
calledAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37263
|
+
callingAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37264
|
+
mode: z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37265
|
+
maxAssociations: z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
37266
|
+
proposedTransferSyntax: z.union([z.enum(PROPOSED_TS_VALUES), z.array(z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
37267
|
+
combineProposedTransferSyntaxes: z.boolean().optional(),
|
|
37268
|
+
maxQueueLength: z.number().int().min(1).optional(),
|
|
37269
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
37270
|
+
maxRetries: z.number().int().min(0).optional(),
|
|
37271
|
+
retryDelayMs: z.number().int().min(0).optional(),
|
|
37272
|
+
bucketFlushMs: z.number().int().positive().optional(),
|
|
37273
|
+
maxBucketSize: z.number().int().min(1).optional(),
|
|
37274
|
+
maxPduReceive: z.number().int().min(4096).max(131072).optional(),
|
|
37275
|
+
maxPduSend: z.number().int().min(4096).max(131072).optional(),
|
|
37276
|
+
associationTimeout: z.number().int().positive().optional(),
|
|
37277
|
+
acseTimeout: z.number().int().positive().optional(),
|
|
37278
|
+
dimseTimeout: z.number().int().positive().optional(),
|
|
37279
|
+
noHostnameLookup: z.boolean().optional(),
|
|
37280
|
+
verbosity: z.enum(["verbose", "debug"]).optional(),
|
|
37281
|
+
required: z.boolean().optional(),
|
|
37282
|
+
signal: z.instanceof(AbortSignal).optional()
|
|
37283
|
+
}).strict();
|
|
37284
|
+
function createStorescuExecutor(options) {
|
|
37285
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37286
|
+
const result = await storescu({
|
|
37287
|
+
host: options.host,
|
|
37288
|
+
port: options.port,
|
|
37289
|
+
files: [...files],
|
|
37290
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37291
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37292
|
+
proposedTransferSyntax: params.proposedTransferSyntax ?? options.proposedTransferSyntax,
|
|
37293
|
+
combineProposedTransferSyntaxes: params.combineProposedTransferSyntaxes ?? options.combineProposedTransferSyntaxes,
|
|
37294
|
+
maxPduReceive: options.maxPduReceive,
|
|
37295
|
+
maxPduSend: options.maxPduSend,
|
|
37296
|
+
associationTimeout: options.associationTimeout,
|
|
37297
|
+
acseTimeout: options.acseTimeout,
|
|
37298
|
+
dimseTimeout: options.dimseTimeout,
|
|
37299
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37300
|
+
verbosity: options.verbosity,
|
|
37301
|
+
required: params.required ?? options.required,
|
|
37302
|
+
timeoutMs,
|
|
37303
|
+
signal
|
|
37304
|
+
});
|
|
37305
|
+
if (!result.ok) return err(result.error);
|
|
37306
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37307
|
+
};
|
|
37308
|
+
}
|
|
37309
|
+
function resolveConfig(options) {
|
|
37310
|
+
const mode = options.mode ?? "multiple";
|
|
37311
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37312
|
+
return {
|
|
37313
|
+
mode,
|
|
37314
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37315
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
37316
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37317
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
37318
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
37319
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
37320
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
37321
|
+
};
|
|
37322
|
+
}
|
|
37323
|
+
var DicomSender = class _DicomSender extends EventEmitter {
|
|
37324
|
+
constructor(engine, cfg, signal) {
|
|
37325
|
+
super();
|
|
37326
|
+
__publicField(this, "engine");
|
|
37327
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37328
|
+
__publicField(this, "defaultMaxRetries");
|
|
37329
|
+
__publicField(this, "signal");
|
|
37330
|
+
__publicField(this, "abortHandler");
|
|
37331
|
+
this.setMaxListeners(20);
|
|
37332
|
+
this.on("error", () => {
|
|
37333
|
+
});
|
|
37334
|
+
this.engine = engine;
|
|
37335
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37336
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37337
|
+
this.signal = signal;
|
|
37338
|
+
if (signal !== void 0) {
|
|
37339
|
+
this.wireAbortSignal(signal);
|
|
37340
|
+
}
|
|
37341
|
+
}
|
|
37342
|
+
// -----------------------------------------------------------------------
|
|
37343
|
+
// Public API
|
|
37344
|
+
// -----------------------------------------------------------------------
|
|
37345
|
+
/**
|
|
37346
|
+
* Creates a new DicomSender instance.
|
|
37347
|
+
*
|
|
37348
|
+
* @param options - Configuration options
|
|
37349
|
+
* @returns A Result containing the instance or a validation error
|
|
37350
|
+
*/
|
|
37351
|
+
static create(options) {
|
|
37352
|
+
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
37353
|
+
if (!validation.success) {
|
|
37354
|
+
return err(createValidationError("DicomSender", validation.error));
|
|
37355
|
+
}
|
|
37356
|
+
const cfg = resolveConfig(options);
|
|
37357
|
+
const senderRef = { current: void 0 };
|
|
37358
|
+
const engine = new SenderEngine({
|
|
37359
|
+
...cfg,
|
|
37360
|
+
executor: createStorescuExecutor(options),
|
|
37361
|
+
signal: options.signal,
|
|
37362
|
+
senderName: "DicomSender",
|
|
37363
|
+
emitters: {
|
|
37364
|
+
emitSendComplete: (data) => {
|
|
37365
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37366
|
+
},
|
|
37367
|
+
emitSendFailed: (data) => {
|
|
37368
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37369
|
+
},
|
|
37370
|
+
emitHealthChanged: (data) => {
|
|
37371
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37372
|
+
},
|
|
37373
|
+
emitBucketFlushed: (data) => {
|
|
37374
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37375
|
+
}
|
|
37376
|
+
}
|
|
37377
|
+
});
|
|
37378
|
+
const sender = new _DicomSender(engine, cfg, options.signal);
|
|
37379
|
+
senderRef.current = sender;
|
|
37380
|
+
return ok(sender);
|
|
37381
|
+
}
|
|
37382
|
+
/**
|
|
37383
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37384
|
+
*
|
|
37385
|
+
* In single/multiple mode, files are sent as one storescu call.
|
|
37386
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37387
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37388
|
+
*
|
|
37389
|
+
* The returned promise resolves when the files are actually sent
|
|
37390
|
+
* (not just queued). Callers can await for confirmation or
|
|
37391
|
+
* fire-and-forget with `void sender.send(files)`.
|
|
37392
|
+
*
|
|
37393
|
+
* @param files - One or more DICOM file paths
|
|
37394
|
+
* @param options - Per-send overrides
|
|
37395
|
+
* @returns A Result containing the send result or an error
|
|
37396
|
+
*/
|
|
37397
|
+
send(files, options) {
|
|
37398
|
+
const params = buildStorescuParams(options);
|
|
37399
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37400
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37401
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37402
|
+
}
|
|
37403
|
+
/**
|
|
37404
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37405
|
+
* In single/multiple mode this is a no-op.
|
|
37406
|
+
*/
|
|
37407
|
+
flush() {
|
|
37408
|
+
this.engine.flush();
|
|
37409
|
+
}
|
|
37410
|
+
/**
|
|
37411
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37412
|
+
* waits for active associations to complete.
|
|
37413
|
+
*/
|
|
37414
|
+
async stop() {
|
|
37415
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37416
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37417
|
+
}
|
|
37418
|
+
await this.engine.stop();
|
|
37419
|
+
}
|
|
37420
|
+
/** Current sender status. */
|
|
37421
|
+
get status() {
|
|
37422
|
+
return this.engine.status;
|
|
37423
|
+
}
|
|
37424
|
+
// -----------------------------------------------------------------------
|
|
37425
|
+
// Typed event listener convenience methods
|
|
37426
|
+
// -----------------------------------------------------------------------
|
|
37427
|
+
/**
|
|
37428
|
+
* Registers a typed listener for a DicomSender-specific event.
|
|
37429
|
+
*
|
|
37430
|
+
* @param event - The event name from DicomSenderEventMap
|
|
37431
|
+
* @param listener - Callback receiving typed event data
|
|
37432
|
+
* @returns this for chaining
|
|
37433
|
+
*/
|
|
37434
|
+
onEvent(event, listener) {
|
|
37435
|
+
return this.on(event, listener);
|
|
37436
|
+
}
|
|
37437
|
+
/**
|
|
37438
|
+
* Registers a listener for successful sends.
|
|
37439
|
+
*
|
|
37440
|
+
* @param listener - Callback receiving send complete data
|
|
37441
|
+
* @returns this for chaining
|
|
37442
|
+
*/
|
|
37443
|
+
onSendComplete(listener) {
|
|
37444
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37445
|
+
}
|
|
37446
|
+
/**
|
|
37447
|
+
* Registers a listener for failed sends.
|
|
37448
|
+
*
|
|
37449
|
+
* @param listener - Callback receiving send failed data
|
|
37450
|
+
* @returns this for chaining
|
|
37451
|
+
*/
|
|
37452
|
+
onSendFailed(listener) {
|
|
37453
|
+
return this.on("SEND_FAILED", listener);
|
|
37454
|
+
}
|
|
37455
|
+
/**
|
|
37456
|
+
* Registers a listener for health state changes.
|
|
37457
|
+
*
|
|
37458
|
+
* @param listener - Callback receiving health change data
|
|
37459
|
+
* @returns this for chaining
|
|
37460
|
+
*/
|
|
37461
|
+
onHealthChanged(listener) {
|
|
37462
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37463
|
+
}
|
|
37464
|
+
/**
|
|
37465
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37466
|
+
*
|
|
37467
|
+
* @param listener - Callback receiving bucket flush data
|
|
37468
|
+
* @returns this for chaining
|
|
37469
|
+
*/
|
|
37470
|
+
onBucketFlushed(listener) {
|
|
37471
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37472
|
+
}
|
|
37473
|
+
// -----------------------------------------------------------------------
|
|
37474
|
+
// Abort signal
|
|
37475
|
+
// -----------------------------------------------------------------------
|
|
37476
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37477
|
+
wireAbortSignal(signal) {
|
|
37478
|
+
if (signal.aborted) {
|
|
37479
|
+
void this.stop();
|
|
37480
|
+
return;
|
|
37481
|
+
}
|
|
37482
|
+
this.abortHandler = () => {
|
|
37483
|
+
void this.stop();
|
|
37484
|
+
};
|
|
37485
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37486
|
+
}
|
|
37487
|
+
};
|
|
37488
|
+
function buildStorescuParams(options) {
|
|
37489
|
+
return {
|
|
37490
|
+
calledAETitle: options?.calledAETitle,
|
|
37491
|
+
callingAETitle: options?.callingAETitle,
|
|
37492
|
+
required: options?.required,
|
|
37493
|
+
proposedTransferSyntax: options?.proposedTransferSyntax,
|
|
37494
|
+
combineProposedTransferSyntaxes: options?.combineProposedTransferSyntaxes
|
|
37495
|
+
};
|
|
37496
|
+
}
|
|
37497
|
+
var DEFAULT_MAX_ASSOCIATIONS2 = 4;
|
|
37498
|
+
var DEFAULT_MAX_QUEUE_LENGTH2 = 1e3;
|
|
37499
|
+
var DEFAULT_MAX_RETRIES2 = 3;
|
|
37500
|
+
var DEFAULT_RETRY_DELAY_MS2 = 1e3;
|
|
37501
|
+
var DEFAULT_BUCKET_FLUSH_MS2 = 5e3;
|
|
37502
|
+
var DEFAULT_MAX_BUCKET_SIZE2 = 50;
|
|
37503
|
+
var MAX_ASSOCIATIONS_LIMIT2 = 64;
|
|
37504
|
+
var DicomSendOptionsSchema = z.object({
|
|
37505
|
+
host: z.string().min(1),
|
|
37506
|
+
port: z.number().int().min(1).max(65535),
|
|
37507
|
+
calledAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37508
|
+
callingAETitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37509
|
+
mode: z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37510
|
+
maxAssociations: z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT2).optional(),
|
|
37511
|
+
maxQueueLength: z.number().int().min(1).optional(),
|
|
37512
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
37513
|
+
maxRetries: z.number().int().min(0).optional(),
|
|
37514
|
+
retryDelayMs: z.number().int().min(0).optional(),
|
|
37515
|
+
bucketFlushMs: z.number().int().positive().optional(),
|
|
37516
|
+
maxBucketSize: z.number().int().min(1).optional(),
|
|
37517
|
+
maxPduReceive: z.number().int().min(4096).max(131072).optional(),
|
|
37518
|
+
maxPduSend: z.number().int().min(4096).max(131072).optional(),
|
|
37519
|
+
associationTimeout: z.number().int().positive().optional(),
|
|
37520
|
+
acseTimeout: z.number().int().positive().optional(),
|
|
37521
|
+
dimseTimeout: z.number().int().positive().optional(),
|
|
37522
|
+
noHostnameLookup: z.boolean().optional(),
|
|
37523
|
+
verbosity: z.enum(["verbose", "debug"]).optional(),
|
|
37524
|
+
noHalt: z.boolean().optional(),
|
|
37525
|
+
noIllegalProposal: z.boolean().optional(),
|
|
37526
|
+
decompress: z.enum(["never", "lossless", "lossy"]).optional(),
|
|
37527
|
+
multiAssociations: z.boolean().optional(),
|
|
37528
|
+
noUidChecks: z.boolean().optional(),
|
|
37529
|
+
signal: z.instanceof(AbortSignal).optional()
|
|
37530
|
+
}).strict();
|
|
37531
|
+
function createDcmsendExecutor(options) {
|
|
37532
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37533
|
+
const result = await dcmsend({
|
|
37534
|
+
host: options.host,
|
|
37535
|
+
port: options.port,
|
|
37536
|
+
files: [...files],
|
|
37537
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37538
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37539
|
+
maxPduReceive: options.maxPduReceive,
|
|
37540
|
+
maxPduSend: options.maxPduSend,
|
|
37541
|
+
associationTimeout: options.associationTimeout,
|
|
37542
|
+
acseTimeout: options.acseTimeout,
|
|
37543
|
+
dimseTimeout: options.dimseTimeout,
|
|
37544
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37545
|
+
verbosity: options.verbosity,
|
|
37546
|
+
noHalt: options.noHalt,
|
|
37547
|
+
noIllegalProposal: options.noIllegalProposal,
|
|
37548
|
+
decompress: options.decompress,
|
|
37549
|
+
multiAssociations: options.multiAssociations,
|
|
37550
|
+
noUidChecks: options.noUidChecks,
|
|
37551
|
+
timeoutMs,
|
|
37552
|
+
signal
|
|
37553
|
+
});
|
|
37554
|
+
if (!result.ok) return err(result.error);
|
|
37555
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37556
|
+
};
|
|
37557
|
+
}
|
|
37558
|
+
function resolveConfig2(options) {
|
|
37559
|
+
const mode = options.mode ?? "multiple";
|
|
37560
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37561
|
+
return {
|
|
37562
|
+
mode,
|
|
37563
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37564
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH2,
|
|
37565
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37566
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES2,
|
|
37567
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS2,
|
|
37568
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS2,
|
|
37569
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE2
|
|
37570
|
+
};
|
|
37571
|
+
}
|
|
37572
|
+
var DicomSend = class _DicomSend extends EventEmitter {
|
|
37573
|
+
constructor(engine, cfg, signal) {
|
|
37574
|
+
super();
|
|
37575
|
+
__publicField(this, "engine");
|
|
37576
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37577
|
+
__publicField(this, "defaultMaxRetries");
|
|
37578
|
+
__publicField(this, "signal");
|
|
37579
|
+
__publicField(this, "abortHandler");
|
|
37580
|
+
this.setMaxListeners(20);
|
|
37581
|
+
this.on("error", () => {
|
|
37582
|
+
});
|
|
37583
|
+
this.engine = engine;
|
|
37584
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37585
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37586
|
+
this.signal = signal;
|
|
37587
|
+
if (signal !== void 0) {
|
|
37588
|
+
this.wireAbortSignal(signal);
|
|
37589
|
+
}
|
|
37590
|
+
}
|
|
37591
|
+
// -----------------------------------------------------------------------
|
|
37592
|
+
// Public API
|
|
37593
|
+
// -----------------------------------------------------------------------
|
|
37594
|
+
/**
|
|
37595
|
+
* Creates a new DicomSend instance.
|
|
37596
|
+
*
|
|
37597
|
+
* @param options - Configuration options
|
|
37598
|
+
* @returns A Result containing the instance or a validation error
|
|
37599
|
+
*/
|
|
37600
|
+
static create(options) {
|
|
37601
|
+
const validation = DicomSendOptionsSchema.safeParse(options);
|
|
37602
|
+
if (!validation.success) {
|
|
37603
|
+
return err(createValidationError("DicomSend", validation.error));
|
|
37604
|
+
}
|
|
37605
|
+
const cfg = resolveConfig2(options);
|
|
37606
|
+
const senderRef = { current: void 0 };
|
|
37607
|
+
const engine = new SenderEngine({
|
|
37608
|
+
...cfg,
|
|
37609
|
+
executor: createDcmsendExecutor(options),
|
|
37610
|
+
signal: options.signal,
|
|
37611
|
+
senderName: "DicomSend",
|
|
37612
|
+
emitters: {
|
|
37613
|
+
emitSendComplete: (data) => {
|
|
37614
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37615
|
+
},
|
|
37616
|
+
emitSendFailed: (data) => {
|
|
37617
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37618
|
+
},
|
|
37619
|
+
emitHealthChanged: (data) => {
|
|
37620
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37621
|
+
},
|
|
37622
|
+
emitBucketFlushed: (data) => {
|
|
37623
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37624
|
+
}
|
|
37625
|
+
}
|
|
37626
|
+
});
|
|
37627
|
+
const sender = new _DicomSend(engine, cfg, options.signal);
|
|
37628
|
+
senderRef.current = sender;
|
|
37629
|
+
return ok(sender);
|
|
37630
|
+
}
|
|
37631
|
+
/**
|
|
37632
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37633
|
+
*
|
|
37634
|
+
* In single/multiple mode, files are sent as one dcmsend call.
|
|
37635
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37636
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37637
|
+
*
|
|
37638
|
+
* @param files - One or more DICOM file paths
|
|
37639
|
+
* @param options - Per-send overrides
|
|
37640
|
+
* @returns A Result containing the send result or an error
|
|
37641
|
+
*/
|
|
37642
|
+
send(files, options) {
|
|
37643
|
+
const params = {
|
|
37644
|
+
calledAETitle: options?.calledAETitle,
|
|
37645
|
+
callingAETitle: options?.callingAETitle
|
|
37646
|
+
};
|
|
37647
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37648
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37649
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37650
|
+
}
|
|
37651
|
+
/**
|
|
37652
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37653
|
+
* In single/multiple mode this is a no-op.
|
|
37654
|
+
*/
|
|
37655
|
+
flush() {
|
|
37656
|
+
this.engine.flush();
|
|
37657
|
+
}
|
|
37658
|
+
/**
|
|
37659
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37660
|
+
* waits for active associations to complete.
|
|
37661
|
+
*/
|
|
37662
|
+
async stop() {
|
|
37663
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37664
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37665
|
+
}
|
|
37666
|
+
await this.engine.stop();
|
|
37667
|
+
}
|
|
37668
|
+
/** Current sender status. */
|
|
37669
|
+
get status() {
|
|
37670
|
+
return this.engine.status;
|
|
37671
|
+
}
|
|
37672
|
+
// -----------------------------------------------------------------------
|
|
37673
|
+
// Typed event listener convenience methods
|
|
37674
|
+
// -----------------------------------------------------------------------
|
|
37675
|
+
/**
|
|
37676
|
+
* Registers a typed listener for a DicomSend-specific event.
|
|
37677
|
+
*
|
|
37678
|
+
* @param event - The event name from DicomSendEventMap
|
|
37679
|
+
* @param listener - Callback receiving typed event data
|
|
37680
|
+
* @returns this for chaining
|
|
37681
|
+
*/
|
|
37682
|
+
onEvent(event, listener) {
|
|
37683
|
+
return this.on(event, listener);
|
|
37684
|
+
}
|
|
37685
|
+
/**
|
|
37686
|
+
* Registers a listener for successful sends.
|
|
37687
|
+
*
|
|
37688
|
+
* @param listener - Callback receiving send complete data
|
|
37689
|
+
* @returns this for chaining
|
|
37690
|
+
*/
|
|
37691
|
+
onSendComplete(listener) {
|
|
37692
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37693
|
+
}
|
|
37694
|
+
/**
|
|
37695
|
+
* Registers a listener for failed sends.
|
|
37696
|
+
*
|
|
37697
|
+
* @param listener - Callback receiving send failed data
|
|
37698
|
+
* @returns this for chaining
|
|
37699
|
+
*/
|
|
37700
|
+
onSendFailed(listener) {
|
|
37701
|
+
return this.on("SEND_FAILED", listener);
|
|
37702
|
+
}
|
|
37703
|
+
/**
|
|
37704
|
+
* Registers a listener for health state changes.
|
|
37705
|
+
*
|
|
37706
|
+
* @param listener - Callback receiving health change data
|
|
37707
|
+
* @returns this for chaining
|
|
37708
|
+
*/
|
|
37709
|
+
onHealthChanged(listener) {
|
|
37710
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37711
|
+
}
|
|
37712
|
+
/**
|
|
37713
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37714
|
+
*
|
|
37715
|
+
* @param listener - Callback receiving bucket flush data
|
|
37716
|
+
* @returns this for chaining
|
|
37717
|
+
*/
|
|
37718
|
+
onBucketFlushed(listener) {
|
|
37719
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37720
|
+
}
|
|
37721
|
+
// -----------------------------------------------------------------------
|
|
37722
|
+
// Abort signal
|
|
37723
|
+
// -----------------------------------------------------------------------
|
|
37724
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37725
|
+
wireAbortSignal(signal) {
|
|
37726
|
+
if (signal.aborted) {
|
|
37727
|
+
void this.stop();
|
|
37728
|
+
return;
|
|
37729
|
+
}
|
|
37730
|
+
this.abortHandler = () => {
|
|
37731
|
+
void this.stop();
|
|
37732
|
+
};
|
|
37733
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37734
|
+
}
|
|
37735
|
+
};
|
|
37736
|
+
|
|
37419
37737
|
// src/pacs/types.ts
|
|
37420
37738
|
var QueryLevel = {
|
|
37421
37739
|
STUDY: "STUDY",
|
|
@@ -37965,7 +38283,7 @@ var DEFAULT_CONFIG = {
|
|
|
37965
38283
|
signal: void 0,
|
|
37966
38284
|
onRetry: void 0
|
|
37967
38285
|
};
|
|
37968
|
-
function
|
|
38286
|
+
function resolveConfig3(opts) {
|
|
37969
38287
|
if (!opts) return DEFAULT_CONFIG;
|
|
37970
38288
|
return {
|
|
37971
38289
|
...DEFAULT_CONFIG,
|
|
@@ -38010,7 +38328,7 @@ function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
|
38010
38328
|
return false;
|
|
38011
38329
|
}
|
|
38012
38330
|
async function retry(operation, options) {
|
|
38013
|
-
const config =
|
|
38331
|
+
const config = resolveConfig3(options);
|
|
38014
38332
|
let lastResult = err(new Error("No attempts executed"));
|
|
38015
38333
|
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38016
38334
|
lastResult = await operation();
|
|
@@ -38024,6 +38342,6 @@ async function retry(operation, options) {
|
|
|
38024
38342
|
return lastResult;
|
|
38025
38343
|
}
|
|
38026
38344
|
|
|
38027
|
-
export { AETitleSchema, AssociationTracker, ChangeSet, ColorConversion, DCMPRSCP_FATAL_EVENTS, DCMPRSCP_PATTERNS, DCMPSRCV_FATAL_EVENTS, DCMPSRCV_PATTERNS, DCMQRSCP_FATAL_EVENTS, DCMQRSCP_PATTERNS, DCMRECV_FATAL_EVENTS, DCMRECV_PATTERNS, DEFAULT_BLOCK_TIMEOUT_MS, DEFAULT_DICOM_PORT, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_PARSE_CONCURRENCY, DEFAULT_START_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, Dcm2pnmOutputFormat, Dcm2xmlCharset, DcmQRSCP, DcmdumpFormat, Dcmj2pnmOutputFormat, DcmprsCP, DcmprscpEvent, Dcmpsrcv, DcmpsrcvEvent, DcmqrscpEvent, Dcmrecv, DcmrecvEvent, DcmtkProcess, DicomDataset, DicomInstance, DicomReceiver, DicomSender, DicomTagPathSchema, DicomTagSchema, FilenameMode, GetQueryModel, Img2dcmInputFormat, JplsColorConversion, LineParser, LutType, MAX_BLOCK_LINES, MAX_CHANGESET_OPERATIONS, MAX_EVENT_PATTERNS, MAX_TRAVERSAL_DEPTH, MoveQueryModel, PDU_SIZE, PacsClient, PortSchema, PreferredTransferSyntax, ProcessState, ProposedTransferSyntax, QueryLevel, QueryModel, REQUIRED_BINARIES, RetrieveMode, SOP_CLASSES, STORESCP_FATAL_EVENTS, STORESCP_PATTERNS, SenderHealth, SenderMode, StorageMode, StoreSCP, StoreSCPPreset, StorescpEvent, SubdirectoryMode, TransferSyntax, UIDSchema, UNIX_SEARCH_PATHS, VR, VR_CATEGORY, VR_CATEGORY_NAME, VR_META, WINDOWS_SEARCH_PATHS, WLMSCPFS_FATAL_EVENTS, WLMSCPFS_PATTERNS, Wlmscpfs, WlmscpfsEvent, assertUnreachable, batch, cda2dcm, clearDcmtkPathCache, createAETitle, createDicomFilePath, createDicomTag, createDicomTagPath, createPort, createSOPClassUID, createTransferSyntaxUID, dcm2cda, dcm2json, dcm2pdf, dcm2pnm, dcm2xml, dcmcjpeg, dcmcjpls, dcmconv, dcmcrle, dcmdecap, dcmdjpeg, dcmdjpls, dcmdrle, dcmdspfn, dcmdump, dcmencap, dcmftest, dcmgpdir, dcmj2pnm, dcmmkcrv, dcmmkdir, dcmmklut, dcmodify, dcmp2pgm, dcmprscu, dcmpschk, dcmpsmk, dcmpsprt, dcmqridx, dcmquant, dcmscale, dcmsend, dcod2lum, dconvlum, drtdump, dsr2xml, dsrdump, dump2dcm, echoscu, err, execCommand, findDcmtkPath, findscu, getVRCategory, getscu, img2dcm, isBinaryVR, isNumericVR, isStringVR, json2dcm, lookupTag, lookupTagByKeyword, lookupTagByName, mapResult, movescu, ok, parseAETitle, parseDicomTag, parseDicomTagPath, parsePort, parseSOPClassUID, parseTransferSyntaxUID, pdf2dcm, retry, segmentsToModifyPath, segmentsToString, sopClassNameFromUID, spawnCommand, stl2dcm, storescu, tag, tagPathToSegments, termscu, walkTags, xml2dcm, xml2dsr, xmlToJson };
|
|
38345
|
+
export { AETitleSchema, AssociationTracker, ChangeSet, ColorConversion, DCMPRSCP_FATAL_EVENTS, DCMPRSCP_PATTERNS, DCMPSRCV_FATAL_EVENTS, DCMPSRCV_PATTERNS, DCMQRSCP_FATAL_EVENTS, DCMQRSCP_PATTERNS, DCMRECV_FATAL_EVENTS, DCMRECV_PATTERNS, DEFAULT_BLOCK_TIMEOUT_MS, DEFAULT_DICOM_PORT, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_PARSE_CONCURRENCY, DEFAULT_START_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, Dcm2pnmOutputFormat, Dcm2xmlCharset, DcmQRSCP, DcmdumpFormat, Dcmj2pnmOutputFormat, DcmprsCP, DcmprscpEvent, Dcmpsrcv, DcmpsrcvEvent, DcmqrscpEvent, Dcmrecv, DcmrecvEvent, DcmtkProcess, DicomDataset, DicomInstance, DicomReceiver, DicomSend, DicomSender, DicomTagPathSchema, DicomTagSchema, FilenameMode, GetQueryModel, Img2dcmInputFormat, JplsColorConversion, LineParser, LutType, MAX_BLOCK_LINES, MAX_CHANGESET_OPERATIONS, MAX_EVENT_PATTERNS, MAX_TRAVERSAL_DEPTH, MoveQueryModel, PDU_SIZE, PacsClient, PortSchema, PreferredTransferSyntax, ProcessState, ProposedTransferSyntax, QueryLevel, QueryModel, REQUIRED_BINARIES, RetrieveMode, SOP_CLASSES, STORESCP_FATAL_EVENTS, STORESCP_PATTERNS, SenderHealth, SenderMode, StorageMode, StoreSCP, StoreSCPPreset, StorescpEvent, SubdirectoryMode, TransferSyntax, UIDSchema, UNIX_SEARCH_PATHS, VR, VR_CATEGORY, VR_CATEGORY_NAME, VR_META, WINDOWS_SEARCH_PATHS, WLMSCPFS_FATAL_EVENTS, WLMSCPFS_PATTERNS, Wlmscpfs, WlmscpfsEvent, assertUnreachable, batch, cda2dcm, clearDcmtkPathCache, createAETitle, createDicomFilePath, createDicomTag, createDicomTagPath, createPort, createSOPClassUID, createTransferSyntaxUID, dcm2cda, dcm2json, dcm2pdf, dcm2pnm, dcm2xml, dcmcjpeg, dcmcjpls, dcmconv, dcmcrle, dcmdecap, dcmdjpeg, dcmdjpls, dcmdrle, dcmdspfn, dcmdump, dcmencap, dcmftest, dcmgpdir, dcmj2pnm, dcmmkcrv, dcmmkdir, dcmmklut, dcmodify, dcmp2pgm, dcmprscu, dcmpschk, dcmpsmk, dcmpsprt, dcmqridx, dcmquant, dcmscale, dcmsend, dcod2lum, dconvlum, drtdump, dsr2xml, dsrdump, dump2dcm, echoscu, err, execCommand, findDcmtkPath, findscu, getVRCategory, getscu, img2dcm, isBinaryVR, isNumericVR, isStringVR, json2dcm, lookupTag, lookupTagByKeyword, lookupTagByName, mapResult, movescu, ok, parseAETitle, parseDicomTag, parseDicomTagPath, parsePort, parseSOPClassUID, parseTransferSyntaxUID, pdf2dcm, retry, segmentsToModifyPath, segmentsToString, sopClassNameFromUID, spawnCommand, stl2dcm, storescu, tag, tagPathToSegments, termscu, walkTags, xml2dcm, xml2dsr, xmlToJson };
|
|
38028
38346
|
//# sourceMappingURL=index.js.map
|
|
38029
38347
|
//# sourceMappingURL=index.js.map
|