@ubercode/dcmtk 0.4.0 → 0.5.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-CsYd7SMF.d.ts +1735 -0
- package/dist/index-DbUfqT7o.d.cts +1735 -0
- package/dist/index.cjs +130 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +130 -11
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +130 -11
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +5 -1653
- package/dist/servers.d.ts +5 -1653
- package/dist/servers.js +130 -11
- package/dist/servers.js.map +1 -1
- package/package.json +1 -1
package/dist/servers.cjs
CHANGED
|
@@ -2701,8 +2701,9 @@ var DEFAULT_MAX_POOL_SIZE = 10;
|
|
|
2701
2701
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
2702
2702
|
var CONNECTION_RETRY_INTERVAL_MS = 500;
|
|
2703
2703
|
var MAX_CONNECTION_RETRIES = 200;
|
|
2704
|
+
var MAX_OUTPUT_LINES_PER_ASSOCIATION = 500;
|
|
2704
2705
|
var DicomReceiverOptionsSchema = zod.z.object({
|
|
2705
|
-
port: zod.z.number().int().min(
|
|
2706
|
+
port: zod.z.number().int().min(0).max(65535),
|
|
2706
2707
|
storageDir: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in storageDir" }),
|
|
2707
2708
|
aeTitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
2708
2709
|
minPoolSize: zod.z.number().int().min(1).max(100).optional(),
|
|
@@ -2710,6 +2711,9 @@ var DicomReceiverOptionsSchema = zod.z.object({
|
|
|
2710
2711
|
connectionTimeoutMs: zod.z.number().int().positive().optional(),
|
|
2711
2712
|
configFile: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in configFile" }).optional(),
|
|
2712
2713
|
configProfile: zod.z.string().min(1).optional(),
|
|
2714
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
2715
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
2716
|
+
maxPdu: zod.z.number().int().min(4096).max(131072).optional(),
|
|
2713
2717
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
2714
2718
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
2715
2719
|
message: "minPoolSize must be <= maxPoolSize"
|
|
@@ -2838,6 +2842,57 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2838
2842
|
onAssociationComplete(listener) {
|
|
2839
2843
|
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
2840
2844
|
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Registers a listener for new associations (bubbled from workers).
|
|
2847
|
+
*
|
|
2848
|
+
* @param listener - Callback receiving association received data
|
|
2849
|
+
* @returns this for chaining
|
|
2850
|
+
*/
|
|
2851
|
+
onAssociationReceived(listener) {
|
|
2852
|
+
return this.on("ASSOCIATION_RECEIVED", listener);
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Registers a listener for C-STORE requests (bubbled from workers).
|
|
2856
|
+
*
|
|
2857
|
+
* @param listener - Callback receiving C-STORE request data
|
|
2858
|
+
* @returns this for chaining
|
|
2859
|
+
*/
|
|
2860
|
+
onCStoreRequest(listener) {
|
|
2861
|
+
return this.on("C_STORE_REQUEST", listener);
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Registers a listener for C-ECHO requests (bubbled from workers).
|
|
2865
|
+
*
|
|
2866
|
+
* @param listener - Callback receiving echo request data
|
|
2867
|
+
* @returns this for chaining
|
|
2868
|
+
*/
|
|
2869
|
+
onEchoRequest(listener) {
|
|
2870
|
+
return this.on("ECHO_REQUEST", listener);
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Registers a listener for refused associations (bubbled from workers).
|
|
2874
|
+
*
|
|
2875
|
+
* @param listener - Callback receiving refusing association data
|
|
2876
|
+
* @returns this for chaining
|
|
2877
|
+
*/
|
|
2878
|
+
onRefusingAssociation(listener) {
|
|
2879
|
+
return this.on("REFUSING_ASSOCIATION", listener);
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Routes an external socket to an idle worker.
|
|
2883
|
+
*
|
|
2884
|
+
* The pool must be started via `start()` before calling this method.
|
|
2885
|
+
* Use this when managing your own TCP listener (e.g., protocol router).
|
|
2886
|
+
*
|
|
2887
|
+
* @param socket - An incoming net.Socket to route to a worker
|
|
2888
|
+
*/
|
|
2889
|
+
handleSocket(socket) {
|
|
2890
|
+
if (!this.started) {
|
|
2891
|
+
socket.destroy(new Error("DicomReceiver: not started"));
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
void this.handleConnection(socket);
|
|
2895
|
+
}
|
|
2841
2896
|
/** Current pool status. */
|
|
2842
2897
|
get poolStatus() {
|
|
2843
2898
|
let idle = 0;
|
|
@@ -2851,8 +2906,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2851
2906
|
// -----------------------------------------------------------------------
|
|
2852
2907
|
// TCP proxy
|
|
2853
2908
|
// -----------------------------------------------------------------------
|
|
2854
|
-
/** Starts the TCP proxy on the configured port. */
|
|
2909
|
+
/** Starts the TCP proxy on the configured port. Skips if port is 0. */
|
|
2855
2910
|
startTcpProxy() {
|
|
2911
|
+
if (this.options.port === 0) {
|
|
2912
|
+
return Promise.resolve(ok(void 0));
|
|
2913
|
+
}
|
|
2856
2914
|
return new Promise((resolve) => {
|
|
2857
2915
|
this.tcpServer = net__namespace.createServer((socket) => {
|
|
2858
2916
|
void this.handleConnection(socket);
|
|
@@ -2905,6 +2963,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2905
2963
|
worker.associationDir = associationDir;
|
|
2906
2964
|
worker.files = [];
|
|
2907
2965
|
worker.fileSizes = [];
|
|
2966
|
+
worker.outputLines = [];
|
|
2908
2967
|
worker.startAt = Date.now();
|
|
2909
2968
|
this.pipeConnection(worker, remoteSocket);
|
|
2910
2969
|
void this.replenishPool();
|
|
@@ -2963,6 +3022,19 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2963
3022
|
}
|
|
2964
3023
|
return ok(void 0);
|
|
2965
3024
|
}
|
|
3025
|
+
/** Creates a Dcmrecv instance with the pool's shared options. */
|
|
3026
|
+
createDcmrecv(port, tempDir) {
|
|
3027
|
+
return Dcmrecv.create({
|
|
3028
|
+
port,
|
|
3029
|
+
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
3030
|
+
outputDirectory: tempDir,
|
|
3031
|
+
configFile: this.options.configFile,
|
|
3032
|
+
configProfile: this.options.configProfile,
|
|
3033
|
+
acseTimeout: this.options.acseTimeout,
|
|
3034
|
+
dimseTimeout: this.options.dimseTimeout,
|
|
3035
|
+
maxPdu: this.options.maxPdu
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
2966
3038
|
/** Spawns a single Dcmrecv worker with an ephemeral port. */
|
|
2967
3039
|
async spawnWorker() {
|
|
2968
3040
|
const portResult = await allocatePort();
|
|
@@ -2971,13 +3043,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2971
3043
|
const tempDir = path__namespace.join(os__namespace.tmpdir(), `dcmrecv-pool-${String(port)}-${String(Date.now())}`);
|
|
2972
3044
|
const mkdirResult = await ensureDirectory(tempDir);
|
|
2973
3045
|
if (!mkdirResult.ok) return mkdirResult;
|
|
2974
|
-
const createResult =
|
|
2975
|
-
port,
|
|
2976
|
-
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
2977
|
-
outputDirectory: tempDir,
|
|
2978
|
-
configFile: this.options.configFile,
|
|
2979
|
-
configProfile: this.options.configProfile
|
|
2980
|
-
});
|
|
3046
|
+
const createResult = this.createDcmrecv(port, tempDir);
|
|
2981
3047
|
if (!createResult.ok) return createResult;
|
|
2982
3048
|
const dcmrecv = createResult.value;
|
|
2983
3049
|
const worker = {
|
|
@@ -2989,6 +3055,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
2989
3055
|
associationDir: void 0,
|
|
2990
3056
|
files: [],
|
|
2991
3057
|
fileSizes: [],
|
|
3058
|
+
outputLines: [],
|
|
2992
3059
|
startAt: void 0,
|
|
2993
3060
|
remoteSocket: void 0,
|
|
2994
3061
|
workerSocket: void 0
|
|
@@ -3050,10 +3117,15 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3050
3117
|
// -----------------------------------------------------------------------
|
|
3051
3118
|
// Worker event wiring
|
|
3052
3119
|
// -----------------------------------------------------------------------
|
|
3053
|
-
/** Wires
|
|
3120
|
+
/** Wires all events on a worker: file handling, association lifecycle, output capture. */
|
|
3054
3121
|
wireWorkerEvents(worker) {
|
|
3055
3122
|
this.wireFileReceived(worker);
|
|
3056
3123
|
this.wireAssociationComplete(worker);
|
|
3124
|
+
this.wireAssociationReceived(worker);
|
|
3125
|
+
this.wireCStoreRequest(worker);
|
|
3126
|
+
this.wireEchoRequest(worker);
|
|
3127
|
+
this.wireRefusingAssociation(worker);
|
|
3128
|
+
this.wireOutputCapture(worker);
|
|
3057
3129
|
}
|
|
3058
3130
|
/** Wires FILE_RECEIVED from dcmrecv worker to handleFileReceived. */
|
|
3059
3131
|
wireFileReceived(worker) {
|
|
@@ -3101,6 +3173,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3101
3173
|
const assocId = worker.associationId ?? data.associationId;
|
|
3102
3174
|
const assocDir = worker.associationDir ?? "";
|
|
3103
3175
|
const files = [...worker.files];
|
|
3176
|
+
const output = [...worker.outputLines];
|
|
3104
3177
|
const endAt = Date.now();
|
|
3105
3178
|
const startAt = worker.startAt ?? endAt;
|
|
3106
3179
|
const totalBytes = sumArray(worker.fileSizes);
|
|
@@ -3118,19 +3191,65 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3118
3191
|
totalBytes,
|
|
3119
3192
|
bytesPerSecond,
|
|
3120
3193
|
startAt,
|
|
3121
|
-
endAt
|
|
3194
|
+
endAt,
|
|
3195
|
+
output
|
|
3122
3196
|
});
|
|
3123
3197
|
worker.state = "idle";
|
|
3124
3198
|
worker.associationId = void 0;
|
|
3125
3199
|
worker.associationDir = void 0;
|
|
3126
3200
|
worker.files = [];
|
|
3127
3201
|
worker.fileSizes = [];
|
|
3202
|
+
worker.outputLines = [];
|
|
3128
3203
|
worker.startAt = void 0;
|
|
3129
3204
|
worker.remoteSocket = void 0;
|
|
3130
3205
|
worker.workerSocket = void 0;
|
|
3131
3206
|
void this.scaleDown();
|
|
3132
3207
|
});
|
|
3133
3208
|
}
|
|
3209
|
+
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
3210
|
+
wireAssociationReceived(worker) {
|
|
3211
|
+
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
3212
|
+
this.emit("ASSOCIATION_RECEIVED", {
|
|
3213
|
+
associationId: worker.associationId ?? "",
|
|
3214
|
+
callingAE: data.callingAE,
|
|
3215
|
+
calledAE: data.calledAE,
|
|
3216
|
+
source: data.source
|
|
3217
|
+
});
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
/** Bubbles C_STORE_REQUEST from dcmrecv worker. */
|
|
3221
|
+
wireCStoreRequest(worker) {
|
|
3222
|
+
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
3223
|
+
this.emit("C_STORE_REQUEST", {
|
|
3224
|
+
associationId: worker.associationId ?? "",
|
|
3225
|
+
raw: data.raw
|
|
3226
|
+
});
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
/** Bubbles ECHO_REQUEST from dcmrecv worker. */
|
|
3230
|
+
wireEchoRequest(worker) {
|
|
3231
|
+
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
3232
|
+
this.emit("ECHO_REQUEST", {
|
|
3233
|
+
associationId: worker.associationId ?? ""
|
|
3234
|
+
});
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
/** Bubbles REFUSING_ASSOCIATION from dcmrecv worker. */
|
|
3238
|
+
wireRefusingAssociation(worker) {
|
|
3239
|
+
worker.dcmrecv.onEvent("REFUSING_ASSOCIATION", (data) => {
|
|
3240
|
+
this.emit("REFUSING_ASSOCIATION", {
|
|
3241
|
+
reason: data.reason
|
|
3242
|
+
});
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
/** Captures worker output lines during busy associations. */
|
|
3246
|
+
wireOutputCapture(worker) {
|
|
3247
|
+
worker.dcmrecv.on("line", ({ text }) => {
|
|
3248
|
+
if (worker.state === "busy" && worker.outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
3249
|
+
worker.outputLines.push(text);
|
|
3250
|
+
}
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3134
3253
|
// -----------------------------------------------------------------------
|
|
3135
3254
|
// Abort signal
|
|
3136
3255
|
// -----------------------------------------------------------------------
|