@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.cjs
CHANGED
|
@@ -31184,6 +31184,7 @@ var Dcm2xmlOptionsSchema = zod.z.object({
|
|
|
31184
31184
|
charset: zod.z.enum(["utf8", "latin1", "ascii"]).optional(),
|
|
31185
31185
|
writeBinaryData: zod.z.boolean().optional(),
|
|
31186
31186
|
encodeBinaryBase64: zod.z.boolean().optional(),
|
|
31187
|
+
charsetAssume: zod.z.string().min(1).optional(),
|
|
31187
31188
|
verbosity: zod.z.enum(["verbose", "debug"]).optional()
|
|
31188
31189
|
}).strict().optional();
|
|
31189
31190
|
function pushEncodingArgs(args, options) {
|
|
@@ -31207,6 +31208,9 @@ function buildArgs(inputPath, options) {
|
|
|
31207
31208
|
if (options?.namespace === true) {
|
|
31208
31209
|
args.push("+Xn");
|
|
31209
31210
|
}
|
|
31211
|
+
if (options?.charsetAssume !== void 0) {
|
|
31212
|
+
args.push("+Ca", options.charsetAssume);
|
|
31213
|
+
}
|
|
31210
31214
|
pushEncodingArgs(args, options);
|
|
31211
31215
|
args.push(inputPath);
|
|
31212
31216
|
return args;
|
|
@@ -31456,6 +31460,7 @@ var Dcm2jsonOptionsSchema = zod.z.object({
|
|
|
31456
31460
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31457
31461
|
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
31458
31462
|
directOnly: zod.z.boolean().optional(),
|
|
31463
|
+
charsetAssume: zod.z.string().min(1).optional(),
|
|
31459
31464
|
verbosity: zod.z.enum(["verbose", "debug"]).optional()
|
|
31460
31465
|
}).strict().optional();
|
|
31461
31466
|
var VERBOSITY_FLAGS2 = { verbose: "-v", debug: "-d" };
|
|
@@ -31465,12 +31470,19 @@ function buildVerbosityArgs(verbosity) {
|
|
|
31465
31470
|
}
|
|
31466
31471
|
return [];
|
|
31467
31472
|
}
|
|
31468
|
-
|
|
31473
|
+
function buildXmlOpts(options) {
|
|
31474
|
+
const result = {};
|
|
31475
|
+
if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
|
|
31476
|
+
if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
|
|
31477
|
+
return result;
|
|
31478
|
+
}
|
|
31479
|
+
async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
|
|
31469
31480
|
const xmlBinary = resolveBinary("dcm2xml");
|
|
31470
31481
|
if (!xmlBinary.ok) {
|
|
31471
31482
|
return err(xmlBinary.error);
|
|
31472
31483
|
}
|
|
31473
|
-
const
|
|
31484
|
+
const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
|
|
31485
|
+
const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
|
|
31474
31486
|
const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
|
|
31475
31487
|
if (!xmlResult.ok) {
|
|
31476
31488
|
return err(xmlResult.error);
|
|
@@ -31516,7 +31528,7 @@ async function dcm2json(inputPath, options) {
|
|
|
31516
31528
|
if (options?.directOnly === true) {
|
|
31517
31529
|
return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
|
|
31518
31530
|
}
|
|
31519
|
-
const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal,
|
|
31531
|
+
const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal, buildXmlOpts(options));
|
|
31520
31532
|
if (xmlResult.ok) {
|
|
31521
31533
|
return xmlResult;
|
|
31522
31534
|
}
|
|
@@ -33131,6 +33143,30 @@ async function echoscu(options) {
|
|
|
33131
33143
|
return ok({ success: true, stderr: result.value.stderr });
|
|
33132
33144
|
}
|
|
33133
33145
|
var VERBOSITY_FLAGS19 = { verbose: "-v", debug: "-d" };
|
|
33146
|
+
var DECOMPRESS_FLAGS = {
|
|
33147
|
+
never: "--decompress-never",
|
|
33148
|
+
lossless: "--decompress-lossless",
|
|
33149
|
+
lossy: "--decompress-lossy"
|
|
33150
|
+
};
|
|
33151
|
+
function pushSendModeArgs(args, options) {
|
|
33152
|
+
if (options.noHalt === true) {
|
|
33153
|
+
args.push("--no-halt");
|
|
33154
|
+
}
|
|
33155
|
+
if (options.noIllegalProposal === true) {
|
|
33156
|
+
args.push("--no-illegal-proposal");
|
|
33157
|
+
}
|
|
33158
|
+
if (options.decompress !== void 0) {
|
|
33159
|
+
args.push(DECOMPRESS_FLAGS[options.decompress]);
|
|
33160
|
+
}
|
|
33161
|
+
if (options.multiAssociations === true) {
|
|
33162
|
+
args.push("+ma");
|
|
33163
|
+
} else if (options.multiAssociations === false) {
|
|
33164
|
+
args.push("-ma");
|
|
33165
|
+
}
|
|
33166
|
+
if (options.createReportFile !== void 0) {
|
|
33167
|
+
args.push("--create-report-file", options.createReportFile);
|
|
33168
|
+
}
|
|
33169
|
+
}
|
|
33134
33170
|
var DcmsendOptionsSchema = zod.z.object({
|
|
33135
33171
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
33136
33172
|
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
@@ -33140,8 +33176,15 @@ var DcmsendOptionsSchema = zod.z.object({
|
|
|
33140
33176
|
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33141
33177
|
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33142
33178
|
scanDirectory: zod.z.boolean().optional(),
|
|
33179
|
+
recurse: zod.z.boolean().optional(),
|
|
33180
|
+
scanPattern: zod.z.string().min(1).optional(),
|
|
33143
33181
|
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
33144
33182
|
noUidChecks: zod.z.boolean().optional(),
|
|
33183
|
+
noHalt: zod.z.boolean().optional(),
|
|
33184
|
+
noIllegalProposal: zod.z.boolean().optional(),
|
|
33185
|
+
decompress: zod.z.enum(["never", "lossless", "lossy"]).optional(),
|
|
33186
|
+
multiAssociations: zod.z.boolean().optional(),
|
|
33187
|
+
createReportFile: zod.z.string().min(1).optional(),
|
|
33145
33188
|
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
33146
33189
|
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
33147
33190
|
noHostnameLookup: zod.z.boolean().optional(),
|
|
@@ -33187,6 +33230,13 @@ function buildArgs33(options) {
|
|
|
33187
33230
|
if (options.scanDirectory === true) {
|
|
33188
33231
|
args.push("--scan-directories");
|
|
33189
33232
|
}
|
|
33233
|
+
if (options.recurse === true) {
|
|
33234
|
+
args.push("+r");
|
|
33235
|
+
}
|
|
33236
|
+
if (options.scanPattern !== void 0) {
|
|
33237
|
+
args.push("--scan-pattern", options.scanPattern);
|
|
33238
|
+
}
|
|
33239
|
+
pushSendModeArgs(args, options);
|
|
33190
33240
|
args.push(options.host, String(options.port));
|
|
33191
33241
|
args.push(...options.files);
|
|
33192
33242
|
return args;
|
|
@@ -34313,7 +34363,7 @@ var DicomInstance = class _DicomInstance {
|
|
|
34313
34363
|
* Opens a DICOM file and creates a DicomInstance.
|
|
34314
34364
|
*
|
|
34315
34365
|
* @param path - Filesystem path to the DICOM file
|
|
34316
|
-
* @param options - Timeout and
|
|
34366
|
+
* @param options - Timeout, abort, and charset options
|
|
34317
34367
|
* @returns A Result containing the DicomInstance or an error
|
|
34318
34368
|
*/
|
|
34319
34369
|
static async open(path2, options) {
|
|
@@ -34321,7 +34371,8 @@ var DicomInstance = class _DicomInstance {
|
|
|
34321
34371
|
if (!filePathResult.ok) return err(filePathResult.error);
|
|
34322
34372
|
const jsonResult = await dcm2json(path2, {
|
|
34323
34373
|
timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
34324
|
-
signal: options?.signal
|
|
34374
|
+
signal: options?.signal,
|
|
34375
|
+
charsetAssume: options?.charsetAssume
|
|
34325
34376
|
});
|
|
34326
34377
|
if (!jsonResult.ok) return err(jsonResult.error);
|
|
34327
34378
|
const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
|
|
@@ -36877,68 +36928,13 @@ var SenderHealth = {
|
|
|
36877
36928
|
DOWN: "down"
|
|
36878
36929
|
};
|
|
36879
36930
|
|
|
36880
|
-
// src/senders/
|
|
36881
|
-
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
36882
|
-
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
36883
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
36884
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
36885
|
-
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
36886
|
-
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
36887
|
-
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
36931
|
+
// src/senders/SenderEngine.ts
|
|
36888
36932
|
var DEGRADE_THRESHOLD = 3;
|
|
36889
36933
|
var DOWN_THRESHOLD = 10;
|
|
36890
36934
|
var RECOVERY_THRESHOLD = 3;
|
|
36891
|
-
var
|
|
36892
|
-
|
|
36893
|
-
|
|
36894
|
-
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36895
|
-
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36896
|
-
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
36897
|
-
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
36898
|
-
proposedTransferSyntax: zod.z.union([zod.z.enum(PROPOSED_TS_VALUES), zod.z.array(zod.z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
36899
|
-
combineProposedTransferSyntaxes: zod.z.boolean().optional(),
|
|
36900
|
-
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
36901
|
-
timeoutMs: zod.z.number().int().positive().optional(),
|
|
36902
|
-
maxRetries: zod.z.number().int().min(0).optional(),
|
|
36903
|
-
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
36904
|
-
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
36905
|
-
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
36906
|
-
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36907
|
-
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36908
|
-
associationTimeout: zod.z.number().int().positive().optional(),
|
|
36909
|
-
acseTimeout: zod.z.number().int().positive().optional(),
|
|
36910
|
-
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
36911
|
-
noHostnameLookup: zod.z.boolean().optional(),
|
|
36912
|
-
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
36913
|
-
required: zod.z.boolean().optional(),
|
|
36914
|
-
signal: zod.z.instanceof(AbortSignal).optional()
|
|
36915
|
-
}).strict();
|
|
36916
|
-
function resolveConfig(options) {
|
|
36917
|
-
const mode = options.mode ?? "multiple";
|
|
36918
|
-
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
36919
|
-
return {
|
|
36920
|
-
mode,
|
|
36921
|
-
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
36922
|
-
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
36923
|
-
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
36924
|
-
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
36925
|
-
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
36926
|
-
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
36927
|
-
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
36928
|
-
};
|
|
36929
|
-
}
|
|
36930
|
-
var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
36931
|
-
constructor(options) {
|
|
36932
|
-
super();
|
|
36933
|
-
__publicField(this, "options");
|
|
36934
|
-
__publicField(this, "mode");
|
|
36935
|
-
__publicField(this, "configuredMaxAssociations");
|
|
36936
|
-
__publicField(this, "maxQueueLength");
|
|
36937
|
-
__publicField(this, "defaultTimeoutMs");
|
|
36938
|
-
__publicField(this, "defaultMaxRetries");
|
|
36939
|
-
__publicField(this, "retryDelayMs");
|
|
36940
|
-
__publicField(this, "bucketFlushMs");
|
|
36941
|
-
__publicField(this, "maxBucketSize");
|
|
36935
|
+
var SenderEngine = class {
|
|
36936
|
+
constructor(config) {
|
|
36937
|
+
__publicField(this, "config");
|
|
36942
36938
|
// Queue and concurrency state
|
|
36943
36939
|
__publicField(this, "queue", []);
|
|
36944
36940
|
__publicField(this, "activeAssociations", 0);
|
|
@@ -36951,113 +36947,36 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
36951
36947
|
// Bucket state (bucket mode only)
|
|
36952
36948
|
__publicField(this, "currentBucket", []);
|
|
36953
36949
|
__publicField(this, "bucketTimer");
|
|
36954
|
-
|
|
36955
|
-
|
|
36956
|
-
this.setMaxListeners(20);
|
|
36957
|
-
this.on("error", () => {
|
|
36958
|
-
});
|
|
36959
|
-
this.options = options;
|
|
36960
|
-
const cfg = resolveConfig(options);
|
|
36961
|
-
this.mode = cfg.mode;
|
|
36962
|
-
this.configuredMaxAssociations = cfg.configuredMaxAssociations;
|
|
36963
|
-
this.effectiveMaxAssociations = cfg.configuredMaxAssociations;
|
|
36964
|
-
this.maxQueueLength = cfg.maxQueueLength;
|
|
36965
|
-
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
36966
|
-
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
36967
|
-
this.retryDelayMs = cfg.retryDelayMs;
|
|
36968
|
-
this.bucketFlushMs = cfg.bucketFlushMs;
|
|
36969
|
-
this.maxBucketSize = cfg.maxBucketSize;
|
|
36970
|
-
if (options.signal !== void 0) {
|
|
36971
|
-
this.wireAbortSignal(options.signal);
|
|
36972
|
-
}
|
|
36950
|
+
this.config = config;
|
|
36951
|
+
this.effectiveMaxAssociations = config.configuredMaxAssociations;
|
|
36973
36952
|
}
|
|
36974
36953
|
// -----------------------------------------------------------------------
|
|
36975
36954
|
// Public API
|
|
36976
36955
|
// -----------------------------------------------------------------------
|
|
36977
|
-
/**
|
|
36978
|
-
|
|
36979
|
-
*
|
|
36980
|
-
* @param options - Configuration options
|
|
36981
|
-
* @returns A Result containing the instance or a validation error
|
|
36982
|
-
*/
|
|
36983
|
-
static create(options) {
|
|
36984
|
-
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
36985
|
-
if (!validation.success) {
|
|
36986
|
-
return err(createValidationError("DicomSender", validation.error));
|
|
36987
|
-
}
|
|
36988
|
-
return ok(new _DicomSender(options));
|
|
36989
|
-
}
|
|
36990
|
-
/**
|
|
36991
|
-
* Sends one or more DICOM files to the remote endpoint.
|
|
36992
|
-
*
|
|
36993
|
-
* In single/multiple mode, files are sent as one storescu call.
|
|
36994
|
-
* In bucket mode, files are accumulated into a bucket and flushed
|
|
36995
|
-
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
36996
|
-
*
|
|
36997
|
-
* The returned promise resolves when the files are actually sent
|
|
36998
|
-
* (not just queued). Callers can await for confirmation or
|
|
36999
|
-
* fire-and-forget with `void sender.send(files)`.
|
|
37000
|
-
*
|
|
37001
|
-
* @param files - One or more DICOM file paths
|
|
37002
|
-
* @param options - Per-send overrides
|
|
37003
|
-
* @returns A Result containing the send result or an error
|
|
37004
|
-
*/
|
|
37005
|
-
send(files, options) {
|
|
36956
|
+
/** Sends files through the engine's queue/bucket system. */
|
|
36957
|
+
send(files, timeoutMs, maxRetries, binaryParams) {
|
|
37006
36958
|
if (this.isStopped) {
|
|
37007
|
-
return Promise.resolve(err(new Error(
|
|
36959
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: sender is stopped`)));
|
|
37008
36960
|
}
|
|
37009
36961
|
if (files.length === 0) {
|
|
37010
|
-
return Promise.resolve(err(new Error(
|
|
37011
|
-
}
|
|
37012
|
-
return this.dispatchSend(files, this.buildSendParams(options));
|
|
37013
|
-
}
|
|
37014
|
-
/** Builds SendParams from per-send options, applying instance defaults. */
|
|
37015
|
-
buildSendParams(options) {
|
|
37016
|
-
return {
|
|
37017
|
-
timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
|
|
37018
|
-
maxRetries: options?.maxRetries ?? this.defaultMaxRetries,
|
|
37019
|
-
calledAETitle: options?.calledAETitle,
|
|
37020
|
-
callingAETitle: options?.callingAETitle,
|
|
37021
|
-
required: options?.required,
|
|
37022
|
-
proposedTransferSyntax: options?.proposedTransferSyntax,
|
|
37023
|
-
combineProposedTransferSyntaxes: options?.combineProposedTransferSyntaxes
|
|
37024
|
-
};
|
|
37025
|
-
}
|
|
37026
|
-
/** Dispatches a send to the appropriate mode handler. */
|
|
37027
|
-
dispatchSend(files, params) {
|
|
37028
|
-
switch (this.mode) {
|
|
37029
|
-
case "single":
|
|
37030
|
-
case "multiple":
|
|
37031
|
-
return this.enqueueSend(files, params);
|
|
37032
|
-
case "bucket":
|
|
37033
|
-
return this.enqueueBucket(files, params);
|
|
37034
|
-
default:
|
|
37035
|
-
assertUnreachable(this.mode);
|
|
36962
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: no files provided`)));
|
|
37036
36963
|
}
|
|
36964
|
+
return this.dispatchSend(files, timeoutMs, maxRetries, binaryParams);
|
|
37037
36965
|
}
|
|
37038
|
-
/**
|
|
37039
|
-
* Flushes the current bucket immediately (bucket mode only).
|
|
37040
|
-
* In single/multiple mode this is a no-op.
|
|
37041
|
-
*/
|
|
36966
|
+
/** Flushes the current bucket immediately (bucket mode only). */
|
|
37042
36967
|
flush() {
|
|
37043
|
-
if (this.mode !== "bucket") return;
|
|
36968
|
+
if (this.config.mode !== "bucket") return;
|
|
37044
36969
|
if (this.currentBucket.length === 0) return;
|
|
37045
36970
|
this.clearBucketTimer();
|
|
37046
36971
|
this.flushBucketInternal("timer");
|
|
37047
36972
|
}
|
|
37048
|
-
/**
|
|
37049
|
-
* Gracefully stops the sender. Rejects all queued items and
|
|
37050
|
-
* waits for active associations to complete.
|
|
37051
|
-
*/
|
|
36973
|
+
/** Gracefully stops the engine. Rejects all queued items and waits for active associations. */
|
|
37052
36974
|
async stop() {
|
|
37053
36975
|
if (this.isStopped) return;
|
|
37054
36976
|
this.isStopped = true;
|
|
37055
|
-
if (this.options.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37056
|
-
this.options.signal.removeEventListener("abort", this.abortHandler);
|
|
37057
|
-
}
|
|
37058
36977
|
this.clearBucketTimer();
|
|
37059
|
-
this.rejectBucket(
|
|
37060
|
-
this.rejectQueue(
|
|
36978
|
+
this.rejectBucket(`${this.config.senderName}: sender stopped`);
|
|
36979
|
+
this.rejectQueue(`${this.config.senderName}: sender stopped`);
|
|
37061
36980
|
await this.waitForActive();
|
|
37062
36981
|
}
|
|
37063
36982
|
/** Current sender status. */
|
|
@@ -37072,77 +36991,37 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37072
36991
|
stopped: this.isStopped
|
|
37073
36992
|
};
|
|
37074
36993
|
}
|
|
36994
|
+
/** Whether the engine has been stopped. */
|
|
36995
|
+
get stopped() {
|
|
36996
|
+
return this.isStopped;
|
|
36997
|
+
}
|
|
37075
36998
|
// -----------------------------------------------------------------------
|
|
37076
|
-
//
|
|
36999
|
+
// Dispatch
|
|
37077
37000
|
// -----------------------------------------------------------------------
|
|
37078
|
-
/**
|
|
37079
|
-
|
|
37080
|
-
|
|
37081
|
-
|
|
37082
|
-
|
|
37083
|
-
|
|
37084
|
-
|
|
37085
|
-
|
|
37086
|
-
|
|
37087
|
-
|
|
37088
|
-
|
|
37089
|
-
* Registers a listener for successful sends.
|
|
37090
|
-
*
|
|
37091
|
-
* @param listener - Callback receiving send complete data
|
|
37092
|
-
* @returns this for chaining
|
|
37093
|
-
*/
|
|
37094
|
-
onSendComplete(listener) {
|
|
37095
|
-
return this.on("SEND_COMPLETE", listener);
|
|
37096
|
-
}
|
|
37097
|
-
/**
|
|
37098
|
-
* Registers a listener for failed sends.
|
|
37099
|
-
*
|
|
37100
|
-
* @param listener - Callback receiving send failed data
|
|
37101
|
-
* @returns this for chaining
|
|
37102
|
-
*/
|
|
37103
|
-
onSendFailed(listener) {
|
|
37104
|
-
return this.on("SEND_FAILED", listener);
|
|
37105
|
-
}
|
|
37106
|
-
/**
|
|
37107
|
-
* Registers a listener for health state changes.
|
|
37108
|
-
*
|
|
37109
|
-
* @param listener - Callback receiving health change data
|
|
37110
|
-
* @returns this for chaining
|
|
37111
|
-
*/
|
|
37112
|
-
onHealthChanged(listener) {
|
|
37113
|
-
return this.on("HEALTH_CHANGED", listener);
|
|
37114
|
-
}
|
|
37115
|
-
/**
|
|
37116
|
-
* Registers a listener for bucket flushes (bucket mode only).
|
|
37117
|
-
*
|
|
37118
|
-
* @param listener - Callback receiving bucket flush data
|
|
37119
|
-
* @returns this for chaining
|
|
37120
|
-
*/
|
|
37121
|
-
onBucketFlushed(listener) {
|
|
37122
|
-
return this.on("BUCKET_FLUSHED", listener);
|
|
37001
|
+
/** Dispatches a send to the appropriate mode handler. */
|
|
37002
|
+
dispatchSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
37003
|
+
switch (this.config.mode) {
|
|
37004
|
+
case "single":
|
|
37005
|
+
case "multiple":
|
|
37006
|
+
return this.enqueueSend(files, timeoutMs, maxRetries, binaryParams);
|
|
37007
|
+
case "bucket":
|
|
37008
|
+
return this.enqueueBucket(files, timeoutMs, maxRetries, binaryParams);
|
|
37009
|
+
default:
|
|
37010
|
+
assertUnreachable(this.config.mode);
|
|
37011
|
+
}
|
|
37123
37012
|
}
|
|
37124
37013
|
// -----------------------------------------------------------------------
|
|
37125
37014
|
// Single/Multiple mode: queue-based dispatch
|
|
37126
37015
|
// -----------------------------------------------------------------------
|
|
37127
37016
|
/** Enqueues a send and dispatches immediately if capacity allows. */
|
|
37128
|
-
enqueueSend(files,
|
|
37017
|
+
enqueueSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
37129
37018
|
return new Promise((resolve) => {
|
|
37130
37019
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37131
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37132
|
-
resolve(err(new Error(
|
|
37020
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
37021
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37133
37022
|
return;
|
|
37134
37023
|
}
|
|
37135
|
-
const entry = {
|
|
37136
|
-
files,
|
|
37137
|
-
timeoutMs: params.timeoutMs,
|
|
37138
|
-
maxRetries: params.maxRetries,
|
|
37139
|
-
calledAETitle: params.calledAETitle,
|
|
37140
|
-
callingAETitle: params.callingAETitle,
|
|
37141
|
-
required: params.required,
|
|
37142
|
-
proposedTransferSyntax: params.proposedTransferSyntax,
|
|
37143
|
-
combineProposedTransferSyntaxes: params.combineProposedTransferSyntaxes,
|
|
37144
|
-
resolve
|
|
37145
|
-
};
|
|
37024
|
+
const entry = { files, timeoutMs, maxRetries, binaryParams, resolve };
|
|
37146
37025
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37147
37026
|
void this.executeEntry(entry);
|
|
37148
37027
|
} else {
|
|
@@ -37162,27 +37041,16 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37162
37041
|
// Bucket mode: accumulate-then-flush
|
|
37163
37042
|
// -----------------------------------------------------------------------
|
|
37164
37043
|
/** Adds files to the current bucket and triggers flush if full. */
|
|
37165
|
-
enqueueBucket(files,
|
|
37044
|
+
enqueueBucket(files, timeoutMs, maxRetries, binaryParams) {
|
|
37166
37045
|
return new Promise((resolve) => {
|
|
37167
37046
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37168
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37169
|
-
resolve(err(new Error(
|
|
37047
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
37048
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37170
37049
|
return;
|
|
37171
37050
|
}
|
|
37172
|
-
|
|
37173
|
-
this.currentBucket.push({
|
|
37174
|
-
files,
|
|
37175
|
-
resolve,
|
|
37176
|
-
timeoutMs,
|
|
37177
|
-
maxRetries,
|
|
37178
|
-
calledAETitle,
|
|
37179
|
-
callingAETitle,
|
|
37180
|
-
required,
|
|
37181
|
-
proposedTransferSyntax,
|
|
37182
|
-
combineProposedTransferSyntaxes
|
|
37183
|
-
});
|
|
37051
|
+
this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, binaryParams });
|
|
37184
37052
|
const totalFiles = this.countBucketFiles();
|
|
37185
|
-
if (totalFiles >= this.maxBucketSize) {
|
|
37053
|
+
if (totalFiles >= this.config.maxBucketSize) {
|
|
37186
37054
|
this.clearBucketTimer();
|
|
37187
37055
|
void this.flushBucketInternal("maxSize");
|
|
37188
37056
|
} else {
|
|
@@ -37214,11 +37082,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37214
37082
|
files: allFiles,
|
|
37215
37083
|
timeoutMs,
|
|
37216
37084
|
maxRetries,
|
|
37217
|
-
|
|
37218
|
-
callingAETitle: entries[0]?.callingAETitle,
|
|
37219
|
-
required: entries[0]?.required,
|
|
37220
|
-
proposedTransferSyntax: entries[0]?.proposedTransferSyntax,
|
|
37221
|
-
combineProposedTransferSyntaxes: entries[0]?.combineProposedTransferSyntaxes,
|
|
37085
|
+
binaryParams: entries[0].binaryParams,
|
|
37222
37086
|
resolve: (result) => {
|
|
37223
37087
|
for (let i = 0; i < entries.length; i++) {
|
|
37224
37088
|
entries[i].resolve(result);
|
|
@@ -37232,7 +37096,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37232
37096
|
const entries = [...this.currentBucket];
|
|
37233
37097
|
this.currentBucket = [];
|
|
37234
37098
|
const bucketEntry = this.mergeBucketEntries(entries);
|
|
37235
|
-
this.
|
|
37099
|
+
this.config.emitters.emitBucketFlushed({ fileCount: bucketEntry.files.length, reason });
|
|
37236
37100
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37237
37101
|
void this.executeEntry(bucketEntry);
|
|
37238
37102
|
} else {
|
|
@@ -37245,7 +37109,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37245
37109
|
this.bucketTimer = setTimeout(() => {
|
|
37246
37110
|
this.bucketTimer = void 0;
|
|
37247
37111
|
void this.flushBucketInternal("timer");
|
|
37248
|
-
}, this.bucketFlushMs);
|
|
37112
|
+
}, this.config.bucketFlushMs);
|
|
37249
37113
|
}
|
|
37250
37114
|
/** Clears the bucket flush timer. */
|
|
37251
37115
|
clearBucketTimer() {
|
|
@@ -37257,7 +37121,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37257
37121
|
// -----------------------------------------------------------------------
|
|
37258
37122
|
// Core send execution with retry
|
|
37259
37123
|
// -----------------------------------------------------------------------
|
|
37260
|
-
/** Executes a single queue entry: calls
|
|
37124
|
+
/** Executes a single queue entry: calls the binary with retry. */
|
|
37261
37125
|
async executeEntry(entry) {
|
|
37262
37126
|
this.activeAssociations++;
|
|
37263
37127
|
const startMs = Date.now();
|
|
@@ -37266,7 +37130,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37266
37130
|
if (failure === void 0) return;
|
|
37267
37131
|
this.activeAssociations--;
|
|
37268
37132
|
this.recordFailure();
|
|
37269
|
-
this.
|
|
37133
|
+
this.config.emitters.emitSendFailed({
|
|
37270
37134
|
files: entry.files,
|
|
37271
37135
|
error: failure.error,
|
|
37272
37136
|
attempts: maxAttempts,
|
|
@@ -37276,49 +37140,27 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37276
37140
|
entry.resolve(err(failure.error));
|
|
37277
37141
|
this.drainQueue();
|
|
37278
37142
|
}
|
|
37279
|
-
/** Attempts
|
|
37143
|
+
/** Attempts the binary call up to maxAttempts times. Returns undefined on success. */
|
|
37280
37144
|
async attemptSend(entry, maxAttempts, startMs) {
|
|
37281
37145
|
let lastError;
|
|
37282
37146
|
const lastOutput = { stdout: "", stderr: "" };
|
|
37283
37147
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
37284
37148
|
if (this.isStopped) {
|
|
37285
37149
|
this.activeAssociations--;
|
|
37286
|
-
entry.resolve(err(new Error(
|
|
37150
|
+
entry.resolve(err(new Error(`${this.config.senderName}: sender stopped`)));
|
|
37287
37151
|
return void 0;
|
|
37288
37152
|
}
|
|
37289
|
-
const result = await this.
|
|
37153
|
+
const result = await this.config.executor(entry.files, entry.timeoutMs, entry.binaryParams, this.config.signal);
|
|
37290
37154
|
if (result.ok) {
|
|
37291
37155
|
this.handleSendSuccess(entry, startMs, result.value);
|
|
37292
37156
|
return void 0;
|
|
37293
37157
|
}
|
|
37294
37158
|
lastError = result.error;
|
|
37295
37159
|
if (attempt < maxAttempts - 1) {
|
|
37296
|
-
await delay2(this.retryDelayMs * (attempt + 1));
|
|
37160
|
+
await delay2(this.config.retryDelayMs * (attempt + 1));
|
|
37297
37161
|
}
|
|
37298
37162
|
}
|
|
37299
|
-
return { error: lastError ?? new Error(
|
|
37300
|
-
}
|
|
37301
|
-
/** Calls storescu with the configured options. */
|
|
37302
|
-
callStorescu(entry) {
|
|
37303
|
-
return storescu({
|
|
37304
|
-
host: this.options.host,
|
|
37305
|
-
port: this.options.port,
|
|
37306
|
-
files: [...entry.files],
|
|
37307
|
-
calledAETitle: entry.calledAETitle ?? this.options.calledAETitle,
|
|
37308
|
-
callingAETitle: entry.callingAETitle ?? this.options.callingAETitle,
|
|
37309
|
-
proposedTransferSyntax: entry.proposedTransferSyntax ?? this.options.proposedTransferSyntax,
|
|
37310
|
-
combineProposedTransferSyntaxes: entry.combineProposedTransferSyntaxes ?? this.options.combineProposedTransferSyntaxes,
|
|
37311
|
-
maxPduReceive: this.options.maxPduReceive,
|
|
37312
|
-
maxPduSend: this.options.maxPduSend,
|
|
37313
|
-
associationTimeout: this.options.associationTimeout,
|
|
37314
|
-
acseTimeout: this.options.acseTimeout,
|
|
37315
|
-
dimseTimeout: this.options.dimseTimeout,
|
|
37316
|
-
noHostnameLookup: this.options.noHostnameLookup,
|
|
37317
|
-
verbosity: this.options.verbosity,
|
|
37318
|
-
required: entry.required ?? this.options.required,
|
|
37319
|
-
timeoutMs: entry.timeoutMs,
|
|
37320
|
-
signal: this.options.signal
|
|
37321
|
-
});
|
|
37163
|
+
return { error: lastError ?? new Error(`${this.config.senderName}: send failed`), output: lastOutput };
|
|
37322
37164
|
}
|
|
37323
37165
|
/** Handles a successful send: updates state, emits event, resolves promise. */
|
|
37324
37166
|
handleSendSuccess(entry, startMs, output) {
|
|
@@ -37326,7 +37168,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37326
37168
|
const durationMs = Date.now() - startMs;
|
|
37327
37169
|
this.recordSuccess();
|
|
37328
37170
|
const data = { files: entry.files, fileCount: entry.files.length, durationMs, stdout: output.stdout, stderr: output.stderr };
|
|
37329
|
-
this.
|
|
37171
|
+
this.config.emitters.emitSendComplete(data);
|
|
37330
37172
|
entry.resolve(ok(data));
|
|
37331
37173
|
this.drainQueue();
|
|
37332
37174
|
}
|
|
@@ -37344,8 +37186,8 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37344
37186
|
if (this.health === SenderHealth.DOWN) {
|
|
37345
37187
|
this.health = SenderHealth.DEGRADED;
|
|
37346
37188
|
} else {
|
|
37347
|
-
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.configuredMaxAssociations);
|
|
37348
|
-
if (this.effectiveMaxAssociations >= this.configuredMaxAssociations) {
|
|
37189
|
+
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.config.configuredMaxAssociations);
|
|
37190
|
+
if (this.effectiveMaxAssociations >= this.config.configuredMaxAssociations) {
|
|
37349
37191
|
this.health = SenderHealth.HEALTHY;
|
|
37350
37192
|
}
|
|
37351
37193
|
}
|
|
@@ -37365,22 +37207,26 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37365
37207
|
this.emitHealthChanged(previousHealth);
|
|
37366
37208
|
}
|
|
37367
37209
|
} else if (this.consecutiveFailures >= DEGRADE_THRESHOLD && this.consecutiveFailures % DEGRADE_THRESHOLD === 0) {
|
|
37368
|
-
|
|
37369
|
-
|
|
37370
|
-
|
|
37210
|
+
this.degradeHealth(previousHealth);
|
|
37211
|
+
}
|
|
37212
|
+
}
|
|
37213
|
+
/** Handles HEALTHY→DEGRADED or DEGRADED→DEGRADED transitions. */
|
|
37214
|
+
degradeHealth(previousHealth) {
|
|
37215
|
+
if (this.health === SenderHealth.HEALTHY) {
|
|
37216
|
+
this.health = SenderHealth.DEGRADED;
|
|
37217
|
+
this.effectiveMaxAssociations = Math.max(1, Math.floor(this.config.configuredMaxAssociations / 2));
|
|
37218
|
+
this.emitHealthChanged(previousHealth);
|
|
37219
|
+
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37220
|
+
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37221
|
+
if (newMax !== this.effectiveMaxAssociations) {
|
|
37222
|
+
this.effectiveMaxAssociations = newMax;
|
|
37371
37223
|
this.emitHealthChanged(previousHealth);
|
|
37372
|
-
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37373
|
-
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37374
|
-
if (newMax !== this.effectiveMaxAssociations) {
|
|
37375
|
-
this.effectiveMaxAssociations = newMax;
|
|
37376
|
-
this.emitHealthChanged(previousHealth);
|
|
37377
|
-
}
|
|
37378
37224
|
}
|
|
37379
37225
|
}
|
|
37380
37226
|
}
|
|
37381
|
-
/** Emits a HEALTH_CHANGED event. */
|
|
37227
|
+
/** Emits a HEALTH_CHANGED event via the wrapper's emitter. */
|
|
37382
37228
|
emitHealthChanged(previousHealth) {
|
|
37383
|
-
this.
|
|
37229
|
+
this.config.emitters.emitHealthChanged({
|
|
37384
37230
|
previousHealth,
|
|
37385
37231
|
newHealth: this.health,
|
|
37386
37232
|
effectiveMaxAssociations: this.effectiveMaxAssociations,
|
|
@@ -37420,20 +37266,6 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37420
37266
|
check();
|
|
37421
37267
|
});
|
|
37422
37268
|
}
|
|
37423
|
-
// -----------------------------------------------------------------------
|
|
37424
|
-
// Abort signal
|
|
37425
|
-
// -----------------------------------------------------------------------
|
|
37426
|
-
/** Wires an AbortSignal to stop the sender. */
|
|
37427
|
-
wireAbortSignal(signal) {
|
|
37428
|
-
if (signal.aborted) {
|
|
37429
|
-
void this.stop();
|
|
37430
|
-
return;
|
|
37431
|
-
}
|
|
37432
|
-
this.abortHandler = () => {
|
|
37433
|
-
void this.stop();
|
|
37434
|
-
};
|
|
37435
|
-
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37436
|
-
}
|
|
37437
37269
|
};
|
|
37438
37270
|
function delay2(ms) {
|
|
37439
37271
|
return new Promise((resolve) => {
|
|
@@ -37441,6 +37273,492 @@ function delay2(ms) {
|
|
|
37441
37273
|
});
|
|
37442
37274
|
}
|
|
37443
37275
|
|
|
37276
|
+
// src/senders/DicomSender.ts
|
|
37277
|
+
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
37278
|
+
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
37279
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
37280
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
37281
|
+
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
37282
|
+
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
37283
|
+
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
37284
|
+
var DicomSenderOptionsSchema = zod.z.object({
|
|
37285
|
+
host: zod.z.string().min(1),
|
|
37286
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
37287
|
+
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37288
|
+
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37289
|
+
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37290
|
+
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
37291
|
+
proposedTransferSyntax: zod.z.union([zod.z.enum(PROPOSED_TS_VALUES), zod.z.array(zod.z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
37292
|
+
combineProposedTransferSyntaxes: zod.z.boolean().optional(),
|
|
37293
|
+
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
37294
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
37295
|
+
maxRetries: zod.z.number().int().min(0).optional(),
|
|
37296
|
+
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
37297
|
+
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
37298
|
+
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
37299
|
+
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37300
|
+
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37301
|
+
associationTimeout: zod.z.number().int().positive().optional(),
|
|
37302
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
37303
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
37304
|
+
noHostnameLookup: zod.z.boolean().optional(),
|
|
37305
|
+
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
37306
|
+
required: zod.z.boolean().optional(),
|
|
37307
|
+
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37308
|
+
}).strict();
|
|
37309
|
+
function createStorescuExecutor(options) {
|
|
37310
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37311
|
+
const result = await storescu({
|
|
37312
|
+
host: options.host,
|
|
37313
|
+
port: options.port,
|
|
37314
|
+
files: [...files],
|
|
37315
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37316
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37317
|
+
proposedTransferSyntax: params.proposedTransferSyntax ?? options.proposedTransferSyntax,
|
|
37318
|
+
combineProposedTransferSyntaxes: params.combineProposedTransferSyntaxes ?? options.combineProposedTransferSyntaxes,
|
|
37319
|
+
maxPduReceive: options.maxPduReceive,
|
|
37320
|
+
maxPduSend: options.maxPduSend,
|
|
37321
|
+
associationTimeout: options.associationTimeout,
|
|
37322
|
+
acseTimeout: options.acseTimeout,
|
|
37323
|
+
dimseTimeout: options.dimseTimeout,
|
|
37324
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37325
|
+
verbosity: options.verbosity,
|
|
37326
|
+
required: params.required ?? options.required,
|
|
37327
|
+
timeoutMs,
|
|
37328
|
+
signal
|
|
37329
|
+
});
|
|
37330
|
+
if (!result.ok) return err(result.error);
|
|
37331
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37332
|
+
};
|
|
37333
|
+
}
|
|
37334
|
+
function resolveConfig(options) {
|
|
37335
|
+
const mode = options.mode ?? "multiple";
|
|
37336
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37337
|
+
return {
|
|
37338
|
+
mode,
|
|
37339
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37340
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
37341
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37342
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
37343
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
37344
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
37345
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
37346
|
+
};
|
|
37347
|
+
}
|
|
37348
|
+
var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
37349
|
+
constructor(engine, cfg, signal) {
|
|
37350
|
+
super();
|
|
37351
|
+
__publicField(this, "engine");
|
|
37352
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37353
|
+
__publicField(this, "defaultMaxRetries");
|
|
37354
|
+
__publicField(this, "signal");
|
|
37355
|
+
__publicField(this, "abortHandler");
|
|
37356
|
+
this.setMaxListeners(20);
|
|
37357
|
+
this.on("error", () => {
|
|
37358
|
+
});
|
|
37359
|
+
this.engine = engine;
|
|
37360
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37361
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37362
|
+
this.signal = signal;
|
|
37363
|
+
if (signal !== void 0) {
|
|
37364
|
+
this.wireAbortSignal(signal);
|
|
37365
|
+
}
|
|
37366
|
+
}
|
|
37367
|
+
// -----------------------------------------------------------------------
|
|
37368
|
+
// Public API
|
|
37369
|
+
// -----------------------------------------------------------------------
|
|
37370
|
+
/**
|
|
37371
|
+
* Creates a new DicomSender instance.
|
|
37372
|
+
*
|
|
37373
|
+
* @param options - Configuration options
|
|
37374
|
+
* @returns A Result containing the instance or a validation error
|
|
37375
|
+
*/
|
|
37376
|
+
static create(options) {
|
|
37377
|
+
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
37378
|
+
if (!validation.success) {
|
|
37379
|
+
return err(createValidationError("DicomSender", validation.error));
|
|
37380
|
+
}
|
|
37381
|
+
const cfg = resolveConfig(options);
|
|
37382
|
+
const senderRef = { current: void 0 };
|
|
37383
|
+
const engine = new SenderEngine({
|
|
37384
|
+
...cfg,
|
|
37385
|
+
executor: createStorescuExecutor(options),
|
|
37386
|
+
signal: options.signal,
|
|
37387
|
+
senderName: "DicomSender",
|
|
37388
|
+
emitters: {
|
|
37389
|
+
emitSendComplete: (data) => {
|
|
37390
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37391
|
+
},
|
|
37392
|
+
emitSendFailed: (data) => {
|
|
37393
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37394
|
+
},
|
|
37395
|
+
emitHealthChanged: (data) => {
|
|
37396
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37397
|
+
},
|
|
37398
|
+
emitBucketFlushed: (data) => {
|
|
37399
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37400
|
+
}
|
|
37401
|
+
}
|
|
37402
|
+
});
|
|
37403
|
+
const sender = new _DicomSender(engine, cfg, options.signal);
|
|
37404
|
+
senderRef.current = sender;
|
|
37405
|
+
return ok(sender);
|
|
37406
|
+
}
|
|
37407
|
+
/**
|
|
37408
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37409
|
+
*
|
|
37410
|
+
* In single/multiple mode, files are sent as one storescu call.
|
|
37411
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37412
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37413
|
+
*
|
|
37414
|
+
* The returned promise resolves when the files are actually sent
|
|
37415
|
+
* (not just queued). Callers can await for confirmation or
|
|
37416
|
+
* fire-and-forget with `void sender.send(files)`.
|
|
37417
|
+
*
|
|
37418
|
+
* @param files - One or more DICOM file paths
|
|
37419
|
+
* @param options - Per-send overrides
|
|
37420
|
+
* @returns A Result containing the send result or an error
|
|
37421
|
+
*/
|
|
37422
|
+
send(files, options) {
|
|
37423
|
+
const params = buildStorescuParams(options);
|
|
37424
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37425
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37426
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37427
|
+
}
|
|
37428
|
+
/**
|
|
37429
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37430
|
+
* In single/multiple mode this is a no-op.
|
|
37431
|
+
*/
|
|
37432
|
+
flush() {
|
|
37433
|
+
this.engine.flush();
|
|
37434
|
+
}
|
|
37435
|
+
/**
|
|
37436
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37437
|
+
* waits for active associations to complete.
|
|
37438
|
+
*/
|
|
37439
|
+
async stop() {
|
|
37440
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37441
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37442
|
+
}
|
|
37443
|
+
await this.engine.stop();
|
|
37444
|
+
}
|
|
37445
|
+
/** Current sender status. */
|
|
37446
|
+
get status() {
|
|
37447
|
+
return this.engine.status;
|
|
37448
|
+
}
|
|
37449
|
+
// -----------------------------------------------------------------------
|
|
37450
|
+
// Typed event listener convenience methods
|
|
37451
|
+
// -----------------------------------------------------------------------
|
|
37452
|
+
/**
|
|
37453
|
+
* Registers a typed listener for a DicomSender-specific event.
|
|
37454
|
+
*
|
|
37455
|
+
* @param event - The event name from DicomSenderEventMap
|
|
37456
|
+
* @param listener - Callback receiving typed event data
|
|
37457
|
+
* @returns this for chaining
|
|
37458
|
+
*/
|
|
37459
|
+
onEvent(event, listener) {
|
|
37460
|
+
return this.on(event, listener);
|
|
37461
|
+
}
|
|
37462
|
+
/**
|
|
37463
|
+
* Registers a listener for successful sends.
|
|
37464
|
+
*
|
|
37465
|
+
* @param listener - Callback receiving send complete data
|
|
37466
|
+
* @returns this for chaining
|
|
37467
|
+
*/
|
|
37468
|
+
onSendComplete(listener) {
|
|
37469
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37470
|
+
}
|
|
37471
|
+
/**
|
|
37472
|
+
* Registers a listener for failed sends.
|
|
37473
|
+
*
|
|
37474
|
+
* @param listener - Callback receiving send failed data
|
|
37475
|
+
* @returns this for chaining
|
|
37476
|
+
*/
|
|
37477
|
+
onSendFailed(listener) {
|
|
37478
|
+
return this.on("SEND_FAILED", listener);
|
|
37479
|
+
}
|
|
37480
|
+
/**
|
|
37481
|
+
* Registers a listener for health state changes.
|
|
37482
|
+
*
|
|
37483
|
+
* @param listener - Callback receiving health change data
|
|
37484
|
+
* @returns this for chaining
|
|
37485
|
+
*/
|
|
37486
|
+
onHealthChanged(listener) {
|
|
37487
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37488
|
+
}
|
|
37489
|
+
/**
|
|
37490
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37491
|
+
*
|
|
37492
|
+
* @param listener - Callback receiving bucket flush data
|
|
37493
|
+
* @returns this for chaining
|
|
37494
|
+
*/
|
|
37495
|
+
onBucketFlushed(listener) {
|
|
37496
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37497
|
+
}
|
|
37498
|
+
// -----------------------------------------------------------------------
|
|
37499
|
+
// Abort signal
|
|
37500
|
+
// -----------------------------------------------------------------------
|
|
37501
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37502
|
+
wireAbortSignal(signal) {
|
|
37503
|
+
if (signal.aborted) {
|
|
37504
|
+
void this.stop();
|
|
37505
|
+
return;
|
|
37506
|
+
}
|
|
37507
|
+
this.abortHandler = () => {
|
|
37508
|
+
void this.stop();
|
|
37509
|
+
};
|
|
37510
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37511
|
+
}
|
|
37512
|
+
};
|
|
37513
|
+
function buildStorescuParams(options) {
|
|
37514
|
+
return {
|
|
37515
|
+
calledAETitle: options?.calledAETitle,
|
|
37516
|
+
callingAETitle: options?.callingAETitle,
|
|
37517
|
+
required: options?.required,
|
|
37518
|
+
proposedTransferSyntax: options?.proposedTransferSyntax,
|
|
37519
|
+
combineProposedTransferSyntaxes: options?.combineProposedTransferSyntaxes
|
|
37520
|
+
};
|
|
37521
|
+
}
|
|
37522
|
+
var DEFAULT_MAX_ASSOCIATIONS2 = 4;
|
|
37523
|
+
var DEFAULT_MAX_QUEUE_LENGTH2 = 1e3;
|
|
37524
|
+
var DEFAULT_MAX_RETRIES2 = 3;
|
|
37525
|
+
var DEFAULT_RETRY_DELAY_MS2 = 1e3;
|
|
37526
|
+
var DEFAULT_BUCKET_FLUSH_MS2 = 5e3;
|
|
37527
|
+
var DEFAULT_MAX_BUCKET_SIZE2 = 50;
|
|
37528
|
+
var MAX_ASSOCIATIONS_LIMIT2 = 64;
|
|
37529
|
+
var DicomSendOptionsSchema = zod.z.object({
|
|
37530
|
+
host: zod.z.string().min(1),
|
|
37531
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
37532
|
+
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37533
|
+
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37534
|
+
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37535
|
+
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT2).optional(),
|
|
37536
|
+
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
37537
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
37538
|
+
maxRetries: zod.z.number().int().min(0).optional(),
|
|
37539
|
+
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
37540
|
+
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
37541
|
+
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
37542
|
+
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37543
|
+
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37544
|
+
associationTimeout: zod.z.number().int().positive().optional(),
|
|
37545
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
37546
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
37547
|
+
noHostnameLookup: zod.z.boolean().optional(),
|
|
37548
|
+
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
37549
|
+
noHalt: zod.z.boolean().optional(),
|
|
37550
|
+
noIllegalProposal: zod.z.boolean().optional(),
|
|
37551
|
+
decompress: zod.z.enum(["never", "lossless", "lossy"]).optional(),
|
|
37552
|
+
multiAssociations: zod.z.boolean().optional(),
|
|
37553
|
+
noUidChecks: zod.z.boolean().optional(),
|
|
37554
|
+
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37555
|
+
}).strict();
|
|
37556
|
+
function createDcmsendExecutor(options) {
|
|
37557
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37558
|
+
const result = await dcmsend({
|
|
37559
|
+
host: options.host,
|
|
37560
|
+
port: options.port,
|
|
37561
|
+
files: [...files],
|
|
37562
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37563
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37564
|
+
maxPduReceive: options.maxPduReceive,
|
|
37565
|
+
maxPduSend: options.maxPduSend,
|
|
37566
|
+
associationTimeout: options.associationTimeout,
|
|
37567
|
+
acseTimeout: options.acseTimeout,
|
|
37568
|
+
dimseTimeout: options.dimseTimeout,
|
|
37569
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37570
|
+
verbosity: options.verbosity,
|
|
37571
|
+
noHalt: options.noHalt,
|
|
37572
|
+
noIllegalProposal: options.noIllegalProposal,
|
|
37573
|
+
decompress: options.decompress,
|
|
37574
|
+
multiAssociations: options.multiAssociations,
|
|
37575
|
+
noUidChecks: options.noUidChecks,
|
|
37576
|
+
timeoutMs,
|
|
37577
|
+
signal
|
|
37578
|
+
});
|
|
37579
|
+
if (!result.ok) return err(result.error);
|
|
37580
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37581
|
+
};
|
|
37582
|
+
}
|
|
37583
|
+
function resolveConfig2(options) {
|
|
37584
|
+
const mode = options.mode ?? "multiple";
|
|
37585
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37586
|
+
return {
|
|
37587
|
+
mode,
|
|
37588
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37589
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH2,
|
|
37590
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37591
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES2,
|
|
37592
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS2,
|
|
37593
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS2,
|
|
37594
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE2
|
|
37595
|
+
};
|
|
37596
|
+
}
|
|
37597
|
+
var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
37598
|
+
constructor(engine, cfg, signal) {
|
|
37599
|
+
super();
|
|
37600
|
+
__publicField(this, "engine");
|
|
37601
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37602
|
+
__publicField(this, "defaultMaxRetries");
|
|
37603
|
+
__publicField(this, "signal");
|
|
37604
|
+
__publicField(this, "abortHandler");
|
|
37605
|
+
this.setMaxListeners(20);
|
|
37606
|
+
this.on("error", () => {
|
|
37607
|
+
});
|
|
37608
|
+
this.engine = engine;
|
|
37609
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37610
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37611
|
+
this.signal = signal;
|
|
37612
|
+
if (signal !== void 0) {
|
|
37613
|
+
this.wireAbortSignal(signal);
|
|
37614
|
+
}
|
|
37615
|
+
}
|
|
37616
|
+
// -----------------------------------------------------------------------
|
|
37617
|
+
// Public API
|
|
37618
|
+
// -----------------------------------------------------------------------
|
|
37619
|
+
/**
|
|
37620
|
+
* Creates a new DicomSend instance.
|
|
37621
|
+
*
|
|
37622
|
+
* @param options - Configuration options
|
|
37623
|
+
* @returns A Result containing the instance or a validation error
|
|
37624
|
+
*/
|
|
37625
|
+
static create(options) {
|
|
37626
|
+
const validation = DicomSendOptionsSchema.safeParse(options);
|
|
37627
|
+
if (!validation.success) {
|
|
37628
|
+
return err(createValidationError("DicomSend", validation.error));
|
|
37629
|
+
}
|
|
37630
|
+
const cfg = resolveConfig2(options);
|
|
37631
|
+
const senderRef = { current: void 0 };
|
|
37632
|
+
const engine = new SenderEngine({
|
|
37633
|
+
...cfg,
|
|
37634
|
+
executor: createDcmsendExecutor(options),
|
|
37635
|
+
signal: options.signal,
|
|
37636
|
+
senderName: "DicomSend",
|
|
37637
|
+
emitters: {
|
|
37638
|
+
emitSendComplete: (data) => {
|
|
37639
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37640
|
+
},
|
|
37641
|
+
emitSendFailed: (data) => {
|
|
37642
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37643
|
+
},
|
|
37644
|
+
emitHealthChanged: (data) => {
|
|
37645
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37646
|
+
},
|
|
37647
|
+
emitBucketFlushed: (data) => {
|
|
37648
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37649
|
+
}
|
|
37650
|
+
}
|
|
37651
|
+
});
|
|
37652
|
+
const sender = new _DicomSend(engine, cfg, options.signal);
|
|
37653
|
+
senderRef.current = sender;
|
|
37654
|
+
return ok(sender);
|
|
37655
|
+
}
|
|
37656
|
+
/**
|
|
37657
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37658
|
+
*
|
|
37659
|
+
* In single/multiple mode, files are sent as one dcmsend call.
|
|
37660
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37661
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37662
|
+
*
|
|
37663
|
+
* @param files - One or more DICOM file paths
|
|
37664
|
+
* @param options - Per-send overrides
|
|
37665
|
+
* @returns A Result containing the send result or an error
|
|
37666
|
+
*/
|
|
37667
|
+
send(files, options) {
|
|
37668
|
+
const params = {
|
|
37669
|
+
calledAETitle: options?.calledAETitle,
|
|
37670
|
+
callingAETitle: options?.callingAETitle
|
|
37671
|
+
};
|
|
37672
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37673
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37674
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37675
|
+
}
|
|
37676
|
+
/**
|
|
37677
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37678
|
+
* In single/multiple mode this is a no-op.
|
|
37679
|
+
*/
|
|
37680
|
+
flush() {
|
|
37681
|
+
this.engine.flush();
|
|
37682
|
+
}
|
|
37683
|
+
/**
|
|
37684
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37685
|
+
* waits for active associations to complete.
|
|
37686
|
+
*/
|
|
37687
|
+
async stop() {
|
|
37688
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37689
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37690
|
+
}
|
|
37691
|
+
await this.engine.stop();
|
|
37692
|
+
}
|
|
37693
|
+
/** Current sender status. */
|
|
37694
|
+
get status() {
|
|
37695
|
+
return this.engine.status;
|
|
37696
|
+
}
|
|
37697
|
+
// -----------------------------------------------------------------------
|
|
37698
|
+
// Typed event listener convenience methods
|
|
37699
|
+
// -----------------------------------------------------------------------
|
|
37700
|
+
/**
|
|
37701
|
+
* Registers a typed listener for a DicomSend-specific event.
|
|
37702
|
+
*
|
|
37703
|
+
* @param event - The event name from DicomSendEventMap
|
|
37704
|
+
* @param listener - Callback receiving typed event data
|
|
37705
|
+
* @returns this for chaining
|
|
37706
|
+
*/
|
|
37707
|
+
onEvent(event, listener) {
|
|
37708
|
+
return this.on(event, listener);
|
|
37709
|
+
}
|
|
37710
|
+
/**
|
|
37711
|
+
* Registers a listener for successful sends.
|
|
37712
|
+
*
|
|
37713
|
+
* @param listener - Callback receiving send complete data
|
|
37714
|
+
* @returns this for chaining
|
|
37715
|
+
*/
|
|
37716
|
+
onSendComplete(listener) {
|
|
37717
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37718
|
+
}
|
|
37719
|
+
/**
|
|
37720
|
+
* Registers a listener for failed sends.
|
|
37721
|
+
*
|
|
37722
|
+
* @param listener - Callback receiving send failed data
|
|
37723
|
+
* @returns this for chaining
|
|
37724
|
+
*/
|
|
37725
|
+
onSendFailed(listener) {
|
|
37726
|
+
return this.on("SEND_FAILED", listener);
|
|
37727
|
+
}
|
|
37728
|
+
/**
|
|
37729
|
+
* Registers a listener for health state changes.
|
|
37730
|
+
*
|
|
37731
|
+
* @param listener - Callback receiving health change data
|
|
37732
|
+
* @returns this for chaining
|
|
37733
|
+
*/
|
|
37734
|
+
onHealthChanged(listener) {
|
|
37735
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37736
|
+
}
|
|
37737
|
+
/**
|
|
37738
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37739
|
+
*
|
|
37740
|
+
* @param listener - Callback receiving bucket flush data
|
|
37741
|
+
* @returns this for chaining
|
|
37742
|
+
*/
|
|
37743
|
+
onBucketFlushed(listener) {
|
|
37744
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37745
|
+
}
|
|
37746
|
+
// -----------------------------------------------------------------------
|
|
37747
|
+
// Abort signal
|
|
37748
|
+
// -----------------------------------------------------------------------
|
|
37749
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37750
|
+
wireAbortSignal(signal) {
|
|
37751
|
+
if (signal.aborted) {
|
|
37752
|
+
void this.stop();
|
|
37753
|
+
return;
|
|
37754
|
+
}
|
|
37755
|
+
this.abortHandler = () => {
|
|
37756
|
+
void this.stop();
|
|
37757
|
+
};
|
|
37758
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37759
|
+
}
|
|
37760
|
+
};
|
|
37761
|
+
|
|
37444
37762
|
// src/pacs/types.ts
|
|
37445
37763
|
var QueryLevel = {
|
|
37446
37764
|
STUDY: "STUDY",
|
|
@@ -37990,7 +38308,7 @@ var DEFAULT_CONFIG = {
|
|
|
37990
38308
|
signal: void 0,
|
|
37991
38309
|
onRetry: void 0
|
|
37992
38310
|
};
|
|
37993
|
-
function
|
|
38311
|
+
function resolveConfig3(opts) {
|
|
37994
38312
|
if (!opts) return DEFAULT_CONFIG;
|
|
37995
38313
|
return {
|
|
37996
38314
|
...DEFAULT_CONFIG,
|
|
@@ -38035,7 +38353,7 @@ function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
|
38035
38353
|
return false;
|
|
38036
38354
|
}
|
|
38037
38355
|
async function retry(operation, options) {
|
|
38038
|
-
const config =
|
|
38356
|
+
const config = resolveConfig3(options);
|
|
38039
38357
|
let lastResult = err(new Error("No attempts executed"));
|
|
38040
38358
|
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38041
38359
|
lastResult = await operation();
|
|
@@ -38083,6 +38401,7 @@ exports.DcmtkProcess = DcmtkProcess;
|
|
|
38083
38401
|
exports.DicomDataset = DicomDataset;
|
|
38084
38402
|
exports.DicomInstance = DicomInstance;
|
|
38085
38403
|
exports.DicomReceiver = DicomReceiver;
|
|
38404
|
+
exports.DicomSend = DicomSend;
|
|
38086
38405
|
exports.DicomSender = DicomSender;
|
|
38087
38406
|
exports.DicomTagPathSchema = DicomTagPathSchema;
|
|
38088
38407
|
exports.DicomTagSchema = DicomTagSchema;
|