@ubercode/dcmtk 0.9.4 → 0.10.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-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 +338 -285
- 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 +325 -272
- 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 +464 -413
- 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 +457 -406
- package/dist/servers.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
|
}
|
|
@@ -31508,12 +31508,12 @@ async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
|
|
|
31508
31508
|
const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
|
|
31509
31509
|
return await execAndParse(jsonBinary.value, directArgs, inputPath, execOpts);
|
|
31510
31510
|
} finally {
|
|
31511
|
-
|
|
31511
|
+
promises$1.rm(bulkDir, { recursive: true, force: true }).catch(() => {
|
|
31512
31512
|
});
|
|
31513
31513
|
}
|
|
31514
31514
|
}
|
|
31515
31515
|
async function createBulkTempDir() {
|
|
31516
|
-
return
|
|
31516
|
+
return promises$1.mkdtemp(path.join(os.tmpdir(), "dcm2json-bulk-"));
|
|
31517
31517
|
}
|
|
31518
31518
|
async function execAndParse(binary, args, inputPath, execOpts) {
|
|
31519
31519
|
const result = await execCommand(binary, args, execOpts);
|
|
@@ -34341,19 +34341,19 @@ async function applyModifications(filePath, changeset, options) {
|
|
|
34341
34341
|
}
|
|
34342
34342
|
async function copyFileSafe(source, dest) {
|
|
34343
34343
|
return stderrLib.tryCatch(
|
|
34344
|
-
() =>
|
|
34344
|
+
() => promises$1.copyFile(source, dest),
|
|
34345
34345
|
(e) => new Error(`Failed to copy file: ${e.message}`)
|
|
34346
34346
|
);
|
|
34347
34347
|
}
|
|
34348
34348
|
async function statFileSize(path2) {
|
|
34349
34349
|
return stderrLib.tryCatch(
|
|
34350
|
-
async () => (await
|
|
34350
|
+
async () => (await promises$1.stat(path2)).size,
|
|
34351
34351
|
(e) => new Error(`Failed to stat file: ${e.message}`)
|
|
34352
34352
|
);
|
|
34353
34353
|
}
|
|
34354
34354
|
async function unlinkFile(path2) {
|
|
34355
34355
|
return stderrLib.tryCatch(
|
|
34356
|
-
() =>
|
|
34356
|
+
() => promises$1.unlink(path2),
|
|
34357
34357
|
(e) => new Error(`Failed to delete file: ${e.message}`)
|
|
34358
34358
|
);
|
|
34359
34359
|
}
|
|
@@ -36272,6 +36272,184 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
36272
36272
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36273
36273
|
}
|
|
36274
36274
|
};
|
|
36275
|
+
var MIN_CONCURRENCY = 1;
|
|
36276
|
+
var MAX_CONCURRENCY = 64;
|
|
36277
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
36278
|
+
function clampConcurrency(value) {
|
|
36279
|
+
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
36280
|
+
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
36281
|
+
}
|
|
36282
|
+
function createBatchState(total) {
|
|
36283
|
+
const results = [];
|
|
36284
|
+
for (let i = 0; i < total; i += 1) {
|
|
36285
|
+
results.push(void 0);
|
|
36286
|
+
}
|
|
36287
|
+
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
36288
|
+
}
|
|
36289
|
+
async function processItem(item, index, operation, state) {
|
|
36290
|
+
let result;
|
|
36291
|
+
try {
|
|
36292
|
+
result = await operation(item);
|
|
36293
|
+
} catch (thrown) {
|
|
36294
|
+
result = err(stderrLib.stderr(thrown));
|
|
36295
|
+
}
|
|
36296
|
+
state.results[index] = result;
|
|
36297
|
+
state.completed += 1;
|
|
36298
|
+
if (result.ok) {
|
|
36299
|
+
state.succeeded += 1;
|
|
36300
|
+
} else {
|
|
36301
|
+
state.failed += 1;
|
|
36302
|
+
}
|
|
36303
|
+
}
|
|
36304
|
+
async function batch(items, operation, options) {
|
|
36305
|
+
const concurrency = clampConcurrency(options?.concurrency);
|
|
36306
|
+
const signal = options?.signal;
|
|
36307
|
+
const onProgress = options?.onProgress;
|
|
36308
|
+
const total = items.length;
|
|
36309
|
+
if (total === 0) {
|
|
36310
|
+
return { results: [], succeeded: 0, failed: 0 };
|
|
36311
|
+
}
|
|
36312
|
+
const state = createBatchState(total);
|
|
36313
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
36314
|
+
let nextIndex = 0;
|
|
36315
|
+
while (nextIndex < total) {
|
|
36316
|
+
if (signal?.aborted) break;
|
|
36317
|
+
const itemIndex = nextIndex;
|
|
36318
|
+
nextIndex += 1;
|
|
36319
|
+
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
36320
|
+
if (onProgress) {
|
|
36321
|
+
const result = state.results[itemIndex];
|
|
36322
|
+
onProgress(state.completed, total, result);
|
|
36323
|
+
}
|
|
36324
|
+
});
|
|
36325
|
+
inFlight.add(promise);
|
|
36326
|
+
promise.then(
|
|
36327
|
+
() => inFlight.delete(promise),
|
|
36328
|
+
() => inFlight.delete(promise)
|
|
36329
|
+
);
|
|
36330
|
+
if (inFlight.size >= concurrency) {
|
|
36331
|
+
await Promise.race(inFlight);
|
|
36332
|
+
}
|
|
36333
|
+
}
|
|
36334
|
+
if (inFlight.size > 0) {
|
|
36335
|
+
await Promise.all(inFlight);
|
|
36336
|
+
}
|
|
36337
|
+
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
36338
|
+
}
|
|
36339
|
+
|
|
36340
|
+
// src/utils/retry.ts
|
|
36341
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
36342
|
+
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
36343
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
36344
|
+
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
36345
|
+
var JITTER_FRACTION = 0.1;
|
|
36346
|
+
var DEFAULT_CONFIG = {
|
|
36347
|
+
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
36348
|
+
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
36349
|
+
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
36350
|
+
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
36351
|
+
shouldRetry: void 0,
|
|
36352
|
+
signal: void 0,
|
|
36353
|
+
onRetry: void 0
|
|
36354
|
+
};
|
|
36355
|
+
function resolveConfig(opts) {
|
|
36356
|
+
if (!opts) return DEFAULT_CONFIG;
|
|
36357
|
+
return {
|
|
36358
|
+
...DEFAULT_CONFIG,
|
|
36359
|
+
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
36360
|
+
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
36361
|
+
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
36362
|
+
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
36363
|
+
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
36364
|
+
};
|
|
36365
|
+
}
|
|
36366
|
+
function computeDelay(attempt, config) {
|
|
36367
|
+
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
36368
|
+
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
36369
|
+
return Math.max(0, Math.round(baseDelay + jitter));
|
|
36370
|
+
}
|
|
36371
|
+
async function waitWithAbort(delayMs, signal) {
|
|
36372
|
+
if (signal?.aborted) {
|
|
36373
|
+
return false;
|
|
36374
|
+
}
|
|
36375
|
+
return new Promise((resolve) => {
|
|
36376
|
+
const timer = setTimeout(() => {
|
|
36377
|
+
cleanup();
|
|
36378
|
+
resolve(true);
|
|
36379
|
+
}, delayMs);
|
|
36380
|
+
const onAbort = () => {
|
|
36381
|
+
clearTimeout(timer);
|
|
36382
|
+
cleanup();
|
|
36383
|
+
resolve(false);
|
|
36384
|
+
};
|
|
36385
|
+
const cleanup = () => {
|
|
36386
|
+
signal?.removeEventListener("abort", onAbort);
|
|
36387
|
+
};
|
|
36388
|
+
if (signal) {
|
|
36389
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
36390
|
+
}
|
|
36391
|
+
});
|
|
36392
|
+
}
|
|
36393
|
+
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
36394
|
+
if (attempt === config.maxAttempts - 1) return true;
|
|
36395
|
+
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
36396
|
+
if (config.signal?.aborted) return true;
|
|
36397
|
+
return false;
|
|
36398
|
+
}
|
|
36399
|
+
async function retry(operation, options) {
|
|
36400
|
+
const config = resolveConfig(options);
|
|
36401
|
+
let lastResult = err(new Error("No attempts executed"));
|
|
36402
|
+
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
36403
|
+
lastResult = await operation();
|
|
36404
|
+
if (lastResult.ok) return lastResult;
|
|
36405
|
+
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
36406
|
+
const delayMs = computeDelay(attempt, config);
|
|
36407
|
+
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
36408
|
+
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
36409
|
+
if (!waitCompleted) break;
|
|
36410
|
+
}
|
|
36411
|
+
return lastResult;
|
|
36412
|
+
}
|
|
36413
|
+
async function ensureDirectory(dirPath) {
|
|
36414
|
+
try {
|
|
36415
|
+
await promises$1.mkdir(dirPath, { recursive: true });
|
|
36416
|
+
return ok(void 0);
|
|
36417
|
+
} catch (e) {
|
|
36418
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36419
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36420
|
+
}
|
|
36421
|
+
}
|
|
36422
|
+
async function moveFile(src, dest) {
|
|
36423
|
+
try {
|
|
36424
|
+
await promises$1.rename(src, dest);
|
|
36425
|
+
return ok(void 0);
|
|
36426
|
+
} catch {
|
|
36427
|
+
try {
|
|
36428
|
+
await promises$1.copyFile(src, dest);
|
|
36429
|
+
await promises$1.unlink(src);
|
|
36430
|
+
return ok(void 0);
|
|
36431
|
+
} catch (e) {
|
|
36432
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36433
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36434
|
+
}
|
|
36435
|
+
}
|
|
36436
|
+
}
|
|
36437
|
+
async function statFileSafe(filePath) {
|
|
36438
|
+
try {
|
|
36439
|
+
const s = await promises$1.stat(filePath);
|
|
36440
|
+
return s.size;
|
|
36441
|
+
} catch {
|
|
36442
|
+
return 0;
|
|
36443
|
+
}
|
|
36444
|
+
}
|
|
36445
|
+
async function removeDirSafe(dirPath) {
|
|
36446
|
+
try {
|
|
36447
|
+
await promises$1.rm(dirPath, { recursive: true, force: true });
|
|
36448
|
+
} catch {
|
|
36449
|
+
}
|
|
36450
|
+
}
|
|
36451
|
+
|
|
36452
|
+
// src/servers/DicomReceiver.ts
|
|
36275
36453
|
var DEFAULT_MIN_POOL_SIZE = 2;
|
|
36276
36454
|
var DEFAULT_MAX_POOL_SIZE = 10;
|
|
36277
36455
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
@@ -36297,6 +36475,102 @@ var DicomReceiverOptionsSchema = zod.z.object({
|
|
|
36297
36475
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
36298
36476
|
message: "minPoolSize must be <= maxPoolSize"
|
|
36299
36477
|
});
|
|
36478
|
+
var Worker = class {
|
|
36479
|
+
constructor(dcmrecv, port, tempDir) {
|
|
36480
|
+
__publicField(this, "dcmrecv");
|
|
36481
|
+
__publicField(this, "port");
|
|
36482
|
+
__publicField(this, "tempDir");
|
|
36483
|
+
__publicField(this, "_state", "idle");
|
|
36484
|
+
__publicField(this, "_context");
|
|
36485
|
+
__publicField(this, "_pending", /* @__PURE__ */ new Set());
|
|
36486
|
+
__publicField(this, "_files", []);
|
|
36487
|
+
__publicField(this, "_fileSizes", []);
|
|
36488
|
+
__publicField(this, "_outputLines", []);
|
|
36489
|
+
__publicField(this, "_remoteSocket");
|
|
36490
|
+
__publicField(this, "_workerSocket");
|
|
36491
|
+
this.dcmrecv = dcmrecv;
|
|
36492
|
+
this.port = port;
|
|
36493
|
+
this.tempDir = tempDir;
|
|
36494
|
+
}
|
|
36495
|
+
/** Current worker state. */
|
|
36496
|
+
get state() {
|
|
36497
|
+
return this._state;
|
|
36498
|
+
}
|
|
36499
|
+
/** Active association context, or undefined when idle. */
|
|
36500
|
+
get context() {
|
|
36501
|
+
return this._context;
|
|
36502
|
+
}
|
|
36503
|
+
/** Files moved to the association directory during the current association. */
|
|
36504
|
+
get files() {
|
|
36505
|
+
return this._files;
|
|
36506
|
+
}
|
|
36507
|
+
/** Byte sizes parallel to files[], for transfer stats. */
|
|
36508
|
+
get fileSizes() {
|
|
36509
|
+
return this._fileSizes;
|
|
36510
|
+
}
|
|
36511
|
+
/** Captured output lines during the current association. */
|
|
36512
|
+
get outputLines() {
|
|
36513
|
+
return this._outputLines;
|
|
36514
|
+
}
|
|
36515
|
+
/** Marks the worker busy with a new association context. */
|
|
36516
|
+
beginAssociation(ctx) {
|
|
36517
|
+
this._state = "busy";
|
|
36518
|
+
this._context = ctx;
|
|
36519
|
+
this._files.length = 0;
|
|
36520
|
+
this._fileSizes.length = 0;
|
|
36521
|
+
this._outputLines.length = 0;
|
|
36522
|
+
this._pending.clear();
|
|
36523
|
+
}
|
|
36524
|
+
/** Returns the worker to idle and clears all association state. */
|
|
36525
|
+
endAssociation() {
|
|
36526
|
+
this._state = "idle";
|
|
36527
|
+
this._context = void 0;
|
|
36528
|
+
this._files.length = 0;
|
|
36529
|
+
this._fileSizes.length = 0;
|
|
36530
|
+
this._outputLines.length = 0;
|
|
36531
|
+
this._pending.clear();
|
|
36532
|
+
this._remoteSocket = void 0;
|
|
36533
|
+
this._workerSocket = void 0;
|
|
36534
|
+
}
|
|
36535
|
+
/** Tracks an in-flight file handling promise; auto-removes on completion. */
|
|
36536
|
+
trackFile(promise) {
|
|
36537
|
+
this._pending.add(promise);
|
|
36538
|
+
void promise.finally(() => {
|
|
36539
|
+
this._pending.delete(promise);
|
|
36540
|
+
});
|
|
36541
|
+
}
|
|
36542
|
+
/** Records a successfully received file path and its size. */
|
|
36543
|
+
recordFile(filePath, size) {
|
|
36544
|
+
this._files.push(filePath);
|
|
36545
|
+
this._fileSizes.push(size);
|
|
36546
|
+
}
|
|
36547
|
+
/** Awaits all in-flight file handling promises. */
|
|
36548
|
+
async drainPendingFiles() {
|
|
36549
|
+
if (this._pending.size > 0) {
|
|
36550
|
+
await Promise.all(this._pending);
|
|
36551
|
+
}
|
|
36552
|
+
}
|
|
36553
|
+
/** Appends a line to the output buffer, respecting the cap. */
|
|
36554
|
+
captureOutput(text) {
|
|
36555
|
+
if (this._outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
36556
|
+
this._outputLines.push(text);
|
|
36557
|
+
}
|
|
36558
|
+
}
|
|
36559
|
+
/** Stores the remote and worker sockets for later cleanup. */
|
|
36560
|
+
setSockets(remote, worker) {
|
|
36561
|
+
this._remoteSocket = remote;
|
|
36562
|
+
this._workerSocket = worker;
|
|
36563
|
+
}
|
|
36564
|
+
/** Destroys both sockets if they exist and are not already destroyed. */
|
|
36565
|
+
destroySockets() {
|
|
36566
|
+
if (this._remoteSocket !== void 0 && !this._remoteSocket.destroyed) {
|
|
36567
|
+
this._remoteSocket.destroy();
|
|
36568
|
+
}
|
|
36569
|
+
if (this._workerSocket !== void 0 && !this._workerSocket.destroyed) {
|
|
36570
|
+
this._workerSocket.destroy();
|
|
36571
|
+
}
|
|
36572
|
+
}
|
|
36573
|
+
};
|
|
36300
36574
|
function allocatePort() {
|
|
36301
36575
|
return new Promise((resolve) => {
|
|
36302
36576
|
const server = net__namespace.createServer();
|
|
@@ -36314,6 +36588,13 @@ function allocatePort() {
|
|
|
36314
36588
|
});
|
|
36315
36589
|
});
|
|
36316
36590
|
}
|
|
36591
|
+
function sumArray(arr) {
|
|
36592
|
+
let total = 0;
|
|
36593
|
+
for (let i = 0; i < arr.length; i++) {
|
|
36594
|
+
total += arr[i] ?? 0;
|
|
36595
|
+
}
|
|
36596
|
+
return total;
|
|
36597
|
+
}
|
|
36317
36598
|
var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
36318
36599
|
constructor(options) {
|
|
36319
36600
|
super();
|
|
@@ -36328,8 +36609,6 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36328
36609
|
__publicField(this, "stopping", false);
|
|
36329
36610
|
__publicField(this, "abortHandler");
|
|
36330
36611
|
this.setMaxListeners(20);
|
|
36331
|
-
this.on("error", () => {
|
|
36332
|
-
});
|
|
36333
36612
|
this.options = options;
|
|
36334
36613
|
this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
|
|
36335
36614
|
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
@@ -36537,14 +36816,12 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36537
36816
|
this.emit("error", { error: mkdirResult.error });
|
|
36538
36817
|
return;
|
|
36539
36818
|
}
|
|
36540
|
-
|
|
36541
|
-
|
|
36542
|
-
|
|
36543
|
-
|
|
36544
|
-
|
|
36545
|
-
worker.
|
|
36546
|
-
worker.pendingFiles = [];
|
|
36547
|
-
worker.startAt = Date.now();
|
|
36819
|
+
const ctx = {
|
|
36820
|
+
associationId,
|
|
36821
|
+
associationDir,
|
|
36822
|
+
startAt: Date.now()
|
|
36823
|
+
};
|
|
36824
|
+
worker.beginAssociation(ctx);
|
|
36548
36825
|
this.pipeConnection(worker, remoteSocket);
|
|
36549
36826
|
void this.replenishPool();
|
|
36550
36827
|
}
|
|
@@ -36554,7 +36831,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36554
36831
|
if (idle !== void 0) return idle;
|
|
36555
36832
|
const maxRetries = Math.min(Math.ceil(this.connectionTimeoutMs / CONNECTION_RETRY_INTERVAL_MS), MAX_CONNECTION_RETRIES);
|
|
36556
36833
|
for (let i = 0; i < maxRetries; i++) {
|
|
36557
|
-
await
|
|
36834
|
+
await promises.setTimeout(CONNECTION_RETRY_INTERVAL_MS);
|
|
36558
36835
|
const found = this.getIdleWorker();
|
|
36559
36836
|
if (found !== void 0) return found;
|
|
36560
36837
|
if (this.stopping) return void 0;
|
|
@@ -36571,8 +36848,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36571
36848
|
/** Pipes remote socket bidirectionally to the worker's port. */
|
|
36572
36849
|
pipeConnection(worker, remoteSocket) {
|
|
36573
36850
|
const workerSocket = net__namespace.createConnection({ port: worker.port, host: "127.0.0.1" });
|
|
36574
|
-
worker.remoteSocket
|
|
36575
|
-
worker.workerSocket = workerSocket;
|
|
36851
|
+
worker.setSockets(remoteSocket, workerSocket);
|
|
36576
36852
|
const cleanup = () => {
|
|
36577
36853
|
remoteSocket.unpipe(workerSocket);
|
|
36578
36854
|
workerSocket.unpipe(remoteSocket);
|
|
@@ -36631,21 +36907,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36631
36907
|
const createResult = this.createDcmrecv(port, tempDir);
|
|
36632
36908
|
if (!createResult.ok) return createResult;
|
|
36633
36909
|
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
|
-
};
|
|
36910
|
+
const worker = new Worker(dcmrecv, port, tempDir);
|
|
36649
36911
|
this.wireWorkerEvents(worker);
|
|
36650
36912
|
const startResult = await dcmrecv.start();
|
|
36651
36913
|
if (!startResult.ok) {
|
|
@@ -36654,14 +36916,9 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36654
36916
|
this.workers.set(port, worker);
|
|
36655
36917
|
return ok(worker);
|
|
36656
36918
|
}
|
|
36657
|
-
/** Stops a single worker: stop process, clean temp dir, remove from pool. */
|
|
36919
|
+
/** Stops a single worker: destroy sockets, stop process, clean temp dir, remove from pool. */
|
|
36658
36920
|
async stopWorker(worker) {
|
|
36659
|
-
|
|
36660
|
-
worker.remoteSocket.destroy();
|
|
36661
|
-
}
|
|
36662
|
-
if (worker.workerSocket !== void 0 && !worker.workerSocket.destroyed) {
|
|
36663
|
-
worker.workerSocket.destroy();
|
|
36664
|
-
}
|
|
36921
|
+
worker.destroySockets();
|
|
36665
36922
|
await worker.dcmrecv.stop();
|
|
36666
36923
|
worker.dcmrecv[Symbol.dispose]();
|
|
36667
36924
|
await removeDirSafe(worker.tempDir);
|
|
@@ -36713,12 +36970,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36713
36970
|
this.wireRefusingAssociation(worker);
|
|
36714
36971
|
this.wireOutputCapture(worker);
|
|
36715
36972
|
}
|
|
36716
|
-
/** Wires FILE_RECEIVED from dcmrecv worker —
|
|
36973
|
+
/** Wires FILE_RECEIVED from dcmrecv worker — captures context synchronously. */
|
|
36717
36974
|
wireFileReceived(worker) {
|
|
36718
36975
|
worker.dcmrecv.onFileReceived((data) => {
|
|
36719
|
-
const
|
|
36720
|
-
|
|
36721
|
-
if (assocDir === void 0 || assocId === void 0) {
|
|
36976
|
+
const ctx = worker.context;
|
|
36977
|
+
if (ctx === void 0) {
|
|
36722
36978
|
this.emit("error", {
|
|
36723
36979
|
error: new Error(`DicomReceiver: FILE_RECEIVED with no active association (worker state: ${worker.state})`),
|
|
36724
36980
|
filePath: data.filePath,
|
|
@@ -36728,21 +36984,21 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36728
36984
|
});
|
|
36729
36985
|
return;
|
|
36730
36986
|
}
|
|
36731
|
-
const promise = this.handleFileReceived(worker, data,
|
|
36732
|
-
worker.
|
|
36987
|
+
const promise = this.handleFileReceived(worker, data, ctx);
|
|
36988
|
+
worker.trackFile(promise);
|
|
36733
36989
|
});
|
|
36734
36990
|
}
|
|
36735
36991
|
/** Moves a received file, opens it as DicomInstance, and emits FILE_RECEIVED. */
|
|
36736
|
-
async handleFileReceived(worker, data,
|
|
36992
|
+
async handleFileReceived(worker, data, ctx) {
|
|
36737
36993
|
try {
|
|
36738
|
-
await this.moveAndEmitFile(worker, data,
|
|
36994
|
+
await this.moveAndEmitFile(worker, data, ctx);
|
|
36739
36995
|
} catch (thrown) {
|
|
36740
36996
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
36741
36997
|
this.emit("error", {
|
|
36742
36998
|
error,
|
|
36743
36999
|
filePath: data.filePath,
|
|
36744
|
-
associationId:
|
|
36745
|
-
associationDir:
|
|
37000
|
+
associationId: ctx.associationId,
|
|
37001
|
+
associationDir: ctx.associationDir,
|
|
36746
37002
|
callingAE: data.callingAE,
|
|
36747
37003
|
calledAE: data.calledAE,
|
|
36748
37004
|
source: data.source
|
|
@@ -36750,21 +37006,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36750
37006
|
}
|
|
36751
37007
|
}
|
|
36752
37008
|
/** Inner handler: move file, parse DICOM, emit FILE_RECEIVED or error. */
|
|
36753
|
-
async moveAndEmitFile(worker, data,
|
|
37009
|
+
async moveAndEmitFile(worker, data, ctx) {
|
|
36754
37010
|
const srcPath = data.filePath;
|
|
36755
|
-
const destPath = path__namespace.join(
|
|
37011
|
+
const destPath = path__namespace.join(ctx.associationDir, path__namespace.basename(srcPath));
|
|
36756
37012
|
const moveResult = await moveFile(srcPath, destPath);
|
|
36757
37013
|
const finalPath = moveResult.ok ? destPath : srcPath;
|
|
36758
37014
|
const fileSize = await statFileSafe(finalPath);
|
|
36759
|
-
worker.
|
|
36760
|
-
worker.fileSizes.push(fileSize);
|
|
37015
|
+
worker.recordFile(finalPath, fileSize);
|
|
36761
37016
|
const openResult = await DicomInstance.open(finalPath);
|
|
36762
37017
|
if (!openResult.ok) {
|
|
36763
37018
|
this.emit("error", {
|
|
36764
37019
|
error: openResult.error,
|
|
36765
37020
|
filePath: finalPath,
|
|
36766
|
-
associationId:
|
|
36767
|
-
associationDir:
|
|
37021
|
+
associationId: ctx.associationId,
|
|
37022
|
+
associationDir: ctx.associationDir,
|
|
36768
37023
|
callingAE: data.callingAE,
|
|
36769
37024
|
calledAE: data.calledAE,
|
|
36770
37025
|
source: data.source
|
|
@@ -36774,8 +37029,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36774
37029
|
this.emit("FILE_RECEIVED", {
|
|
36775
37030
|
filePath: finalPath,
|
|
36776
37031
|
fileSize,
|
|
36777
|
-
associationId:
|
|
36778
|
-
associationDir:
|
|
37032
|
+
associationId: ctx.associationId,
|
|
37033
|
+
associationDir: ctx.associationDir,
|
|
36779
37034
|
callingAE: data.callingAE,
|
|
36780
37035
|
calledAE: data.calledAE,
|
|
36781
37036
|
source: data.source,
|
|
@@ -36790,21 +37045,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36790
37045
|
}
|
|
36791
37046
|
/** Awaits ALL pending file operations, emits ASSOCIATION_COMPLETE, resets worker state. */
|
|
36792
37047
|
async finalizeAssociation(worker, data) {
|
|
36793
|
-
|
|
36794
|
-
await Promise.all(worker.pendingFiles);
|
|
36795
|
-
}
|
|
37048
|
+
await worker.drainPendingFiles();
|
|
36796
37049
|
this.emitAssociationComplete(worker, data);
|
|
36797
|
-
|
|
37050
|
+
worker.endAssociation();
|
|
36798
37051
|
void this.scaleDown();
|
|
36799
37052
|
}
|
|
36800
37053
|
/** Emits the ASSOCIATION_COMPLETE event with transfer stats. */
|
|
36801
37054
|
emitAssociationComplete(worker, data) {
|
|
36802
|
-
const
|
|
36803
|
-
const
|
|
37055
|
+
const ctx = worker.context;
|
|
37056
|
+
const assocId = ctx?.associationId ?? data.associationId;
|
|
37057
|
+
const assocDir = ctx?.associationDir ?? "";
|
|
37058
|
+
const startAt = ctx?.startAt ?? Date.now();
|
|
36804
37059
|
const files = [...worker.files];
|
|
36805
37060
|
const output = [...worker.outputLines];
|
|
36806
37061
|
const endAt = Date.now();
|
|
36807
|
-
const startAt = worker.startAt ?? endAt;
|
|
36808
37062
|
const totalBytes = sumArray(worker.fileSizes);
|
|
36809
37063
|
const elapsedMs = endAt - startAt;
|
|
36810
37064
|
const bytesPerSecond = elapsedMs > 0 ? Math.round(totalBytes / elapsedMs * 1e3) : 0;
|
|
@@ -36824,24 +37078,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36824
37078
|
output
|
|
36825
37079
|
});
|
|
36826
37080
|
}
|
|
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
37081
|
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36841
37082
|
wireAssociationReceived(worker) {
|
|
36842
37083
|
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36843
37084
|
this.emit("ASSOCIATION_RECEIVED", {
|
|
36844
|
-
associationId: worker.associationId ?? "",
|
|
37085
|
+
associationId: worker.context?.associationId ?? "",
|
|
36845
37086
|
callingAE: data.callingAE,
|
|
36846
37087
|
calledAE: data.calledAE,
|
|
36847
37088
|
source: data.source
|
|
@@ -36852,7 +37093,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36852
37093
|
wireCStoreRequest(worker) {
|
|
36853
37094
|
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36854
37095
|
this.emit("C_STORE_REQUEST", {
|
|
36855
|
-
associationId: worker.associationId ?? "",
|
|
37096
|
+
associationId: worker.context?.associationId ?? "",
|
|
36856
37097
|
raw: data.raw
|
|
36857
37098
|
});
|
|
36858
37099
|
});
|
|
@@ -36861,7 +37102,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36861
37102
|
wireEchoRequest(worker) {
|
|
36862
37103
|
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36863
37104
|
this.emit("ECHO_REQUEST", {
|
|
36864
|
-
associationId: worker.associationId ?? ""
|
|
37105
|
+
associationId: worker.context?.associationId ?? ""
|
|
36865
37106
|
});
|
|
36866
37107
|
});
|
|
36867
37108
|
}
|
|
@@ -36876,8 +37117,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36876
37117
|
/** Captures worker output lines during busy associations. */
|
|
36877
37118
|
wireOutputCapture(worker) {
|
|
36878
37119
|
worker.dcmrecv.on("line", ({ text }) => {
|
|
36879
|
-
if (worker.state === "busy"
|
|
36880
|
-
worker.
|
|
37120
|
+
if (worker.state === "busy") {
|
|
37121
|
+
worker.captureOutput(text);
|
|
36881
37122
|
}
|
|
36882
37123
|
});
|
|
36883
37124
|
}
|
|
@@ -36896,56 +37137,6 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36896
37137
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36897
37138
|
}
|
|
36898
37139
|
};
|
|
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
37140
|
|
|
36950
37141
|
// src/senders/types.ts
|
|
36951
37142
|
var SenderMode = {
|
|
@@ -37368,7 +37559,7 @@ function createStorescuExecutor(options) {
|
|
|
37368
37559
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37369
37560
|
};
|
|
37370
37561
|
}
|
|
37371
|
-
function
|
|
37562
|
+
function resolveConfig2(options) {
|
|
37372
37563
|
const mode = options.mode ?? "multiple";
|
|
37373
37564
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37374
37565
|
return {
|
|
@@ -37415,7 +37606,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37415
37606
|
if (!validation.success) {
|
|
37416
37607
|
return err(createValidationError("DicomSender", validation.error));
|
|
37417
37608
|
}
|
|
37418
|
-
const cfg =
|
|
37609
|
+
const cfg = resolveConfig2(options);
|
|
37419
37610
|
const senderRef = { current: void 0 };
|
|
37420
37611
|
const engine = new SenderEngine({
|
|
37421
37612
|
...cfg,
|
|
@@ -37617,7 +37808,7 @@ function createDcmsendExecutor(options) {
|
|
|
37617
37808
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37618
37809
|
};
|
|
37619
37810
|
}
|
|
37620
|
-
function
|
|
37811
|
+
function resolveConfig3(options) {
|
|
37621
37812
|
const mode = options.mode ?? "multiple";
|
|
37622
37813
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37623
37814
|
return {
|
|
@@ -37664,7 +37855,7 @@ var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
|
37664
37855
|
if (!validation.success) {
|
|
37665
37856
|
return err(createValidationError("DicomSend", validation.error));
|
|
37666
37857
|
}
|
|
37667
|
-
const cfg =
|
|
37858
|
+
const cfg = resolveConfig3(options);
|
|
37668
37859
|
const senderRef = { current: void 0 };
|
|
37669
37860
|
const engine = new SenderEngine({
|
|
37670
37861
|
...cfg,
|
|
@@ -37922,13 +38113,13 @@ function buildWorklistKeys(filter) {
|
|
|
37922
38113
|
var MAX_RESPONSE_FILES = 1e4;
|
|
37923
38114
|
async function createTempDir() {
|
|
37924
38115
|
return stderrLib.tryCatch(
|
|
37925
|
-
() =>
|
|
38116
|
+
() => promises$1.mkdtemp(path.join(os.tmpdir(), "dcmtk-pacs-")),
|
|
37926
38117
|
(e) => new Error(`Failed to create temp directory: ${e.message}`)
|
|
37927
38118
|
);
|
|
37928
38119
|
}
|
|
37929
38120
|
async function listDcmFiles(directory) {
|
|
37930
38121
|
try {
|
|
37931
|
-
const entries = await
|
|
38122
|
+
const entries = await promises$1.readdir(directory);
|
|
37932
38123
|
const dcmFiles = [];
|
|
37933
38124
|
for (let i = 0; i < entries.length; i += 1) {
|
|
37934
38125
|
const entry = entries[i];
|
|
@@ -37946,7 +38137,7 @@ async function listDcmFiles(directory) {
|
|
|
37946
38137
|
}
|
|
37947
38138
|
async function cleanupTempDir(directory) {
|
|
37948
38139
|
try {
|
|
37949
|
-
await
|
|
38140
|
+
await promises$1.rm(directory, { recursive: true, force: true });
|
|
37950
38141
|
} catch {
|
|
37951
38142
|
}
|
|
37952
38143
|
}
|
|
@@ -38265,70 +38456,6 @@ var PacsClient = class _PacsClient {
|
|
|
38265
38456
|
return ok({ success: true, outputDirectory: options.outputDirectory });
|
|
38266
38457
|
}
|
|
38267
38458
|
};
|
|
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
38459
|
|
|
38333
38460
|
// src/testing/types.ts
|
|
38334
38461
|
var HammerPhase = {
|
|
@@ -38465,7 +38592,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38465
38592
|
const start = Date.now();
|
|
38466
38593
|
const dir = path.join(this.opts.outputDir, `dcmtk-hammer-${Date.now()}`);
|
|
38467
38594
|
try {
|
|
38468
|
-
await
|
|
38595
|
+
await promises$1.mkdir(dir, { recursive: true });
|
|
38469
38596
|
} catch {
|
|
38470
38597
|
return err(new Error(`Failed to create output directory: ${dir}`));
|
|
38471
38598
|
}
|
|
@@ -38587,7 +38714,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38587
38714
|
/** Gets the file size in bytes, returning 0 on error. */
|
|
38588
38715
|
async getFileSize(filePath) {
|
|
38589
38716
|
try {
|
|
38590
|
-
const s = await
|
|
38717
|
+
const s = await promises$1.stat(filePath);
|
|
38591
38718
|
return s.size;
|
|
38592
38719
|
} catch {
|
|
38593
38720
|
return 0;
|
|
@@ -38628,7 +38755,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38628
38755
|
async cleanup() {
|
|
38629
38756
|
if (this.generatedDir === void 0) return ok(void 0);
|
|
38630
38757
|
try {
|
|
38631
|
-
await
|
|
38758
|
+
await promises$1.rm(this.generatedDir, { recursive: true, force: true });
|
|
38632
38759
|
this.generatedDir = void 0;
|
|
38633
38760
|
this.generatedFiles = [];
|
|
38634
38761
|
return ok(void 0);
|
|
@@ -38665,80 +38792,6 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38665
38792
|
}
|
|
38666
38793
|
};
|
|
38667
38794
|
|
|
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
38795
|
exports.AETitleSchema = AETitleSchema;
|
|
38743
38796
|
exports.AssociationTracker = AssociationTracker;
|
|
38744
38797
|
exports.ChangeSet = ChangeSet;
|