@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/utils.cjs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var stderrLib = require('stderr-lib');
|
|
4
|
+
var promises = require('fs/promises');
|
|
4
5
|
|
|
5
6
|
// src/utils/batch.ts
|
|
6
7
|
|
|
7
8
|
// src/types.ts
|
|
9
|
+
function ok(value) {
|
|
10
|
+
return { ok: true, value };
|
|
11
|
+
}
|
|
8
12
|
function err(error) {
|
|
9
13
|
return { ok: false, error };
|
|
10
14
|
}
|
|
@@ -148,8 +152,50 @@ async function retry(operation, options) {
|
|
|
148
152
|
}
|
|
149
153
|
return lastResult;
|
|
150
154
|
}
|
|
155
|
+
async function ensureDirectory(dirPath) {
|
|
156
|
+
try {
|
|
157
|
+
await promises.mkdir(dirPath, { recursive: true });
|
|
158
|
+
return ok(void 0);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
161
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function moveFile(src, dest) {
|
|
165
|
+
try {
|
|
166
|
+
await promises.rename(src, dest);
|
|
167
|
+
return ok(void 0);
|
|
168
|
+
} catch {
|
|
169
|
+
try {
|
|
170
|
+
await promises.copyFile(src, dest);
|
|
171
|
+
await promises.unlink(src);
|
|
172
|
+
return ok(void 0);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
175
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function statFileSafe(filePath) {
|
|
180
|
+
try {
|
|
181
|
+
const s = await promises.stat(filePath);
|
|
182
|
+
return s.size;
|
|
183
|
+
} catch {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function removeDirSafe(dirPath) {
|
|
188
|
+
try {
|
|
189
|
+
await promises.rm(dirPath, { recursive: true, force: true });
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
151
193
|
|
|
152
194
|
exports.batch = batch;
|
|
195
|
+
exports.ensureDirectory = ensureDirectory;
|
|
196
|
+
exports.moveFile = moveFile;
|
|
197
|
+
exports.removeDirSafe = removeDirSafe;
|
|
153
198
|
exports.retry = retry;
|
|
199
|
+
exports.statFileSafe = statFileSafe;
|
|
154
200
|
//# sourceMappingURL=utils.cjs.map
|
|
155
201
|
//# sourceMappingURL=utils.cjs.map
|
package/dist/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/utils/batch.ts","../src/utils/retry.ts"],"names":["stderr"],"mappings":"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","file":"utils.cjs","sourcesContent":["/**\n * Represents the outcome of an operation that can succeed or fail.\n *\n * @typeParam T - The type of the success value\n * @typeParam E - The type of the error (defaults to Error)\n *\n * @example\n * ```ts\n * function divide(a: number, b: number): Result<number> {\n * if (b === 0) {\n * return err(new Error('Division by zero'));\n * }\n * return ok(a / b);\n * }\n *\n * const result = divide(10, 2);\n * if (result.ok) {\n * console.log(result.value); // 5\n * } else {\n * console.error(result.error.message);\n * }\n * ```\n */\ntype Result<T, E = Error> = { readonly ok: true; readonly value: T } | { readonly ok: false; readonly error: E };\n\n/**\n * Creates a successful Result containing the given value.\n *\n * @param value - The success value to wrap\n * @returns A Result representing success\n *\n * @example\n * ```ts\n * const result = ok(42);\n * // result.ok === true, result.value === 42\n * ```\n */\nfunction ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/**\n * Creates a failed Result containing the given error.\n *\n * @param error - The error to wrap\n * @returns A Result representing failure\n *\n * @example\n * ```ts\n * const result = err(new Error('not found'));\n * // result.ok === false, result.error.message === 'not found'\n * ```\n */\nfunction err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/**\n * Exhaustive check helper for discriminated unions and switch statements.\n * Calling this in a default case ensures all variants are handled at compile time.\n *\n * @param x - A value that should be of type `never` if all cases are handled\n * @returns Never returns; always throws\n * @throws Always throws an Error indicating an unhandled case\n *\n * @example\n * ```ts\n * type Shape = { kind: 'circle' } | { kind: 'square' };\n *\n * function area(shape: Shape): number {\n * switch (shape.kind) {\n * case 'circle': return Math.PI;\n * case 'square': return 1;\n * default: assertUnreachable(shape);\n * }\n * }\n * ```\n */\nfunction assertUnreachable(x: never): never {\n throw new Error(`Exhaustive check failed: ${JSON.stringify(x)}`);\n}\n\n// ---------------------------------------------------------------------------\n// Process result types (Rule 7.1: immutable)\n// ---------------------------------------------------------------------------\n\n/**\n * The result of executing a short-lived DCMTK binary.\n * All properties are readonly to enforce immutability.\n */\ninterface DcmtkProcessResult {\n /** The captured stdout output from the process. */\n readonly stdout: string;\n /** The captured stderr output from the process. */\n readonly stderr: string;\n /** The exit code of the process (0 typically means success). */\n readonly exitCode: number;\n}\n\n// ---------------------------------------------------------------------------\n// Common option types (Rule 8.4: options objects for > 4 params)\n// ---------------------------------------------------------------------------\n\n/**\n * Options for short-lived DCMTK process execution.\n * Provides configurable timeout and abort support per Rule 4.2.\n */\ninterface ExecOptions {\n /** Working directory for the child process. */\n readonly cwd?: string | undefined;\n /** Timeout in milliseconds. Defaults to DEFAULT_TIMEOUT_MS. */\n readonly timeoutMs?: number | undefined;\n /** AbortSignal for external cancellation. */\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * Options for spawning a DCMTK process with user-supplied arguments.\n * Uses spawn() instead of exec() to avoid shell injection (Rule 7.4).\n */\ninterface SpawnOptions extends ExecOptions {\n /** Additional environment variables merged with process.env. */\n readonly env?: Readonly<Record<string, string>> | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Event types (Rule 8.3: discriminated unions)\n// ---------------------------------------------------------------------------\n\n/**\n * Line source discriminant for output parsing.\n */\ntype LineSource = 'stdout' | 'stderr';\n\n/**\n * A single line of output from a DCMTK process.\n */\ninterface ProcessLine {\n readonly source: LineSource;\n readonly text: string;\n}\n\n// ---------------------------------------------------------------------------\n// Result utility functions\n// ---------------------------------------------------------------------------\n\n/**\n * Transforms the success value of a Result, passing through errors unchanged.\n *\n * @param result - The Result to transform\n * @param fn - The transformation function\n * @returns A new Result with the transformed value, or the original error\n *\n * @example\n * ```ts\n * const result = ok(42);\n * const doubled = mapResult(result, x => x * 2);\n * // doubled.ok === true, doubled.value === 84\n * ```\n */\nfunction mapResult<T, U>(result: Result<T>, fn: (value: T) => U): Result<U> {\n if (result.ok) {\n return ok(fn(result.value));\n }\n return result;\n}\n\n/**\n * Extracts the success type from a Result.\n * Useful for extracting branded types from factory function returns.\n *\n * @example\n * ```ts\n * type Tag = ResultValue<ReturnType<typeof createDicomTag>>; // DicomTag\n * ```\n */\ntype ResultValue<R> = R extends Result<infer T> ? T : never;\n\nexport { ok, err, assertUnreachable, mapResult };\nexport type { Result, ResultValue, DcmtkProcessResult, ExecOptions, SpawnOptions, LineSource, ProcessLine };\n","/**\n * Batch processing utility for running DCMTK operations on multiple files.\n * Provides concurrency control and progress tracking.\n *\n * Note: Streaming APIs are not provided because DCMTK binaries operate on\n * complete files, not streams. For large file processing, use {@link batch}\n * to process files in parallel with concurrency control.\n *\n * @module utils/batch\n */\n\nimport { stderr } from 'stderr-lib';\nimport type { Result } from '../types';\nimport { err } from '../types';\n\n/** Minimum allowed concurrency. */\nconst MIN_CONCURRENCY = 1;\n\n/** Maximum allowed concurrency. */\nconst MAX_CONCURRENCY = 64;\n\n/** Default concurrency when not specified. */\nconst DEFAULT_CONCURRENCY = 4;\n\n/**\n * Options for batch processing.\n */\ninterface BatchOptions<T> {\n /** Maximum number of concurrent operations. Defaults to 4. Min 1, max 64. */\n readonly concurrency?: number | undefined;\n /** Called after each item completes (success or failure). */\n readonly onProgress?: ((completed: number, total: number, result: Result<T>) => void) | undefined;\n /** AbortSignal for cancelling all remaining work. */\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * Aggregated results from a batch operation.\n */\ninterface BatchResult<T> {\n /** Results in the same order as the input items. */\n readonly results: ReadonlyArray<Result<T>>;\n /** Number of successful operations. */\n readonly succeeded: number;\n /** Number of failed operations. */\n readonly failed: number;\n}\n\n/** Mutable state for tracking batch progress. */\ninterface BatchState<TResult> {\n readonly results: Array<Result<TResult> | undefined>;\n succeeded: number;\n failed: number;\n completed: number;\n}\n\n/**\n * Clamps the concurrency value to the valid range [1, 64].\n */\nfunction clampConcurrency(value: number | undefined): number {\n if (value === undefined) return DEFAULT_CONCURRENCY;\n return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));\n}\n\n/**\n * Creates a fresh batch state for tracking results.\n */\nfunction createBatchState<TResult>(total: number): BatchState<TResult> {\n const results: Array<Result<TResult> | undefined> = [];\n for (let i = 0; i < total; i += 1) {\n results.push(undefined);\n }\n return { results, succeeded: 0, failed: 0, completed: 0 };\n}\n\n/**\n * Processes a single item and records the result in the batch state.\n */\nasync function processItem<TItem, TResult>(\n item: TItem,\n index: number,\n operation: (item: TItem) => Promise<Result<TResult>>,\n state: BatchState<TResult>\n): Promise<void> {\n let result: Result<TResult>;\n try {\n result = await operation(item);\n } catch (thrown: unknown) {\n result = err(stderr(thrown));\n }\n state.results[index] = result;\n state.completed += 1;\n if (result.ok) {\n state.succeeded += 1;\n } else {\n state.failed += 1;\n }\n}\n\n/**\n * Processes items in parallel with concurrency control.\n *\n * Items are launched in order but may complete out of order. Results are\n * always returned in the same order as the input items. When the AbortSignal\n * fires, no new items are launched, but in-flight operations are allowed to\n * complete.\n *\n * @param items - Array of input items to process\n * @param operation - Async function to apply to each item, returning Result<T>\n * @param options - Batch processing options\n * @returns Aggregated results with success/failure counts\n *\n * @example\n * ```ts\n * import { batch, dcmconv, TransferSyntax } from 'dcmtk';\n *\n * const files = ['a.dcm', 'b.dcm', 'c.dcm'];\n * const results = await batch(\n * files,\n * file => dcmconv(file, `${file}.converted`, { transferSyntax: TransferSyntax.JPEG_LOSSLESS }),\n * { concurrency: 2, onProgress: (done, total) => console.log(`${done}/${total}`) }\n * );\n * console.log(`Succeeded: ${results.succeeded}, Failed: ${results.failed}`);\n * ```\n */\nasync function batch<TItem, TResult>(\n items: readonly TItem[],\n operation: (item: TItem) => Promise<Result<TResult>>,\n options?: BatchOptions<TResult>\n): Promise<BatchResult<TResult>> {\n const concurrency = clampConcurrency(options?.concurrency);\n const signal = options?.signal;\n const onProgress = options?.onProgress;\n const total = items.length;\n\n if (total === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = createBatchState<TResult>(total);\n const inFlight = new Set<Promise<void>>();\n let nextIndex = 0;\n\n while (nextIndex < total) {\n if (signal?.aborted) break;\n\n const itemIndex = nextIndex;\n nextIndex += 1;\n\n const promise = processItem(items[itemIndex] as TItem, itemIndex, operation, state).then(() => {\n if (onProgress) {\n const result = state.results[itemIndex] as Result<TResult>;\n onProgress(state.completed, total, result);\n }\n });\n inFlight.add(promise);\n promise.then(\n () => inFlight.delete(promise),\n () => inFlight.delete(promise)\n );\n\n if (inFlight.size >= concurrency) {\n await Promise.race(inFlight);\n }\n }\n\n if (inFlight.size > 0) {\n await Promise.all(inFlight);\n }\n\n return { results: state.results as Array<Result<TResult>>, succeeded: state.succeeded, failed: state.failed };\n}\n\nexport { batch };\nexport type { BatchOptions, BatchResult };\n","/**\n * Retry utility with exponential backoff for unreliable operations.\n *\n * @module utils/retry\n */\n\nimport type { Result } from '../types';\nimport { err } from '../types';\n\n/** Default maximum number of attempts (including initial). */\nconst DEFAULT_MAX_ATTEMPTS = 3;\n\n/** Default initial delay in milliseconds before first retry. */\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\n\n/** Default maximum delay in milliseconds. */\nconst DEFAULT_MAX_DELAY_MS = 30_000;\n\n/** Default backoff multiplier. */\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\n\n/** Jitter range as a fraction of the delay (10%). */\nconst JITTER_FRACTION = 0.1;\n\n/**\n * Options for retry behavior.\n */\ninterface RetryOptions {\n /** Maximum number of attempts (including initial). Defaults to 3. */\n readonly maxAttempts?: number | undefined;\n /** Initial delay in milliseconds before first retry. Defaults to 1000. */\n readonly initialDelayMs?: number | undefined;\n /** Maximum delay in milliseconds. Defaults to 30000. */\n readonly maxDelayMs?: number | undefined;\n /** Backoff multiplier. Defaults to 2. */\n readonly backoffMultiplier?: number | undefined;\n /** Optional predicate to determine if a failed result should be retried. */\n readonly shouldRetry?: ((error: Error, attempt: number) => boolean) | undefined;\n /** AbortSignal for cancelling retries. */\n readonly signal?: AbortSignal | undefined;\n /** Called before each retry attempt. */\n readonly onRetry?: ((error: Error, attempt: number, delayMs: number) => void) | undefined;\n}\n\n/** Resolved retry configuration with defaults applied. */\ninterface ResolvedRetryConfig {\n readonly maxAttempts: number;\n readonly initialDelayMs: number;\n readonly maxDelayMs: number;\n readonly backoffMultiplier: number;\n readonly shouldRetry: ((error: Error, attempt: number) => boolean) | undefined;\n readonly signal: AbortSignal | undefined;\n readonly onRetry: ((error: Error, attempt: number, delayMs: number) => void) | undefined;\n}\n\n/** Default configuration applied when no options provided. */\nconst DEFAULT_CONFIG: ResolvedRetryConfig = {\n maxAttempts: DEFAULT_MAX_ATTEMPTS,\n initialDelayMs: DEFAULT_INITIAL_DELAY_MS,\n maxDelayMs: DEFAULT_MAX_DELAY_MS,\n backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,\n shouldRetry: undefined,\n signal: undefined,\n onRetry: undefined,\n};\n\n/**\n * Resolves retry options with defaults applied.\n */\nfunction resolveConfig(opts: RetryOptions | undefined): ResolvedRetryConfig {\n if (!opts) return DEFAULT_CONFIG;\n return {\n ...DEFAULT_CONFIG,\n ...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== undefined)),\n maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),\n initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),\n maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),\n backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER),\n };\n}\n\n/**\n * Computes the delay for a given attempt with exponential backoff and jitter.\n *\n * @param attempt - The zero-based retry attempt number (0 = first retry)\n * @param config - The resolved retry configuration\n * @returns The delay in milliseconds, with jitter applied\n */\nfunction computeDelay(attempt: number, config: ResolvedRetryConfig): number {\n const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);\n const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);\n return Math.max(0, Math.round(baseDelay + jitter));\n}\n\n/**\n * Waits for the specified delay, but resolves early if the AbortSignal fires.\n *\n * @param delayMs - The delay in milliseconds\n * @param signal - Optional AbortSignal to cancel the wait\n * @returns True if the wait completed normally, false if aborted\n */\nasync function waitWithAbort(delayMs: number, signal: AbortSignal | undefined): Promise<boolean> {\n /* v8 ignore next 3 -- retry loop checks aborted before calling waitWithAbort */\n if (signal?.aborted) {\n return false;\n }\n return new Promise<boolean>(resolve => {\n const timer = setTimeout(() => {\n cleanup();\n resolve(true);\n }, delayMs);\n\n const onAbort = (): void => {\n clearTimeout(timer);\n cleanup();\n resolve(false);\n };\n\n const cleanup = (): void => {\n signal?.removeEventListener('abort', onAbort);\n };\n\n if (signal) {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/**\n * Determines whether the retry loop should break after a failed attempt.\n *\n * @returns True if the loop should break (stop retrying)\n */\nfunction shouldBreakAfterFailure(attempt: number, lastError: Error, config: ResolvedRetryConfig): boolean {\n if (attempt === config.maxAttempts - 1) return true;\n if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;\n if (config.signal?.aborted) return true;\n return false;\n}\n\n/**\n * Retries an operation with exponential backoff.\n *\n * The operation is called up to `maxAttempts` times. On failure, the delay\n * before the next retry grows exponentially (with jitter) up to `maxDelayMs`.\n * An AbortSignal can cancel the retry loop between attempts.\n *\n * @param operation - Async function returning Result<T>\n * @param options - Retry options\n * @returns The first successful result, or the last failure\n *\n * @example\n * ```ts\n * import { retry, echoscu } from 'dcmtk';\n *\n * const result = await retry(\n * () => echoscu({ host: 'pacs.hospital.org', port: 104 }),\n * { maxAttempts: 5, initialDelayMs: 2000 }\n * );\n * ```\n */\nasync function retry<T>(operation: () => Promise<Result<T>>, options?: RetryOptions): Promise<Result<T>> {\n const config = resolveConfig(options);\n let lastResult: Result<T> = err(new Error('No attempts executed'));\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {\n lastResult = await operation();\n if (lastResult.ok) return lastResult;\n if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;\n\n const delayMs = computeDelay(attempt, config);\n if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);\n\n const waitCompleted = await waitWithAbort(delayMs, config.signal);\n if (!waitCompleted) break;\n }\n\n return lastResult;\n}\n\nexport { retry };\nexport type { RetryOptions };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/utils/batch.ts","../src/utils/retry.ts","../src/utils/fs.ts"],"names":["stderr","mkdir","rename","copyFile","unlink","stat","rm"],"mappings":"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","file":"utils.cjs","sourcesContent":["/**\n * Represents the outcome of an operation that can succeed or fail.\n *\n * @typeParam T - The type of the success value\n * @typeParam E - The type of the error (defaults to Error)\n *\n * @example\n * ```ts\n * function divide(a: number, b: number): Result<number> {\n * if (b === 0) {\n * return err(new Error('Division by zero'));\n * }\n * return ok(a / b);\n * }\n *\n * const result = divide(10, 2);\n * if (result.ok) {\n * console.log(result.value); // 5\n * } else {\n * console.error(result.error.message);\n * }\n * ```\n */\ntype Result<T, E = Error> = { readonly ok: true; readonly value: T } | { readonly ok: false; readonly error: E };\n\n/**\n * Creates a successful Result containing the given value.\n *\n * @param value - The success value to wrap\n * @returns A Result representing success\n *\n * @example\n * ```ts\n * const result = ok(42);\n * // result.ok === true, result.value === 42\n * ```\n */\nfunction ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/**\n * Creates a failed Result containing the given error.\n *\n * @param error - The error to wrap\n * @returns A Result representing failure\n *\n * @example\n * ```ts\n * const result = err(new Error('not found'));\n * // result.ok === false, result.error.message === 'not found'\n * ```\n */\nfunction err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/**\n * Exhaustive check helper for discriminated unions and switch statements.\n * Calling this in a default case ensures all variants are handled at compile time.\n *\n * @param x - A value that should be of type `never` if all cases are handled\n * @returns Never returns; always throws\n * @throws Always throws an Error indicating an unhandled case\n *\n * @example\n * ```ts\n * type Shape = { kind: 'circle' } | { kind: 'square' };\n *\n * function area(shape: Shape): number {\n * switch (shape.kind) {\n * case 'circle': return Math.PI;\n * case 'square': return 1;\n * default: assertUnreachable(shape);\n * }\n * }\n * ```\n */\nfunction assertUnreachable(x: never): never {\n throw new Error(`Exhaustive check failed: ${JSON.stringify(x)}`);\n}\n\n// ---------------------------------------------------------------------------\n// Process result types (Rule 7.1: immutable)\n// ---------------------------------------------------------------------------\n\n/**\n * The result of executing a short-lived DCMTK binary.\n * All properties are readonly to enforce immutability.\n */\ninterface DcmtkProcessResult {\n /** The captured stdout output from the process. */\n readonly stdout: string;\n /** The captured stderr output from the process. */\n readonly stderr: string;\n /** The exit code of the process (0 typically means success). */\n readonly exitCode: number;\n}\n\n// ---------------------------------------------------------------------------\n// Common option types (Rule 8.4: options objects for > 4 params)\n// ---------------------------------------------------------------------------\n\n/**\n * Options for short-lived DCMTK process execution.\n * Provides configurable timeout and abort support per Rule 4.2.\n */\ninterface ExecOptions {\n /** Working directory for the child process. */\n readonly cwd?: string | undefined;\n /** Timeout in milliseconds. Defaults to DEFAULT_TIMEOUT_MS. */\n readonly timeoutMs?: number | undefined;\n /** AbortSignal for external cancellation. */\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * Options for spawning a DCMTK process with user-supplied arguments.\n * Uses spawn() instead of exec() to avoid shell injection (Rule 7.4).\n */\ninterface SpawnOptions extends ExecOptions {\n /** Additional environment variables merged with process.env. */\n readonly env?: Readonly<Record<string, string>> | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Event types (Rule 8.3: discriminated unions)\n// ---------------------------------------------------------------------------\n\n/**\n * Line source discriminant for output parsing.\n */\ntype LineSource = 'stdout' | 'stderr';\n\n/**\n * A single line of output from a DCMTK process.\n */\ninterface ProcessLine {\n readonly source: LineSource;\n readonly text: string;\n}\n\n// ---------------------------------------------------------------------------\n// Result utility functions\n// ---------------------------------------------------------------------------\n\n/**\n * Transforms the success value of a Result, passing through errors unchanged.\n *\n * @param result - The Result to transform\n * @param fn - The transformation function\n * @returns A new Result with the transformed value, or the original error\n *\n * @example\n * ```ts\n * const result = ok(42);\n * const doubled = mapResult(result, x => x * 2);\n * // doubled.ok === true, doubled.value === 84\n * ```\n */\nfunction mapResult<T, U>(result: Result<T>, fn: (value: T) => U): Result<U> {\n if (result.ok) {\n return ok(fn(result.value));\n }\n return result;\n}\n\n/**\n * Extracts the success type from a Result.\n * Useful for extracting branded types from factory function returns.\n *\n * @example\n * ```ts\n * type Tag = ResultValue<ReturnType<typeof createDicomTag>>; // DicomTag\n * ```\n */\ntype ResultValue<R> = R extends Result<infer T> ? T : never;\n\nexport { ok, err, assertUnreachable, mapResult };\nexport type { Result, ResultValue, DcmtkProcessResult, ExecOptions, SpawnOptions, LineSource, ProcessLine };\n","/**\n * Batch processing utility for running DCMTK operations on multiple files.\n * Provides concurrency control and progress tracking.\n *\n * Note: Streaming APIs are not provided because DCMTK binaries operate on\n * complete files, not streams. For large file processing, use {@link batch}\n * to process files in parallel with concurrency control.\n *\n * @module utils/batch\n */\n\nimport { stderr } from 'stderr-lib';\nimport type { Result } from '../types';\nimport { err } from '../types';\n\n/** Minimum allowed concurrency. */\nconst MIN_CONCURRENCY = 1;\n\n/** Maximum allowed concurrency. */\nconst MAX_CONCURRENCY = 64;\n\n/** Default concurrency when not specified. */\nconst DEFAULT_CONCURRENCY = 4;\n\n/**\n * Options for batch processing.\n */\ninterface BatchOptions<T> {\n /** Maximum number of concurrent operations. Defaults to 4. Min 1, max 64. */\n readonly concurrency?: number | undefined;\n /** Called after each item completes (success or failure). */\n readonly onProgress?: ((completed: number, total: number, result: Result<T>) => void) | undefined;\n /** AbortSignal for cancelling all remaining work. */\n readonly signal?: AbortSignal | undefined;\n}\n\n/**\n * Aggregated results from a batch operation.\n */\ninterface BatchResult<T> {\n /** Results in the same order as the input items. */\n readonly results: ReadonlyArray<Result<T>>;\n /** Number of successful operations. */\n readonly succeeded: number;\n /** Number of failed operations. */\n readonly failed: number;\n}\n\n/** Mutable state for tracking batch progress. */\ninterface BatchState<TResult> {\n readonly results: Array<Result<TResult> | undefined>;\n succeeded: number;\n failed: number;\n completed: number;\n}\n\n/**\n * Clamps the concurrency value to the valid range [1, 64].\n */\nfunction clampConcurrency(value: number | undefined): number {\n if (value === undefined) return DEFAULT_CONCURRENCY;\n return Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(value)));\n}\n\n/**\n * Creates a fresh batch state for tracking results.\n */\nfunction createBatchState<TResult>(total: number): BatchState<TResult> {\n const results: Array<Result<TResult> | undefined> = [];\n for (let i = 0; i < total; i += 1) {\n results.push(undefined);\n }\n return { results, succeeded: 0, failed: 0, completed: 0 };\n}\n\n/**\n * Processes a single item and records the result in the batch state.\n */\nasync function processItem<TItem, TResult>(\n item: TItem,\n index: number,\n operation: (item: TItem) => Promise<Result<TResult>>,\n state: BatchState<TResult>\n): Promise<void> {\n let result: Result<TResult>;\n try {\n result = await operation(item);\n } catch (thrown: unknown) {\n result = err(stderr(thrown));\n }\n state.results[index] = result;\n state.completed += 1;\n if (result.ok) {\n state.succeeded += 1;\n } else {\n state.failed += 1;\n }\n}\n\n/**\n * Processes items in parallel with concurrency control.\n *\n * Items are launched in order but may complete out of order. Results are\n * always returned in the same order as the input items. When the AbortSignal\n * fires, no new items are launched, but in-flight operations are allowed to\n * complete.\n *\n * @param items - Array of input items to process\n * @param operation - Async function to apply to each item, returning Result<T>\n * @param options - Batch processing options\n * @returns Aggregated results with success/failure counts\n *\n * @example\n * ```ts\n * import { batch, dcmconv, TransferSyntax } from 'dcmtk';\n *\n * const files = ['a.dcm', 'b.dcm', 'c.dcm'];\n * const results = await batch(\n * files,\n * file => dcmconv(file, `${file}.converted`, { transferSyntax: TransferSyntax.JPEG_LOSSLESS }),\n * { concurrency: 2, onProgress: (done, total) => console.log(`${done}/${total}`) }\n * );\n * console.log(`Succeeded: ${results.succeeded}, Failed: ${results.failed}`);\n * ```\n */\nasync function batch<TItem, TResult>(\n items: readonly TItem[],\n operation: (item: TItem) => Promise<Result<TResult>>,\n options?: BatchOptions<TResult>\n): Promise<BatchResult<TResult>> {\n const concurrency = clampConcurrency(options?.concurrency);\n const signal = options?.signal;\n const onProgress = options?.onProgress;\n const total = items.length;\n\n if (total === 0) {\n return { results: [], succeeded: 0, failed: 0 };\n }\n\n const state = createBatchState<TResult>(total);\n const inFlight = new Set<Promise<void>>();\n let nextIndex = 0;\n\n while (nextIndex < total) {\n if (signal?.aborted) break;\n\n const itemIndex = nextIndex;\n nextIndex += 1;\n\n const promise = processItem(items[itemIndex] as TItem, itemIndex, operation, state).then(() => {\n if (onProgress) {\n const result = state.results[itemIndex] as Result<TResult>;\n onProgress(state.completed, total, result);\n }\n });\n inFlight.add(promise);\n promise.then(\n () => inFlight.delete(promise),\n () => inFlight.delete(promise)\n );\n\n if (inFlight.size >= concurrency) {\n await Promise.race(inFlight);\n }\n }\n\n if (inFlight.size > 0) {\n await Promise.all(inFlight);\n }\n\n return { results: state.results as Array<Result<TResult>>, succeeded: state.succeeded, failed: state.failed };\n}\n\nexport { batch };\nexport type { BatchOptions, BatchResult };\n","/**\n * Retry utility with exponential backoff for unreliable operations.\n *\n * @module utils/retry\n */\n\nimport type { Result } from '../types';\nimport { err } from '../types';\n\n/** Default maximum number of attempts (including initial). */\nconst DEFAULT_MAX_ATTEMPTS = 3;\n\n/** Default initial delay in milliseconds before first retry. */\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\n\n/** Default maximum delay in milliseconds. */\nconst DEFAULT_MAX_DELAY_MS = 30_000;\n\n/** Default backoff multiplier. */\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\n\n/** Jitter range as a fraction of the delay (10%). */\nconst JITTER_FRACTION = 0.1;\n\n/**\n * Options for retry behavior.\n */\ninterface RetryOptions {\n /** Maximum number of attempts (including initial). Defaults to 3. */\n readonly maxAttempts?: number | undefined;\n /** Initial delay in milliseconds before first retry. Defaults to 1000. */\n readonly initialDelayMs?: number | undefined;\n /** Maximum delay in milliseconds. Defaults to 30000. */\n readonly maxDelayMs?: number | undefined;\n /** Backoff multiplier. Defaults to 2. */\n readonly backoffMultiplier?: number | undefined;\n /** Optional predicate to determine if a failed result should be retried. */\n readonly shouldRetry?: ((error: Error, attempt: number) => boolean) | undefined;\n /** AbortSignal for cancelling retries. */\n readonly signal?: AbortSignal | undefined;\n /** Called before each retry attempt. */\n readonly onRetry?: ((error: Error, attempt: number, delayMs: number) => void) | undefined;\n}\n\n/** Resolved retry configuration with defaults applied. */\ninterface ResolvedRetryConfig {\n readonly maxAttempts: number;\n readonly initialDelayMs: number;\n readonly maxDelayMs: number;\n readonly backoffMultiplier: number;\n readonly shouldRetry: ((error: Error, attempt: number) => boolean) | undefined;\n readonly signal: AbortSignal | undefined;\n readonly onRetry: ((error: Error, attempt: number, delayMs: number) => void) | undefined;\n}\n\n/** Default configuration applied when no options provided. */\nconst DEFAULT_CONFIG: ResolvedRetryConfig = {\n maxAttempts: DEFAULT_MAX_ATTEMPTS,\n initialDelayMs: DEFAULT_INITIAL_DELAY_MS,\n maxDelayMs: DEFAULT_MAX_DELAY_MS,\n backoffMultiplier: DEFAULT_BACKOFF_MULTIPLIER,\n shouldRetry: undefined,\n signal: undefined,\n onRetry: undefined,\n};\n\n/**\n * Resolves retry options with defaults applied.\n */\nfunction resolveConfig(opts: RetryOptions | undefined): ResolvedRetryConfig {\n if (!opts) return DEFAULT_CONFIG;\n return {\n ...DEFAULT_CONFIG,\n ...Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== undefined)),\n maxAttempts: Math.max(1, Math.floor(opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS)),\n initialDelayMs: Math.max(0, opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS),\n maxDelayMs: Math.max(0, opts.maxDelayMs ?? DEFAULT_MAX_DELAY_MS),\n backoffMultiplier: Math.max(1, opts.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER),\n };\n}\n\n/**\n * Computes the delay for a given attempt with exponential backoff and jitter.\n *\n * @param attempt - The zero-based retry attempt number (0 = first retry)\n * @param config - The resolved retry configuration\n * @returns The delay in milliseconds, with jitter applied\n */\nfunction computeDelay(attempt: number, config: ResolvedRetryConfig): number {\n const baseDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);\n const jitter = baseDelay * JITTER_FRACTION * (2 * Math.random() - 1);\n return Math.max(0, Math.round(baseDelay + jitter));\n}\n\n/**\n * Waits for the specified delay, but resolves early if the AbortSignal fires.\n *\n * @param delayMs - The delay in milliseconds\n * @param signal - Optional AbortSignal to cancel the wait\n * @returns True if the wait completed normally, false if aborted\n */\nasync function waitWithAbort(delayMs: number, signal: AbortSignal | undefined): Promise<boolean> {\n /* v8 ignore next 3 -- retry loop checks aborted before calling waitWithAbort */\n if (signal?.aborted) {\n return false;\n }\n return new Promise<boolean>(resolve => {\n const timer = setTimeout(() => {\n cleanup();\n resolve(true);\n }, delayMs);\n\n const onAbort = (): void => {\n clearTimeout(timer);\n cleanup();\n resolve(false);\n };\n\n const cleanup = (): void => {\n signal?.removeEventListener('abort', onAbort);\n };\n\n if (signal) {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/**\n * Determines whether the retry loop should break after a failed attempt.\n *\n * @returns True if the loop should break (stop retrying)\n */\nfunction shouldBreakAfterFailure(attempt: number, lastError: Error, config: ResolvedRetryConfig): boolean {\n if (attempt === config.maxAttempts - 1) return true;\n if (config.shouldRetry && !config.shouldRetry(lastError, attempt + 1)) return true;\n if (config.signal?.aborted) return true;\n return false;\n}\n\n/**\n * Retries an operation with exponential backoff.\n *\n * The operation is called up to `maxAttempts` times. On failure, the delay\n * before the next retry grows exponentially (with jitter) up to `maxDelayMs`.\n * An AbortSignal can cancel the retry loop between attempts.\n *\n * @param operation - Async function returning Result<T>\n * @param options - Retry options\n * @returns The first successful result, or the last failure\n *\n * @example\n * ```ts\n * import { retry, echoscu } from 'dcmtk';\n *\n * const result = await retry(\n * () => echoscu({ host: 'pacs.hospital.org', port: 104 }),\n * { maxAttempts: 5, initialDelayMs: 2000 }\n * );\n * ```\n */\nasync function retry<T>(operation: () => Promise<Result<T>>, options?: RetryOptions): Promise<Result<T>> {\n const config = resolveConfig(options);\n let lastResult: Result<T> = err(new Error('No attempts executed'));\n\n for (let attempt = 0; attempt < config.maxAttempts; attempt += 1) {\n lastResult = await operation();\n if (lastResult.ok) return lastResult;\n if (shouldBreakAfterFailure(attempt, lastResult.error, config)) break;\n\n const delayMs = computeDelay(attempt, config);\n if (config.onRetry) config.onRetry(lastResult.error, attempt + 1, delayMs);\n\n const waitCompleted = await waitWithAbort(delayMs, config.signal);\n if (!waitCompleted) break;\n }\n\n return lastResult;\n}\n\nexport { retry };\nexport type { RetryOptions };\n","/**\n * Shared filesystem utilities returning Result types.\n *\n * @module utils/fs\n */\n\nimport { mkdir, rename, copyFile, unlink, stat, rm } from 'node:fs/promises';\nimport type { Result } from '../types';\nimport { ok, err } from '../types';\n\n/**\n * Ensures a directory exists, creating it recursively if needed.\n *\n * @param dirPath - Directory path to create\n * @returns A Result indicating success or failure\n */\nasync function ensureDirectory(dirPath: string): Promise<Result<void>> {\n try {\n await mkdir(dirPath, { recursive: true });\n return ok(undefined);\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : String(e);\n return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));\n }\n}\n\n/**\n * Moves a file from src to dest, falling back to copy+delete on cross-device.\n *\n * @param src - Source file path\n * @param dest - Destination file path\n * @returns A Result indicating success or failure\n */\nasync function moveFile(src: string, dest: string): Promise<Result<void>> {\n try {\n await rename(src, dest);\n return ok(undefined);\n } catch {\n try {\n await copyFile(src, dest);\n await unlink(src);\n return ok(undefined);\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : String(e);\n return err(new Error(`Failed to move file ${src} → ${dest}: ${msg}`));\n }\n }\n}\n\n/**\n * Returns the file size in bytes, or 0 on error.\n *\n * @param filePath - Path to the file\n * @returns The file size in bytes, or 0 if the file cannot be stat'd\n */\nasync function statFileSafe(filePath: string): Promise<number> {\n try {\n const s = await stat(filePath);\n return s.size;\n } catch {\n /* v8 ignore next */\n return 0;\n }\n}\n\n/**\n * Removes a directory recursively, ignoring errors.\n *\n * @param dirPath - Directory path to remove\n */\nasync function removeDirSafe(dirPath: string): Promise<void> {\n try {\n await rm(dirPath, { recursive: true, force: true });\n } catch {\n /* best-effort cleanup */\n }\n}\n\nexport { ensureDirectory, moveFile, statFileSafe, removeDirSafe };\n"]}
|
package/dist/utils.d.cts
CHANGED
|
@@ -1,112 +1,39 @@
|
|
|
1
|
+
export { B as BatchOptions, a as BatchResult, R as RetryOptions, b as batch, r as retry } from './retry-CJ60nZxE.cjs';
|
|
1
2
|
import { R as Result } from './types-Cgumy1N4.cjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Provides concurrency control and progress tracking.
|
|
5
|
+
* Shared filesystem utilities returning Result types.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* complete files, not streams. For large file processing, use {@link batch}
|
|
9
|
-
* to process files in parallel with concurrency control.
|
|
10
|
-
*
|
|
11
|
-
* @module utils/batch
|
|
7
|
+
* @module utils/fs
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
interface BatchOptions<T> {
|
|
18
|
-
/** Maximum number of concurrent operations. Defaults to 4. Min 1, max 64. */
|
|
19
|
-
readonly concurrency?: number | undefined;
|
|
20
|
-
/** Called after each item completes (success or failure). */
|
|
21
|
-
readonly onProgress?: ((completed: number, total: number, result: Result<T>) => void) | undefined;
|
|
22
|
-
/** AbortSignal for cancelling all remaining work. */
|
|
23
|
-
readonly signal?: AbortSignal | undefined;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Aggregated results from a batch operation.
|
|
27
|
-
*/
|
|
28
|
-
interface BatchResult<T> {
|
|
29
|
-
/** Results in the same order as the input items. */
|
|
30
|
-
readonly results: ReadonlyArray<Result<T>>;
|
|
31
|
-
/** Number of successful operations. */
|
|
32
|
-
readonly succeeded: number;
|
|
33
|
-
/** Number of failed operations. */
|
|
34
|
-
readonly failed: number;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Processes items in parallel with concurrency control.
|
|
38
|
-
*
|
|
39
|
-
* Items are launched in order but may complete out of order. Results are
|
|
40
|
-
* always returned in the same order as the input items. When the AbortSignal
|
|
41
|
-
* fires, no new items are launched, but in-flight operations are allowed to
|
|
42
|
-
* complete.
|
|
43
|
-
*
|
|
44
|
-
* @param items - Array of input items to process
|
|
45
|
-
* @param operation - Async function to apply to each item, returning Result<T>
|
|
46
|
-
* @param options - Batch processing options
|
|
47
|
-
* @returns Aggregated results with success/failure counts
|
|
11
|
+
* Ensures a directory exists, creating it recursively if needed.
|
|
48
12
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
* import { batch, dcmconv, TransferSyntax } from 'dcmtk';
|
|
52
|
-
*
|
|
53
|
-
* const files = ['a.dcm', 'b.dcm', 'c.dcm'];
|
|
54
|
-
* const results = await batch(
|
|
55
|
-
* files,
|
|
56
|
-
* file => dcmconv(file, `${file}.converted`, { transferSyntax: TransferSyntax.JPEG_LOSSLESS }),
|
|
57
|
-
* { concurrency: 2, onProgress: (done, total) => console.log(`${done}/${total}`) }
|
|
58
|
-
* );
|
|
59
|
-
* console.log(`Succeeded: ${results.succeeded}, Failed: ${results.failed}`);
|
|
60
|
-
* ```
|
|
13
|
+
* @param dirPath - Directory path to create
|
|
14
|
+
* @returns A Result indicating success or failure
|
|
61
15
|
*/
|
|
62
|
-
declare function
|
|
63
|
-
|
|
16
|
+
declare function ensureDirectory(dirPath: string): Promise<Result<void>>;
|
|
64
17
|
/**
|
|
65
|
-
*
|
|
18
|
+
* Moves a file from src to dest, falling back to copy+delete on cross-device.
|
|
66
19
|
*
|
|
67
|
-
* @
|
|
20
|
+
* @param src - Source file path
|
|
21
|
+
* @param dest - Destination file path
|
|
22
|
+
* @returns A Result indicating success or failure
|
|
68
23
|
*/
|
|
69
|
-
|
|
24
|
+
declare function moveFile(src: string, dest: string): Promise<Result<void>>;
|
|
70
25
|
/**
|
|
71
|
-
*
|
|
26
|
+
* Returns the file size in bytes, or 0 on error.
|
|
27
|
+
*
|
|
28
|
+
* @param filePath - Path to the file
|
|
29
|
+
* @returns The file size in bytes, or 0 if the file cannot be stat'd
|
|
72
30
|
*/
|
|
73
|
-
|
|
74
|
-
/** Maximum number of attempts (including initial). Defaults to 3. */
|
|
75
|
-
readonly maxAttempts?: number | undefined;
|
|
76
|
-
/** Initial delay in milliseconds before first retry. Defaults to 1000. */
|
|
77
|
-
readonly initialDelayMs?: number | undefined;
|
|
78
|
-
/** Maximum delay in milliseconds. Defaults to 30000. */
|
|
79
|
-
readonly maxDelayMs?: number | undefined;
|
|
80
|
-
/** Backoff multiplier. Defaults to 2. */
|
|
81
|
-
readonly backoffMultiplier?: number | undefined;
|
|
82
|
-
/** Optional predicate to determine if a failed result should be retried. */
|
|
83
|
-
readonly shouldRetry?: ((error: Error, attempt: number) => boolean) | undefined;
|
|
84
|
-
/** AbortSignal for cancelling retries. */
|
|
85
|
-
readonly signal?: AbortSignal | undefined;
|
|
86
|
-
/** Called before each retry attempt. */
|
|
87
|
-
readonly onRetry?: ((error: Error, attempt: number, delayMs: number) => void) | undefined;
|
|
88
|
-
}
|
|
31
|
+
declare function statFileSafe(filePath: string): Promise<number>;
|
|
89
32
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* The operation is called up to `maxAttempts` times. On failure, the delay
|
|
93
|
-
* before the next retry grows exponentially (with jitter) up to `maxDelayMs`.
|
|
94
|
-
* An AbortSignal can cancel the retry loop between attempts.
|
|
95
|
-
*
|
|
96
|
-
* @param operation - Async function returning Result<T>
|
|
97
|
-
* @param options - Retry options
|
|
98
|
-
* @returns The first successful result, or the last failure
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* ```ts
|
|
102
|
-
* import { retry, echoscu } from 'dcmtk';
|
|
33
|
+
* Removes a directory recursively, ignoring errors.
|
|
103
34
|
*
|
|
104
|
-
*
|
|
105
|
-
* () => echoscu({ host: 'pacs.hospital.org', port: 104 }),
|
|
106
|
-
* { maxAttempts: 5, initialDelayMs: 2000 }
|
|
107
|
-
* );
|
|
108
|
-
* ```
|
|
35
|
+
* @param dirPath - Directory path to remove
|
|
109
36
|
*/
|
|
110
|
-
declare function
|
|
37
|
+
declare function removeDirSafe(dirPath: string): Promise<void>;
|
|
111
38
|
|
|
112
|
-
export {
|
|
39
|
+
export { ensureDirectory, moveFile, removeDirSafe, statFileSafe };
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,112 +1,39 @@
|
|
|
1
|
+
export { B as BatchOptions, a as BatchResult, R as RetryOptions, b as batch, r as retry } from './retry-Aryrk1uP.js';
|
|
1
2
|
import { R as Result } from './types-Cgumy1N4.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Provides concurrency control and progress tracking.
|
|
5
|
+
* Shared filesystem utilities returning Result types.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* complete files, not streams. For large file processing, use {@link batch}
|
|
9
|
-
* to process files in parallel with concurrency control.
|
|
10
|
-
*
|
|
11
|
-
* @module utils/batch
|
|
7
|
+
* @module utils/fs
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
interface BatchOptions<T> {
|
|
18
|
-
/** Maximum number of concurrent operations. Defaults to 4. Min 1, max 64. */
|
|
19
|
-
readonly concurrency?: number | undefined;
|
|
20
|
-
/** Called after each item completes (success or failure). */
|
|
21
|
-
readonly onProgress?: ((completed: number, total: number, result: Result<T>) => void) | undefined;
|
|
22
|
-
/** AbortSignal for cancelling all remaining work. */
|
|
23
|
-
readonly signal?: AbortSignal | undefined;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Aggregated results from a batch operation.
|
|
27
|
-
*/
|
|
28
|
-
interface BatchResult<T> {
|
|
29
|
-
/** Results in the same order as the input items. */
|
|
30
|
-
readonly results: ReadonlyArray<Result<T>>;
|
|
31
|
-
/** Number of successful operations. */
|
|
32
|
-
readonly succeeded: number;
|
|
33
|
-
/** Number of failed operations. */
|
|
34
|
-
readonly failed: number;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Processes items in parallel with concurrency control.
|
|
38
|
-
*
|
|
39
|
-
* Items are launched in order but may complete out of order. Results are
|
|
40
|
-
* always returned in the same order as the input items. When the AbortSignal
|
|
41
|
-
* fires, no new items are launched, but in-flight operations are allowed to
|
|
42
|
-
* complete.
|
|
43
|
-
*
|
|
44
|
-
* @param items - Array of input items to process
|
|
45
|
-
* @param operation - Async function to apply to each item, returning Result<T>
|
|
46
|
-
* @param options - Batch processing options
|
|
47
|
-
* @returns Aggregated results with success/failure counts
|
|
11
|
+
* Ensures a directory exists, creating it recursively if needed.
|
|
48
12
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
* import { batch, dcmconv, TransferSyntax } from 'dcmtk';
|
|
52
|
-
*
|
|
53
|
-
* const files = ['a.dcm', 'b.dcm', 'c.dcm'];
|
|
54
|
-
* const results = await batch(
|
|
55
|
-
* files,
|
|
56
|
-
* file => dcmconv(file, `${file}.converted`, { transferSyntax: TransferSyntax.JPEG_LOSSLESS }),
|
|
57
|
-
* { concurrency: 2, onProgress: (done, total) => console.log(`${done}/${total}`) }
|
|
58
|
-
* );
|
|
59
|
-
* console.log(`Succeeded: ${results.succeeded}, Failed: ${results.failed}`);
|
|
60
|
-
* ```
|
|
13
|
+
* @param dirPath - Directory path to create
|
|
14
|
+
* @returns A Result indicating success or failure
|
|
61
15
|
*/
|
|
62
|
-
declare function
|
|
63
|
-
|
|
16
|
+
declare function ensureDirectory(dirPath: string): Promise<Result<void>>;
|
|
64
17
|
/**
|
|
65
|
-
*
|
|
18
|
+
* Moves a file from src to dest, falling back to copy+delete on cross-device.
|
|
66
19
|
*
|
|
67
|
-
* @
|
|
20
|
+
* @param src - Source file path
|
|
21
|
+
* @param dest - Destination file path
|
|
22
|
+
* @returns A Result indicating success or failure
|
|
68
23
|
*/
|
|
69
|
-
|
|
24
|
+
declare function moveFile(src: string, dest: string): Promise<Result<void>>;
|
|
70
25
|
/**
|
|
71
|
-
*
|
|
26
|
+
* Returns the file size in bytes, or 0 on error.
|
|
27
|
+
*
|
|
28
|
+
* @param filePath - Path to the file
|
|
29
|
+
* @returns The file size in bytes, or 0 if the file cannot be stat'd
|
|
72
30
|
*/
|
|
73
|
-
|
|
74
|
-
/** Maximum number of attempts (including initial). Defaults to 3. */
|
|
75
|
-
readonly maxAttempts?: number | undefined;
|
|
76
|
-
/** Initial delay in milliseconds before first retry. Defaults to 1000. */
|
|
77
|
-
readonly initialDelayMs?: number | undefined;
|
|
78
|
-
/** Maximum delay in milliseconds. Defaults to 30000. */
|
|
79
|
-
readonly maxDelayMs?: number | undefined;
|
|
80
|
-
/** Backoff multiplier. Defaults to 2. */
|
|
81
|
-
readonly backoffMultiplier?: number | undefined;
|
|
82
|
-
/** Optional predicate to determine if a failed result should be retried. */
|
|
83
|
-
readonly shouldRetry?: ((error: Error, attempt: number) => boolean) | undefined;
|
|
84
|
-
/** AbortSignal for cancelling retries. */
|
|
85
|
-
readonly signal?: AbortSignal | undefined;
|
|
86
|
-
/** Called before each retry attempt. */
|
|
87
|
-
readonly onRetry?: ((error: Error, attempt: number, delayMs: number) => void) | undefined;
|
|
88
|
-
}
|
|
31
|
+
declare function statFileSafe(filePath: string): Promise<number>;
|
|
89
32
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* The operation is called up to `maxAttempts` times. On failure, the delay
|
|
93
|
-
* before the next retry grows exponentially (with jitter) up to `maxDelayMs`.
|
|
94
|
-
* An AbortSignal can cancel the retry loop between attempts.
|
|
95
|
-
*
|
|
96
|
-
* @param operation - Async function returning Result<T>
|
|
97
|
-
* @param options - Retry options
|
|
98
|
-
* @returns The first successful result, or the last failure
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* ```ts
|
|
102
|
-
* import { retry, echoscu } from 'dcmtk';
|
|
33
|
+
* Removes a directory recursively, ignoring errors.
|
|
103
34
|
*
|
|
104
|
-
*
|
|
105
|
-
* () => echoscu({ host: 'pacs.hospital.org', port: 104 }),
|
|
106
|
-
* { maxAttempts: 5, initialDelayMs: 2000 }
|
|
107
|
-
* );
|
|
108
|
-
* ```
|
|
35
|
+
* @param dirPath - Directory path to remove
|
|
109
36
|
*/
|
|
110
|
-
declare function
|
|
37
|
+
declare function removeDirSafe(dirPath: string): Promise<void>;
|
|
111
38
|
|
|
112
|
-
export {
|
|
39
|
+
export { ensureDirectory, moveFile, removeDirSafe, statFileSafe };
|
package/dist/utils.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { stderr } from 'stderr-lib';
|
|
2
|
+
import { mkdir, rename, copyFile, unlink, stat, rm } from 'fs/promises';
|
|
2
3
|
|
|
3
4
|
// src/utils/batch.ts
|
|
4
5
|
|
|
5
6
|
// src/types.ts
|
|
7
|
+
function ok(value) {
|
|
8
|
+
return { ok: true, value };
|
|
9
|
+
}
|
|
6
10
|
function err(error) {
|
|
7
11
|
return { ok: false, error };
|
|
8
12
|
}
|
|
@@ -146,7 +150,45 @@ async function retry(operation, options) {
|
|
|
146
150
|
}
|
|
147
151
|
return lastResult;
|
|
148
152
|
}
|
|
153
|
+
async function ensureDirectory(dirPath) {
|
|
154
|
+
try {
|
|
155
|
+
await mkdir(dirPath, { recursive: true });
|
|
156
|
+
return ok(void 0);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
159
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function moveFile(src, dest) {
|
|
163
|
+
try {
|
|
164
|
+
await rename(src, dest);
|
|
165
|
+
return ok(void 0);
|
|
166
|
+
} catch {
|
|
167
|
+
try {
|
|
168
|
+
await copyFile(src, dest);
|
|
169
|
+
await unlink(src);
|
|
170
|
+
return ok(void 0);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
173
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function statFileSafe(filePath) {
|
|
178
|
+
try {
|
|
179
|
+
const s = await stat(filePath);
|
|
180
|
+
return s.size;
|
|
181
|
+
} catch {
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function removeDirSafe(dirPath) {
|
|
186
|
+
try {
|
|
187
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
149
191
|
|
|
150
|
-
export { batch, retry };
|
|
192
|
+
export { batch, ensureDirectory, moveFile, removeDirSafe, retry, statFileSafe };
|
|
151
193
|
//# sourceMappingURL=utils.js.map
|
|
152
194
|
//# sourceMappingURL=utils.js.map
|