@ubercode/dcmtk 0.9.3 → 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-vXxeheHY.d.cts → index-CEqwLKgE.d.cts} +2 -4
- package/dist/{index-CWZOBvrt.d.ts → index-Hp4Ri9y5.d.ts} +2 -4
- package/dist/index.cjs +338 -290
- 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 -277
- 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 +463 -417
- 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 +456 -410
- 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,22 +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.
|
|
36733
|
-
void promise.finally(() => removePending(worker.pendingFiles, promise));
|
|
36987
|
+
const promise = this.handleFileReceived(worker, data, ctx);
|
|
36988
|
+
worker.trackFile(promise);
|
|
36734
36989
|
});
|
|
36735
36990
|
}
|
|
36736
36991
|
/** Moves a received file, opens it as DicomInstance, and emits FILE_RECEIVED. */
|
|
36737
|
-
async handleFileReceived(worker, data,
|
|
36992
|
+
async handleFileReceived(worker, data, ctx) {
|
|
36738
36993
|
try {
|
|
36739
|
-
await this.moveAndEmitFile(worker, data,
|
|
36994
|
+
await this.moveAndEmitFile(worker, data, ctx);
|
|
36740
36995
|
} catch (thrown) {
|
|
36741
36996
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
36742
36997
|
this.emit("error", {
|
|
36743
36998
|
error,
|
|
36744
36999
|
filePath: data.filePath,
|
|
36745
|
-
associationId:
|
|
36746
|
-
associationDir:
|
|
37000
|
+
associationId: ctx.associationId,
|
|
37001
|
+
associationDir: ctx.associationDir,
|
|
36747
37002
|
callingAE: data.callingAE,
|
|
36748
37003
|
calledAE: data.calledAE,
|
|
36749
37004
|
source: data.source
|
|
@@ -36751,21 +37006,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36751
37006
|
}
|
|
36752
37007
|
}
|
|
36753
37008
|
/** Inner handler: move file, parse DICOM, emit FILE_RECEIVED or error. */
|
|
36754
|
-
async moveAndEmitFile(worker, data,
|
|
37009
|
+
async moveAndEmitFile(worker, data, ctx) {
|
|
36755
37010
|
const srcPath = data.filePath;
|
|
36756
|
-
const destPath = path__namespace.join(
|
|
37011
|
+
const destPath = path__namespace.join(ctx.associationDir, path__namespace.basename(srcPath));
|
|
36757
37012
|
const moveResult = await moveFile(srcPath, destPath);
|
|
36758
37013
|
const finalPath = moveResult.ok ? destPath : srcPath;
|
|
36759
37014
|
const fileSize = await statFileSafe(finalPath);
|
|
36760
|
-
worker.
|
|
36761
|
-
worker.fileSizes.push(fileSize);
|
|
37015
|
+
worker.recordFile(finalPath, fileSize);
|
|
36762
37016
|
const openResult = await DicomInstance.open(finalPath);
|
|
36763
37017
|
if (!openResult.ok) {
|
|
36764
37018
|
this.emit("error", {
|
|
36765
37019
|
error: openResult.error,
|
|
36766
37020
|
filePath: finalPath,
|
|
36767
|
-
associationId:
|
|
36768
|
-
associationDir:
|
|
37021
|
+
associationId: ctx.associationId,
|
|
37022
|
+
associationDir: ctx.associationDir,
|
|
36769
37023
|
callingAE: data.callingAE,
|
|
36770
37024
|
calledAE: data.calledAE,
|
|
36771
37025
|
source: data.source
|
|
@@ -36775,8 +37029,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36775
37029
|
this.emit("FILE_RECEIVED", {
|
|
36776
37030
|
filePath: finalPath,
|
|
36777
37031
|
fileSize,
|
|
36778
|
-
associationId:
|
|
36779
|
-
associationDir:
|
|
37032
|
+
associationId: ctx.associationId,
|
|
37033
|
+
associationDir: ctx.associationDir,
|
|
36780
37034
|
callingAE: data.callingAE,
|
|
36781
37035
|
calledAE: data.calledAE,
|
|
36782
37036
|
source: data.source,
|
|
@@ -36791,21 +37045,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36791
37045
|
}
|
|
36792
37046
|
/** Awaits ALL pending file operations, emits ASSOCIATION_COMPLETE, resets worker state. */
|
|
36793
37047
|
async finalizeAssociation(worker, data) {
|
|
36794
|
-
|
|
36795
|
-
await Promise.all(worker.pendingFiles);
|
|
36796
|
-
}
|
|
37048
|
+
await worker.drainPendingFiles();
|
|
36797
37049
|
this.emitAssociationComplete(worker, data);
|
|
36798
|
-
|
|
37050
|
+
worker.endAssociation();
|
|
36799
37051
|
void this.scaleDown();
|
|
36800
37052
|
}
|
|
36801
37053
|
/** Emits the ASSOCIATION_COMPLETE event with transfer stats. */
|
|
36802
37054
|
emitAssociationComplete(worker, data) {
|
|
36803
|
-
const
|
|
36804
|
-
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();
|
|
36805
37059
|
const files = [...worker.files];
|
|
36806
37060
|
const output = [...worker.outputLines];
|
|
36807
37061
|
const endAt = Date.now();
|
|
36808
|
-
const startAt = worker.startAt ?? endAt;
|
|
36809
37062
|
const totalBytes = sumArray(worker.fileSizes);
|
|
36810
37063
|
const elapsedMs = endAt - startAt;
|
|
36811
37064
|
const bytesPerSecond = elapsedMs > 0 ? Math.round(totalBytes / elapsedMs * 1e3) : 0;
|
|
@@ -36825,24 +37078,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36825
37078
|
output
|
|
36826
37079
|
});
|
|
36827
37080
|
}
|
|
36828
|
-
/** Resets worker state to idle after an association completes. */
|
|
36829
|
-
resetWorker(worker) {
|
|
36830
|
-
worker.state = "idle";
|
|
36831
|
-
worker.associationId = void 0;
|
|
36832
|
-
worker.associationDir = void 0;
|
|
36833
|
-
worker.files = [];
|
|
36834
|
-
worker.fileSizes = [];
|
|
36835
|
-
worker.outputLines = [];
|
|
36836
|
-
worker.pendingFiles = [];
|
|
36837
|
-
worker.startAt = void 0;
|
|
36838
|
-
worker.remoteSocket = void 0;
|
|
36839
|
-
worker.workerSocket = void 0;
|
|
36840
|
-
}
|
|
36841
37081
|
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36842
37082
|
wireAssociationReceived(worker) {
|
|
36843
37083
|
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36844
37084
|
this.emit("ASSOCIATION_RECEIVED", {
|
|
36845
|
-
associationId: worker.associationId ?? "",
|
|
37085
|
+
associationId: worker.context?.associationId ?? "",
|
|
36846
37086
|
callingAE: data.callingAE,
|
|
36847
37087
|
calledAE: data.calledAE,
|
|
36848
37088
|
source: data.source
|
|
@@ -36853,7 +37093,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36853
37093
|
wireCStoreRequest(worker) {
|
|
36854
37094
|
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36855
37095
|
this.emit("C_STORE_REQUEST", {
|
|
36856
|
-
associationId: worker.associationId ?? "",
|
|
37096
|
+
associationId: worker.context?.associationId ?? "",
|
|
36857
37097
|
raw: data.raw
|
|
36858
37098
|
});
|
|
36859
37099
|
});
|
|
@@ -36862,7 +37102,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36862
37102
|
wireEchoRequest(worker) {
|
|
36863
37103
|
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36864
37104
|
this.emit("ECHO_REQUEST", {
|
|
36865
|
-
associationId: worker.associationId ?? ""
|
|
37105
|
+
associationId: worker.context?.associationId ?? ""
|
|
36866
37106
|
});
|
|
36867
37107
|
});
|
|
36868
37108
|
}
|
|
@@ -36877,8 +37117,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36877
37117
|
/** Captures worker output lines during busy associations. */
|
|
36878
37118
|
wireOutputCapture(worker) {
|
|
36879
37119
|
worker.dcmrecv.on("line", ({ text }) => {
|
|
36880
|
-
if (worker.state === "busy"
|
|
36881
|
-
worker.
|
|
37120
|
+
if (worker.state === "busy") {
|
|
37121
|
+
worker.captureOutput(text);
|
|
36882
37122
|
}
|
|
36883
37123
|
});
|
|
36884
37124
|
}
|
|
@@ -36897,60 +37137,6 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36897
37137
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36898
37138
|
}
|
|
36899
37139
|
};
|
|
36900
|
-
async function ensureDirectory(dirPath) {
|
|
36901
|
-
try {
|
|
36902
|
-
await fs__namespace.mkdir(dirPath, { recursive: true });
|
|
36903
|
-
return ok(void 0);
|
|
36904
|
-
} catch (e) {
|
|
36905
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36906
|
-
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36907
|
-
}
|
|
36908
|
-
}
|
|
36909
|
-
async function moveFile(src, dest) {
|
|
36910
|
-
try {
|
|
36911
|
-
await fs__namespace.rename(src, dest);
|
|
36912
|
-
return ok(void 0);
|
|
36913
|
-
} catch {
|
|
36914
|
-
try {
|
|
36915
|
-
await fs__namespace.copyFile(src, dest);
|
|
36916
|
-
await fs__namespace.unlink(src);
|
|
36917
|
-
return ok(void 0);
|
|
36918
|
-
} catch (e) {
|
|
36919
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36920
|
-
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36921
|
-
}
|
|
36922
|
-
}
|
|
36923
|
-
}
|
|
36924
|
-
async function statFileSafe(filePath) {
|
|
36925
|
-
try {
|
|
36926
|
-
const stat4 = await fs__namespace.stat(filePath);
|
|
36927
|
-
return stat4.size;
|
|
36928
|
-
} catch {
|
|
36929
|
-
return 0;
|
|
36930
|
-
}
|
|
36931
|
-
}
|
|
36932
|
-
function sumArray(arr) {
|
|
36933
|
-
let total = 0;
|
|
36934
|
-
for (let i = 0; i < arr.length; i++) {
|
|
36935
|
-
total += arr[i] ?? 0;
|
|
36936
|
-
}
|
|
36937
|
-
return total;
|
|
36938
|
-
}
|
|
36939
|
-
async function removeDirSafe(dirPath) {
|
|
36940
|
-
try {
|
|
36941
|
-
await fs__namespace.rm(dirPath, { recursive: true, force: true });
|
|
36942
|
-
} catch {
|
|
36943
|
-
}
|
|
36944
|
-
}
|
|
36945
|
-
function removePending(arr, promise) {
|
|
36946
|
-
const idx = arr.indexOf(promise);
|
|
36947
|
-
if (idx !== -1) void arr.splice(idx, 1);
|
|
36948
|
-
}
|
|
36949
|
-
function delay(ms) {
|
|
36950
|
-
return new Promise((resolve) => {
|
|
36951
|
-
setTimeout(resolve, ms);
|
|
36952
|
-
});
|
|
36953
|
-
}
|
|
36954
37140
|
|
|
36955
37141
|
// src/senders/types.ts
|
|
36956
37142
|
var SenderMode = {
|
|
@@ -37373,7 +37559,7 @@ function createStorescuExecutor(options) {
|
|
|
37373
37559
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37374
37560
|
};
|
|
37375
37561
|
}
|
|
37376
|
-
function
|
|
37562
|
+
function resolveConfig2(options) {
|
|
37377
37563
|
const mode = options.mode ?? "multiple";
|
|
37378
37564
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37379
37565
|
return {
|
|
@@ -37420,7 +37606,7 @@ var DicomSender = class _DicomSender extends events.EventEmitter {
|
|
|
37420
37606
|
if (!validation.success) {
|
|
37421
37607
|
return err(createValidationError("DicomSender", validation.error));
|
|
37422
37608
|
}
|
|
37423
|
-
const cfg =
|
|
37609
|
+
const cfg = resolveConfig2(options);
|
|
37424
37610
|
const senderRef = { current: void 0 };
|
|
37425
37611
|
const engine = new SenderEngine({
|
|
37426
37612
|
...cfg,
|
|
@@ -37622,7 +37808,7 @@ function createDcmsendExecutor(options) {
|
|
|
37622
37808
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37623
37809
|
};
|
|
37624
37810
|
}
|
|
37625
|
-
function
|
|
37811
|
+
function resolveConfig3(options) {
|
|
37626
37812
|
const mode = options.mode ?? "multiple";
|
|
37627
37813
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37628
37814
|
return {
|
|
@@ -37669,7 +37855,7 @@ var DicomSend = class _DicomSend extends events.EventEmitter {
|
|
|
37669
37855
|
if (!validation.success) {
|
|
37670
37856
|
return err(createValidationError("DicomSend", validation.error));
|
|
37671
37857
|
}
|
|
37672
|
-
const cfg =
|
|
37858
|
+
const cfg = resolveConfig3(options);
|
|
37673
37859
|
const senderRef = { current: void 0 };
|
|
37674
37860
|
const engine = new SenderEngine({
|
|
37675
37861
|
...cfg,
|
|
@@ -37927,13 +38113,13 @@ function buildWorklistKeys(filter) {
|
|
|
37927
38113
|
var MAX_RESPONSE_FILES = 1e4;
|
|
37928
38114
|
async function createTempDir() {
|
|
37929
38115
|
return stderrLib.tryCatch(
|
|
37930
|
-
() =>
|
|
38116
|
+
() => promises$1.mkdtemp(path.join(os.tmpdir(), "dcmtk-pacs-")),
|
|
37931
38117
|
(e) => new Error(`Failed to create temp directory: ${e.message}`)
|
|
37932
38118
|
);
|
|
37933
38119
|
}
|
|
37934
38120
|
async function listDcmFiles(directory) {
|
|
37935
38121
|
try {
|
|
37936
|
-
const entries = await
|
|
38122
|
+
const entries = await promises$1.readdir(directory);
|
|
37937
38123
|
const dcmFiles = [];
|
|
37938
38124
|
for (let i = 0; i < entries.length; i += 1) {
|
|
37939
38125
|
const entry = entries[i];
|
|
@@ -37951,7 +38137,7 @@ async function listDcmFiles(directory) {
|
|
|
37951
38137
|
}
|
|
37952
38138
|
async function cleanupTempDir(directory) {
|
|
37953
38139
|
try {
|
|
37954
|
-
await
|
|
38140
|
+
await promises$1.rm(directory, { recursive: true, force: true });
|
|
37955
38141
|
} catch {
|
|
37956
38142
|
}
|
|
37957
38143
|
}
|
|
@@ -38270,70 +38456,6 @@ var PacsClient = class _PacsClient {
|
|
|
38270
38456
|
return ok({ success: true, outputDirectory: options.outputDirectory });
|
|
38271
38457
|
}
|
|
38272
38458
|
};
|
|
38273
|
-
var MIN_CONCURRENCY = 1;
|
|
38274
|
-
var MAX_CONCURRENCY = 64;
|
|
38275
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
38276
|
-
function clampConcurrency(value) {
|
|
38277
|
-
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
38278
|
-
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
38279
|
-
}
|
|
38280
|
-
function createBatchState(total) {
|
|
38281
|
-
const results = [];
|
|
38282
|
-
for (let i = 0; i < total; i += 1) {
|
|
38283
|
-
results.push(void 0);
|
|
38284
|
-
}
|
|
38285
|
-
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
38286
|
-
}
|
|
38287
|
-
async function processItem(item, index, operation, state) {
|
|
38288
|
-
let result;
|
|
38289
|
-
try {
|
|
38290
|
-
result = await operation(item);
|
|
38291
|
-
} catch (thrown) {
|
|
38292
|
-
result = err(stderrLib.stderr(thrown));
|
|
38293
|
-
}
|
|
38294
|
-
state.results[index] = result;
|
|
38295
|
-
state.completed += 1;
|
|
38296
|
-
if (result.ok) {
|
|
38297
|
-
state.succeeded += 1;
|
|
38298
|
-
} else {
|
|
38299
|
-
state.failed += 1;
|
|
38300
|
-
}
|
|
38301
|
-
}
|
|
38302
|
-
async function batch(items, operation, options) {
|
|
38303
|
-
const concurrency = clampConcurrency(options?.concurrency);
|
|
38304
|
-
const signal = options?.signal;
|
|
38305
|
-
const onProgress = options?.onProgress;
|
|
38306
|
-
const total = items.length;
|
|
38307
|
-
if (total === 0) {
|
|
38308
|
-
return { results: [], succeeded: 0, failed: 0 };
|
|
38309
|
-
}
|
|
38310
|
-
const state = createBatchState(total);
|
|
38311
|
-
const inFlight = /* @__PURE__ */ new Set();
|
|
38312
|
-
let nextIndex = 0;
|
|
38313
|
-
while (nextIndex < total) {
|
|
38314
|
-
if (signal?.aborted) break;
|
|
38315
|
-
const itemIndex = nextIndex;
|
|
38316
|
-
nextIndex += 1;
|
|
38317
|
-
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
38318
|
-
if (onProgress) {
|
|
38319
|
-
const result = state.results[itemIndex];
|
|
38320
|
-
onProgress(state.completed, total, result);
|
|
38321
|
-
}
|
|
38322
|
-
});
|
|
38323
|
-
inFlight.add(promise);
|
|
38324
|
-
promise.then(
|
|
38325
|
-
() => inFlight.delete(promise),
|
|
38326
|
-
() => inFlight.delete(promise)
|
|
38327
|
-
);
|
|
38328
|
-
if (inFlight.size >= concurrency) {
|
|
38329
|
-
await Promise.race(inFlight);
|
|
38330
|
-
}
|
|
38331
|
-
}
|
|
38332
|
-
if (inFlight.size > 0) {
|
|
38333
|
-
await Promise.all(inFlight);
|
|
38334
|
-
}
|
|
38335
|
-
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
38336
|
-
}
|
|
38337
38459
|
|
|
38338
38460
|
// src/testing/types.ts
|
|
38339
38461
|
var HammerPhase = {
|
|
@@ -38470,7 +38592,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38470
38592
|
const start = Date.now();
|
|
38471
38593
|
const dir = path.join(this.opts.outputDir, `dcmtk-hammer-${Date.now()}`);
|
|
38472
38594
|
try {
|
|
38473
|
-
await
|
|
38595
|
+
await promises$1.mkdir(dir, { recursive: true });
|
|
38474
38596
|
} catch {
|
|
38475
38597
|
return err(new Error(`Failed to create output directory: ${dir}`));
|
|
38476
38598
|
}
|
|
@@ -38592,7 +38714,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38592
38714
|
/** Gets the file size in bytes, returning 0 on error. */
|
|
38593
38715
|
async getFileSize(filePath) {
|
|
38594
38716
|
try {
|
|
38595
|
-
const s = await
|
|
38717
|
+
const s = await promises$1.stat(filePath);
|
|
38596
38718
|
return s.size;
|
|
38597
38719
|
} catch {
|
|
38598
38720
|
return 0;
|
|
@@ -38633,7 +38755,7 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38633
38755
|
async cleanup() {
|
|
38634
38756
|
if (this.generatedDir === void 0) return ok(void 0);
|
|
38635
38757
|
try {
|
|
38636
|
-
await
|
|
38758
|
+
await promises$1.rm(this.generatedDir, { recursive: true, force: true });
|
|
38637
38759
|
this.generatedDir = void 0;
|
|
38638
38760
|
this.generatedFiles = [];
|
|
38639
38761
|
return ok(void 0);
|
|
@@ -38670,80 +38792,6 @@ var DicomHammer = class _DicomHammer extends events.EventEmitter {
|
|
|
38670
38792
|
}
|
|
38671
38793
|
};
|
|
38672
38794
|
|
|
38673
|
-
// src/utils/retry.ts
|
|
38674
|
-
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
38675
|
-
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
38676
|
-
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
38677
|
-
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
38678
|
-
var JITTER_FRACTION = 0.1;
|
|
38679
|
-
var DEFAULT_CONFIG = {
|
|
38680
|
-
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
38681
|
-
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
38682
|
-
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
38683
|
-
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
38684
|
-
shouldRetry: void 0,
|
|
38685
|
-
signal: void 0,
|
|
38686
|
-
onRetry: void 0
|
|
38687
|
-
};
|
|
38688
|
-
function resolveConfig3(opts) {
|
|
38689
|
-
if (!opts) return DEFAULT_CONFIG;
|
|
38690
|
-
return {
|
|
38691
|
-
...DEFAULT_CONFIG,
|
|
38692
|
-
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
38693
|
-
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
38694
|
-
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
38695
|
-
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
38696
|
-
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
38697
|
-
};
|
|
38698
|
-
}
|
|
38699
|
-
function computeDelay(attempt, config) {
|
|
38700
|
-
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
38701
|
-
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
38702
|
-
return Math.max(0, Math.round(baseDelay + jitter));
|
|
38703
|
-
}
|
|
38704
|
-
async function waitWithAbort(delayMs, signal) {
|
|
38705
|
-
if (signal?.aborted) {
|
|
38706
|
-
return false;
|
|
38707
|
-
}
|
|
38708
|
-
return new Promise((resolve) => {
|
|
38709
|
-
const timer = setTimeout(() => {
|
|
38710
|
-
cleanup();
|
|
38711
|
-
resolve(true);
|
|
38712
|
-
}, delayMs);
|
|
38713
|
-
const onAbort = () => {
|
|
38714
|
-
clearTimeout(timer);
|
|
38715
|
-
cleanup();
|
|
38716
|
-
resolve(false);
|
|
38717
|
-
};
|
|
38718
|
-
const cleanup = () => {
|
|
38719
|
-
signal?.removeEventListener("abort", onAbort);
|
|
38720
|
-
};
|
|
38721
|
-
if (signal) {
|
|
38722
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
38723
|
-
}
|
|
38724
|
-
});
|
|
38725
|
-
}
|
|
38726
|
-
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
38727
|
-
if (attempt === config.maxAttempts - 1) return true;
|
|
38728
|
-
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
38729
|
-
if (config.signal?.aborted) return true;
|
|
38730
|
-
return false;
|
|
38731
|
-
}
|
|
38732
|
-
async function retry(operation, options) {
|
|
38733
|
-
const config = resolveConfig3(options);
|
|
38734
|
-
let lastResult = err(new Error("No attempts executed"));
|
|
38735
|
-
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38736
|
-
lastResult = await operation();
|
|
38737
|
-
if (lastResult.ok) return lastResult;
|
|
38738
|
-
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
38739
|
-
const delayMs = computeDelay(attempt, config);
|
|
38740
|
-
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
38741
|
-
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
38742
|
-
if (!waitCompleted) break;
|
|
38743
|
-
}
|
|
38744
|
-
return lastResult;
|
|
38745
|
-
}
|
|
38746
|
-
|
|
38747
38795
|
exports.AETitleSchema = AETitleSchema;
|
|
38748
38796
|
exports.AssociationTracker = AssociationTracker;
|
|
38749
38797
|
exports.ChangeSet = ChangeSet;
|