@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.js
CHANGED
|
@@ -2675,8 +2675,9 @@ var DEFAULT_MAX_POOL_SIZE = 10;
|
|
|
2675
2675
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
2676
2676
|
var CONNECTION_RETRY_INTERVAL_MS = 500;
|
|
2677
2677
|
var MAX_CONNECTION_RETRIES = 200;
|
|
2678
|
+
var MAX_OUTPUT_LINES_PER_ASSOCIATION = 500;
|
|
2678
2679
|
var DicomReceiverOptionsSchema = z.object({
|
|
2679
|
-
port: z.number().int().min(
|
|
2680
|
+
port: z.number().int().min(0).max(65535),
|
|
2680
2681
|
storageDir: z.string().min(1).refine(isSafePath, { message: "path traversal detected in storageDir" }),
|
|
2681
2682
|
aeTitle: z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
2682
2683
|
minPoolSize: z.number().int().min(1).max(100).optional(),
|
|
@@ -2684,6 +2685,9 @@ var DicomReceiverOptionsSchema = z.object({
|
|
|
2684
2685
|
connectionTimeoutMs: z.number().int().positive().optional(),
|
|
2685
2686
|
configFile: z.string().min(1).refine(isSafePath, { message: "path traversal detected in configFile" }).optional(),
|
|
2686
2687
|
configProfile: z.string().min(1).optional(),
|
|
2688
|
+
acseTimeout: z.number().int().positive().optional(),
|
|
2689
|
+
dimseTimeout: z.number().int().positive().optional(),
|
|
2690
|
+
maxPdu: z.number().int().min(4096).max(131072).optional(),
|
|
2687
2691
|
signal: z.instanceof(AbortSignal).optional()
|
|
2688
2692
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
2689
2693
|
message: "minPoolSize must be <= maxPoolSize"
|
|
@@ -2812,6 +2816,57 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2812
2816
|
onAssociationComplete(listener) {
|
|
2813
2817
|
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
2814
2818
|
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Registers a listener for new associations (bubbled from workers).
|
|
2821
|
+
*
|
|
2822
|
+
* @param listener - Callback receiving association received data
|
|
2823
|
+
* @returns this for chaining
|
|
2824
|
+
*/
|
|
2825
|
+
onAssociationReceived(listener) {
|
|
2826
|
+
return this.on("ASSOCIATION_RECEIVED", listener);
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Registers a listener for C-STORE requests (bubbled from workers).
|
|
2830
|
+
*
|
|
2831
|
+
* @param listener - Callback receiving C-STORE request data
|
|
2832
|
+
* @returns this for chaining
|
|
2833
|
+
*/
|
|
2834
|
+
onCStoreRequest(listener) {
|
|
2835
|
+
return this.on("C_STORE_REQUEST", listener);
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Registers a listener for C-ECHO requests (bubbled from workers).
|
|
2839
|
+
*
|
|
2840
|
+
* @param listener - Callback receiving echo request data
|
|
2841
|
+
* @returns this for chaining
|
|
2842
|
+
*/
|
|
2843
|
+
onEchoRequest(listener) {
|
|
2844
|
+
return this.on("ECHO_REQUEST", listener);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Registers a listener for refused associations (bubbled from workers).
|
|
2848
|
+
*
|
|
2849
|
+
* @param listener - Callback receiving refusing association data
|
|
2850
|
+
* @returns this for chaining
|
|
2851
|
+
*/
|
|
2852
|
+
onRefusingAssociation(listener) {
|
|
2853
|
+
return this.on("REFUSING_ASSOCIATION", listener);
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Routes an external socket to an idle worker.
|
|
2857
|
+
*
|
|
2858
|
+
* The pool must be started via `start()` before calling this method.
|
|
2859
|
+
* Use this when managing your own TCP listener (e.g., protocol router).
|
|
2860
|
+
*
|
|
2861
|
+
* @param socket - An incoming net.Socket to route to a worker
|
|
2862
|
+
*/
|
|
2863
|
+
handleSocket(socket) {
|
|
2864
|
+
if (!this.started) {
|
|
2865
|
+
socket.destroy(new Error("DicomReceiver: not started"));
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
void this.handleConnection(socket);
|
|
2869
|
+
}
|
|
2815
2870
|
/** Current pool status. */
|
|
2816
2871
|
get poolStatus() {
|
|
2817
2872
|
let idle = 0;
|
|
@@ -2825,8 +2880,11 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2825
2880
|
// -----------------------------------------------------------------------
|
|
2826
2881
|
// TCP proxy
|
|
2827
2882
|
// -----------------------------------------------------------------------
|
|
2828
|
-
/** Starts the TCP proxy on the configured port. */
|
|
2883
|
+
/** Starts the TCP proxy on the configured port. Skips if port is 0. */
|
|
2829
2884
|
startTcpProxy() {
|
|
2885
|
+
if (this.options.port === 0) {
|
|
2886
|
+
return Promise.resolve(ok(void 0));
|
|
2887
|
+
}
|
|
2830
2888
|
return new Promise((resolve) => {
|
|
2831
2889
|
this.tcpServer = net.createServer((socket) => {
|
|
2832
2890
|
void this.handleConnection(socket);
|
|
@@ -2879,6 +2937,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2879
2937
|
worker.associationDir = associationDir;
|
|
2880
2938
|
worker.files = [];
|
|
2881
2939
|
worker.fileSizes = [];
|
|
2940
|
+
worker.outputLines = [];
|
|
2882
2941
|
worker.startAt = Date.now();
|
|
2883
2942
|
this.pipeConnection(worker, remoteSocket);
|
|
2884
2943
|
void this.replenishPool();
|
|
@@ -2937,6 +2996,19 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2937
2996
|
}
|
|
2938
2997
|
return ok(void 0);
|
|
2939
2998
|
}
|
|
2999
|
+
/** Creates a Dcmrecv instance with the pool's shared options. */
|
|
3000
|
+
createDcmrecv(port, tempDir) {
|
|
3001
|
+
return Dcmrecv.create({
|
|
3002
|
+
port,
|
|
3003
|
+
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
3004
|
+
outputDirectory: tempDir,
|
|
3005
|
+
configFile: this.options.configFile,
|
|
3006
|
+
configProfile: this.options.configProfile,
|
|
3007
|
+
acseTimeout: this.options.acseTimeout,
|
|
3008
|
+
dimseTimeout: this.options.dimseTimeout,
|
|
3009
|
+
maxPdu: this.options.maxPdu
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
2940
3012
|
/** Spawns a single Dcmrecv worker with an ephemeral port. */
|
|
2941
3013
|
async spawnWorker() {
|
|
2942
3014
|
const portResult = await allocatePort();
|
|
@@ -2945,13 +3017,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2945
3017
|
const tempDir = path.join(os.tmpdir(), `dcmrecv-pool-${String(port)}-${String(Date.now())}`);
|
|
2946
3018
|
const mkdirResult = await ensureDirectory(tempDir);
|
|
2947
3019
|
if (!mkdirResult.ok) return mkdirResult;
|
|
2948
|
-
const createResult =
|
|
2949
|
-
port,
|
|
2950
|
-
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
2951
|
-
outputDirectory: tempDir,
|
|
2952
|
-
configFile: this.options.configFile,
|
|
2953
|
-
configProfile: this.options.configProfile
|
|
2954
|
-
});
|
|
3020
|
+
const createResult = this.createDcmrecv(port, tempDir);
|
|
2955
3021
|
if (!createResult.ok) return createResult;
|
|
2956
3022
|
const dcmrecv = createResult.value;
|
|
2957
3023
|
const worker = {
|
|
@@ -2963,6 +3029,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
2963
3029
|
associationDir: void 0,
|
|
2964
3030
|
files: [],
|
|
2965
3031
|
fileSizes: [],
|
|
3032
|
+
outputLines: [],
|
|
2966
3033
|
startAt: void 0,
|
|
2967
3034
|
remoteSocket: void 0,
|
|
2968
3035
|
workerSocket: void 0
|
|
@@ -3024,10 +3091,15 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
3024
3091
|
// -----------------------------------------------------------------------
|
|
3025
3092
|
// Worker event wiring
|
|
3026
3093
|
// -----------------------------------------------------------------------
|
|
3027
|
-
/** Wires
|
|
3094
|
+
/** Wires all events on a worker: file handling, association lifecycle, output capture. */
|
|
3028
3095
|
wireWorkerEvents(worker) {
|
|
3029
3096
|
this.wireFileReceived(worker);
|
|
3030
3097
|
this.wireAssociationComplete(worker);
|
|
3098
|
+
this.wireAssociationReceived(worker);
|
|
3099
|
+
this.wireCStoreRequest(worker);
|
|
3100
|
+
this.wireEchoRequest(worker);
|
|
3101
|
+
this.wireRefusingAssociation(worker);
|
|
3102
|
+
this.wireOutputCapture(worker);
|
|
3031
3103
|
}
|
|
3032
3104
|
/** Wires FILE_RECEIVED from dcmrecv worker to handleFileReceived. */
|
|
3033
3105
|
wireFileReceived(worker) {
|
|
@@ -3075,6 +3147,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
3075
3147
|
const assocId = worker.associationId ?? data.associationId;
|
|
3076
3148
|
const assocDir = worker.associationDir ?? "";
|
|
3077
3149
|
const files = [...worker.files];
|
|
3150
|
+
const output = [...worker.outputLines];
|
|
3078
3151
|
const endAt = Date.now();
|
|
3079
3152
|
const startAt = worker.startAt ?? endAt;
|
|
3080
3153
|
const totalBytes = sumArray(worker.fileSizes);
|
|
@@ -3092,19 +3165,65 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
3092
3165
|
totalBytes,
|
|
3093
3166
|
bytesPerSecond,
|
|
3094
3167
|
startAt,
|
|
3095
|
-
endAt
|
|
3168
|
+
endAt,
|
|
3169
|
+
output
|
|
3096
3170
|
});
|
|
3097
3171
|
worker.state = "idle";
|
|
3098
3172
|
worker.associationId = void 0;
|
|
3099
3173
|
worker.associationDir = void 0;
|
|
3100
3174
|
worker.files = [];
|
|
3101
3175
|
worker.fileSizes = [];
|
|
3176
|
+
worker.outputLines = [];
|
|
3102
3177
|
worker.startAt = void 0;
|
|
3103
3178
|
worker.remoteSocket = void 0;
|
|
3104
3179
|
worker.workerSocket = void 0;
|
|
3105
3180
|
void this.scaleDown();
|
|
3106
3181
|
});
|
|
3107
3182
|
}
|
|
3183
|
+
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
3184
|
+
wireAssociationReceived(worker) {
|
|
3185
|
+
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
3186
|
+
this.emit("ASSOCIATION_RECEIVED", {
|
|
3187
|
+
associationId: worker.associationId ?? "",
|
|
3188
|
+
callingAE: data.callingAE,
|
|
3189
|
+
calledAE: data.calledAE,
|
|
3190
|
+
source: data.source
|
|
3191
|
+
});
|
|
3192
|
+
});
|
|
3193
|
+
}
|
|
3194
|
+
/** Bubbles C_STORE_REQUEST from dcmrecv worker. */
|
|
3195
|
+
wireCStoreRequest(worker) {
|
|
3196
|
+
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
3197
|
+
this.emit("C_STORE_REQUEST", {
|
|
3198
|
+
associationId: worker.associationId ?? "",
|
|
3199
|
+
raw: data.raw
|
|
3200
|
+
});
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
/** Bubbles ECHO_REQUEST from dcmrecv worker. */
|
|
3204
|
+
wireEchoRequest(worker) {
|
|
3205
|
+
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
3206
|
+
this.emit("ECHO_REQUEST", {
|
|
3207
|
+
associationId: worker.associationId ?? ""
|
|
3208
|
+
});
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
/** Bubbles REFUSING_ASSOCIATION from dcmrecv worker. */
|
|
3212
|
+
wireRefusingAssociation(worker) {
|
|
3213
|
+
worker.dcmrecv.onEvent("REFUSING_ASSOCIATION", (data) => {
|
|
3214
|
+
this.emit("REFUSING_ASSOCIATION", {
|
|
3215
|
+
reason: data.reason
|
|
3216
|
+
});
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
/** Captures worker output lines during busy associations. */
|
|
3220
|
+
wireOutputCapture(worker) {
|
|
3221
|
+
worker.dcmrecv.on("line", ({ text }) => {
|
|
3222
|
+
if (worker.state === "busy" && worker.outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
3223
|
+
worker.outputLines.push(text);
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3108
3227
|
// -----------------------------------------------------------------------
|
|
3109
3228
|
// Abort signal
|
|
3110
3229
|
// -----------------------------------------------------------------------
|