@ubercode/dcmtk 0.9.4 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dicom.cjs +8 -7
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.js +8 -7
- package/dist/dicom.js.map +1 -1
- package/dist/{index-zdzuZTms.d.cts → index-CEqwLKgE.d.cts} +2 -4
- package/dist/{index-x98H349Q.d.ts → index-Hp4Ri9y5.d.ts} +2 -4
- package/dist/index.cjs +346 -292
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +333 -279
- package/dist/index.js.map +1 -1
- package/dist/retry-Aryrk1uP.d.ts +112 -0
- package/dist/retry-CJ60nZxE.d.cts +112 -0
- package/dist/servers.cjs +472 -420
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +1 -1
- package/dist/servers.d.ts +1 -1
- package/dist/servers.js +465 -413
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +8 -7
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.js +8 -7
- package/dist/tools.js.map +1 -1
- package/dist/utils.cjs +46 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +21 -94
- package/dist/utils.d.ts +21 -94
- package/dist/utils.js +43 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
var path = require('path');
|
|
4
4
|
var zod = require('zod');
|
|
5
|
-
var fs
|
|
5
|
+
var fs = require('fs');
|
|
6
6
|
var child_process = require('child_process');
|
|
7
7
|
var kill = require('tree-kill');
|
|
8
8
|
var events = require('events');
|
|
9
9
|
var stderrLib = require('stderr-lib');
|
|
10
|
-
var
|
|
10
|
+
var promises$1 = require('fs/promises');
|
|
11
11
|
var os = require('os');
|
|
12
12
|
var fastXmlParser = require('fast-xml-parser');
|
|
13
13
|
var net = require('net');
|
|
14
|
+
var promises = require('timers/promises');
|
|
14
15
|
var crypto = require('crypto');
|
|
15
16
|
|
|
16
17
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -35,7 +36,6 @@ function _interopNamespace(e) {
|
|
|
35
36
|
|
|
36
37
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
37
38
|
var kill__default = /*#__PURE__*/_interopDefault(kill);
|
|
38
|
-
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
39
39
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
40
40
|
var net__namespace = /*#__PURE__*/_interopNamespace(net);
|
|
41
41
|
|
|
@@ -224,7 +224,7 @@ function binaryName(name) {
|
|
|
224
224
|
}
|
|
225
225
|
function hasRequiredBinaries(dir) {
|
|
226
226
|
for (const bin of REQUIRED_BINARIES) {
|
|
227
|
-
if (!fs
|
|
227
|
+
if (!fs.existsSync(path.join(dir, binaryName(bin)))) {
|
|
228
228
|
return false;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
@@ -318,13 +318,14 @@ async function execCommand(binary, args, options) {
|
|
|
318
318
|
windowsHide: true,
|
|
319
319
|
signal: options?.signal
|
|
320
320
|
});
|
|
321
|
-
wireSpawnListeners(child, timeoutMs, resolve);
|
|
321
|
+
wireSpawnListeners(binary, child, timeoutMs, resolve);
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
|
-
function wireSpawnListeners(child, timeoutMs, resolve) {
|
|
324
|
+
function wireSpawnListeners(binary, child, timeoutMs, resolve) {
|
|
325
325
|
let stdout = "";
|
|
326
326
|
let stderr8 = "";
|
|
327
327
|
let settled = false;
|
|
328
|
+
const name = binary.split("/").pop() ?? binary;
|
|
328
329
|
const settle = (result) => {
|
|
329
330
|
if (settled) return;
|
|
330
331
|
settled = true;
|
|
@@ -333,25 +334,25 @@ function wireSpawnListeners(child, timeoutMs, resolve) {
|
|
|
333
334
|
};
|
|
334
335
|
const timer = setTimeout(() => {
|
|
335
336
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
336
|
-
settle(err(new Error(
|
|
337
|
+
settle(err(new Error(`${name} timed out after ${timeoutMs}ms (pid: ${String(child.pid ?? "unknown")})`)));
|
|
337
338
|
}, timeoutMs);
|
|
338
339
|
child.stdout?.on("data", (chunk) => {
|
|
339
340
|
stdout += String(chunk);
|
|
340
341
|
if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
|
|
341
342
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
342
|
-
settle(err(new Error(
|
|
343
|
+
settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
|
|
343
344
|
}
|
|
344
345
|
});
|
|
345
346
|
child.stderr?.on("data", (chunk) => {
|
|
346
347
|
stderr8 += String(chunk);
|
|
347
348
|
if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
|
|
348
349
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
349
|
-
settle(err(new Error(
|
|
350
|
+
settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
|
|
350
351
|
}
|
|
351
352
|
});
|
|
352
353
|
child.on("error", (error) => {
|
|
353
354
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
354
|
-
settle(err(new Error(
|
|
355
|
+
settle(err(new Error(`${name} error: ${error.message}`)));
|
|
355
356
|
});
|
|
356
357
|
child.on("close", (code) => {
|
|
357
358
|
settle(ok({ stdout, stderr: stderr8, exitCode: code ?? 1 }));
|
|
@@ -366,7 +367,7 @@ async function spawnCommand(binary, args, options) {
|
|
|
366
367
|
windowsHide: true,
|
|
367
368
|
signal: options?.signal
|
|
368
369
|
});
|
|
369
|
-
wireSpawnListeners(child, timeoutMs, resolve);
|
|
370
|
+
wireSpawnListeners(binary, child, timeoutMs, resolve);
|
|
370
371
|
});
|
|
371
372
|
}
|
|
372
373
|
var ProcessState = {
|
|
@@ -31508,12 +31509,12 @@ async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
|
|
|
31508
31509
|
const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
|
|
31509
31510
|
return await execAndParse(jsonBinary.value, directArgs, inputPath, execOpts);
|
|
31510
31511
|
} finally {
|
|
31511
|
-
|
|
31512
|
+
promises$1.rm(bulkDir, { recursive: true, force: true }).catch(() => {
|
|
31512
31513
|
});
|
|
31513
31514
|
}
|
|
31514
31515
|
}
|
|
31515
31516
|
async function createBulkTempDir() {
|
|
31516
|
-
return
|
|
31517
|
+
return promises$1.mkdtemp(path.join(os.tmpdir(), "dcm2json-bulk-"));
|
|
31517
31518
|
}
|
|
31518
31519
|
async function execAndParse(binary, args, inputPath, execOpts) {
|
|
31519
31520
|
const result = await execCommand(binary, args, execOpts);
|
|
@@ -34341,19 +34342,19 @@ async function applyModifications(filePath, changeset, options) {
|
|
|
34341
34342
|
}
|
|
34342
34343
|
async function copyFileSafe(source, dest) {
|
|
34343
34344
|
return stderrLib.tryCatch(
|
|
34344
|
-
() =>
|
|
34345
|
+
() => promises$1.copyFile(source, dest),
|
|
34345
34346
|
(e) => new Error(`Failed to copy file: ${e.message}`)
|
|
34346
34347
|
);
|
|
34347
34348
|
}
|
|
34348
34349
|
async function statFileSize(path2) {
|
|
34349
34350
|
return stderrLib.tryCatch(
|
|
34350
|
-
async () => (await
|
|
34351
|
+
async () => (await promises$1.stat(path2)).size,
|
|
34351
34352
|
(e) => new Error(`Failed to stat file: ${e.message}`)
|
|
34352
34353
|
);
|
|
34353
34354
|
}
|
|
34354
34355
|
async function unlinkFile(path2) {
|
|
34355
34356
|
return stderrLib.tryCatch(
|
|
34356
|
-
() =>
|
|
34357
|
+
() => promises$1.unlink(path2),
|
|
34357
34358
|
(e) => new Error(`Failed to delete file: ${e.message}`)
|
|
34358
34359
|
);
|
|
34359
34360
|
}
|
|
@@ -36272,6 +36273,184 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
36272
36273
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36273
36274
|
}
|
|
36274
36275
|
};
|
|
36276
|
+
var MIN_CONCURRENCY = 1;
|
|
36277
|
+
var MAX_CONCURRENCY = 64;
|
|
36278
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
36279
|
+
function clampConcurrency(value) {
|
|
36280
|
+
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
36281
|
+
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
36282
|
+
}
|
|
36283
|
+
function createBatchState(total) {
|
|
36284
|
+
const results = [];
|
|
36285
|
+
for (let i = 0; i < total; i += 1) {
|
|
36286
|
+
results.push(void 0);
|
|
36287
|
+
}
|
|
36288
|
+
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
36289
|
+
}
|
|
36290
|
+
async function processItem(item, index, operation, state) {
|
|
36291
|
+
let result;
|
|
36292
|
+
try {
|
|
36293
|
+
result = await operation(item);
|
|
36294
|
+
} catch (thrown) {
|
|
36295
|
+
result = err(stderrLib.stderr(thrown));
|
|
36296
|
+
}
|
|
36297
|
+
state.results[index] = result;
|
|
36298
|
+
state.completed += 1;
|
|
36299
|
+
if (result.ok) {
|
|
36300
|
+
state.succeeded += 1;
|
|
36301
|
+
} else {
|
|
36302
|
+
state.failed += 1;
|
|
36303
|
+
}
|
|
36304
|
+
}
|
|
36305
|
+
async function batch(items, operation, options) {
|
|
36306
|
+
const concurrency = clampConcurrency(options?.concurrency);
|
|
36307
|
+
const signal = options?.signal;
|
|
36308
|
+
const onProgress = options?.onProgress;
|
|
36309
|
+
const total = items.length;
|
|
36310
|
+
if (total === 0) {
|
|
36311
|
+
return { results: [], succeeded: 0, failed: 0 };
|
|
36312
|
+
}
|
|
36313
|
+
const state = createBatchState(total);
|
|
36314
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
36315
|
+
let nextIndex = 0;
|
|
36316
|
+
while (nextIndex < total) {
|
|
36317
|
+
if (signal?.aborted) break;
|
|
36318
|
+
const itemIndex = nextIndex;
|
|
36319
|
+
nextIndex += 1;
|
|
36320
|
+
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
36321
|
+
if (onProgress) {
|
|
36322
|
+
const result = state.results[itemIndex];
|
|
36323
|
+
onProgress(state.completed, total, result);
|
|
36324
|
+
}
|
|
36325
|
+
});
|
|
36326
|
+
inFlight.add(promise);
|
|
36327
|
+
promise.then(
|
|
36328
|
+
() => inFlight.delete(promise),
|
|
36329
|
+
() => inFlight.delete(promise)
|
|
36330
|
+
);
|
|
36331
|
+
if (inFlight.size >= concurrency) {
|
|
36332
|
+
await Promise.race(inFlight);
|
|
36333
|
+
}
|
|
36334
|
+
}
|
|
36335
|
+
if (inFlight.size > 0) {
|
|
36336
|
+
await Promise.all(inFlight);
|
|
36337
|
+
}
|
|
36338
|
+
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
36339
|
+
}
|
|
36340
|
+
|
|
36341
|
+
// src/utils/retry.ts
|
|
36342
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
36343
|
+
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
36344
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
36345
|
+
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
36346
|
+
var JITTER_FRACTION = 0.1;
|
|
36347
|
+
var DEFAULT_CONFIG = {
|
|
36348
|
+
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
36349
|
+
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
36350
|
+
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
36351
|
+
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
36352
|
+
shouldRetry: void 0,
|
|
36353
|
+
signal: void 0,
|
|
36354
|
+
onRetry: void 0
|
|
36355
|
+
};
|
|
36356
|
+
function resolveConfig(opts) {
|
|
36357
|
+
if (!opts) return DEFAULT_CONFIG;
|
|
36358
|
+
return {
|
|
36359
|
+
...DEFAULT_CONFIG,
|
|
36360
|
+
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
36361
|
+
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
36362
|
+
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
36363
|
+
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
36364
|
+
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
36365
|
+
};
|
|
36366
|
+
}
|
|
36367
|
+
function computeDelay(attempt, config) {
|
|
36368
|
+
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
36369
|
+
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
36370
|
+
return Math.max(0, Math.round(baseDelay + jitter));
|
|
36371
|
+
}
|
|
36372
|
+
async function waitWithAbort(delayMs, signal) {
|
|
36373
|
+
if (signal?.aborted) {
|
|
36374
|
+
return false;
|
|
36375
|
+
}
|
|
36376
|
+
return new Promise((resolve) => {
|
|
36377
|
+
const timer = setTimeout(() => {
|
|
36378
|
+
cleanup();
|
|
36379
|
+
resolve(true);
|
|
36380
|
+
}, delayMs);
|
|
36381
|
+
const onAbort = () => {
|
|
36382
|
+
clearTimeout(timer);
|
|
36383
|
+
cleanup();
|
|
36384
|
+
resolve(false);
|
|
36385
|
+
};
|
|
36386
|
+
const cleanup = () => {
|
|
36387
|
+
signal?.removeEventListener("abort", onAbort);
|
|
36388
|
+
};
|
|
36389
|
+
if (signal) {
|
|
36390
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
36391
|
+
}
|
|
36392
|
+
});
|
|
36393
|
+
}
|
|
36394
|
+
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
36395
|
+
if (attempt === config.maxAttempts - 1) return true;
|
|
36396
|
+
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
36397
|
+
if (config.signal?.aborted) return true;
|
|
36398
|
+
return false;
|
|
36399
|
+
}
|
|
36400
|
+
async function retry(operation, options) {
|
|
36401
|
+
const config = resolveConfig(options);
|
|
36402
|
+
let lastResult = err(new Error("No attempts executed"));
|
|
36403
|
+
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
36404
|
+
lastResult = await operation();
|
|
36405
|
+
if (lastResult.ok) return lastResult;
|
|
36406
|
+
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
36407
|
+
const delayMs = computeDelay(attempt, config);
|
|
36408
|
+
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
36409
|
+
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
36410
|
+
if (!waitCompleted) break;
|
|
36411
|
+
}
|
|
36412
|
+
return lastResult;
|
|
36413
|
+
}
|
|
36414
|
+
async function ensureDirectory(dirPath) {
|
|
36415
|
+
try {
|
|
36416
|
+
await promises$1.mkdir(dirPath, { recursive: true });
|
|
36417
|
+
return ok(void 0);
|
|
36418
|
+
} catch (e) {
|
|
36419
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36420
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36421
|
+
}
|
|
36422
|
+
}
|
|
36423
|
+
async function moveFile(src, dest) {
|
|
36424
|
+
try {
|
|
36425
|
+
await promises$1.rename(src, dest);
|
|
36426
|
+
return ok(void 0);
|
|
36427
|
+
} catch {
|
|
36428
|
+
try {
|
|
36429
|
+
await promises$1.copyFile(src, dest);
|
|
36430
|
+
await promises$1.unlink(src);
|
|
36431
|
+
return ok(void 0);
|
|
36432
|
+
} catch (e) {
|
|
36433
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36434
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36435
|
+
}
|
|
36436
|
+
}
|
|
36437
|
+
}
|
|
36438
|
+
async function statFileSafe(filePath) {
|
|
36439
|
+
try {
|
|
36440
|
+
const s = await promises$1.stat(filePath);
|
|
36441
|
+
return s.size;
|
|
36442
|
+
} catch {
|
|
36443
|
+
return 0;
|
|
36444
|
+
}
|
|
36445
|
+
}
|
|
36446
|
+
async function removeDirSafe(dirPath) {
|
|
36447
|
+
try {
|
|
36448
|
+
await promises$1.rm(dirPath, { recursive: true, force: true });
|
|
36449
|
+
} catch {
|
|
36450
|
+
}
|
|
36451
|
+
}
|
|
36452
|
+
|
|
36453
|
+
// src/servers/DicomReceiver.ts
|
|
36275
36454
|
var DEFAULT_MIN_POOL_SIZE = 2;
|
|
36276
36455
|
var DEFAULT_MAX_POOL_SIZE = 10;
|
|
36277
36456
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
@@ -36297,6 +36476,102 @@ var DicomReceiverOptionsSchema = zod.z.object({
|
|
|
36297
36476
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
36298
36477
|
message: "minPoolSize must be <= maxPoolSize"
|
|
36299
36478
|
});
|
|
36479
|
+
var Worker = class {
|
|
36480
|
+
constructor(dcmrecv, port, tempDir) {
|
|
36481
|
+
__publicField(this, "dcmrecv");
|
|
36482
|
+
__publicField(this, "port");
|
|
36483
|
+
__publicField(this, "tempDir");
|
|
36484
|
+
__publicField(this, "_state", "idle");
|
|
36485
|
+
__publicField(this, "_context");
|
|
36486
|
+
__publicField(this, "_pending", /* @__PURE__ */ new Set());
|
|
36487
|
+
__publicField(this, "_files", []);
|
|
36488
|
+
__publicField(this, "_fileSizes", []);
|
|
36489
|
+
__publicField(this, "_outputLines", []);
|
|
36490
|
+
__publicField(this, "_remoteSocket");
|
|
36491
|
+
__publicField(this, "_workerSocket");
|
|
36492
|
+
this.dcmrecv = dcmrecv;
|
|
36493
|
+
this.port = port;
|
|
36494
|
+
this.tempDir = tempDir;
|
|
36495
|
+
}
|
|
36496
|
+
/** Current worker state. */
|
|
36497
|
+
get state() {
|
|
36498
|
+
return this._state;
|
|
36499
|
+
}
|
|
36500
|
+
/** Active association context, or undefined when idle. */
|
|
36501
|
+
get context() {
|
|
36502
|
+
return this._context;
|
|
36503
|
+
}
|
|
36504
|
+
/** Files moved to the association directory during the current association. */
|
|
36505
|
+
get files() {
|
|
36506
|
+
return this._files;
|
|
36507
|
+
}
|
|
36508
|
+
/** Byte sizes parallel to files[], for transfer stats. */
|
|
36509
|
+
get fileSizes() {
|
|
36510
|
+
return this._fileSizes;
|
|
36511
|
+
}
|
|
36512
|
+
/** Captured output lines during the current association. */
|
|
36513
|
+
get outputLines() {
|
|
36514
|
+
return this._outputLines;
|
|
36515
|
+
}
|
|
36516
|
+
/** Marks the worker busy with a new association context. */
|
|
36517
|
+
beginAssociation(ctx) {
|
|
36518
|
+
this._state = "busy";
|
|
36519
|
+
this._context = ctx;
|
|
36520
|
+
this._files.length = 0;
|
|
36521
|
+
this._fileSizes.length = 0;
|
|
36522
|
+
this._outputLines.length = 0;
|
|
36523
|
+
this._pending.clear();
|
|
36524
|
+
}
|
|
36525
|
+
/** Returns the worker to idle and clears all association state. */
|
|
36526
|
+
endAssociation() {
|
|
36527
|
+
this._state = "idle";
|
|
36528
|
+
this._context = void 0;
|
|
36529
|
+
this._files.length = 0;
|
|
36530
|
+
this._fileSizes.length = 0;
|
|
36531
|
+
this._outputLines.length = 0;
|
|
36532
|
+
this._pending.clear();
|
|
36533
|
+
this._remoteSocket = void 0;
|
|
36534
|
+
this._workerSocket = void 0;
|
|
36535
|
+
}
|
|
36536
|
+
/** Tracks an in-flight file handling promise; auto-removes on completion. */
|
|
36537
|
+
trackFile(promise) {
|
|
36538
|
+
this._pending.add(promise);
|
|
36539
|
+
void promise.finally(() => {
|
|
36540
|
+
this._pending.delete(promise);
|
|
36541
|
+
});
|
|
36542
|
+
}
|
|
36543
|
+
/** Records a successfully received file path and its size. */
|
|
36544
|
+
recordFile(filePath, size) {
|
|
36545
|
+
this._files.push(filePath);
|
|
36546
|
+
this._fileSizes.push(size);
|
|
36547
|
+
}
|
|
36548
|
+
/** Awaits all in-flight file handling promises. */
|
|
36549
|
+
async drainPendingFiles() {
|
|
36550
|
+
if (this._pending.size > 0) {
|
|
36551
|
+
await Promise.all(this._pending);
|
|
36552
|
+
}
|
|
36553
|
+
}
|
|
36554
|
+
/** Appends a line to the output buffer, respecting the cap. */
|
|
36555
|
+
captureOutput(text) {
|
|
36556
|
+
if (this._outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
36557
|
+
this._outputLines.push(text);
|
|
36558
|
+
}
|
|
36559
|
+
}
|
|
36560
|
+
/** Stores the remote and worker sockets for later cleanup. */
|
|
36561
|
+
setSockets(remote, worker) {
|
|
36562
|
+
this._remoteSocket = remote;
|
|
36563
|
+
this._workerSocket = worker;
|
|
36564
|
+
}
|
|
36565
|
+
/** Destroys both sockets if they exist and are not already destroyed. */
|
|
36566
|
+
destroySockets() {
|
|
36567
|
+
if (this._remoteSocket !== void 0 && !this._remoteSocket.destroyed) {
|
|
36568
|
+
this._remoteSocket.destroy();
|
|
36569
|
+
}
|
|
36570
|
+
if (this._workerSocket !== void 0 && !this._workerSocket.destroyed) {
|
|
36571
|
+
this._workerSocket.destroy();
|
|
36572
|
+
}
|
|
36573
|
+
}
|
|
36574
|
+
};
|
|
36300
36575
|
function allocatePort() {
|
|
36301
36576
|
return new Promise((resolve) => {
|
|
36302
36577
|
const server = net__namespace.createServer();
|
|
@@ -36314,6 +36589,13 @@ function allocatePort() {
|
|
|
36314
36589
|
});
|
|
36315
36590
|
});
|
|
36316
36591
|
}
|
|
36592
|
+
function sumArray(arr) {
|
|
36593
|
+
let total = 0;
|
|
36594
|
+
for (let i = 0; i < arr.length; i++) {
|
|
36595
|
+
total += arr[i] ?? 0;
|
|
36596
|
+
}
|
|
36597
|
+
return total;
|
|
36598
|
+
}
|
|
36317
36599
|
var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
36318
36600
|
constructor(options) {
|
|
36319
36601
|
super();
|
|
@@ -36328,8 +36610,6 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36328
36610
|
__publicField(this, "stopping", false);
|
|
36329
36611
|
__publicField(this, "abortHandler");
|
|
36330
36612
|
this.setMaxListeners(20);
|
|
36331
|
-
this.on("error", () => {
|
|
36332
|
-
});
|
|
36333
36613
|
this.options = options;
|
|
36334
36614
|
this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
|
|
36335
36615
|
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
@@ -36537,14 +36817,12 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36537
36817
|
this.emit("error", { error: mkdirResult.error });
|
|
36538
36818
|
return;
|
|
36539
36819
|
}
|
|
36540
|
-
|
|
36541
|
-
|
|
36542
|
-
|
|
36543
|
-
|
|
36544
|
-
|
|
36545
|
-
worker.
|
|
36546
|
-
worker.pendingFiles = [];
|
|
36547
|
-
worker.startAt = Date.now();
|
|
36820
|
+
const ctx = {
|
|
36821
|
+
associationId,
|
|
36822
|
+
associationDir,
|
|
36823
|
+
startAt: Date.now()
|
|
36824
|
+
};
|
|
36825
|
+
worker.beginAssociation(ctx);
|
|
36548
36826
|
this.pipeConnection(worker, remoteSocket);
|
|
36549
36827
|
void this.replenishPool();
|
|
36550
36828
|
}
|
|
@@ -36554,7 +36832,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36554
36832
|
if (idle !== void 0) return idle;
|
|
36555
36833
|
const maxRetries = Math.min(Math.ceil(this.connectionTimeoutMs / CONNECTION_RETRY_INTERVAL_MS), MAX_CONNECTION_RETRIES);
|
|
36556
36834
|
for (let i = 0; i < maxRetries; i++) {
|
|
36557
|
-
await
|
|
36835
|
+
await promises.setTimeout(CONNECTION_RETRY_INTERVAL_MS);
|
|
36558
36836
|
const found = this.getIdleWorker();
|
|
36559
36837
|
if (found !== void 0) return found;
|
|
36560
36838
|
if (this.stopping) return void 0;
|
|
@@ -36571,8 +36849,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36571
36849
|
/** Pipes remote socket bidirectionally to the worker's port. */
|
|
36572
36850
|
pipeConnection(worker, remoteSocket) {
|
|
36573
36851
|
const workerSocket = net__namespace.createConnection({ port: worker.port, host: "127.0.0.1" });
|
|
36574
|
-
worker.remoteSocket
|
|
36575
|
-
worker.workerSocket = workerSocket;
|
|
36852
|
+
worker.setSockets(remoteSocket, workerSocket);
|
|
36576
36853
|
const cleanup = () => {
|
|
36577
36854
|
remoteSocket.unpipe(workerSocket);
|
|
36578
36855
|
workerSocket.unpipe(remoteSocket);
|
|
@@ -36631,21 +36908,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36631
36908
|
const createResult = this.createDcmrecv(port, tempDir);
|
|
36632
36909
|
if (!createResult.ok) return createResult;
|
|
36633
36910
|
const dcmrecv = createResult.value;
|
|
36634
|
-
const worker =
|
|
36635
|
-
dcmrecv,
|
|
36636
|
-
port,
|
|
36637
|
-
tempDir,
|
|
36638
|
-
state: "idle",
|
|
36639
|
-
associationId: void 0,
|
|
36640
|
-
associationDir: void 0,
|
|
36641
|
-
files: [],
|
|
36642
|
-
fileSizes: [],
|
|
36643
|
-
outputLines: [],
|
|
36644
|
-
pendingFiles: [],
|
|
36645
|
-
startAt: void 0,
|
|
36646
|
-
remoteSocket: void 0,
|
|
36647
|
-
workerSocket: void 0
|
|
36648
|
-
};
|
|
36911
|
+
const worker = new Worker(dcmrecv, port, tempDir);
|
|
36649
36912
|
this.wireWorkerEvents(worker);
|
|
36650
36913
|
const startResult = await dcmrecv.start();
|
|
36651
36914
|
if (!startResult.ok) {
|
|
@@ -36654,14 +36917,9 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36654
36917
|
this.workers.set(port, worker);
|
|
36655
36918
|
return ok(worker);
|
|
36656
36919
|
}
|
|
36657
|
-
/** Stops a single worker: stop process, clean temp dir, remove from pool. */
|
|
36920
|
+
/** Stops a single worker: destroy sockets, stop process, clean temp dir, remove from pool. */
|
|
36658
36921
|
async stopWorker(worker) {
|
|
36659
|
-
|
|
36660
|
-
worker.remoteSocket.destroy();
|
|
36661
|
-
}
|
|
36662
|
-
if (worker.workerSocket !== void 0 && !worker.workerSocket.destroyed) {
|
|
36663
|
-
worker.workerSocket.destroy();
|
|
36664
|
-
}
|
|
36922
|
+
worker.destroySockets();
|
|
36665
36923
|
await worker.dcmrecv.stop();
|
|
36666
36924
|
worker.dcmrecv[Symbol.dispose]();
|
|
36667
36925
|
await removeDirSafe(worker.tempDir);
|
|
@@ -36713,12 +36971,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36713
36971
|
this.wireRefusingAssociation(worker);
|
|
36714
36972
|
this.wireOutputCapture(worker);
|
|
36715
36973
|
}
|
|
36716
|
-
/** Wires FILE_RECEIVED from dcmrecv worker —
|
|
36974
|
+
/** Wires FILE_RECEIVED from dcmrecv worker — captures context synchronously. */
|
|
36717
36975
|
wireFileReceived(worker) {
|
|
36718
36976
|
worker.dcmrecv.onFileReceived((data) => {
|
|
36719
|
-
const
|
|
36720
|
-
|
|
36721
|
-
if (assocDir === void 0 || assocId === void 0) {
|
|
36977
|
+
const ctx = worker.context;
|
|
36978
|
+
if (ctx === void 0) {
|
|
36722
36979
|
this.emit("error", {
|
|
36723
36980
|
error: new Error(`DicomReceiver: FILE_RECEIVED with no active association (worker state: ${worker.state})`),
|
|
36724
36981
|
filePath: data.filePath,
|
|
@@ -36728,21 +36985,21 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36728
36985
|
});
|
|
36729
36986
|
return;
|
|
36730
36987
|
}
|
|
36731
|
-
const promise = this.handleFileReceived(worker, data,
|
|
36732
|
-
worker.
|
|
36988
|
+
const promise = this.handleFileReceived(worker, data, ctx);
|
|
36989
|
+
worker.trackFile(promise);
|
|
36733
36990
|
});
|
|
36734
36991
|
}
|
|
36735
36992
|
/** Moves a received file, opens it as DicomInstance, and emits FILE_RECEIVED. */
|
|
36736
|
-
async handleFileReceived(worker, data,
|
|
36993
|
+
async handleFileReceived(worker, data, ctx) {
|
|
36737
36994
|
try {
|
|
36738
|
-
await this.moveAndEmitFile(worker, data,
|
|
36995
|
+
await this.moveAndEmitFile(worker, data, ctx);
|
|
36739
36996
|
} catch (thrown) {
|
|
36740
36997
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
36741
36998
|
this.emit("error", {
|
|
36742
36999
|
error,
|
|
36743
37000
|
filePath: data.filePath,
|
|
36744
|
-
associationId:
|
|
36745
|
-
associationDir:
|
|
37001
|
+
associationId: ctx.associationId,
|
|
37002
|
+
associationDir: ctx.associationDir,
|
|
36746
37003
|
callingAE: data.callingAE,
|
|
36747
37004
|
calledAE: data.calledAE,
|
|
36748
37005
|
source: data.source
|
|
@@ -36750,21 +37007,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36750
37007
|
}
|
|
36751
37008
|
}
|
|
36752
37009
|
/** Inner handler: move file, parse DICOM, emit FILE_RECEIVED or error. */
|
|
36753
|
-
async moveAndEmitFile(worker, data,
|
|
37010
|
+
async moveAndEmitFile(worker, data, ctx) {
|
|
36754
37011
|
const srcPath = data.filePath;
|
|
36755
|
-
const destPath = path__namespace.join(
|
|
37012
|
+
const destPath = path__namespace.join(ctx.associationDir, path__namespace.basename(srcPath));
|
|
36756
37013
|
const moveResult = await moveFile(srcPath, destPath);
|
|
36757
37014
|
const finalPath = moveResult.ok ? destPath : srcPath;
|
|
36758
37015
|
const fileSize = await statFileSafe(finalPath);
|
|
36759
|
-
worker.
|
|
36760
|
-
worker.fileSizes.push(fileSize);
|
|
37016
|
+
worker.recordFile(finalPath, fileSize);
|
|
36761
37017
|
const openResult = await DicomInstance.open(finalPath);
|
|
36762
37018
|
if (!openResult.ok) {
|
|
36763
37019
|
this.emit("error", {
|
|
36764
37020
|
error: openResult.error,
|
|
36765
37021
|
filePath: finalPath,
|
|
36766
|
-
associationId:
|
|
36767
|
-
associationDir:
|
|
37022
|
+
associationId: ctx.associationId,
|
|
37023
|
+
associationDir: ctx.associationDir,
|
|
36768
37024
|
callingAE: data.callingAE,
|
|
36769
37025
|
calledAE: data.calledAE,
|
|
36770
37026
|
source: data.source
|
|
@@ -36774,8 +37030,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36774
37030
|
this.emit("FILE_RECEIVED", {
|
|
36775
37031
|
filePath: finalPath,
|
|
36776
37032
|
fileSize,
|
|
36777
|
-
associationId:
|
|
36778
|
-
associationDir:
|
|
37033
|
+
associationId: ctx.associationId,
|
|
37034
|
+
associationDir: ctx.associationDir,
|
|
36779
37035
|
callingAE: data.callingAE,
|
|
36780
37036
|
calledAE: data.calledAE,
|
|
36781
37037
|
source: data.source,
|
|
@@ -36790,21 +37046,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36790
37046
|
}
|
|
36791
37047
|
/** Awaits ALL pending file operations, emits ASSOCIATION_COMPLETE, resets worker state. */
|
|
36792
37048
|
async finalizeAssociation(worker, data) {
|
|
36793
|
-
|
|
36794
|
-
await Promise.all(worker.pendingFiles);
|
|
36795
|
-
}
|
|
37049
|
+
await worker.drainPendingFiles();
|
|
36796
37050
|
this.emitAssociationComplete(worker, data);
|
|
36797
|
-
|
|
37051
|
+
worker.endAssociation();
|
|
36798
37052
|
void this.scaleDown();
|
|
36799
37053
|
}
|
|
36800
37054
|
/** Emits the ASSOCIATION_COMPLETE event with transfer stats. */
|
|
36801
37055
|
emitAssociationComplete(worker, data) {
|
|
36802
|
-
const
|
|
36803
|
-
const
|
|
37056
|
+
const ctx = worker.context;
|
|
37057
|
+
const assocId = ctx?.associationId ?? data.associationId;
|
|
37058
|
+
const assocDir = ctx?.associationDir ?? "";
|
|
37059
|
+
const startAt = ctx?.startAt ?? Date.now();
|
|
36804
37060
|
const files = [...worker.files];
|
|
36805
37061
|
const output = [...worker.outputLines];
|
|
36806
37062
|
const endAt = Date.now();
|
|
36807
|
-
const startAt = worker.startAt ?? endAt;
|
|
36808
37063
|
const totalBytes = sumArray(worker.fileSizes);
|
|
36809
37064
|
const elapsedMs = endAt - startAt;
|
|
36810
37065
|
const bytesPerSecond = elapsedMs > 0 ? Math.round(totalBytes / elapsedMs * 1e3) : 0;
|
|
@@ -36824,24 +37079,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36824
37079
|
output
|
|
36825
37080
|
});
|
|
36826
37081
|
}
|
|
36827
|
-
/** Resets worker state to idle after an association completes. */
|
|
36828
|
-
resetWorker(worker) {
|
|
36829
|
-
worker.state = "idle";
|
|
36830
|
-
worker.associationId = void 0;
|
|
36831
|
-
worker.associationDir = void 0;
|
|
36832
|
-
worker.files = [];
|
|
36833
|
-
worker.fileSizes = [];
|
|
36834
|
-
worker.outputLines = [];
|
|
36835
|
-
worker.pendingFiles = [];
|
|
36836
|
-
worker.startAt = void 0;
|
|
36837
|
-
worker.remoteSocket = void 0;
|
|
36838
|
-
worker.workerSocket = void 0;
|
|
36839
|
-
}
|
|
36840
37082
|
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36841
37083
|
wireAssociationReceived(worker) {
|
|
36842
37084
|
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36843
37085
|
this.emit("ASSOCIATION_RECEIVED", {
|
|
36844
|
-
associationId: worker.associationId ?? "",
|
|
37086
|
+
associationId: worker.context?.associationId ?? "",
|
|
36845
37087
|
callingAE: data.callingAE,
|
|
36846
37088
|
calledAE: data.calledAE,
|
|
36847
37089
|
source: data.source
|
|
@@ -36852,7 +37094,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36852
37094
|
wireCStoreRequest(worker) {
|
|
36853
37095
|
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36854
37096
|
this.emit("C_STORE_REQUEST", {
|
|
36855
|
-
associationId: worker.associationId ?? "",
|
|
37097
|
+
associationId: worker.context?.associationId ?? "",
|
|
36856
37098
|
raw: data.raw
|
|
36857
37099
|
});
|
|
36858
37100
|
});
|
|
@@ -36861,7 +37103,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36861
37103
|
wireEchoRequest(worker) {
|
|
36862
37104
|
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36863
37105
|
this.emit("ECHO_REQUEST", {
|
|
36864
|
-
associationId: worker.associationId ?? ""
|
|
37106
|
+
associationId: worker.context?.associationId ?? ""
|
|
36865
37107
|
});
|
|
36866
37108
|
});
|
|
36867
37109
|
}
|
|
@@ -36876,8 +37118,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36876
37118
|
/** Captures worker output lines during busy associations. */
|
|
36877
37119
|
wireOutputCapture(worker) {
|
|
36878
37120
|
worker.dcmrecv.on("line", ({ text }) => {
|
|
36879
|
-
if (worker.state === "busy"
|
|
36880
|
-
worker.
|
|
37121
|
+
if (worker.state === "busy") {
|
|
37122
|
+
worker.captureOutput(text);
|
|
36881
37123
|
}
|
|
36882
37124
|
});
|
|
36883
37125
|
}
|
|
@@ -36896,56 +37138,6 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36896
37138
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36897
37139
|
}
|
|
36898
37140
|
};
|
|
36899
|
-
async function ensureDirectory(dirPath) {
|
|
36900
|
-
try {
|
|
36901
|
-
await fs__namespace.mkdir(dirPath, { recursive: true });
|
|
36902
|
-
return ok(void 0);
|
|
36903
|
-
} catch (e) {
|
|
36904
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36905
|
-
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36906
|
-
}
|
|
36907
|
-
}
|
|
36908
|
-
async function moveFile(src, dest) {
|
|
36909
|
-
try {
|
|
36910
|
-
await fs__namespace.rename(src, dest);
|
|
36911
|
-
return ok(void 0);
|
|
36912
|
-
} catch {
|
|
36913
|
-
try {
|
|
36914
|
-
await fs__namespace.copyFile(src, dest);
|
|
36915
|
-
await fs__namespace.unlink(src);
|
|
36916
|
-
return ok(void 0);
|
|
36917
|
-
} catch (e) {
|
|
36918
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36919
|
-
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36920
|
-
}
|
|
36921
|
-
}
|
|
36922
|
-
}
|
|
36923
|
-
async function statFileSafe(filePath) {
|
|
36924
|
-
try {
|
|
36925
|
-
const stat4 = await fs__namespace.stat(filePath);
|
|
36926
|
-
return stat4.size;
|
|
36927
|
-
} catch {
|
|
36928
|
-
return 0;
|
|
36929
|
-
}
|
|
36930
|
-
}
|
|
36931
|
-
function sumArray(arr) {
|
|
36932
|
-
let total = 0;
|
|
36933
|
-
for (let i = 0; i < arr.length; i++) {
|
|
36934
|
-
total += arr[i] ?? 0;
|
|
36935
|
-
}
|
|
36936
|
-
return total;
|
|
36937
|
-
}
|
|
36938
|
-
async function removeDirSafe(dirPath) {
|
|
36939
|
-
try {
|
|
36940
|
-
await fs__namespace.rm(dirPath, { recursive: true, force: true });
|
|
36941
|
-
} catch {
|
|
36942
|
-
}
|
|
36943
|
-
}
|
|
36944
|
-
function delay(ms) {
|
|
36945
|
-
return new Promise((resolve) => {
|
|
36946
|
-
setTimeout(resolve, ms);
|
|
36947
|
-
});
|
|
36948
|
-
}
|
|
36949
37141
|
|
|
36950
37142
|
// src/senders/types.ts
|
|
36951
37143
|
var SenderMode = {
|
|
@@ -37368,7 +37560,7 @@ function createStorescuExecutor(options) {
|
|
|
37368
37560
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37369
37561
|
};
|
|
37370
37562
|
}
|
|
37371
|
-
function
|
|
37563
|
+
function resolveConfig2(options) {
|
|
37372
37564
|
const mode = options.mode ?? "multiple";
|
|
37373
37565
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37374
37566
|
return {
|
|
@@ -37415,7 +37607,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37415
37607
|
if (!validation.success) {
|
|
37416
37608
|
return err(createValidationError("DicomSender", validation.error));
|
|
37417
37609
|
}
|
|
37418
|
-
const cfg =
|
|
37610
|
+
const cfg = resolveConfig2(options);
|
|
37419
37611
|
const senderRef = { current: void 0 };
|
|
37420
37612
|
const engine = new SenderEngine({
|
|
37421
37613
|
...cfg,
|
|
@@ -37617,7 +37809,7 @@ function createDcmsendExecutor(options) {
|
|
|
37617
37809
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37618
37810
|
};
|
|
37619
37811
|
}
|
|
37620
|
-
function
|
|
37812
|
+
function resolveConfig3(options) {
|
|
37621
37813
|
const mode = options.mode ?? "multiple";
|
|
37622
37814
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37623
37815
|
return {
|
|
@@ -37664,7 +37856,7 @@ var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
|
37664
37856
|
if (!validation.success) {
|
|
37665
37857
|
return err(createValidationError("DicomSend", validation.error));
|
|
37666
37858
|
}
|
|
37667
|
-
const cfg =
|
|
37859
|
+
const cfg = resolveConfig3(options);
|
|
37668
37860
|
const senderRef = { current: void 0 };
|
|
37669
37861
|
const engine = new SenderEngine({
|
|
37670
37862
|
...cfg,
|
|
@@ -37922,13 +38114,13 @@ function buildWorklistKeys(filter) {
|
|
|
37922
38114
|
var MAX_RESPONSE_FILES = 1e4;
|
|
37923
38115
|
async function createTempDir() {
|
|
37924
38116
|
return stderrLib.tryCatch(
|
|
37925
|
-
() =>
|
|
38117
|
+
() => promises$1.mkdtemp(path.join(os.tmpdir(), "dcmtk-pacs-")),
|
|
37926
38118
|
(e) => new Error(`Failed to create temp directory: ${e.message}`)
|
|
37927
38119
|
);
|
|
37928
38120
|
}
|
|
37929
38121
|
async function listDcmFiles(directory) {
|
|
37930
38122
|
try {
|
|
37931
|
-
const entries = await
|
|
38123
|
+
const entries = await promises$1.readdir(directory);
|
|
37932
38124
|
const dcmFiles = [];
|
|
37933
38125
|
for (let i = 0; i < entries.length; i += 1) {
|
|
37934
38126
|
const entry = entries[i];
|
|
@@ -37946,7 +38138,7 @@ async function listDcmFiles(directory) {
|
|
|
37946
38138
|
}
|
|
37947
38139
|
async function cleanupTempDir(directory) {
|
|
37948
38140
|
try {
|
|
37949
|
-
await
|
|
38141
|
+
await promises$1.rm(directory, { recursive: true, force: true });
|
|
37950
38142
|
} catch {
|
|
37951
38143
|
}
|
|
37952
38144
|
}
|
|
@@ -38265,70 +38457,6 @@ var PacsClient = class _PacsClient {
|
|
|
38265
38457
|
return ok({ success: true, outputDirectory: options.outputDirectory });
|
|
38266
38458
|
}
|
|
38267
38459
|
};
|
|
38268
|
-
var MIN_CONCURRENCY = 1;
|
|
38269
|
-
var MAX_CONCURRENCY = 64;
|
|
38270
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
38271
|
-
function clampConcurrency(value) {
|
|
38272
|
-
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
38273
|
-
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
38274
|
-
}
|
|
38275
|
-
function createBatchState(total) {
|
|
38276
|
-
const results = [];
|
|
38277
|
-
for (let i = 0; i < total; i += 1) {
|
|
38278
|
-
results.push(void 0);
|
|
38279
|
-
}
|
|
38280
|
-
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
38281
|
-
}
|
|
38282
|
-
async function processItem(item, index, operation, state) {
|
|
38283
|
-
let result;
|
|
38284
|
-
try {
|
|
38285
|
-
result = await operation(item);
|
|
38286
|
-
} catch (thrown) {
|
|
38287
|
-
result = err(stderrLib.stderr(thrown));
|
|
38288
|
-
}
|
|
38289
|
-
state.results[index] = result;
|
|
38290
|
-
state.completed += 1;
|
|
38291
|
-
if (result.ok) {
|
|
38292
|
-
state.succeeded += 1;
|
|
38293
|
-
} else {
|
|
38294
|
-
state.failed += 1;
|
|
38295
|
-
}
|
|
38296
|
-
}
|
|
38297
|
-
async function batch(items, operation, options) {
|
|
38298
|
-
const concurrency = clampConcurrency(options?.concurrency);
|
|
38299
|
-
const signal = options?.signal;
|
|
38300
|
-
const onProgress = options?.onProgress;
|
|
38301
|
-
const total = items.length;
|
|
38302
|
-
if (total === 0) {
|
|
38303
|
-
return { results: [], succeeded: 0, failed: 0 };
|
|
38304
|
-
}
|
|
38305
|
-
const state = createBatchState(total);
|
|
38306
|
-
const inFlight = /* @__PURE__ */ new Set();
|
|
38307
|
-
let nextIndex = 0;
|
|
38308
|
-
while (nextIndex < total) {
|
|
38309
|
-
if (signal?.aborted) break;
|
|
38310
|
-
const itemIndex = nextIndex;
|
|
38311
|
-
nextIndex += 1;
|
|
38312
|
-
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
38313
|
-
if (onProgress) {
|
|
38314
|
-
const result = state.results[itemIndex];
|
|
38315
|
-
onProgress(state.completed, total, result);
|
|
38316
|
-
}
|
|
38317
|
-
});
|
|
38318
|
-
inFlight.add(promise);
|
|
38319
|
-
promise.then(
|
|
38320
|
-
() => inFlight.delete(promise),
|
|
38321
|
-
() => inFlight.delete(promise)
|
|
38322
|
-
);
|
|
38323
|
-
if (inFlight.size >= concurrency) {
|
|
38324
|
-
await Promise.race(inFlight);
|
|
38325
|
-
}
|
|
38326
|
-
}
|
|
38327
|
-
if (inFlight.size > 0) {
|
|
38328
|
-
await Promise.all(inFlight);
|
|
38329
|
-
}
|
|
38330
|
-
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
38331
|
-
}
|
|
38332
38460
|
|
|
38333
38461
|
// src/testing/types.ts
|
|
38334
38462
|
var HammerPhase = {
|
|
@@ -38465,7 +38593,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38465
38593
|
const start = Date.now();
|
|
38466
38594
|
const dir = path.join(this.opts.outputDir, `dcmtk-hammer-${Date.now()}`);
|
|
38467
38595
|
try {
|
|
38468
|
-
await
|
|
38596
|
+
await promises$1.mkdir(dir, { recursive: true });
|
|
38469
38597
|
} catch {
|
|
38470
38598
|
return err(new Error(`Failed to create output directory: ${dir}`));
|
|
38471
38599
|
}
|
|
@@ -38587,7 +38715,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38587
38715
|
/** Gets the file size in bytes, returning 0 on error. */
|
|
38588
38716
|
async getFileSize(filePath) {
|
|
38589
38717
|
try {
|
|
38590
|
-
const s = await
|
|
38718
|
+
const s = await promises$1.stat(filePath);
|
|
38591
38719
|
return s.size;
|
|
38592
38720
|
} catch {
|
|
38593
38721
|
return 0;
|
|
@@ -38628,7 +38756,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38628
38756
|
async cleanup() {
|
|
38629
38757
|
if (this.generatedDir === void 0) return ok(void 0);
|
|
38630
38758
|
try {
|
|
38631
|
-
await
|
|
38759
|
+
await promises$1.rm(this.generatedDir, { recursive: true, force: true });
|
|
38632
38760
|
this.generatedDir = void 0;
|
|
38633
38761
|
this.generatedFiles = [];
|
|
38634
38762
|
return ok(void 0);
|
|
@@ -38665,80 +38793,6 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38665
38793
|
}
|
|
38666
38794
|
};
|
|
38667
38795
|
|
|
38668
|
-
// src/utils/retry.ts
|
|
38669
|
-
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
38670
|
-
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
38671
|
-
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
38672
|
-
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
38673
|
-
var JITTER_FRACTION = 0.1;
|
|
38674
|
-
var DEFAULT_CONFIG = {
|
|
38675
|
-
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
38676
|
-
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
38677
|
-
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
38678
|
-
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
38679
|
-
shouldRetry: void 0,
|
|
38680
|
-
signal: void 0,
|
|
38681
|
-
onRetry: void 0
|
|
38682
|
-
};
|
|
38683
|
-
function resolveConfig3(opts) {
|
|
38684
|
-
if (!opts) return DEFAULT_CONFIG;
|
|
38685
|
-
return {
|
|
38686
|
-
...DEFAULT_CONFIG,
|
|
38687
|
-
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
38688
|
-
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
38689
|
-
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
38690
|
-
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
38691
|
-
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
38692
|
-
};
|
|
38693
|
-
}
|
|
38694
|
-
function computeDelay(attempt, config) {
|
|
38695
|
-
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
38696
|
-
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
38697
|
-
return Math.max(0, Math.round(baseDelay + jitter));
|
|
38698
|
-
}
|
|
38699
|
-
async function waitWithAbort(delayMs, signal) {
|
|
38700
|
-
if (signal?.aborted) {
|
|
38701
|
-
return false;
|
|
38702
|
-
}
|
|
38703
|
-
return new Promise((resolve) => {
|
|
38704
|
-
const timer = setTimeout(() => {
|
|
38705
|
-
cleanup();
|
|
38706
|
-
resolve(true);
|
|
38707
|
-
}, delayMs);
|
|
38708
|
-
const onAbort = () => {
|
|
38709
|
-
clearTimeout(timer);
|
|
38710
|
-
cleanup();
|
|
38711
|
-
resolve(false);
|
|
38712
|
-
};
|
|
38713
|
-
const cleanup = () => {
|
|
38714
|
-
signal?.removeEventListener("abort", onAbort);
|
|
38715
|
-
};
|
|
38716
|
-
if (signal) {
|
|
38717
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
38718
|
-
}
|
|
38719
|
-
});
|
|
38720
|
-
}
|
|
38721
|
-
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
38722
|
-
if (attempt === config.maxAttempts - 1) return true;
|
|
38723
|
-
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
38724
|
-
if (config.signal?.aborted) return true;
|
|
38725
|
-
return false;
|
|
38726
|
-
}
|
|
38727
|
-
async function retry(operation, options) {
|
|
38728
|
-
const config = resolveConfig3(options);
|
|
38729
|
-
let lastResult = err(new Error("No attempts executed"));
|
|
38730
|
-
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38731
|
-
lastResult = await operation();
|
|
38732
|
-
if (lastResult.ok) return lastResult;
|
|
38733
|
-
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
38734
|
-
const delayMs = computeDelay(attempt, config);
|
|
38735
|
-
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
38736
|
-
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
38737
|
-
if (!waitCompleted) break;
|
|
38738
|
-
}
|
|
38739
|
-
return lastResult;
|
|
38740
|
-
}
|
|
38741
|
-
|
|
38742
38796
|
exports.AETitleSchema = AETitleSchema;
|
|
38743
38797
|
exports.AssociationTracker = AssociationTracker;
|
|
38744
38798
|
exports.ChangeSet = ChangeSet;
|