@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.js
CHANGED
|
@@ -6,12 +6,12 @@ import { spawn, execSync } from 'child_process';
|
|
|
6
6
|
import kill from 'tree-kill';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
import { stderr, tryCatch } from 'stderr-lib';
|
|
9
|
-
import
|
|
10
|
-
import { mkdir, stat, rm, copyFile, unlink, mkdtemp, readdir } from 'fs/promises';
|
|
9
|
+
import { mkdir, stat, rm, copyFile, unlink, rename, mkdtemp, readdir } from 'fs/promises';
|
|
11
10
|
import * as os from 'os';
|
|
12
11
|
import { tmpdir } from 'os';
|
|
13
12
|
import { XMLParser } from 'fast-xml-parser';
|
|
14
13
|
import * as net from 'net';
|
|
14
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
15
15
|
import { randomUUID } from 'crypto';
|
|
16
16
|
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
@@ -293,13 +293,14 @@ async function execCommand(binary, args, options) {
|
|
|
293
293
|
windowsHide: true,
|
|
294
294
|
signal: options?.signal
|
|
295
295
|
});
|
|
296
|
-
wireSpawnListeners(child, timeoutMs, resolve);
|
|
296
|
+
wireSpawnListeners(binary, child, timeoutMs, resolve);
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
|
-
function wireSpawnListeners(child, timeoutMs, resolve) {
|
|
299
|
+
function wireSpawnListeners(binary, child, timeoutMs, resolve) {
|
|
300
300
|
let stdout = "";
|
|
301
301
|
let stderr8 = "";
|
|
302
302
|
let settled = false;
|
|
303
|
+
const name = binary.split("/").pop() ?? binary;
|
|
303
304
|
const settle = (result) => {
|
|
304
305
|
if (settled) return;
|
|
305
306
|
settled = true;
|
|
@@ -308,25 +309,25 @@ function wireSpawnListeners(child, timeoutMs, resolve) {
|
|
|
308
309
|
};
|
|
309
310
|
const timer = setTimeout(() => {
|
|
310
311
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
311
|
-
settle(err(new Error(
|
|
312
|
+
settle(err(new Error(`${name} timed out after ${timeoutMs}ms (pid: ${String(child.pid ?? "unknown")})`)));
|
|
312
313
|
}, timeoutMs);
|
|
313
314
|
child.stdout?.on("data", (chunk) => {
|
|
314
315
|
stdout += String(chunk);
|
|
315
316
|
if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
|
|
316
317
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
317
|
-
settle(err(new Error(
|
|
318
|
+
settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
|
|
318
319
|
}
|
|
319
320
|
});
|
|
320
321
|
child.stderr?.on("data", (chunk) => {
|
|
321
322
|
stderr8 += String(chunk);
|
|
322
323
|
if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
|
|
323
324
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
324
|
-
settle(err(new Error(
|
|
325
|
+
settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
|
|
325
326
|
}
|
|
326
327
|
});
|
|
327
328
|
child.on("error", (error) => {
|
|
328
329
|
if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
|
|
329
|
-
settle(err(new Error(
|
|
330
|
+
settle(err(new Error(`${name} error: ${error.message}`)));
|
|
330
331
|
});
|
|
331
332
|
child.on("close", (code) => {
|
|
332
333
|
settle(ok({ stdout, stderr: stderr8, exitCode: code ?? 1 }));
|
|
@@ -341,7 +342,7 @@ async function spawnCommand(binary, args, options) {
|
|
|
341
342
|
windowsHide: true,
|
|
342
343
|
signal: options?.signal
|
|
343
344
|
});
|
|
344
|
-
wireSpawnListeners(child, timeoutMs, resolve);
|
|
345
|
+
wireSpawnListeners(binary, child, timeoutMs, resolve);
|
|
345
346
|
});
|
|
346
347
|
}
|
|
347
348
|
var ProcessState = {
|
|
@@ -36247,6 +36248,184 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
36247
36248
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36248
36249
|
}
|
|
36249
36250
|
};
|
|
36251
|
+
var MIN_CONCURRENCY = 1;
|
|
36252
|
+
var MAX_CONCURRENCY = 64;
|
|
36253
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
36254
|
+
function clampConcurrency(value) {
|
|
36255
|
+
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
36256
|
+
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
36257
|
+
}
|
|
36258
|
+
function createBatchState(total) {
|
|
36259
|
+
const results = [];
|
|
36260
|
+
for (let i = 0; i < total; i += 1) {
|
|
36261
|
+
results.push(void 0);
|
|
36262
|
+
}
|
|
36263
|
+
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
36264
|
+
}
|
|
36265
|
+
async function processItem(item, index, operation, state) {
|
|
36266
|
+
let result;
|
|
36267
|
+
try {
|
|
36268
|
+
result = await operation(item);
|
|
36269
|
+
} catch (thrown) {
|
|
36270
|
+
result = err(stderr(thrown));
|
|
36271
|
+
}
|
|
36272
|
+
state.results[index] = result;
|
|
36273
|
+
state.completed += 1;
|
|
36274
|
+
if (result.ok) {
|
|
36275
|
+
state.succeeded += 1;
|
|
36276
|
+
} else {
|
|
36277
|
+
state.failed += 1;
|
|
36278
|
+
}
|
|
36279
|
+
}
|
|
36280
|
+
async function batch(items, operation, options) {
|
|
36281
|
+
const concurrency = clampConcurrency(options?.concurrency);
|
|
36282
|
+
const signal = options?.signal;
|
|
36283
|
+
const onProgress = options?.onProgress;
|
|
36284
|
+
const total = items.length;
|
|
36285
|
+
if (total === 0) {
|
|
36286
|
+
return { results: [], succeeded: 0, failed: 0 };
|
|
36287
|
+
}
|
|
36288
|
+
const state = createBatchState(total);
|
|
36289
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
36290
|
+
let nextIndex = 0;
|
|
36291
|
+
while (nextIndex < total) {
|
|
36292
|
+
if (signal?.aborted) break;
|
|
36293
|
+
const itemIndex = nextIndex;
|
|
36294
|
+
nextIndex += 1;
|
|
36295
|
+
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
36296
|
+
if (onProgress) {
|
|
36297
|
+
const result = state.results[itemIndex];
|
|
36298
|
+
onProgress(state.completed, total, result);
|
|
36299
|
+
}
|
|
36300
|
+
});
|
|
36301
|
+
inFlight.add(promise);
|
|
36302
|
+
promise.then(
|
|
36303
|
+
() => inFlight.delete(promise),
|
|
36304
|
+
() => inFlight.delete(promise)
|
|
36305
|
+
);
|
|
36306
|
+
if (inFlight.size >= concurrency) {
|
|
36307
|
+
await Promise.race(inFlight);
|
|
36308
|
+
}
|
|
36309
|
+
}
|
|
36310
|
+
if (inFlight.size > 0) {
|
|
36311
|
+
await Promise.all(inFlight);
|
|
36312
|
+
}
|
|
36313
|
+
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
36314
|
+
}
|
|
36315
|
+
|
|
36316
|
+
// src/utils/retry.ts
|
|
36317
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
36318
|
+
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
36319
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
36320
|
+
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
36321
|
+
var JITTER_FRACTION = 0.1;
|
|
36322
|
+
var DEFAULT_CONFIG = {
|
|
36323
|
+
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
36324
|
+
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
36325
|
+
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
36326
|
+
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
36327
|
+
shouldRetry: void 0,
|
|
36328
|
+
signal: void 0,
|
|
36329
|
+
onRetry: void 0
|
|
36330
|
+
};
|
|
36331
|
+
function resolveConfig(opts) {
|
|
36332
|
+
if (!opts) return DEFAULT_CONFIG;
|
|
36333
|
+
return {
|
|
36334
|
+
...DEFAULT_CONFIG,
|
|
36335
|
+
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
36336
|
+
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
36337
|
+
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
36338
|
+
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
36339
|
+
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
36340
|
+
};
|
|
36341
|
+
}
|
|
36342
|
+
function computeDelay(attempt, config) {
|
|
36343
|
+
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
36344
|
+
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
36345
|
+
return Math.max(0, Math.round(baseDelay + jitter));
|
|
36346
|
+
}
|
|
36347
|
+
async function waitWithAbort(delayMs, signal) {
|
|
36348
|
+
if (signal?.aborted) {
|
|
36349
|
+
return false;
|
|
36350
|
+
}
|
|
36351
|
+
return new Promise((resolve) => {
|
|
36352
|
+
const timer = setTimeout(() => {
|
|
36353
|
+
cleanup();
|
|
36354
|
+
resolve(true);
|
|
36355
|
+
}, delayMs);
|
|
36356
|
+
const onAbort = () => {
|
|
36357
|
+
clearTimeout(timer);
|
|
36358
|
+
cleanup();
|
|
36359
|
+
resolve(false);
|
|
36360
|
+
};
|
|
36361
|
+
const cleanup = () => {
|
|
36362
|
+
signal?.removeEventListener("abort", onAbort);
|
|
36363
|
+
};
|
|
36364
|
+
if (signal) {
|
|
36365
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
36366
|
+
}
|
|
36367
|
+
});
|
|
36368
|
+
}
|
|
36369
|
+
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
36370
|
+
if (attempt === config.maxAttempts - 1) return true;
|
|
36371
|
+
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
36372
|
+
if (config.signal?.aborted) return true;
|
|
36373
|
+
return false;
|
|
36374
|
+
}
|
|
36375
|
+
async function retry(operation, options) {
|
|
36376
|
+
const config = resolveConfig(options);
|
|
36377
|
+
let lastResult = err(new Error("No attempts executed"));
|
|
36378
|
+
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
36379
|
+
lastResult = await operation();
|
|
36380
|
+
if (lastResult.ok) return lastResult;
|
|
36381
|
+
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
36382
|
+
const delayMs = computeDelay(attempt, config);
|
|
36383
|
+
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
36384
|
+
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
36385
|
+
if (!waitCompleted) break;
|
|
36386
|
+
}
|
|
36387
|
+
return lastResult;
|
|
36388
|
+
}
|
|
36389
|
+
async function ensureDirectory(dirPath) {
|
|
36390
|
+
try {
|
|
36391
|
+
await mkdir(dirPath, { recursive: true });
|
|
36392
|
+
return ok(void 0);
|
|
36393
|
+
} catch (e) {
|
|
36394
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36395
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36396
|
+
}
|
|
36397
|
+
}
|
|
36398
|
+
async function moveFile(src, dest) {
|
|
36399
|
+
try {
|
|
36400
|
+
await rename(src, dest);
|
|
36401
|
+
return ok(void 0);
|
|
36402
|
+
} catch {
|
|
36403
|
+
try {
|
|
36404
|
+
await copyFile(src, dest);
|
|
36405
|
+
await unlink(src);
|
|
36406
|
+
return ok(void 0);
|
|
36407
|
+
} catch (e) {
|
|
36408
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36409
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36410
|
+
}
|
|
36411
|
+
}
|
|
36412
|
+
}
|
|
36413
|
+
async function statFileSafe(filePath) {
|
|
36414
|
+
try {
|
|
36415
|
+
const s = await stat(filePath);
|
|
36416
|
+
return s.size;
|
|
36417
|
+
} catch {
|
|
36418
|
+
return 0;
|
|
36419
|
+
}
|
|
36420
|
+
}
|
|
36421
|
+
async function removeDirSafe(dirPath) {
|
|
36422
|
+
try {
|
|
36423
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
36424
|
+
} catch {
|
|
36425
|
+
}
|
|
36426
|
+
}
|
|
36427
|
+
|
|
36428
|
+
// src/servers/DicomReceiver.ts
|
|
36250
36429
|
var DEFAULT_MIN_POOL_SIZE = 2;
|
|
36251
36430
|
var DEFAULT_MAX_POOL_SIZE = 10;
|
|
36252
36431
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
@@ -36272,6 +36451,102 @@ var DicomReceiverOptionsSchema = z.object({
|
|
|
36272
36451
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
36273
36452
|
message: "minPoolSize must be <= maxPoolSize"
|
|
36274
36453
|
});
|
|
36454
|
+
var Worker = class {
|
|
36455
|
+
constructor(dcmrecv, port, tempDir) {
|
|
36456
|
+
__publicField(this, "dcmrecv");
|
|
36457
|
+
__publicField(this, "port");
|
|
36458
|
+
__publicField(this, "tempDir");
|
|
36459
|
+
__publicField(this, "_state", "idle");
|
|
36460
|
+
__publicField(this, "_context");
|
|
36461
|
+
__publicField(this, "_pending", /* @__PURE__ */ new Set());
|
|
36462
|
+
__publicField(this, "_files", []);
|
|
36463
|
+
__publicField(this, "_fileSizes", []);
|
|
36464
|
+
__publicField(this, "_outputLines", []);
|
|
36465
|
+
__publicField(this, "_remoteSocket");
|
|
36466
|
+
__publicField(this, "_workerSocket");
|
|
36467
|
+
this.dcmrecv = dcmrecv;
|
|
36468
|
+
this.port = port;
|
|
36469
|
+
this.tempDir = tempDir;
|
|
36470
|
+
}
|
|
36471
|
+
/** Current worker state. */
|
|
36472
|
+
get state() {
|
|
36473
|
+
return this._state;
|
|
36474
|
+
}
|
|
36475
|
+
/** Active association context, or undefined when idle. */
|
|
36476
|
+
get context() {
|
|
36477
|
+
return this._context;
|
|
36478
|
+
}
|
|
36479
|
+
/** Files moved to the association directory during the current association. */
|
|
36480
|
+
get files() {
|
|
36481
|
+
return this._files;
|
|
36482
|
+
}
|
|
36483
|
+
/** Byte sizes parallel to files[], for transfer stats. */
|
|
36484
|
+
get fileSizes() {
|
|
36485
|
+
return this._fileSizes;
|
|
36486
|
+
}
|
|
36487
|
+
/** Captured output lines during the current association. */
|
|
36488
|
+
get outputLines() {
|
|
36489
|
+
return this._outputLines;
|
|
36490
|
+
}
|
|
36491
|
+
/** Marks the worker busy with a new association context. */
|
|
36492
|
+
beginAssociation(ctx) {
|
|
36493
|
+
this._state = "busy";
|
|
36494
|
+
this._context = ctx;
|
|
36495
|
+
this._files.length = 0;
|
|
36496
|
+
this._fileSizes.length = 0;
|
|
36497
|
+
this._outputLines.length = 0;
|
|
36498
|
+
this._pending.clear();
|
|
36499
|
+
}
|
|
36500
|
+
/** Returns the worker to idle and clears all association state. */
|
|
36501
|
+
endAssociation() {
|
|
36502
|
+
this._state = "idle";
|
|
36503
|
+
this._context = void 0;
|
|
36504
|
+
this._files.length = 0;
|
|
36505
|
+
this._fileSizes.length = 0;
|
|
36506
|
+
this._outputLines.length = 0;
|
|
36507
|
+
this._pending.clear();
|
|
36508
|
+
this._remoteSocket = void 0;
|
|
36509
|
+
this._workerSocket = void 0;
|
|
36510
|
+
}
|
|
36511
|
+
/** Tracks an in-flight file handling promise; auto-removes on completion. */
|
|
36512
|
+
trackFile(promise) {
|
|
36513
|
+
this._pending.add(promise);
|
|
36514
|
+
void promise.finally(() => {
|
|
36515
|
+
this._pending.delete(promise);
|
|
36516
|
+
});
|
|
36517
|
+
}
|
|
36518
|
+
/** Records a successfully received file path and its size. */
|
|
36519
|
+
recordFile(filePath, size) {
|
|
36520
|
+
this._files.push(filePath);
|
|
36521
|
+
this._fileSizes.push(size);
|
|
36522
|
+
}
|
|
36523
|
+
/** Awaits all in-flight file handling promises. */
|
|
36524
|
+
async drainPendingFiles() {
|
|
36525
|
+
if (this._pending.size > 0) {
|
|
36526
|
+
await Promise.all(this._pending);
|
|
36527
|
+
}
|
|
36528
|
+
}
|
|
36529
|
+
/** Appends a line to the output buffer, respecting the cap. */
|
|
36530
|
+
captureOutput(text) {
|
|
36531
|
+
if (this._outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
36532
|
+
this._outputLines.push(text);
|
|
36533
|
+
}
|
|
36534
|
+
}
|
|
36535
|
+
/** Stores the remote and worker sockets for later cleanup. */
|
|
36536
|
+
setSockets(remote, worker) {
|
|
36537
|
+
this._remoteSocket = remote;
|
|
36538
|
+
this._workerSocket = worker;
|
|
36539
|
+
}
|
|
36540
|
+
/** Destroys both sockets if they exist and are not already destroyed. */
|
|
36541
|
+
destroySockets() {
|
|
36542
|
+
if (this._remoteSocket !== void 0 && !this._remoteSocket.destroyed) {
|
|
36543
|
+
this._remoteSocket.destroy();
|
|
36544
|
+
}
|
|
36545
|
+
if (this._workerSocket !== void 0 && !this._workerSocket.destroyed) {
|
|
36546
|
+
this._workerSocket.destroy();
|
|
36547
|
+
}
|
|
36548
|
+
}
|
|
36549
|
+
};
|
|
36275
36550
|
function allocatePort() {
|
|
36276
36551
|
return new Promise((resolve) => {
|
|
36277
36552
|
const server = net.createServer();
|
|
@@ -36289,6 +36564,13 @@ function allocatePort() {
|
|
|
36289
36564
|
});
|
|
36290
36565
|
});
|
|
36291
36566
|
}
|
|
36567
|
+
function sumArray(arr) {
|
|
36568
|
+
let total = 0;
|
|
36569
|
+
for (let i = 0; i < arr.length; i++) {
|
|
36570
|
+
total += arr[i] ?? 0;
|
|
36571
|
+
}
|
|
36572
|
+
return total;
|
|
36573
|
+
}
|
|
36292
36574
|
var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
36293
36575
|
constructor(options) {
|
|
36294
36576
|
super();
|
|
@@ -36303,8 +36585,6 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36303
36585
|
__publicField(this, "stopping", false);
|
|
36304
36586
|
__publicField(this, "abortHandler");
|
|
36305
36587
|
this.setMaxListeners(20);
|
|
36306
|
-
this.on("error", () => {
|
|
36307
|
-
});
|
|
36308
36588
|
this.options = options;
|
|
36309
36589
|
this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
|
|
36310
36590
|
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
@@ -36512,14 +36792,12 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36512
36792
|
this.emit("error", { error: mkdirResult.error });
|
|
36513
36793
|
return;
|
|
36514
36794
|
}
|
|
36515
|
-
|
|
36516
|
-
|
|
36517
|
-
|
|
36518
|
-
|
|
36519
|
-
|
|
36520
|
-
worker.
|
|
36521
|
-
worker.pendingFiles = [];
|
|
36522
|
-
worker.startAt = Date.now();
|
|
36795
|
+
const ctx = {
|
|
36796
|
+
associationId,
|
|
36797
|
+
associationDir,
|
|
36798
|
+
startAt: Date.now()
|
|
36799
|
+
};
|
|
36800
|
+
worker.beginAssociation(ctx);
|
|
36523
36801
|
this.pipeConnection(worker, remoteSocket);
|
|
36524
36802
|
void this.replenishPool();
|
|
36525
36803
|
}
|
|
@@ -36529,7 +36807,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36529
36807
|
if (idle !== void 0) return idle;
|
|
36530
36808
|
const maxRetries = Math.min(Math.ceil(this.connectionTimeoutMs / CONNECTION_RETRY_INTERVAL_MS), MAX_CONNECTION_RETRIES);
|
|
36531
36809
|
for (let i = 0; i < maxRetries; i++) {
|
|
36532
|
-
await
|
|
36810
|
+
await setTimeout$1(CONNECTION_RETRY_INTERVAL_MS);
|
|
36533
36811
|
const found = this.getIdleWorker();
|
|
36534
36812
|
if (found !== void 0) return found;
|
|
36535
36813
|
if (this.stopping) return void 0;
|
|
@@ -36546,8 +36824,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36546
36824
|
/** Pipes remote socket bidirectionally to the worker's port. */
|
|
36547
36825
|
pipeConnection(worker, remoteSocket) {
|
|
36548
36826
|
const workerSocket = net.createConnection({ port: worker.port, host: "127.0.0.1" });
|
|
36549
|
-
worker.remoteSocket
|
|
36550
|
-
worker.workerSocket = workerSocket;
|
|
36827
|
+
worker.setSockets(remoteSocket, workerSocket);
|
|
36551
36828
|
const cleanup = () => {
|
|
36552
36829
|
remoteSocket.unpipe(workerSocket);
|
|
36553
36830
|
workerSocket.unpipe(remoteSocket);
|
|
@@ -36606,21 +36883,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36606
36883
|
const createResult = this.createDcmrecv(port, tempDir);
|
|
36607
36884
|
if (!createResult.ok) return createResult;
|
|
36608
36885
|
const dcmrecv = createResult.value;
|
|
36609
|
-
const worker =
|
|
36610
|
-
dcmrecv,
|
|
36611
|
-
port,
|
|
36612
|
-
tempDir,
|
|
36613
|
-
state: "idle",
|
|
36614
|
-
associationId: void 0,
|
|
36615
|
-
associationDir: void 0,
|
|
36616
|
-
files: [],
|
|
36617
|
-
fileSizes: [],
|
|
36618
|
-
outputLines: [],
|
|
36619
|
-
pendingFiles: [],
|
|
36620
|
-
startAt: void 0,
|
|
36621
|
-
remoteSocket: void 0,
|
|
36622
|
-
workerSocket: void 0
|
|
36623
|
-
};
|
|
36886
|
+
const worker = new Worker(dcmrecv, port, tempDir);
|
|
36624
36887
|
this.wireWorkerEvents(worker);
|
|
36625
36888
|
const startResult = await dcmrecv.start();
|
|
36626
36889
|
if (!startResult.ok) {
|
|
@@ -36629,14 +36892,9 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36629
36892
|
this.workers.set(port, worker);
|
|
36630
36893
|
return ok(worker);
|
|
36631
36894
|
}
|
|
36632
|
-
/** Stops a single worker: stop process, clean temp dir, remove from pool. */
|
|
36895
|
+
/** Stops a single worker: destroy sockets, stop process, clean temp dir, remove from pool. */
|
|
36633
36896
|
async stopWorker(worker) {
|
|
36634
|
-
|
|
36635
|
-
worker.remoteSocket.destroy();
|
|
36636
|
-
}
|
|
36637
|
-
if (worker.workerSocket !== void 0 && !worker.workerSocket.destroyed) {
|
|
36638
|
-
worker.workerSocket.destroy();
|
|
36639
|
-
}
|
|
36897
|
+
worker.destroySockets();
|
|
36640
36898
|
await worker.dcmrecv.stop();
|
|
36641
36899
|
worker.dcmrecv[Symbol.dispose]();
|
|
36642
36900
|
await removeDirSafe(worker.tempDir);
|
|
@@ -36688,12 +36946,11 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36688
36946
|
this.wireRefusingAssociation(worker);
|
|
36689
36947
|
this.wireOutputCapture(worker);
|
|
36690
36948
|
}
|
|
36691
|
-
/** Wires FILE_RECEIVED from dcmrecv worker —
|
|
36949
|
+
/** Wires FILE_RECEIVED from dcmrecv worker — captures context synchronously. */
|
|
36692
36950
|
wireFileReceived(worker) {
|
|
36693
36951
|
worker.dcmrecv.onFileReceived((data) => {
|
|
36694
|
-
const
|
|
36695
|
-
|
|
36696
|
-
if (assocDir === void 0 || assocId === void 0) {
|
|
36952
|
+
const ctx = worker.context;
|
|
36953
|
+
if (ctx === void 0) {
|
|
36697
36954
|
this.emit("error", {
|
|
36698
36955
|
error: new Error(`DicomReceiver: FILE_RECEIVED with no active association (worker state: ${worker.state})`),
|
|
36699
36956
|
filePath: data.filePath,
|
|
@@ -36703,21 +36960,21 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36703
36960
|
});
|
|
36704
36961
|
return;
|
|
36705
36962
|
}
|
|
36706
|
-
const promise = this.handleFileReceived(worker, data,
|
|
36707
|
-
worker.
|
|
36963
|
+
const promise = this.handleFileReceived(worker, data, ctx);
|
|
36964
|
+
worker.trackFile(promise);
|
|
36708
36965
|
});
|
|
36709
36966
|
}
|
|
36710
36967
|
/** Moves a received file, opens it as DicomInstance, and emits FILE_RECEIVED. */
|
|
36711
|
-
async handleFileReceived(worker, data,
|
|
36968
|
+
async handleFileReceived(worker, data, ctx) {
|
|
36712
36969
|
try {
|
|
36713
|
-
await this.moveAndEmitFile(worker, data,
|
|
36970
|
+
await this.moveAndEmitFile(worker, data, ctx);
|
|
36714
36971
|
} catch (thrown) {
|
|
36715
36972
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
36716
36973
|
this.emit("error", {
|
|
36717
36974
|
error,
|
|
36718
36975
|
filePath: data.filePath,
|
|
36719
|
-
associationId:
|
|
36720
|
-
associationDir:
|
|
36976
|
+
associationId: ctx.associationId,
|
|
36977
|
+
associationDir: ctx.associationDir,
|
|
36721
36978
|
callingAE: data.callingAE,
|
|
36722
36979
|
calledAE: data.calledAE,
|
|
36723
36980
|
source: data.source
|
|
@@ -36725,21 +36982,20 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36725
36982
|
}
|
|
36726
36983
|
}
|
|
36727
36984
|
/** Inner handler: move file, parse DICOM, emit FILE_RECEIVED or error. */
|
|
36728
|
-
async moveAndEmitFile(worker, data,
|
|
36985
|
+
async moveAndEmitFile(worker, data, ctx) {
|
|
36729
36986
|
const srcPath = data.filePath;
|
|
36730
|
-
const destPath = path.join(
|
|
36987
|
+
const destPath = path.join(ctx.associationDir, path.basename(srcPath));
|
|
36731
36988
|
const moveResult = await moveFile(srcPath, destPath);
|
|
36732
36989
|
const finalPath = moveResult.ok ? destPath : srcPath;
|
|
36733
36990
|
const fileSize = await statFileSafe(finalPath);
|
|
36734
|
-
worker.
|
|
36735
|
-
worker.fileSizes.push(fileSize);
|
|
36991
|
+
worker.recordFile(finalPath, fileSize);
|
|
36736
36992
|
const openResult = await DicomInstance.open(finalPath);
|
|
36737
36993
|
if (!openResult.ok) {
|
|
36738
36994
|
this.emit("error", {
|
|
36739
36995
|
error: openResult.error,
|
|
36740
36996
|
filePath: finalPath,
|
|
36741
|
-
associationId:
|
|
36742
|
-
associationDir:
|
|
36997
|
+
associationId: ctx.associationId,
|
|
36998
|
+
associationDir: ctx.associationDir,
|
|
36743
36999
|
callingAE: data.callingAE,
|
|
36744
37000
|
calledAE: data.calledAE,
|
|
36745
37001
|
source: data.source
|
|
@@ -36749,8 +37005,8 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36749
37005
|
this.emit("FILE_RECEIVED", {
|
|
36750
37006
|
filePath: finalPath,
|
|
36751
37007
|
fileSize,
|
|
36752
|
-
associationId:
|
|
36753
|
-
associationDir:
|
|
37008
|
+
associationId: ctx.associationId,
|
|
37009
|
+
associationDir: ctx.associationDir,
|
|
36754
37010
|
callingAE: data.callingAE,
|
|
36755
37011
|
calledAE: data.calledAE,
|
|
36756
37012
|
source: data.source,
|
|
@@ -36765,21 +37021,20 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36765
37021
|
}
|
|
36766
37022
|
/** Awaits ALL pending file operations, emits ASSOCIATION_COMPLETE, resets worker state. */
|
|
36767
37023
|
async finalizeAssociation(worker, data) {
|
|
36768
|
-
|
|
36769
|
-
await Promise.all(worker.pendingFiles);
|
|
36770
|
-
}
|
|
37024
|
+
await worker.drainPendingFiles();
|
|
36771
37025
|
this.emitAssociationComplete(worker, data);
|
|
36772
|
-
|
|
37026
|
+
worker.endAssociation();
|
|
36773
37027
|
void this.scaleDown();
|
|
36774
37028
|
}
|
|
36775
37029
|
/** Emits the ASSOCIATION_COMPLETE event with transfer stats. */
|
|
36776
37030
|
emitAssociationComplete(worker, data) {
|
|
36777
|
-
const
|
|
36778
|
-
const
|
|
37031
|
+
const ctx = worker.context;
|
|
37032
|
+
const assocId = ctx?.associationId ?? data.associationId;
|
|
37033
|
+
const assocDir = ctx?.associationDir ?? "";
|
|
37034
|
+
const startAt = ctx?.startAt ?? Date.now();
|
|
36779
37035
|
const files = [...worker.files];
|
|
36780
37036
|
const output = [...worker.outputLines];
|
|
36781
37037
|
const endAt = Date.now();
|
|
36782
|
-
const startAt = worker.startAt ?? endAt;
|
|
36783
37038
|
const totalBytes = sumArray(worker.fileSizes);
|
|
36784
37039
|
const elapsedMs = endAt - startAt;
|
|
36785
37040
|
const bytesPerSecond = elapsedMs > 0 ? Math.round(totalBytes / elapsedMs * 1e3) : 0;
|
|
@@ -36799,24 +37054,11 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36799
37054
|
output
|
|
36800
37055
|
});
|
|
36801
37056
|
}
|
|
36802
|
-
/** Resets worker state to idle after an association completes. */
|
|
36803
|
-
resetWorker(worker) {
|
|
36804
|
-
worker.state = "idle";
|
|
36805
|
-
worker.associationId = void 0;
|
|
36806
|
-
worker.associationDir = void 0;
|
|
36807
|
-
worker.files = [];
|
|
36808
|
-
worker.fileSizes = [];
|
|
36809
|
-
worker.outputLines = [];
|
|
36810
|
-
worker.pendingFiles = [];
|
|
36811
|
-
worker.startAt = void 0;
|
|
36812
|
-
worker.remoteSocket = void 0;
|
|
36813
|
-
worker.workerSocket = void 0;
|
|
36814
|
-
}
|
|
36815
37057
|
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36816
37058
|
wireAssociationReceived(worker) {
|
|
36817
37059
|
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36818
37060
|
this.emit("ASSOCIATION_RECEIVED", {
|
|
36819
|
-
associationId: worker.associationId ?? "",
|
|
37061
|
+
associationId: worker.context?.associationId ?? "",
|
|
36820
37062
|
callingAE: data.callingAE,
|
|
36821
37063
|
calledAE: data.calledAE,
|
|
36822
37064
|
source: data.source
|
|
@@ -36827,7 +37069,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36827
37069
|
wireCStoreRequest(worker) {
|
|
36828
37070
|
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36829
37071
|
this.emit("C_STORE_REQUEST", {
|
|
36830
|
-
associationId: worker.associationId ?? "",
|
|
37072
|
+
associationId: worker.context?.associationId ?? "",
|
|
36831
37073
|
raw: data.raw
|
|
36832
37074
|
});
|
|
36833
37075
|
});
|
|
@@ -36836,7 +37078,7 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36836
37078
|
wireEchoRequest(worker) {
|
|
36837
37079
|
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36838
37080
|
this.emit("ECHO_REQUEST", {
|
|
36839
|
-
associationId: worker.associationId ?? ""
|
|
37081
|
+
associationId: worker.context?.associationId ?? ""
|
|
36840
37082
|
});
|
|
36841
37083
|
});
|
|
36842
37084
|
}
|
|
@@ -36851,8 +37093,8 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36851
37093
|
/** Captures worker output lines during busy associations. */
|
|
36852
37094
|
wireOutputCapture(worker) {
|
|
36853
37095
|
worker.dcmrecv.on("line", ({ text }) => {
|
|
36854
|
-
if (worker.state === "busy"
|
|
36855
|
-
worker.
|
|
37096
|
+
if (worker.state === "busy") {
|
|
37097
|
+
worker.captureOutput(text);
|
|
36856
37098
|
}
|
|
36857
37099
|
});
|
|
36858
37100
|
}
|
|
@@ -36871,56 +37113,6 @@ var DicomReceiver = class _DicomReceiver extends EventEmitter {
|
|
|
36871
37113
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
36872
37114
|
}
|
|
36873
37115
|
};
|
|
36874
|
-
async function ensureDirectory(dirPath) {
|
|
36875
|
-
try {
|
|
36876
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
36877
|
-
return ok(void 0);
|
|
36878
|
-
} catch (e) {
|
|
36879
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36880
|
-
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
36881
|
-
}
|
|
36882
|
-
}
|
|
36883
|
-
async function moveFile(src, dest) {
|
|
36884
|
-
try {
|
|
36885
|
-
await fs.rename(src, dest);
|
|
36886
|
-
return ok(void 0);
|
|
36887
|
-
} catch {
|
|
36888
|
-
try {
|
|
36889
|
-
await fs.copyFile(src, dest);
|
|
36890
|
-
await fs.unlink(src);
|
|
36891
|
-
return ok(void 0);
|
|
36892
|
-
} catch (e) {
|
|
36893
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
36894
|
-
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
36895
|
-
}
|
|
36896
|
-
}
|
|
36897
|
-
}
|
|
36898
|
-
async function statFileSafe(filePath) {
|
|
36899
|
-
try {
|
|
36900
|
-
const stat4 = await fs.stat(filePath);
|
|
36901
|
-
return stat4.size;
|
|
36902
|
-
} catch {
|
|
36903
|
-
return 0;
|
|
36904
|
-
}
|
|
36905
|
-
}
|
|
36906
|
-
function sumArray(arr) {
|
|
36907
|
-
let total = 0;
|
|
36908
|
-
for (let i = 0; i < arr.length; i++) {
|
|
36909
|
-
total += arr[i] ?? 0;
|
|
36910
|
-
}
|
|
36911
|
-
return total;
|
|
36912
|
-
}
|
|
36913
|
-
async function removeDirSafe(dirPath) {
|
|
36914
|
-
try {
|
|
36915
|
-
await fs.rm(dirPath, { recursive: true, force: true });
|
|
36916
|
-
} catch {
|
|
36917
|
-
}
|
|
36918
|
-
}
|
|
36919
|
-
function delay(ms) {
|
|
36920
|
-
return new Promise((resolve) => {
|
|
36921
|
-
setTimeout(resolve, ms);
|
|
36922
|
-
});
|
|
36923
|
-
}
|
|
36924
37116
|
|
|
36925
37117
|
// src/senders/types.ts
|
|
36926
37118
|
var SenderMode = {
|
|
@@ -37343,7 +37535,7 @@ function createStorescuExecutor(options) {
|
|
|
37343
37535
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37344
37536
|
};
|
|
37345
37537
|
}
|
|
37346
|
-
function
|
|
37538
|
+
function resolveConfig2(options) {
|
|
37347
37539
|
const mode = options.mode ?? "multiple";
|
|
37348
37540
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS;
|
|
37349
37541
|
return {
|
|
@@ -37390,7 +37582,7 @@ var DicomSender = class _DicomSender extends EventEmitter {
|
|
|
37390
37582
|
if (!validation.success) {
|
|
37391
37583
|
return err(createValidationError("DicomSender", validation.error));
|
|
37392
37584
|
}
|
|
37393
|
-
const cfg =
|
|
37585
|
+
const cfg = resolveConfig2(options);
|
|
37394
37586
|
const senderRef = { current: void 0 };
|
|
37395
37587
|
const engine = new SenderEngine({
|
|
37396
37588
|
...cfg,
|
|
@@ -37592,7 +37784,7 @@ function createDcmsendExecutor(options) {
|
|
|
37592
37784
|
return ok({ stdout: result.value.stdout, stderr: result.value.stderr });
|
|
37593
37785
|
};
|
|
37594
37786
|
}
|
|
37595
|
-
function
|
|
37787
|
+
function resolveConfig3(options) {
|
|
37596
37788
|
const mode = options.mode ?? "multiple";
|
|
37597
37789
|
const rawMax = options.maxAssociations ?? DEFAULT_MAX_ASSOCIATIONS2;
|
|
37598
37790
|
return {
|
|
@@ -37639,7 +37831,7 @@ var DicomSend = class _DicomSend extends EventEmitter {
|
|
|
37639
37831
|
if (!validation.success) {
|
|
37640
37832
|
return err(createValidationError("DicomSend", validation.error));
|
|
37641
37833
|
}
|
|
37642
|
-
const cfg =
|
|
37834
|
+
const cfg = resolveConfig3(options);
|
|
37643
37835
|
const senderRef = { current: void 0 };
|
|
37644
37836
|
const engine = new SenderEngine({
|
|
37645
37837
|
...cfg,
|
|
@@ -38240,70 +38432,6 @@ var PacsClient = class _PacsClient {
|
|
|
38240
38432
|
return ok({ success: true, outputDirectory: options.outputDirectory });
|
|
38241
38433
|
}
|
|
38242
38434
|
};
|
|
38243
|
-
var MIN_CONCURRENCY = 1;
|
|
38244
|
-
var MAX_CONCURRENCY = 64;
|
|
38245
|
-
var DEFAULT_CONCURRENCY = 4;
|
|
38246
|
-
function clampConcurrency(value) {
|
|
38247
|
-
if (value === void 0) return DEFAULT_CONCURRENCY;
|
|
38248
|
-
return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));
|
|
38249
|
-
}
|
|
38250
|
-
function createBatchState(total) {
|
|
38251
|
-
const results = [];
|
|
38252
|
-
for (let i = 0; i < total; i += 1) {
|
|
38253
|
-
results.push(void 0);
|
|
38254
|
-
}
|
|
38255
|
-
return { results, succeeded: 0, failed: 0, completed: 0 };
|
|
38256
|
-
}
|
|
38257
|
-
async function processItem(item, index, operation, state) {
|
|
38258
|
-
let result;
|
|
38259
|
-
try {
|
|
38260
|
-
result = await operation(item);
|
|
38261
|
-
} catch (thrown) {
|
|
38262
|
-
result = err(stderr(thrown));
|
|
38263
|
-
}
|
|
38264
|
-
state.results[index] = result;
|
|
38265
|
-
state.completed += 1;
|
|
38266
|
-
if (result.ok) {
|
|
38267
|
-
state.succeeded += 1;
|
|
38268
|
-
} else {
|
|
38269
|
-
state.failed += 1;
|
|
38270
|
-
}
|
|
38271
|
-
}
|
|
38272
|
-
async function batch(items, operation, options) {
|
|
38273
|
-
const concurrency = clampConcurrency(options?.concurrency);
|
|
38274
|
-
const signal = options?.signal;
|
|
38275
|
-
const onProgress = options?.onProgress;
|
|
38276
|
-
const total = items.length;
|
|
38277
|
-
if (total === 0) {
|
|
38278
|
-
return { results: [], succeeded: 0, failed: 0 };
|
|
38279
|
-
}
|
|
38280
|
-
const state = createBatchState(total);
|
|
38281
|
-
const inFlight = /* @__PURE__ */ new Set();
|
|
38282
|
-
let nextIndex = 0;
|
|
38283
|
-
while (nextIndex < total) {
|
|
38284
|
-
if (signal?.aborted) break;
|
|
38285
|
-
const itemIndex = nextIndex;
|
|
38286
|
-
nextIndex += 1;
|
|
38287
|
-
const promise = processItem(items[itemIndex], itemIndex, operation, state).then(() => {
|
|
38288
|
-
if (onProgress) {
|
|
38289
|
-
const result = state.results[itemIndex];
|
|
38290
|
-
onProgress(state.completed, total, result);
|
|
38291
|
-
}
|
|
38292
|
-
});
|
|
38293
|
-
inFlight.add(promise);
|
|
38294
|
-
promise.then(
|
|
38295
|
-
() => inFlight.delete(promise),
|
|
38296
|
-
() => inFlight.delete(promise)
|
|
38297
|
-
);
|
|
38298
|
-
if (inFlight.size >= concurrency) {
|
|
38299
|
-
await Promise.race(inFlight);
|
|
38300
|
-
}
|
|
38301
|
-
}
|
|
38302
|
-
if (inFlight.size > 0) {
|
|
38303
|
-
await Promise.all(inFlight);
|
|
38304
|
-
}
|
|
38305
|
-
return { results: state.results, succeeded: state.succeeded, failed: state.failed };
|
|
38306
|
-
}
|
|
38307
38435
|
|
|
38308
38436
|
// src/testing/types.ts
|
|
38309
38437
|
var HammerPhase = {
|
|
@@ -38640,80 +38768,6 @@ var DicomHammer = class _DicomHammer extends EventEmitter {
|
|
|
38640
38768
|
}
|
|
38641
38769
|
};
|
|
38642
38770
|
|
|
38643
|
-
// src/utils/retry.ts
|
|
38644
|
-
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
38645
|
-
var DEFAULT_INITIAL_DELAY_MS = 1e3;
|
|
38646
|
-
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
38647
|
-
var DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
38648
|
-
var JITTER_FRACTION = 0.1;
|
|
38649
|
-
var DEFAULT_CONFIG = {
|
|
38650
|
-
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
38651
|
-
initialDelayMs: DEFAULT_INITIAL_DELAY_MS,
|
|
38652
|
-
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
38653
|
-
backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,
|
|
38654
|
-
shouldRetry: void 0,
|
|
38655
|
-
signal: void 0,
|
|
38656
|
-
onRetry: void 0
|
|
38657
|
-
};
|
|
38658
|
-
function resolveConfig3(opts) {
|
|
38659
|
-
if (!opts) return DEFAULT_CONFIG;
|
|
38660
|
-
return {
|
|
38661
|
-
...DEFAULT_CONFIG,
|
|
38662
|
-
...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== void 0)),
|
|
38663
|
-
maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),
|
|
38664
|
-
initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),
|
|
38665
|
-
maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),
|
|
38666
|
-
backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER)
|
|
38667
|
-
};
|
|
38668
|
-
}
|
|
38669
|
-
function computeDelay(attempt, config) {
|
|
38670
|
-
const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
38671
|
-
const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);
|
|
38672
|
-
return Math.max(0, Math.round(baseDelay + jitter));
|
|
38673
|
-
}
|
|
38674
|
-
async function waitWithAbort(delayMs, signal) {
|
|
38675
|
-
if (signal?.aborted) {
|
|
38676
|
-
return false;
|
|
38677
|
-
}
|
|
38678
|
-
return new Promise((resolve) => {
|
|
38679
|
-
const timer = setTimeout(() => {
|
|
38680
|
-
cleanup();
|
|
38681
|
-
resolve(true);
|
|
38682
|
-
}, delayMs);
|
|
38683
|
-
const onAbort = () => {
|
|
38684
|
-
clearTimeout(timer);
|
|
38685
|
-
cleanup();
|
|
38686
|
-
resolve(false);
|
|
38687
|
-
};
|
|
38688
|
-
const cleanup = () => {
|
|
38689
|
-
signal?.removeEventListener("abort", onAbort);
|
|
38690
|
-
};
|
|
38691
|
-
if (signal) {
|
|
38692
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
38693
|
-
}
|
|
38694
|
-
});
|
|
38695
|
-
}
|
|
38696
|
-
function shouldBreakAfterFailure(attempt, lastError, config) {
|
|
38697
|
-
if (attempt === config.maxAttempts - 1) return true;
|
|
38698
|
-
if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;
|
|
38699
|
-
if (config.signal?.aborted) return true;
|
|
38700
|
-
return false;
|
|
38701
|
-
}
|
|
38702
|
-
async function retry(operation, options) {
|
|
38703
|
-
const config = resolveConfig3(options);
|
|
38704
|
-
let lastResult = err(new Error("No attempts executed"));
|
|
38705
|
-
for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {
|
|
38706
|
-
lastResult = await operation();
|
|
38707
|
-
if (lastResult.ok) return lastResult;
|
|
38708
|
-
if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;
|
|
38709
|
-
const delayMs = computeDelay(attempt, config);
|
|
38710
|
-
if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);
|
|
38711
|
-
const waitCompleted = await waitWithAbort(delayMs, config.signal);
|
|
38712
|
-
if (!waitCompleted) break;
|
|
38713
|
-
}
|
|
38714
|
-
return lastResult;
|
|
38715
|
-
}
|
|
38716
|
-
|
|
38717
38771
|
export { AETitleSchema, AssociationTracker, ChangeSet, ColorConversion, DCMPRSCP_FATAL_EVENTS, DCMPRSCP_PATTERNS, DCMPSRCV_FATAL_EVENTS, DCMPSRCV_PATTERNS, DCMQRSCP_FATAL_EVENTS, DCMQRSCP_PATTERNS, DCMRECV_FATAL_EVENTS, DCMRECV_PATTERNS, DEFAULT_BLOCK_TIMEOUT_MS, DEFAULT_DICOM_PORT, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_PARSE_CONCURRENCY, DEFAULT_START_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, Dcm2pnmOutputFormat, Dcm2xmlCharset, DcmQRSCP, DcmdumpFormat, Dcmj2pnmOutputFormat, DcmprsCP, DcmprscpEvent, Dcmpsrcv, DcmpsrcvEvent, DcmqrscpEvent, Dcmrecv, DcmrecvEvent, DcmtkProcess, DicomDataset, DicomHammer, DicomInstance, DicomReceiver, DicomSend, DicomSender, DicomTagPathSchema, DicomTagSchema, FilenameMode, GetQueryModel, HammerPhase, Img2dcmInputFormat, JplsColorConversion, LineParser, LutType, MAX_BLOCK_LINES, MAX_CHANGESET_OPERATIONS, MAX_EVENT_PATTERNS, MAX_TRAVERSAL_DEPTH, MoveQueryModel, PDU_SIZE, PacsClient, PortSchema, PreferredTransferSyntax, ProcessState, ProposedTransferSyntax, QueryLevel, QueryModel, REQUIRED_BINARIES, RetrieveMode, SOP_CLASSES, STORESCP_FATAL_EVENTS, STORESCP_PATTERNS, SenderHealth, SenderMode, StorageMode, StoreSCP, StoreSCPPreset, StorescpEvent, SubdirectoryMode, TransferSyntax, UIDSchema, UNIX_SEARCH_PATHS, VR, VR_CATEGORY, VR_CATEGORY_NAME, VR_META, WINDOWS_SEARCH_PATHS, WLMSCPFS_FATAL_EVENTS, WLMSCPFS_PATTERNS, Wlmscpfs, WlmscpfsEvent, assertUnreachable, batch, cda2dcm, clearDcmtkPathCache, createAETitle, createDicomFilePath, createDicomTag, createDicomTagPath, createPort, createSOPClassUID, createTransferSyntaxUID, dcm2cda, dcm2json, dcm2pdf, dcm2pnm, dcm2xml, dcmcjpeg, dcmcjpls, dcmconv, dcmcrle, dcmdecap, dcmdjpeg, dcmdjpls, dcmdrle, dcmdspfn, dcmdump, dcmencap, dcmftest, dcmgpdir, dcmj2pnm, dcmmkcrv, dcmmkdir, dcmmklut, dcmodify, dcmp2pgm, dcmprscu, dcmpschk, dcmpsmk, dcmpsprt, dcmqridx, dcmquant, dcmscale, dcmsend, dcod2lum, dconvlum, drtdump, dsr2xml, dsrdump, dump2dcm, echoscu, err, execCommand, findDcmtkPath, findscu, getVRCategory, getscu, img2dcm, isBinaryVR, isNumericVR, isStringVR, json2dcm, lookupTag, lookupTagByKeyword, lookupTagByName, mapResult, movescu, ok, parseAETitle, parseDicomTag, parseDicomTagPath, parsePort, parseSOPClassUID, parseTransferSyntaxUID, pdf2dcm, retry, segmentsToModifyPath, segmentsToString, sopClassNameFromUID, spawnCommand, stl2dcm, storescu, tag, tagPathToSegments, termscu, walkTags, xml2dcm, xml2dsr, xmlToJson };
|
|
38718
38772
|
//# sourceMappingURL=index.js.map
|
|
38719
38773
|
//# sourceMappingURL=index.js.map
|