@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/index.cjs
CHANGED
|
@@ -36119,8 +36119,9 @@ var DEFAULT_MAX_POOL_SIZE = 10;
|
|
|
36119
36119
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
36120
36120
|
var CONNECTION_RETRY_INTERVAL_MS = 500;
|
|
36121
36121
|
var MAX_CONNECTION_RETRIES = 200;
|
|
36122
|
+
var MAX_OUTPUT_LINES_PER_ASSOCIATION = 500;
|
|
36122
36123
|
var DicomReceiverOptionsSchema = zod.z.object({
|
|
36123
|
-
port: zod.z.number().int().min(
|
|
36124
|
+
port: zod.z.number().int().min(0).max(65535),
|
|
36124
36125
|
storageDir: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in storageDir" }),
|
|
36125
36126
|
aeTitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36126
36127
|
minPoolSize: zod.z.number().int().min(1).max(100).optional(),
|
|
@@ -36128,6 +36129,9 @@ var DicomReceiverOptionsSchema = zod.z.object({
|
|
|
36128
36129
|
connectionTimeoutMs: zod.z.number().int().positive().optional(),
|
|
36129
36130
|
configFile: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in configFile" }).optional(),
|
|
36130
36131
|
configProfile: zod.z.string().min(1).optional(),
|
|
36132
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
36133
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
36134
|
+
maxPdu: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36131
36135
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
36132
36136
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
36133
36137
|
message: "minPoolSize must be <= maxPoolSize"
|
|
@@ -36256,6 +36260,57 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36256
36260
|
onAssociationComplete(listener) {
|
|
36257
36261
|
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
36258
36262
|
}
|
|
36263
|
+
/**
|
|
36264
|
+
* Registers a listener for new associations (bubbled from workers).
|
|
36265
|
+
*
|
|
36266
|
+
* @param listener - Callback receiving association received data
|
|
36267
|
+
* @returns this for chaining
|
|
36268
|
+
*/
|
|
36269
|
+
onAssociationReceived(listener) {
|
|
36270
|
+
return this.on("ASSOCIATION_RECEIVED", listener);
|
|
36271
|
+
}
|
|
36272
|
+
/**
|
|
36273
|
+
* Registers a listener for C-STORE requests (bubbled from workers).
|
|
36274
|
+
*
|
|
36275
|
+
* @param listener - Callback receiving C-STORE request data
|
|
36276
|
+
* @returns this for chaining
|
|
36277
|
+
*/
|
|
36278
|
+
onCStoreRequest(listener) {
|
|
36279
|
+
return this.on("C_STORE_REQUEST", listener);
|
|
36280
|
+
}
|
|
36281
|
+
/**
|
|
36282
|
+
* Registers a listener for C-ECHO requests (bubbled from workers).
|
|
36283
|
+
*
|
|
36284
|
+
* @param listener - Callback receiving echo request data
|
|
36285
|
+
* @returns this for chaining
|
|
36286
|
+
*/
|
|
36287
|
+
onEchoRequest(listener) {
|
|
36288
|
+
return this.on("ECHO_REQUEST", listener);
|
|
36289
|
+
}
|
|
36290
|
+
/**
|
|
36291
|
+
* Registers a listener for refused associations (bubbled from workers).
|
|
36292
|
+
*
|
|
36293
|
+
* @param listener - Callback receiving refusing association data
|
|
36294
|
+
* @returns this for chaining
|
|
36295
|
+
*/
|
|
36296
|
+
onRefusingAssociation(listener) {
|
|
36297
|
+
return this.on("REFUSING_ASSOCIATION", listener);
|
|
36298
|
+
}
|
|
36299
|
+
/**
|
|
36300
|
+
* Routes an external socket to an idle worker.
|
|
36301
|
+
*
|
|
36302
|
+
* The pool must be started via `start()` before calling this method.
|
|
36303
|
+
* Use this when managing your own TCP listener (e.g., protocol router).
|
|
36304
|
+
*
|
|
36305
|
+
* @param socket - An incoming net.Socket to route to a worker
|
|
36306
|
+
*/
|
|
36307
|
+
handleSocket(socket) {
|
|
36308
|
+
if (!this.started) {
|
|
36309
|
+
socket.destroy(new Error("DicomReceiver: not started"));
|
|
36310
|
+
return;
|
|
36311
|
+
}
|
|
36312
|
+
void this.handleConnection(socket);
|
|
36313
|
+
}
|
|
36259
36314
|
/** Current pool status. */
|
|
36260
36315
|
get poolStatus() {
|
|
36261
36316
|
let idle = 0;
|
|
@@ -36269,8 +36324,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36269
36324
|
// -----------------------------------------------------------------------
|
|
36270
36325
|
// TCP proxy
|
|
36271
36326
|
// -----------------------------------------------------------------------
|
|
36272
|
-
/** Starts the TCP proxy on the configured port. */
|
|
36327
|
+
/** Starts the TCP proxy on the configured port. Skips if port is 0. */
|
|
36273
36328
|
startTcpProxy() {
|
|
36329
|
+
if (this.options.port === 0) {
|
|
36330
|
+
return Promise.resolve(ok(void 0));
|
|
36331
|
+
}
|
|
36274
36332
|
return new Promise((resolve) => {
|
|
36275
36333
|
this.tcpServer = net__namespace.createServer((socket) => {
|
|
36276
36334
|
void this.handleConnection(socket);
|
|
@@ -36323,6 +36381,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36323
36381
|
worker.associationDir = associationDir;
|
|
36324
36382
|
worker.files = [];
|
|
36325
36383
|
worker.fileSizes = [];
|
|
36384
|
+
worker.outputLines = [];
|
|
36326
36385
|
worker.startAt = Date.now();
|
|
36327
36386
|
this.pipeConnection(worker, remoteSocket);
|
|
36328
36387
|
void this.replenishPool();
|
|
@@ -36381,6 +36440,19 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36381
36440
|
}
|
|
36382
36441
|
return ok(void 0);
|
|
36383
36442
|
}
|
|
36443
|
+
/** Creates a Dcmrecv instance with the pool's shared options. */
|
|
36444
|
+
createDcmrecv(port, tempDir) {
|
|
36445
|
+
return Dcmrecv.create({
|
|
36446
|
+
port,
|
|
36447
|
+
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
36448
|
+
outputDirectory: tempDir,
|
|
36449
|
+
configFile: this.options.configFile,
|
|
36450
|
+
configProfile: this.options.configProfile,
|
|
36451
|
+
acseTimeout: this.options.acseTimeout,
|
|
36452
|
+
dimseTimeout: this.options.dimseTimeout,
|
|
36453
|
+
maxPdu: this.options.maxPdu
|
|
36454
|
+
});
|
|
36455
|
+
}
|
|
36384
36456
|
/** Spawns a single Dcmrecv worker with an ephemeral port. */
|
|
36385
36457
|
async spawnWorker() {
|
|
36386
36458
|
const portResult = await allocatePort();
|
|
@@ -36389,13 +36461,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36389
36461
|
const tempDir = path__namespace.join(os__namespace.tmpdir(), `dcmrecv-pool-${String(port)}-${String(Date.now())}`);
|
|
36390
36462
|
const mkdirResult = await ensureDirectory(tempDir);
|
|
36391
36463
|
if (!mkdirResult.ok) return mkdirResult;
|
|
36392
|
-
const createResult =
|
|
36393
|
-
port,
|
|
36394
|
-
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
36395
|
-
outputDirectory: tempDir,
|
|
36396
|
-
configFile: this.options.configFile,
|
|
36397
|
-
configProfile: this.options.configProfile
|
|
36398
|
-
});
|
|
36464
|
+
const createResult = this.createDcmrecv(port, tempDir);
|
|
36399
36465
|
if (!createResult.ok) return createResult;
|
|
36400
36466
|
const dcmrecv = createResult.value;
|
|
36401
36467
|
const worker = {
|
|
@@ -36407,6 +36473,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36407
36473
|
associationDir: void 0,
|
|
36408
36474
|
files: [],
|
|
36409
36475
|
fileSizes: [],
|
|
36476
|
+
outputLines: [],
|
|
36410
36477
|
startAt: void 0,
|
|
36411
36478
|
remoteSocket: void 0,
|
|
36412
36479
|
workerSocket: void 0
|
|
@@ -36468,10 +36535,15 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36468
36535
|
// -----------------------------------------------------------------------
|
|
36469
36536
|
// Worker event wiring
|
|
36470
36537
|
// -----------------------------------------------------------------------
|
|
36471
|
-
/** Wires
|
|
36538
|
+
/** Wires all events on a worker: file handling, association lifecycle, output capture. */
|
|
36472
36539
|
wireWorkerEvents(worker) {
|
|
36473
36540
|
this.wireFileReceived(worker);
|
|
36474
36541
|
this.wireAssociationComplete(worker);
|
|
36542
|
+
this.wireAssociationReceived(worker);
|
|
36543
|
+
this.wireCStoreRequest(worker);
|
|
36544
|
+
this.wireEchoRequest(worker);
|
|
36545
|
+
this.wireRefusingAssociation(worker);
|
|
36546
|
+
this.wireOutputCapture(worker);
|
|
36475
36547
|
}
|
|
36476
36548
|
/** Wires FILE_RECEIVED from dcmrecv worker to handleFileReceived. */
|
|
36477
36549
|
wireFileReceived(worker) {
|
|
@@ -36519,6 +36591,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36519
36591
|
const assocId = worker.associationId ?? data.associationId;
|
|
36520
36592
|
const assocDir = worker.associationDir ?? "";
|
|
36521
36593
|
const files = [...worker.files];
|
|
36594
|
+
const output = [...worker.outputLines];
|
|
36522
36595
|
const endAt = Date.now();
|
|
36523
36596
|
const startAt = worker.startAt ?? endAt;
|
|
36524
36597
|
const totalBytes = sumArray(worker.fileSizes);
|
|
@@ -36536,19 +36609,65 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36536
36609
|
totalBytes,
|
|
36537
36610
|
bytesPerSecond,
|
|
36538
36611
|
startAt,
|
|
36539
|
-
endAt
|
|
36612
|
+
endAt,
|
|
36613
|
+
output
|
|
36540
36614
|
});
|
|
36541
36615
|
worker.state = "idle";
|
|
36542
36616
|
worker.associationId = void 0;
|
|
36543
36617
|
worker.associationDir = void 0;
|
|
36544
36618
|
worker.files = [];
|
|
36545
36619
|
worker.fileSizes = [];
|
|
36620
|
+
worker.outputLines = [];
|
|
36546
36621
|
worker.startAt = void 0;
|
|
36547
36622
|
worker.remoteSocket = void 0;
|
|
36548
36623
|
worker.workerSocket = void 0;
|
|
36549
36624
|
void this.scaleDown();
|
|
36550
36625
|
});
|
|
36551
36626
|
}
|
|
36627
|
+
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36628
|
+
wireAssociationReceived(worker) {
|
|
36629
|
+
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36630
|
+
this.emit("ASSOCIATION_RECEIVED", {
|
|
36631
|
+
associationId: worker.associationId ?? "",
|
|
36632
|
+
callingAE: data.callingAE,
|
|
36633
|
+
calledAE: data.calledAE,
|
|
36634
|
+
source: data.source
|
|
36635
|
+
});
|
|
36636
|
+
});
|
|
36637
|
+
}
|
|
36638
|
+
/** Bubbles C_STORE_REQUEST from dcmrecv worker. */
|
|
36639
|
+
wireCStoreRequest(worker) {
|
|
36640
|
+
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36641
|
+
this.emit("C_STORE_REQUEST", {
|
|
36642
|
+
associationId: worker.associationId ?? "",
|
|
36643
|
+
raw: data.raw
|
|
36644
|
+
});
|
|
36645
|
+
});
|
|
36646
|
+
}
|
|
36647
|
+
/** Bubbles ECHO_REQUEST from dcmrecv worker. */
|
|
36648
|
+
wireEchoRequest(worker) {
|
|
36649
|
+
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36650
|
+
this.emit("ECHO_REQUEST", {
|
|
36651
|
+
associationId: worker.associationId ?? ""
|
|
36652
|
+
});
|
|
36653
|
+
});
|
|
36654
|
+
}
|
|
36655
|
+
/** Bubbles REFUSING_ASSOCIATION from dcmrecv worker. */
|
|
36656
|
+
wireRefusingAssociation(worker) {
|
|
36657
|
+
worker.dcmrecv.onEvent("REFUSING_ASSOCIATION", (data) => {
|
|
36658
|
+
this.emit("REFUSING_ASSOCIATION", {
|
|
36659
|
+
reason: data.reason
|
|
36660
|
+
});
|
|
36661
|
+
});
|
|
36662
|
+
}
|
|
36663
|
+
/** Captures worker output lines during busy associations. */
|
|
36664
|
+
wireOutputCapture(worker) {
|
|
36665
|
+
worker.dcmrecv.on("line", ({ text }) => {
|
|
36666
|
+
if (worker.state === "busy" && worker.outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
36667
|
+
worker.outputLines.push(text);
|
|
36668
|
+
}
|
|
36669
|
+
});
|
|
36670
|
+
}
|
|
36552
36671
|
// -----------------------------------------------------------------------
|
|
36553
36672
|
// Abort signal
|
|
36554
36673
|
// -----------------------------------------------------------------------
|