@ubercode/dcmtk 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +636 -297
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +206 -57
- package/dist/index.d.ts +206 -57
- package/dist/index.js +636 -298
- package/dist/index.js.map +1 -1
- package/dist/tools.cjs +78 -16
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +29 -2
- package/dist/tools.d.ts +29 -2
- package/dist/tools.js +78 -16
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33131,6 +33131,30 @@ async function echoscu(options) {
|
|
|
33131
33131
|
return ok({ success: true, stderr: result.value.stderr });
|
|
33132
33132
|
}
|
|
33133
33133
|
var VERBOSITY_FLAGS19 = { verbose: "-v", debug: "-d" };
|
|
33134
|
+
var DECOMPRESS_FLAGS = {
|
|
33135
|
+
never: "--decompress-never",
|
|
33136
|
+
lossless: "--decompress-lossless",
|
|
33137
|
+
lossy: "--decompress-lossy"
|
|
33138
|
+
};
|
|
33139
|
+
function pushSendModeArgs(args, options) {
|
|
33140
|
+
if (options.noHalt === true) {
|
|
33141
|
+
args.push("--no-halt");
|
|
33142
|
+
}
|
|
33143
|
+
if (options.noIllegalProposal === true) {
|
|
33144
|
+
args.push("--no-illegal-proposal");
|
|
33145
|
+
}
|
|
33146
|
+
if (options.decompress !== void 0) {
|
|
33147
|
+
args.push(DECOMPRESS_FLAGS[options.decompress]);
|
|
33148
|
+
}
|
|
33149
|
+
if (options.multiAssociations === true) {
|
|
33150
|
+
args.push("+ma");
|
|
33151
|
+
} else if (options.multiAssociations === false) {
|
|
33152
|
+
args.push("-ma");
|
|
33153
|
+
}
|
|
33154
|
+
if (options.createReportFile !== void 0) {
|
|
33155
|
+
args.push("--create-report-file", options.createReportFile);
|
|
33156
|
+
}
|
|
33157
|
+
}
|
|
33134
33158
|
var DcmsendOptionsSchema = zod.z.object({
|
|
33135
33159
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
33136
33160
|
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
@@ -33140,8 +33164,15 @@ var DcmsendOptionsSchema = zod.z.object({
|
|
|
33140
33164
|
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33141
33165
|
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
33142
33166
|
scanDirectory: zod.z.boolean().optional(),
|
|
33167
|
+
recurse: zod.z.boolean().optional(),
|
|
33168
|
+
scanPattern: zod.z.string().min(1).optional(),
|
|
33143
33169
|
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
33144
33170
|
noUidChecks: zod.z.boolean().optional(),
|
|
33171
|
+
noHalt: zod.z.boolean().optional(),
|
|
33172
|
+
noIllegalProposal: zod.z.boolean().optional(),
|
|
33173
|
+
decompress: zod.z.enum(["never", "lossless", "lossy"]).optional(),
|
|
33174
|
+
multiAssociations: zod.z.boolean().optional(),
|
|
33175
|
+
createReportFile: zod.z.string().min(1).optional(),
|
|
33145
33176
|
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
33146
33177
|
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
33147
33178
|
noHostnameLookup: zod.z.boolean().optional(),
|
|
@@ -33187,6 +33218,13 @@ function buildArgs33(options) {
|
|
|
33187
33218
|
if (options.scanDirectory === true) {
|
|
33188
33219
|
args.push("--scan-directories");
|
|
33189
33220
|
}
|
|
33221
|
+
if (options.recurse === true) {
|
|
33222
|
+
args.push("+r");
|
|
33223
|
+
}
|
|
33224
|
+
if (options.scanPattern !== void 0) {
|
|
33225
|
+
args.push("--scan-pattern", options.scanPattern);
|
|
33226
|
+
}
|
|
33227
|
+
pushSendModeArgs(args, options);
|
|
33190
33228
|
args.push(options.host, String(options.port));
|
|
33191
33229
|
args.push(...options.files);
|
|
33192
33230
|
return args;
|
|
@@ -33225,7 +33263,18 @@ var ProposedTransferSyntax = {
|
|
|
33225
33263
|
J2K_LOSSLESS: "j2kLossless",
|
|
33226
33264
|
J2K_LOSSY: "j2kLossy",
|
|
33227
33265
|
JLS_LOSSLESS: "jlsLossless",
|
|
33228
|
-
JLS_LOSSY: "jlsLossy"
|
|
33266
|
+
JLS_LOSSY: "jlsLossy",
|
|
33267
|
+
MPEG2: "mpeg2",
|
|
33268
|
+
MPEG2_HIGH: "mpeg2High",
|
|
33269
|
+
MPEG4: "mpeg4",
|
|
33270
|
+
MPEG4_BD: "mpeg4Bd",
|
|
33271
|
+
MPEG4_2_2D: "mpeg4_2_2d",
|
|
33272
|
+
MPEG4_2_3D: "mpeg4_2_3d",
|
|
33273
|
+
MPEG4_2_ST: "mpeg4_2_st",
|
|
33274
|
+
HEVC: "hevc",
|
|
33275
|
+
HEVC10: "hevc10",
|
|
33276
|
+
RLE: "rle",
|
|
33277
|
+
DEFLATED: "deflated"
|
|
33229
33278
|
};
|
|
33230
33279
|
var PROPOSED_TS_FLAG_MAP = {
|
|
33231
33280
|
[ProposedTransferSyntax.UNCOMPRESSED]: "-x=",
|
|
@@ -33238,8 +33287,20 @@ var PROPOSED_TS_FLAG_MAP = {
|
|
|
33238
33287
|
[ProposedTransferSyntax.J2K_LOSSLESS]: "-xv",
|
|
33239
33288
|
[ProposedTransferSyntax.J2K_LOSSY]: "-xw",
|
|
33240
33289
|
[ProposedTransferSyntax.JLS_LOSSLESS]: "-xt",
|
|
33241
|
-
[ProposedTransferSyntax.JLS_LOSSY]: "-xu"
|
|
33290
|
+
[ProposedTransferSyntax.JLS_LOSSY]: "-xu",
|
|
33291
|
+
[ProposedTransferSyntax.MPEG2]: "-xm",
|
|
33292
|
+
[ProposedTransferSyntax.MPEG2_HIGH]: "-xh",
|
|
33293
|
+
[ProposedTransferSyntax.MPEG4]: "-xn",
|
|
33294
|
+
[ProposedTransferSyntax.MPEG4_BD]: "-xl",
|
|
33295
|
+
[ProposedTransferSyntax.MPEG4_2_2D]: "-x2",
|
|
33296
|
+
[ProposedTransferSyntax.MPEG4_2_3D]: "-x3",
|
|
33297
|
+
[ProposedTransferSyntax.MPEG4_2_ST]: "-xo",
|
|
33298
|
+
[ProposedTransferSyntax.HEVC]: "-x4",
|
|
33299
|
+
[ProposedTransferSyntax.HEVC10]: "-x5",
|
|
33300
|
+
[ProposedTransferSyntax.RLE]: "-xr",
|
|
33301
|
+
[ProposedTransferSyntax.DEFLATED]: "-xd"
|
|
33242
33302
|
};
|
|
33303
|
+
var PROPOSED_TS_VALUES = Object.values(ProposedTransferSyntax);
|
|
33243
33304
|
var StorescuOptionsSchema = zod.z.object({
|
|
33244
33305
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
33245
33306
|
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
@@ -33258,20 +33319,18 @@ var StorescuOptionsSchema = zod.z.object({
|
|
|
33258
33319
|
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
33259
33320
|
noHostnameLookup: zod.z.boolean().optional(),
|
|
33260
33321
|
required: zod.z.boolean().optional(),
|
|
33261
|
-
proposedTransferSyntax: zod.z.enum(
|
|
33262
|
-
|
|
33263
|
-
"littleEndian",
|
|
33264
|
-
"bigEndian",
|
|
33265
|
-
"implicitVR",
|
|
33266
|
-
"jpegLossless",
|
|
33267
|
-
"jpeg8Bit",
|
|
33268
|
-
"jpeg12Bit",
|
|
33269
|
-
"j2kLossless",
|
|
33270
|
-
"j2kLossy",
|
|
33271
|
-
"jlsLossless",
|
|
33272
|
-
"jlsLossy"
|
|
33273
|
-
]).optional()
|
|
33322
|
+
proposedTransferSyntax: zod.z.union([zod.z.enum(PROPOSED_TS_VALUES), zod.z.array(zod.z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
33323
|
+
combineProposedTransferSyntaxes: zod.z.boolean().optional()
|
|
33274
33324
|
}).strict();
|
|
33325
|
+
function pushProposedTsArgs(args, ts) {
|
|
33326
|
+
if (typeof ts === "string") {
|
|
33327
|
+
args.push(PROPOSED_TS_FLAG_MAP[ts]);
|
|
33328
|
+
} else {
|
|
33329
|
+
for (let i = 0; i < ts.length; i++) {
|
|
33330
|
+
args.push(PROPOSED_TS_FLAG_MAP[ts[i]]);
|
|
33331
|
+
}
|
|
33332
|
+
}
|
|
33333
|
+
}
|
|
33275
33334
|
var VERBOSITY_FLAGS20 = { verbose: "-v", debug: "-d" };
|
|
33276
33335
|
var DIMSE_ERROR_PATTERN = /^E:.*(?:DIMSE Failed|Store Failed)/m;
|
|
33277
33336
|
function pushNetworkArgs3(args, options) {
|
|
@@ -33313,7 +33372,10 @@ function buildArgs34(options) {
|
|
|
33313
33372
|
args.push("+r");
|
|
33314
33373
|
}
|
|
33315
33374
|
if (options.proposedTransferSyntax !== void 0) {
|
|
33316
|
-
args
|
|
33375
|
+
pushProposedTsArgs(args, options.proposedTransferSyntax);
|
|
33376
|
+
}
|
|
33377
|
+
if (options.combineProposedTransferSyntaxes === true) {
|
|
33378
|
+
args.push("+C");
|
|
33317
33379
|
}
|
|
33318
33380
|
if (options.required === true) {
|
|
33319
33381
|
args.push("-R");
|
|
@@ -36853,79 +36915,13 @@ var SenderHealth = {
|
|
|
36853
36915
|
DOWN: "down"
|
|
36854
36916
|
};
|
|
36855
36917
|
|
|
36856
|
-
// src/senders/
|
|
36857
|
-
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
36858
|
-
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
36859
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
36860
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
36861
|
-
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
36862
|
-
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
36863
|
-
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
36918
|
+
// src/senders/SenderEngine.ts
|
|
36864
36919
|
var DEGRADE_THRESHOLD = 3;
|
|
36865
36920
|
var DOWN_THRESHOLD = 10;
|
|
36866
36921
|
var RECOVERY_THRESHOLD = 3;
|
|
36867
|
-
var
|
|
36868
|
-
|
|
36869
|
-
|
|
36870
|
-
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36871
|
-
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36872
|
-
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
36873
|
-
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
36874
|
-
proposedTransferSyntax: zod.z.enum([
|
|
36875
|
-
"uncompressed",
|
|
36876
|
-
"littleEndian",
|
|
36877
|
-
"bigEndian",
|
|
36878
|
-
"implicitVR",
|
|
36879
|
-
"jpegLossless",
|
|
36880
|
-
"jpeg8Bit",
|
|
36881
|
-
"jpeg12Bit",
|
|
36882
|
-
"j2kLossless",
|
|
36883
|
-
"j2kLossy",
|
|
36884
|
-
"jlsLossless",
|
|
36885
|
-
"jlsLossy"
|
|
36886
|
-
]).optional(),
|
|
36887
|
-
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
36888
|
-
timeoutMs: zod.z.number().int().positive().optional(),
|
|
36889
|
-
maxRetries: zod.z.number().int().min(0).optional(),
|
|
36890
|
-
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
36891
|
-
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
36892
|
-
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
36893
|
-
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36894
|
-
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36895
|
-
associationTimeout: zod.z.number().int().positive().optional(),
|
|
36896
|
-
acseTimeout: zod.z.number().int().positive().optional(),
|
|
36897
|
-
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
36898
|
-
noHostnameLookup: zod.z.boolean().optional(),
|
|
36899
|
-
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
36900
|
-
required: zod.z.boolean().optional(),
|
|
36901
|
-
signal: zod.z.instanceof(AbortSignal).optional()
|
|
36902
|
-
}).strict();
|
|
36903
|
-
function resolveConfig(options) {
|
|
36904
|
-
const mode = options.mode ?? "multiple";
|
|
36905
|
-
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
36906
|
-
return {
|
|
36907
|
-
mode,
|
|
36908
|
-
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
36909
|
-
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
36910
|
-
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
36911
|
-
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
36912
|
-
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
36913
|
-
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
36914
|
-
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
36915
|
-
};
|
|
36916
|
-
}
|
|
36917
|
-
var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
36918
|
-
constructor(options) {
|
|
36919
|
-
super();
|
|
36920
|
-
__publicField(this, "options");
|
|
36921
|
-
__publicField(this, "mode");
|
|
36922
|
-
__publicField(this, "configuredMaxAssociations");
|
|
36923
|
-
__publicField(this, "maxQueueLength");
|
|
36924
|
-
__publicField(this, "defaultTimeoutMs");
|
|
36925
|
-
__publicField(this, "defaultMaxRetries");
|
|
36926
|
-
__publicField(this, "retryDelayMs");
|
|
36927
|
-
__publicField(this, "bucketFlushMs");
|
|
36928
|
-
__publicField(this, "maxBucketSize");
|
|
36922
|
+
var SenderEngine = class {
|
|
36923
|
+
constructor(config) {
|
|
36924
|
+
__publicField(this, "config");
|
|
36929
36925
|
// Queue and concurrency state
|
|
36930
36926
|
__publicField(this, "queue", []);
|
|
36931
36927
|
__publicField(this, "activeAssociations", 0);
|
|
@@ -36938,108 +36934,36 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
36938
36934
|
// Bucket state (bucket mode only)
|
|
36939
36935
|
__publicField(this, "currentBucket", []);
|
|
36940
36936
|
__publicField(this, "bucketTimer");
|
|
36941
|
-
|
|
36942
|
-
|
|
36943
|
-
this.setMaxListeners(20);
|
|
36944
|
-
this.on("error", () => {
|
|
36945
|
-
});
|
|
36946
|
-
this.options = options;
|
|
36947
|
-
const cfg = resolveConfig(options);
|
|
36948
|
-
this.mode = cfg.mode;
|
|
36949
|
-
this.configuredMaxAssociations = cfg.configuredMaxAssociations;
|
|
36950
|
-
this.effectiveMaxAssociations = cfg.configuredMaxAssociations;
|
|
36951
|
-
this.maxQueueLength = cfg.maxQueueLength;
|
|
36952
|
-
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
36953
|
-
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
36954
|
-
this.retryDelayMs = cfg.retryDelayMs;
|
|
36955
|
-
this.bucketFlushMs = cfg.bucketFlushMs;
|
|
36956
|
-
this.maxBucketSize = cfg.maxBucketSize;
|
|
36957
|
-
if (options.signal !== void 0) {
|
|
36958
|
-
this.wireAbortSignal(options.signal);
|
|
36959
|
-
}
|
|
36937
|
+
this.config = config;
|
|
36938
|
+
this.effectiveMaxAssociations = config.configuredMaxAssociations;
|
|
36960
36939
|
}
|
|
36961
36940
|
// -----------------------------------------------------------------------
|
|
36962
36941
|
// Public API
|
|
36963
36942
|
// -----------------------------------------------------------------------
|
|
36964
|
-
/**
|
|
36965
|
-
|
|
36966
|
-
*
|
|
36967
|
-
* @param options - Configuration options
|
|
36968
|
-
* @returns A Result containing the instance or a validation error
|
|
36969
|
-
*/
|
|
36970
|
-
static create(options) {
|
|
36971
|
-
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
36972
|
-
if (!validation.success) {
|
|
36973
|
-
return err(createValidationError("DicomSender", validation.error));
|
|
36974
|
-
}
|
|
36975
|
-
return ok(new _DicomSender(options));
|
|
36976
|
-
}
|
|
36977
|
-
/**
|
|
36978
|
-
* Sends one or more DICOM files to the remote endpoint.
|
|
36979
|
-
*
|
|
36980
|
-
* In single/multiple mode, files are sent as one storescu call.
|
|
36981
|
-
* In bucket mode, files are accumulated into a bucket and flushed
|
|
36982
|
-
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
36983
|
-
*
|
|
36984
|
-
* The returned promise resolves when the files are actually sent
|
|
36985
|
-
* (not just queued). Callers can await for confirmation or
|
|
36986
|
-
* fire-and-forget with `void sender.send(files)`.
|
|
36987
|
-
*
|
|
36988
|
-
* @param files - One or more DICOM file paths
|
|
36989
|
-
* @param options - Per-send overrides
|
|
36990
|
-
* @returns A Result containing the send result or an error
|
|
36991
|
-
*/
|
|
36992
|
-
send(files, options) {
|
|
36943
|
+
/** Sends files through the engine's queue/bucket system. */
|
|
36944
|
+
send(files, timeoutMs, maxRetries, binaryParams) {
|
|
36993
36945
|
if (this.isStopped) {
|
|
36994
|
-
return Promise.resolve(err(new Error(
|
|
36946
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: sender is stopped`)));
|
|
36995
36947
|
}
|
|
36996
36948
|
if (files.length === 0) {
|
|
36997
|
-
return Promise.resolve(err(new Error(
|
|
36998
|
-
}
|
|
36999
|
-
const params = {
|
|
37000
|
-
timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
|
|
37001
|
-
maxRetries: options?.maxRetries ?? this.defaultMaxRetries,
|
|
37002
|
-
calledAETitle: options?.calledAETitle,
|
|
37003
|
-
callingAETitle: options?.callingAETitle,
|
|
37004
|
-
required: options?.required
|
|
37005
|
-
};
|
|
37006
|
-
return this.dispatchSend(files, params);
|
|
37007
|
-
}
|
|
37008
|
-
/** Dispatches a send to the appropriate mode handler. */
|
|
37009
|
-
dispatchSend(files, params) {
|
|
37010
|
-
switch (this.mode) {
|
|
37011
|
-
case "single":
|
|
37012
|
-
case "multiple":
|
|
37013
|
-
return this.enqueueSend(files, params);
|
|
37014
|
-
case "bucket":
|
|
37015
|
-
return this.enqueueBucket(files, params);
|
|
37016
|
-
default:
|
|
37017
|
-
assertUnreachable(this.mode);
|
|
36949
|
+
return Promise.resolve(err(new Error(`${this.config.senderName}: no files provided`)));
|
|
37018
36950
|
}
|
|
36951
|
+
return this.dispatchSend(files, timeoutMs, maxRetries, binaryParams);
|
|
37019
36952
|
}
|
|
37020
|
-
/**
|
|
37021
|
-
* Flushes the current bucket immediately (bucket mode only).
|
|
37022
|
-
* In single/multiple mode this is a no-op.
|
|
37023
|
-
*/
|
|
36953
|
+
/** Flushes the current bucket immediately (bucket mode only). */
|
|
37024
36954
|
flush() {
|
|
37025
|
-
if (this.mode !== "bucket") return;
|
|
36955
|
+
if (this.config.mode !== "bucket") return;
|
|
37026
36956
|
if (this.currentBucket.length === 0) return;
|
|
37027
36957
|
this.clearBucketTimer();
|
|
37028
36958
|
this.flushBucketInternal("timer");
|
|
37029
36959
|
}
|
|
37030
|
-
/**
|
|
37031
|
-
* Gracefully stops the sender. Rejects all queued items and
|
|
37032
|
-
* waits for active associations to complete.
|
|
37033
|
-
*/
|
|
36960
|
+
/** Gracefully stops the engine. Rejects all queued items and waits for active associations. */
|
|
37034
36961
|
async stop() {
|
|
37035
36962
|
if (this.isStopped) return;
|
|
37036
36963
|
this.isStopped = true;
|
|
37037
|
-
if (this.options.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37038
|
-
this.options.signal.removeEventListener("abort", this.abortHandler);
|
|
37039
|
-
}
|
|
37040
36964
|
this.clearBucketTimer();
|
|
37041
|
-
this.rejectBucket(
|
|
37042
|
-
this.rejectQueue(
|
|
36965
|
+
this.rejectBucket(`${this.config.senderName}: sender stopped`);
|
|
36966
|
+
this.rejectQueue(`${this.config.senderName}: sender stopped`);
|
|
37043
36967
|
await this.waitForActive();
|
|
37044
36968
|
}
|
|
37045
36969
|
/** Current sender status. */
|
|
@@ -37054,75 +36978,37 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37054
36978
|
stopped: this.isStopped
|
|
37055
36979
|
};
|
|
37056
36980
|
}
|
|
36981
|
+
/** Whether the engine has been stopped. */
|
|
36982
|
+
get stopped() {
|
|
36983
|
+
return this.isStopped;
|
|
36984
|
+
}
|
|
37057
36985
|
// -----------------------------------------------------------------------
|
|
37058
|
-
//
|
|
36986
|
+
// Dispatch
|
|
37059
36987
|
// -----------------------------------------------------------------------
|
|
37060
|
-
/**
|
|
37061
|
-
|
|
37062
|
-
|
|
37063
|
-
|
|
37064
|
-
|
|
37065
|
-
|
|
37066
|
-
|
|
37067
|
-
|
|
37068
|
-
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
* Registers a listener for successful sends.
|
|
37072
|
-
*
|
|
37073
|
-
* @param listener - Callback receiving send complete data
|
|
37074
|
-
* @returns this for chaining
|
|
37075
|
-
*/
|
|
37076
|
-
onSendComplete(listener) {
|
|
37077
|
-
return this.on("SEND_COMPLETE", listener);
|
|
37078
|
-
}
|
|
37079
|
-
/**
|
|
37080
|
-
* Registers a listener for failed sends.
|
|
37081
|
-
*
|
|
37082
|
-
* @param listener - Callback receiving send failed data
|
|
37083
|
-
* @returns this for chaining
|
|
37084
|
-
*/
|
|
37085
|
-
onSendFailed(listener) {
|
|
37086
|
-
return this.on("SEND_FAILED", listener);
|
|
37087
|
-
}
|
|
37088
|
-
/**
|
|
37089
|
-
* Registers a listener for health state changes.
|
|
37090
|
-
*
|
|
37091
|
-
* @param listener - Callback receiving health change data
|
|
37092
|
-
* @returns this for chaining
|
|
37093
|
-
*/
|
|
37094
|
-
onHealthChanged(listener) {
|
|
37095
|
-
return this.on("HEALTH_CHANGED", listener);
|
|
37096
|
-
}
|
|
37097
|
-
/**
|
|
37098
|
-
* Registers a listener for bucket flushes (bucket mode only).
|
|
37099
|
-
*
|
|
37100
|
-
* @param listener - Callback receiving bucket flush data
|
|
37101
|
-
* @returns this for chaining
|
|
37102
|
-
*/
|
|
37103
|
-
onBucketFlushed(listener) {
|
|
37104
|
-
return this.on("BUCKET_FLUSHED", listener);
|
|
36988
|
+
/** Dispatches a send to the appropriate mode handler. */
|
|
36989
|
+
dispatchSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
36990
|
+
switch (this.config.mode) {
|
|
36991
|
+
case "single":
|
|
36992
|
+
case "multiple":
|
|
36993
|
+
return this.enqueueSend(files, timeoutMs, maxRetries, binaryParams);
|
|
36994
|
+
case "bucket":
|
|
36995
|
+
return this.enqueueBucket(files, timeoutMs, maxRetries, binaryParams);
|
|
36996
|
+
default:
|
|
36997
|
+
assertUnreachable(this.config.mode);
|
|
36998
|
+
}
|
|
37105
36999
|
}
|
|
37106
37000
|
// -----------------------------------------------------------------------
|
|
37107
37001
|
// Single/Multiple mode: queue-based dispatch
|
|
37108
37002
|
// -----------------------------------------------------------------------
|
|
37109
37003
|
/** Enqueues a send and dispatches immediately if capacity allows. */
|
|
37110
|
-
enqueueSend(files,
|
|
37004
|
+
enqueueSend(files, timeoutMs, maxRetries, binaryParams) {
|
|
37111
37005
|
return new Promise((resolve) => {
|
|
37112
37006
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37113
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37114
|
-
resolve(err(new Error(
|
|
37007
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
37008
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37115
37009
|
return;
|
|
37116
37010
|
}
|
|
37117
|
-
const entry = {
|
|
37118
|
-
files,
|
|
37119
|
-
timeoutMs: params.timeoutMs,
|
|
37120
|
-
maxRetries: params.maxRetries,
|
|
37121
|
-
calledAETitle: params.calledAETitle,
|
|
37122
|
-
callingAETitle: params.callingAETitle,
|
|
37123
|
-
required: params.required,
|
|
37124
|
-
resolve
|
|
37125
|
-
};
|
|
37011
|
+
const entry = { files, timeoutMs, maxRetries, binaryParams, resolve };
|
|
37126
37012
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37127
37013
|
void this.executeEntry(entry);
|
|
37128
37014
|
} else {
|
|
@@ -37142,17 +37028,16 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37142
37028
|
// Bucket mode: accumulate-then-flush
|
|
37143
37029
|
// -----------------------------------------------------------------------
|
|
37144
37030
|
/** Adds files to the current bucket and triggers flush if full. */
|
|
37145
|
-
enqueueBucket(files,
|
|
37031
|
+
enqueueBucket(files, timeoutMs, maxRetries, binaryParams) {
|
|
37146
37032
|
return new Promise((resolve) => {
|
|
37147
37033
|
const totalQueued = this.queue.length + this.currentBucket.length;
|
|
37148
|
-
if (totalQueued >= this.maxQueueLength) {
|
|
37149
|
-
resolve(err(new Error(
|
|
37034
|
+
if (totalQueued >= this.config.maxQueueLength) {
|
|
37035
|
+
resolve(err(new Error(`${this.config.senderName}: queue full`)));
|
|
37150
37036
|
return;
|
|
37151
37037
|
}
|
|
37152
|
-
|
|
37153
|
-
this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, calledAETitle, callingAETitle, required });
|
|
37038
|
+
this.currentBucket.push({ files, resolve, timeoutMs, maxRetries, binaryParams });
|
|
37154
37039
|
const totalFiles = this.countBucketFiles();
|
|
37155
|
-
if (totalFiles >= this.maxBucketSize) {
|
|
37040
|
+
if (totalFiles >= this.config.maxBucketSize) {
|
|
37156
37041
|
this.clearBucketTimer();
|
|
37157
37042
|
void this.flushBucketInternal("maxSize");
|
|
37158
37043
|
} else {
|
|
@@ -37184,9 +37069,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37184
37069
|
files: allFiles,
|
|
37185
37070
|
timeoutMs,
|
|
37186
37071
|
maxRetries,
|
|
37187
|
-
|
|
37188
|
-
callingAETitle: entries[0]?.callingAETitle,
|
|
37189
|
-
required: entries[0]?.required,
|
|
37072
|
+
binaryParams: entries[0].binaryParams,
|
|
37190
37073
|
resolve: (result) => {
|
|
37191
37074
|
for (let i = 0; i < entries.length; i++) {
|
|
37192
37075
|
entries[i].resolve(result);
|
|
@@ -37200,7 +37083,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37200
37083
|
const entries = [...this.currentBucket];
|
|
37201
37084
|
this.currentBucket = [];
|
|
37202
37085
|
const bucketEntry = this.mergeBucketEntries(entries);
|
|
37203
|
-
this.
|
|
37086
|
+
this.config.emitters.emitBucketFlushed({ fileCount: bucketEntry.files.length, reason });
|
|
37204
37087
|
if (this.activeAssociations < this.effectiveMaxAssociations) {
|
|
37205
37088
|
void this.executeEntry(bucketEntry);
|
|
37206
37089
|
} else {
|
|
@@ -37213,7 +37096,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37213
37096
|
this.bucketTimer = setTimeout(() => {
|
|
37214
37097
|
this.bucketTimer = void 0;
|
|
37215
37098
|
void this.flushBucketInternal("timer");
|
|
37216
|
-
}, this.bucketFlushMs);
|
|
37099
|
+
}, this.config.bucketFlushMs);
|
|
37217
37100
|
}
|
|
37218
37101
|
/** Clears the bucket flush timer. */
|
|
37219
37102
|
clearBucketTimer() {
|
|
@@ -37225,7 +37108,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37225
37108
|
// -----------------------------------------------------------------------
|
|
37226
37109
|
// Core send execution with retry
|
|
37227
37110
|
// -----------------------------------------------------------------------
|
|
37228
|
-
/** Executes a single queue entry: calls
|
|
37111
|
+
/** Executes a single queue entry: calls the binary with retry. */
|
|
37229
37112
|
async executeEntry(entry) {
|
|
37230
37113
|
this.activeAssociations++;
|
|
37231
37114
|
const startMs = Date.now();
|
|
@@ -37234,7 +37117,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37234
37117
|
if (failure === void 0) return;
|
|
37235
37118
|
this.activeAssociations--;
|
|
37236
37119
|
this.recordFailure();
|
|
37237
|
-
this.
|
|
37120
|
+
this.config.emitters.emitSendFailed({
|
|
37238
37121
|
files: entry.files,
|
|
37239
37122
|
error: failure.error,
|
|
37240
37123
|
attempts: maxAttempts,
|
|
@@ -37244,48 +37127,27 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37244
37127
|
entry.resolve(err(failure.error));
|
|
37245
37128
|
this.drainQueue();
|
|
37246
37129
|
}
|
|
37247
|
-
/** Attempts
|
|
37130
|
+
/** Attempts the binary call up to maxAttempts times. Returns undefined on success. */
|
|
37248
37131
|
async attemptSend(entry, maxAttempts, startMs) {
|
|
37249
37132
|
let lastError;
|
|
37250
37133
|
const lastOutput = { stdout: "", stderr: "" };
|
|
37251
37134
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
37252
37135
|
if (this.isStopped) {
|
|
37253
37136
|
this.activeAssociations--;
|
|
37254
|
-
entry.resolve(err(new Error(
|
|
37137
|
+
entry.resolve(err(new Error(`${this.config.senderName}: sender stopped`)));
|
|
37255
37138
|
return void 0;
|
|
37256
37139
|
}
|
|
37257
|
-
const result = await this.
|
|
37140
|
+
const result = await this.config.executor(entry.files, entry.timeoutMs, entry.binaryParams, this.config.signal);
|
|
37258
37141
|
if (result.ok) {
|
|
37259
37142
|
this.handleSendSuccess(entry, startMs, result.value);
|
|
37260
37143
|
return void 0;
|
|
37261
37144
|
}
|
|
37262
37145
|
lastError = result.error;
|
|
37263
37146
|
if (attempt < maxAttempts - 1) {
|
|
37264
|
-
await delay2(this.retryDelayMs * (attempt + 1));
|
|
37147
|
+
await delay2(this.config.retryDelayMs * (attempt + 1));
|
|
37265
37148
|
}
|
|
37266
37149
|
}
|
|
37267
|
-
return { error: lastError ?? new Error(
|
|
37268
|
-
}
|
|
37269
|
-
/** Calls storescu with the configured options. */
|
|
37270
|
-
callStorescu(entry) {
|
|
37271
|
-
return storescu({
|
|
37272
|
-
host: this.options.host,
|
|
37273
|
-
port: this.options.port,
|
|
37274
|
-
files: [...entry.files],
|
|
37275
|
-
calledAETitle: entry.calledAETitle ?? this.options.calledAETitle,
|
|
37276
|
-
callingAETitle: entry.callingAETitle ?? this.options.callingAETitle,
|
|
37277
|
-
proposedTransferSyntax: this.options.proposedTransferSyntax,
|
|
37278
|
-
maxPduReceive: this.options.maxPduReceive,
|
|
37279
|
-
maxPduSend: this.options.maxPduSend,
|
|
37280
|
-
associationTimeout: this.options.associationTimeout,
|
|
37281
|
-
acseTimeout: this.options.acseTimeout,
|
|
37282
|
-
dimseTimeout: this.options.dimseTimeout,
|
|
37283
|
-
noHostnameLookup: this.options.noHostnameLookup,
|
|
37284
|
-
verbosity: this.options.verbosity,
|
|
37285
|
-
required: entry.required ?? this.options.required,
|
|
37286
|
-
timeoutMs: entry.timeoutMs,
|
|
37287
|
-
signal: this.options.signal
|
|
37288
|
-
});
|
|
37150
|
+
return { error: lastError ?? new Error(`${this.config.senderName}: send failed`), output: lastOutput };
|
|
37289
37151
|
}
|
|
37290
37152
|
/** Handles a successful send: updates state, emits event, resolves promise. */
|
|
37291
37153
|
handleSendSuccess(entry, startMs, output) {
|
|
@@ -37293,7 +37155,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37293
37155
|
const durationMs = Date.now() - startMs;
|
|
37294
37156
|
this.recordSuccess();
|
|
37295
37157
|
const data = { files: entry.files, fileCount: entry.files.length, durationMs, stdout: output.stdout, stderr: output.stderr };
|
|
37296
|
-
this.
|
|
37158
|
+
this.config.emitters.emitSendComplete(data);
|
|
37297
37159
|
entry.resolve(ok(data));
|
|
37298
37160
|
this.drainQueue();
|
|
37299
37161
|
}
|
|
@@ -37311,8 +37173,8 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37311
37173
|
if (this.health === SenderHealth.DOWN) {
|
|
37312
37174
|
this.health = SenderHealth.DEGRADED;
|
|
37313
37175
|
} else {
|
|
37314
|
-
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.configuredMaxAssociations);
|
|
37315
|
-
if (this.effectiveMaxAssociations >= this.configuredMaxAssociations) {
|
|
37176
|
+
this.effectiveMaxAssociations = Math.min(this.effectiveMaxAssociations * 2, this.config.configuredMaxAssociations);
|
|
37177
|
+
if (this.effectiveMaxAssociations >= this.config.configuredMaxAssociations) {
|
|
37316
37178
|
this.health = SenderHealth.HEALTHY;
|
|
37317
37179
|
}
|
|
37318
37180
|
}
|
|
@@ -37332,22 +37194,26 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37332
37194
|
this.emitHealthChanged(previousHealth);
|
|
37333
37195
|
}
|
|
37334
37196
|
} else if (this.consecutiveFailures >= DEGRADE_THRESHOLD && this.consecutiveFailures % DEGRADE_THRESHOLD === 0) {
|
|
37335
|
-
|
|
37336
|
-
|
|
37337
|
-
|
|
37197
|
+
this.degradeHealth(previousHealth);
|
|
37198
|
+
}
|
|
37199
|
+
}
|
|
37200
|
+
/** Handles HEALTHY→DEGRADED or DEGRADED→DEGRADED transitions. */
|
|
37201
|
+
degradeHealth(previousHealth) {
|
|
37202
|
+
if (this.health === SenderHealth.HEALTHY) {
|
|
37203
|
+
this.health = SenderHealth.DEGRADED;
|
|
37204
|
+
this.effectiveMaxAssociations = Math.max(1, Math.floor(this.config.configuredMaxAssociations / 2));
|
|
37205
|
+
this.emitHealthChanged(previousHealth);
|
|
37206
|
+
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37207
|
+
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37208
|
+
if (newMax !== this.effectiveMaxAssociations) {
|
|
37209
|
+
this.effectiveMaxAssociations = newMax;
|
|
37338
37210
|
this.emitHealthChanged(previousHealth);
|
|
37339
|
-
} else if (this.health === SenderHealth.DEGRADED) {
|
|
37340
|
-
const newMax = Math.max(1, Math.floor(this.effectiveMaxAssociations / 2));
|
|
37341
|
-
if (newMax !== this.effectiveMaxAssociations) {
|
|
37342
|
-
this.effectiveMaxAssociations = newMax;
|
|
37343
|
-
this.emitHealthChanged(previousHealth);
|
|
37344
|
-
}
|
|
37345
37211
|
}
|
|
37346
37212
|
}
|
|
37347
37213
|
}
|
|
37348
|
-
/** Emits a HEALTH_CHANGED event. */
|
|
37214
|
+
/** Emits a HEALTH_CHANGED event via the wrapper's emitter. */
|
|
37349
37215
|
emitHealthChanged(previousHealth) {
|
|
37350
|
-
this.
|
|
37216
|
+
this.config.emitters.emitHealthChanged({
|
|
37351
37217
|
previousHealth,
|
|
37352
37218
|
newHealth: this.health,
|
|
37353
37219
|
effectiveMaxAssociations: this.effectiveMaxAssociations,
|
|
@@ -37387,20 +37253,6 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37387
37253
|
check();
|
|
37388
37254
|
});
|
|
37389
37255
|
}
|
|
37390
|
-
// -----------------------------------------------------------------------
|
|
37391
|
-
// Abort signal
|
|
37392
|
-
// -----------------------------------------------------------------------
|
|
37393
|
-
/** Wires an AbortSignal to stop the sender. */
|
|
37394
|
-
wireAbortSignal(signal) {
|
|
37395
|
-
if (signal.aborted) {
|
|
37396
|
-
void this.stop();
|
|
37397
|
-
return;
|
|
37398
|
-
}
|
|
37399
|
-
this.abortHandler = () => {
|
|
37400
|
-
void this.stop();
|
|
37401
|
-
};
|
|
37402
|
-
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37403
|
-
}
|
|
37404
37256
|
};
|
|
37405
37257
|
function delay2(ms) {
|
|
37406
37258
|
return new Promise((resolve) => {
|
|
@@ -37408,6 +37260,492 @@ function delay2(ms) {
|
|
|
37408
37260
|
});
|
|
37409
37261
|
}
|
|
37410
37262
|
|
|
37263
|
+
// src/senders/DicomSender.ts
|
|
37264
|
+
var DEFAULT_MAX_ASSOCIATIONS = 4;
|
|
37265
|
+
var DEFAULT_MAX_QUEUE_LENGTH = 1e3;
|
|
37266
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
37267
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
37268
|
+
var DEFAULT_BUCKET_FLUSH_MS = 5e3;
|
|
37269
|
+
var DEFAULT_MAX_BUCKET_SIZE = 50;
|
|
37270
|
+
var MAX_ASSOCIATIONS_LIMIT = 64;
|
|
37271
|
+
var DicomSenderOptionsSchema = zod.z.object({
|
|
37272
|
+
host: zod.z.string().min(1),
|
|
37273
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
37274
|
+
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37275
|
+
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37276
|
+
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37277
|
+
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT).optional(),
|
|
37278
|
+
proposedTransferSyntax: zod.z.union([zod.z.enum(PROPOSED_TS_VALUES), zod.z.array(zod.z.enum(PROPOSED_TS_VALUES)).min(1)]).optional(),
|
|
37279
|
+
combineProposedTransferSyntaxes: zod.z.boolean().optional(),
|
|
37280
|
+
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
37281
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
37282
|
+
maxRetries: zod.z.number().int().min(0).optional(),
|
|
37283
|
+
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
37284
|
+
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
37285
|
+
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
37286
|
+
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37287
|
+
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37288
|
+
associationTimeout: zod.z.number().int().positive().optional(),
|
|
37289
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
37290
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
37291
|
+
noHostnameLookup: zod.z.boolean().optional(),
|
|
37292
|
+
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
37293
|
+
required: zod.z.boolean().optional(),
|
|
37294
|
+
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37295
|
+
}).strict();
|
|
37296
|
+
function createStorescuExecutor(options) {
|
|
37297
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37298
|
+
const result = await storescu({
|
|
37299
|
+
host: options.host,
|
|
37300
|
+
port: options.port,
|
|
37301
|
+
files: [...files],
|
|
37302
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37303
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37304
|
+
proposedTransferSyntax: params.proposedTransferSyntax ?? options.proposedTransferSyntax,
|
|
37305
|
+
combineProposedTransferSyntaxes: params.combineProposedTransferSyntaxes ?? options.combineProposedTransferSyntaxes,
|
|
37306
|
+
maxPduReceive: options.maxPduReceive,
|
|
37307
|
+
maxPduSend: options.maxPduSend,
|
|
37308
|
+
associationTimeout: options.associationTimeout,
|
|
37309
|
+
acseTimeout: options.acseTimeout,
|
|
37310
|
+
dimseTimeout: options.dimseTimeout,
|
|
37311
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37312
|
+
verbosity: options.verbosity,
|
|
37313
|
+
required: params.required ?? options.required,
|
|
37314
|
+
timeoutMs,
|
|
37315
|
+
signal
|
|
37316
|
+
});
|
|
37317
|
+
if (!result.ok) return err(result.error);
|
|
37318
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37319
|
+
};
|
|
37320
|
+
}
|
|
37321
|
+
function resolveConfig(options) {
|
|
37322
|
+
const mode = options.mode ?? "multiple";
|
|
37323
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37324
|
+
return {
|
|
37325
|
+
mode,
|
|
37326
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37327
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH,
|
|
37328
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37329
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
37330
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
37331
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS,
|
|
37332
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE
|
|
37333
|
+
};
|
|
37334
|
+
}
|
|
37335
|
+
var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
37336
|
+
constructor(engine, cfg, signal) {
|
|
37337
|
+
super();
|
|
37338
|
+
__publicField(this, "engine");
|
|
37339
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37340
|
+
__publicField(this, "defaultMaxRetries");
|
|
37341
|
+
__publicField(this, "signal");
|
|
37342
|
+
__publicField(this, "abortHandler");
|
|
37343
|
+
this.setMaxListeners(20);
|
|
37344
|
+
this.on("error", () => {
|
|
37345
|
+
});
|
|
37346
|
+
this.engine = engine;
|
|
37347
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37348
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37349
|
+
this.signal = signal;
|
|
37350
|
+
if (signal !== void 0) {
|
|
37351
|
+
this.wireAbortSignal(signal);
|
|
37352
|
+
}
|
|
37353
|
+
}
|
|
37354
|
+
// -----------------------------------------------------------------------
|
|
37355
|
+
// Public API
|
|
37356
|
+
// -----------------------------------------------------------------------
|
|
37357
|
+
/**
|
|
37358
|
+
* Creates a new DicomSender instance.
|
|
37359
|
+
*
|
|
37360
|
+
* @param options - Configuration options
|
|
37361
|
+
* @returns A Result containing the instance or a validation error
|
|
37362
|
+
*/
|
|
37363
|
+
static create(options) {
|
|
37364
|
+
const validation = DicomSenderOptionsSchema.safeParse(options);
|
|
37365
|
+
if (!validation.success) {
|
|
37366
|
+
return err(createValidationError("DicomSender", validation.error));
|
|
37367
|
+
}
|
|
37368
|
+
const cfg = resolveConfig(options);
|
|
37369
|
+
const senderRef = { current: void 0 };
|
|
37370
|
+
const engine = new SenderEngine({
|
|
37371
|
+
...cfg,
|
|
37372
|
+
executor: createStorescuExecutor(options),
|
|
37373
|
+
signal: options.signal,
|
|
37374
|
+
senderName: "DicomSender",
|
|
37375
|
+
emitters: {
|
|
37376
|
+
emitSendComplete: (data) => {
|
|
37377
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37378
|
+
},
|
|
37379
|
+
emitSendFailed: (data) => {
|
|
37380
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37381
|
+
},
|
|
37382
|
+
emitHealthChanged: (data) => {
|
|
37383
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37384
|
+
},
|
|
37385
|
+
emitBucketFlushed: (data) => {
|
|
37386
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37387
|
+
}
|
|
37388
|
+
}
|
|
37389
|
+
});
|
|
37390
|
+
const sender = new _DicomSender(engine, cfg, options.signal);
|
|
37391
|
+
senderRef.current = sender;
|
|
37392
|
+
return ok(sender);
|
|
37393
|
+
}
|
|
37394
|
+
/**
|
|
37395
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37396
|
+
*
|
|
37397
|
+
* In single/multiple mode, files are sent as one storescu call.
|
|
37398
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37399
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37400
|
+
*
|
|
37401
|
+
* The returned promise resolves when the files are actually sent
|
|
37402
|
+
* (not just queued). Callers can await for confirmation or
|
|
37403
|
+
* fire-and-forget with `void sender.send(files)`.
|
|
37404
|
+
*
|
|
37405
|
+
* @param files - One or more DICOM file paths
|
|
37406
|
+
* @param options - Per-send overrides
|
|
37407
|
+
* @returns A Result containing the send result or an error
|
|
37408
|
+
*/
|
|
37409
|
+
send(files, options) {
|
|
37410
|
+
const params = buildStorescuParams(options);
|
|
37411
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37412
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37413
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37414
|
+
}
|
|
37415
|
+
/**
|
|
37416
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37417
|
+
* In single/multiple mode this is a no-op.
|
|
37418
|
+
*/
|
|
37419
|
+
flush() {
|
|
37420
|
+
this.engine.flush();
|
|
37421
|
+
}
|
|
37422
|
+
/**
|
|
37423
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37424
|
+
* waits for active associations to complete.
|
|
37425
|
+
*/
|
|
37426
|
+
async stop() {
|
|
37427
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37428
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37429
|
+
}
|
|
37430
|
+
await this.engine.stop();
|
|
37431
|
+
}
|
|
37432
|
+
/** Current sender status. */
|
|
37433
|
+
get status() {
|
|
37434
|
+
return this.engine.status;
|
|
37435
|
+
}
|
|
37436
|
+
// -----------------------------------------------------------------------
|
|
37437
|
+
// Typed event listener convenience methods
|
|
37438
|
+
// -----------------------------------------------------------------------
|
|
37439
|
+
/**
|
|
37440
|
+
* Registers a typed listener for a DicomSender-specific event.
|
|
37441
|
+
*
|
|
37442
|
+
* @param event - The event name from DicomSenderEventMap
|
|
37443
|
+
* @param listener - Callback receiving typed event data
|
|
37444
|
+
* @returns this for chaining
|
|
37445
|
+
*/
|
|
37446
|
+
onEvent(event, listener) {
|
|
37447
|
+
return this.on(event, listener);
|
|
37448
|
+
}
|
|
37449
|
+
/**
|
|
37450
|
+
* Registers a listener for successful sends.
|
|
37451
|
+
*
|
|
37452
|
+
* @param listener - Callback receiving send complete data
|
|
37453
|
+
* @returns this for chaining
|
|
37454
|
+
*/
|
|
37455
|
+
onSendComplete(listener) {
|
|
37456
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37457
|
+
}
|
|
37458
|
+
/**
|
|
37459
|
+
* Registers a listener for failed sends.
|
|
37460
|
+
*
|
|
37461
|
+
* @param listener - Callback receiving send failed data
|
|
37462
|
+
* @returns this for chaining
|
|
37463
|
+
*/
|
|
37464
|
+
onSendFailed(listener) {
|
|
37465
|
+
return this.on("SEND_FAILED", listener);
|
|
37466
|
+
}
|
|
37467
|
+
/**
|
|
37468
|
+
* Registers a listener for health state changes.
|
|
37469
|
+
*
|
|
37470
|
+
* @param listener - Callback receiving health change data
|
|
37471
|
+
* @returns this for chaining
|
|
37472
|
+
*/
|
|
37473
|
+
onHealthChanged(listener) {
|
|
37474
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37475
|
+
}
|
|
37476
|
+
/**
|
|
37477
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37478
|
+
*
|
|
37479
|
+
* @param listener - Callback receiving bucket flush data
|
|
37480
|
+
* @returns this for chaining
|
|
37481
|
+
*/
|
|
37482
|
+
onBucketFlushed(listener) {
|
|
37483
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37484
|
+
}
|
|
37485
|
+
// -----------------------------------------------------------------------
|
|
37486
|
+
// Abort signal
|
|
37487
|
+
// -----------------------------------------------------------------------
|
|
37488
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37489
|
+
wireAbortSignal(signal) {
|
|
37490
|
+
if (signal.aborted) {
|
|
37491
|
+
void this.stop();
|
|
37492
|
+
return;
|
|
37493
|
+
}
|
|
37494
|
+
this.abortHandler = () => {
|
|
37495
|
+
void this.stop();
|
|
37496
|
+
};
|
|
37497
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37498
|
+
}
|
|
37499
|
+
};
|
|
37500
|
+
function buildStorescuParams(options) {
|
|
37501
|
+
return {
|
|
37502
|
+
calledAETitle: options?.calledAETitle,
|
|
37503
|
+
callingAETitle: options?.callingAETitle,
|
|
37504
|
+
required: options?.required,
|
|
37505
|
+
proposedTransferSyntax: options?.proposedTransferSyntax,
|
|
37506
|
+
combineProposedTransferSyntaxes: options?.combineProposedTransferSyntaxes
|
|
37507
|
+
};
|
|
37508
|
+
}
|
|
37509
|
+
var DEFAULT_MAX_ASSOCIATIONS2 = 4;
|
|
37510
|
+
var DEFAULT_MAX_QUEUE_LENGTH2 = 1e3;
|
|
37511
|
+
var DEFAULT_MAX_RETRIES2 = 3;
|
|
37512
|
+
var DEFAULT_RETRY_DELAY_MS2 = 1e3;
|
|
37513
|
+
var DEFAULT_BUCKET_FLUSH_MS2 = 5e3;
|
|
37514
|
+
var DEFAULT_MAX_BUCKET_SIZE2 = 50;
|
|
37515
|
+
var MAX_ASSOCIATIONS_LIMIT2 = 64;
|
|
37516
|
+
var DicomSendOptionsSchema = zod.z.object({
|
|
37517
|
+
host: zod.z.string().min(1),
|
|
37518
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
37519
|
+
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37520
|
+
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
37521
|
+
mode: zod.z.enum(["single", "multiple", "bucket"]).optional(),
|
|
37522
|
+
maxAssociations: zod.z.number().int().min(1).max(MAX_ASSOCIATIONS_LIMIT2).optional(),
|
|
37523
|
+
maxQueueLength: zod.z.number().int().min(1).optional(),
|
|
37524
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
37525
|
+
maxRetries: zod.z.number().int().min(0).optional(),
|
|
37526
|
+
retryDelayMs: zod.z.number().int().min(0).optional(),
|
|
37527
|
+
bucketFlushMs: zod.z.number().int().positive().optional(),
|
|
37528
|
+
maxBucketSize: zod.z.number().int().min(1).optional(),
|
|
37529
|
+
maxPduReceive: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37530
|
+
maxPduSend: zod.z.number().int().min(4096).max(131072).optional(),
|
|
37531
|
+
associationTimeout: zod.z.number().int().positive().optional(),
|
|
37532
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
37533
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
37534
|
+
noHostnameLookup: zod.z.boolean().optional(),
|
|
37535
|
+
verbosity: zod.z.enum(["verbose", "debug"]).optional(),
|
|
37536
|
+
noHalt: zod.z.boolean().optional(),
|
|
37537
|
+
noIllegalProposal: zod.z.boolean().optional(),
|
|
37538
|
+
decompress: zod.z.enum(["never", "lossless", "lossy"]).optional(),
|
|
37539
|
+
multiAssociations: zod.z.boolean().optional(),
|
|
37540
|
+
noUidChecks: zod.z.boolean().optional(),
|
|
37541
|
+
signal: zod.z.instanceof(AbortSignal).optional()
|
|
37542
|
+
}).strict();
|
|
37543
|
+
function createDcmsendExecutor(options) {
|
|
37544
|
+
return async (files, timeoutMs, params, signal) => {
|
|
37545
|
+
const result = await dcmsend({
|
|
37546
|
+
host: options.host,
|
|
37547
|
+
port: options.port,
|
|
37548
|
+
files: [...files],
|
|
37549
|
+
calledAETitle: params.calledAETitle ?? options.calledAETitle,
|
|
37550
|
+
callingAETitle: params.callingAETitle ?? options.callingAETitle,
|
|
37551
|
+
maxPduReceive: options.maxPduReceive,
|
|
37552
|
+
maxPduSend: options.maxPduSend,
|
|
37553
|
+
associationTimeout: options.associationTimeout,
|
|
37554
|
+
acseTimeout: options.acseTimeout,
|
|
37555
|
+
dimseTimeout: options.dimseTimeout,
|
|
37556
|
+
noHostnameLookup: options.noHostnameLookup,
|
|
37557
|
+
verbosity: options.verbosity,
|
|
37558
|
+
noHalt: options.noHalt,
|
|
37559
|
+
noIllegalProposal: options.noIllegalProposal,
|
|
37560
|
+
decompress: options.decompress,
|
|
37561
|
+
multiAssociations: options.multiAssociations,
|
|
37562
|
+
noUidChecks: options.noUidChecks,
|
|
37563
|
+
timeoutMs,
|
|
37564
|
+
signal
|
|
37565
|
+
});
|
|
37566
|
+
if (!result.ok) return err(result.error);
|
|
37567
|
+
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37568
|
+
};
|
|
37569
|
+
}
|
|
37570
|
+
function resolveConfig2(options) {
|
|
37571
|
+
const mode = options.mode ?? "multiple";
|
|
37572
|
+
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37573
|
+
return {
|
|
37574
|
+
mode,
|
|
37575
|
+
configuredMaxAssociations: mode === "single" ? 1 : rawMax,
|
|
37576
|
+
maxQueueLength: options.maxQueueLength ?? DEFAULT_MAX_QUEUE_LENGTH2,
|
|
37577
|
+
defaultTimeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
37578
|
+
defaultMaxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES2,
|
|
37579
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS2,
|
|
37580
|
+
bucketFlushMs: options.bucketFlushMs ?? DEFAULT_BUCKET_FLUSH_MS2,
|
|
37581
|
+
maxBucketSize: options.maxBucketSize ?? DEFAULT_MAX_BUCKET_SIZE2
|
|
37582
|
+
};
|
|
37583
|
+
}
|
|
37584
|
+
var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
37585
|
+
constructor(engine, cfg, signal) {
|
|
37586
|
+
super();
|
|
37587
|
+
__publicField(this, "engine");
|
|
37588
|
+
__publicField(this, "defaultTimeoutMs");
|
|
37589
|
+
__publicField(this, "defaultMaxRetries");
|
|
37590
|
+
__publicField(this, "signal");
|
|
37591
|
+
__publicField(this, "abortHandler");
|
|
37592
|
+
this.setMaxListeners(20);
|
|
37593
|
+
this.on("error", () => {
|
|
37594
|
+
});
|
|
37595
|
+
this.engine = engine;
|
|
37596
|
+
this.defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
37597
|
+
this.defaultMaxRetries = cfg.defaultMaxRetries;
|
|
37598
|
+
this.signal = signal;
|
|
37599
|
+
if (signal !== void 0) {
|
|
37600
|
+
this.wireAbortSignal(signal);
|
|
37601
|
+
}
|
|
37602
|
+
}
|
|
37603
|
+
// -----------------------------------------------------------------------
|
|
37604
|
+
// Public API
|
|
37605
|
+
// -----------------------------------------------------------------------
|
|
37606
|
+
/**
|
|
37607
|
+
* Creates a new DicomSend instance.
|
|
37608
|
+
*
|
|
37609
|
+
* @param options - Configuration options
|
|
37610
|
+
* @returns A Result containing the instance or a validation error
|
|
37611
|
+
*/
|
|
37612
|
+
static create(options) {
|
|
37613
|
+
const validation = DicomSendOptionsSchema.safeParse(options);
|
|
37614
|
+
if (!validation.success) {
|
|
37615
|
+
return err(createValidationError("DicomSend", validation.error));
|
|
37616
|
+
}
|
|
37617
|
+
const cfg = resolveConfig2(options);
|
|
37618
|
+
const senderRef = { current: void 0 };
|
|
37619
|
+
const engine = new SenderEngine({
|
|
37620
|
+
...cfg,
|
|
37621
|
+
executor: createDcmsendExecutor(options),
|
|
37622
|
+
signal: options.signal,
|
|
37623
|
+
senderName: "DicomSend",
|
|
37624
|
+
emitters: {
|
|
37625
|
+
emitSendComplete: (data) => {
|
|
37626
|
+
senderRef.current.emit("SEND_COMPLETE", data);
|
|
37627
|
+
},
|
|
37628
|
+
emitSendFailed: (data) => {
|
|
37629
|
+
senderRef.current.emit("SEND_FAILED", data);
|
|
37630
|
+
},
|
|
37631
|
+
emitHealthChanged: (data) => {
|
|
37632
|
+
senderRef.current.emit("HEALTH_CHANGED", data);
|
|
37633
|
+
},
|
|
37634
|
+
emitBucketFlushed: (data) => {
|
|
37635
|
+
senderRef.current.emit("BUCKET_FLUSHED", data);
|
|
37636
|
+
}
|
|
37637
|
+
}
|
|
37638
|
+
});
|
|
37639
|
+
const sender = new _DicomSend(engine, cfg, options.signal);
|
|
37640
|
+
senderRef.current = sender;
|
|
37641
|
+
return ok(sender);
|
|
37642
|
+
}
|
|
37643
|
+
/**
|
|
37644
|
+
* Sends one or more DICOM files to the remote endpoint.
|
|
37645
|
+
*
|
|
37646
|
+
* In single/multiple mode, files are sent as one dcmsend call.
|
|
37647
|
+
* In bucket mode, files are accumulated into a bucket and flushed
|
|
37648
|
+
* when the bucket reaches maxBucketSize or the flush timer fires.
|
|
37649
|
+
*
|
|
37650
|
+
* @param files - One or more DICOM file paths
|
|
37651
|
+
* @param options - Per-send overrides
|
|
37652
|
+
* @returns A Result containing the send result or an error
|
|
37653
|
+
*/
|
|
37654
|
+
send(files, options) {
|
|
37655
|
+
const params = {
|
|
37656
|
+
calledAETitle: options?.calledAETitle,
|
|
37657
|
+
callingAETitle: options?.callingAETitle
|
|
37658
|
+
};
|
|
37659
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
37660
|
+
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
37661
|
+
return this.engine.send(files, timeoutMs, maxRetries, params);
|
|
37662
|
+
}
|
|
37663
|
+
/**
|
|
37664
|
+
* Flushes the current bucket immediately (bucket mode only).
|
|
37665
|
+
* In single/multiple mode this is a no-op.
|
|
37666
|
+
*/
|
|
37667
|
+
flush() {
|
|
37668
|
+
this.engine.flush();
|
|
37669
|
+
}
|
|
37670
|
+
/**
|
|
37671
|
+
* Gracefully stops the sender. Rejects all queued items and
|
|
37672
|
+
* waits for active associations to complete.
|
|
37673
|
+
*/
|
|
37674
|
+
async stop() {
|
|
37675
|
+
if (this.signal !== void 0 && this.abortHandler !== void 0) {
|
|
37676
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
37677
|
+
}
|
|
37678
|
+
await this.engine.stop();
|
|
37679
|
+
}
|
|
37680
|
+
/** Current sender status. */
|
|
37681
|
+
get status() {
|
|
37682
|
+
return this.engine.status;
|
|
37683
|
+
}
|
|
37684
|
+
// -----------------------------------------------------------------------
|
|
37685
|
+
// Typed event listener convenience methods
|
|
37686
|
+
// -----------------------------------------------------------------------
|
|
37687
|
+
/**
|
|
37688
|
+
* Registers a typed listener for a DicomSend-specific event.
|
|
37689
|
+
*
|
|
37690
|
+
* @param event - The event name from DicomSendEventMap
|
|
37691
|
+
* @param listener - Callback receiving typed event data
|
|
37692
|
+
* @returns this for chaining
|
|
37693
|
+
*/
|
|
37694
|
+
onEvent(event, listener) {
|
|
37695
|
+
return this.on(event, listener);
|
|
37696
|
+
}
|
|
37697
|
+
/**
|
|
37698
|
+
* Registers a listener for successful sends.
|
|
37699
|
+
*
|
|
37700
|
+
* @param listener - Callback receiving send complete data
|
|
37701
|
+
* @returns this for chaining
|
|
37702
|
+
*/
|
|
37703
|
+
onSendComplete(listener) {
|
|
37704
|
+
return this.on("SEND_COMPLETE", listener);
|
|
37705
|
+
}
|
|
37706
|
+
/**
|
|
37707
|
+
* Registers a listener for failed sends.
|
|
37708
|
+
*
|
|
37709
|
+
* @param listener - Callback receiving send failed data
|
|
37710
|
+
* @returns this for chaining
|
|
37711
|
+
*/
|
|
37712
|
+
onSendFailed(listener) {
|
|
37713
|
+
return this.on("SEND_FAILED", listener);
|
|
37714
|
+
}
|
|
37715
|
+
/**
|
|
37716
|
+
* Registers a listener for health state changes.
|
|
37717
|
+
*
|
|
37718
|
+
* @param listener - Callback receiving health change data
|
|
37719
|
+
* @returns this for chaining
|
|
37720
|
+
*/
|
|
37721
|
+
onHealthChanged(listener) {
|
|
37722
|
+
return this.on("HEALTH_CHANGED", listener);
|
|
37723
|
+
}
|
|
37724
|
+
/**
|
|
37725
|
+
* Registers a listener for bucket flushes (bucket mode only).
|
|
37726
|
+
*
|
|
37727
|
+
* @param listener - Callback receiving bucket flush data
|
|
37728
|
+
* @returns this for chaining
|
|
37729
|
+
*/
|
|
37730
|
+
onBucketFlushed(listener) {
|
|
37731
|
+
return this.on("BUCKET_FLUSHED", listener);
|
|
37732
|
+
}
|
|
37733
|
+
// -----------------------------------------------------------------------
|
|
37734
|
+
// Abort signal
|
|
37735
|
+
// -----------------------------------------------------------------------
|
|
37736
|
+
/** Wires an AbortSignal to stop the sender. */
|
|
37737
|
+
wireAbortSignal(signal) {
|
|
37738
|
+
if (signal.aborted) {
|
|
37739
|
+
void this.stop();
|
|
37740
|
+
return;
|
|
37741
|
+
}
|
|
37742
|
+
this.abortHandler = () => {
|
|
37743
|
+
void this.stop();
|
|
37744
|
+
};
|
|
37745
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
37746
|
+
}
|
|
37747
|
+
};
|
|
37748
|
+
|
|
37411
37749
|
// src/pacs/types.ts
|
|
37412
37750
|
var QueryLevel = {
|
|
37413
37751
|
STUDY: "STUDY",
|
|
@@ -37957,7 +38295,7 @@ var DEFAULT_CONFIG = {
|
|
|
37957
38295
|
signal: void 0,
|
|
37958
38296
|
onRetry: void 0
|
|
37959
38297
|
};
|
|
37960
|
-
function
|
|
38298
|
+
function resolveConfig3(opts) {
|
|
37961
38299
|
if (!opts) return DEFAULT_CONFIG;
|
|
37962
38300
|
return {
|
|
37963
38301
|
...DEFAULT_CONFIG,
|
|
@@ -38002,7 +38340,7 @@ function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
|
38002
38340
|
return false;
|
|
38003
38341
|
}
|
|
38004
38342
|
async function retry(operation, options) {
|
|
38005
|
-
const config =
|
|
38343
|
+
const config = resolveConfig3(options);
|
|
38006
38344
|
let lastResult = err(new Error("No attempts executed"));
|
|
38007
38345
|
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38008
38346
|
lastResult = await operation();
|
|
@@ -38050,6 +38388,7 @@ exports.DcmtkProcess = DcmtkProcess;
|
|
|
38050
38388
|
exports.DicomDataset = DicomDataset;
|
|
38051
38389
|
exports.DicomInstance = DicomInstance;
|
|
38052
38390
|
exports.DicomReceiver = DicomReceiver;
|
|
38391
|
+
exports.DicomSend = DicomSend;
|
|
38053
38392
|
exports.DicomSender = DicomSender;
|
|
38054
38393
|
exports.DicomTagPathSchema = DicomTagPathSchema;
|
|
38055
38394
|
exports.DicomTagSchema = DicomTagSchema;
|