@ubercode/dcmtk 0.1.4 → 0.2.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/README.md +18 -15
- package/dist/DicomInstance-By9zd7GM.d.cts +625 -0
- package/dist/DicomInstance-CQEIuF_x.d.ts +625 -0
- package/dist/{dcmodify-CTXBWKU9.d.cts → dcmodify-B-_uUIKB.d.ts} +4 -2
- package/dist/{dcmodify-Daeafqrm.d.ts → dcmodify-Gds9u5Vj.d.cts} +4 -2
- package/dist/dicom.cjs +329 -51
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.d.cts +368 -3
- package/dist/dicom.d.ts +368 -3
- package/dist/dicom.js +329 -51
- package/dist/dicom.js.map +1 -1
- package/dist/index.cjs +1460 -419
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +1432 -413
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +2379 -196
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +1654 -3
- package/dist/servers.d.ts +1654 -3
- package/dist/servers.js +2305 -145
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +97 -50
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +21 -4
- package/dist/tools.d.ts +21 -4
- package/dist/tools.js +97 -51
- package/dist/tools.js.map +1 -1
- package/dist/{types-zHhxS7d2.d.cts → types-Cgumy1N4.d.cts} +1 -24
- package/dist/{types-zHhxS7d2.d.ts → types-Cgumy1N4.d.ts} +1 -24
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +8 -8
- package/dist/index-BZxi4104.d.ts +0 -826
- package/dist/index-CapkWqxy.d.ts +0 -1295
- package/dist/index-DX4C3zbo.d.cts +0 -826
- package/dist/index-r7AvpkCE.d.cts +0 -1295
|
@@ -119,29 +119,6 @@ interface ProcessLine {
|
|
|
119
119
|
readonly source: LineSource;
|
|
120
120
|
readonly text: string;
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Extracts the value from a successful Result, or throws the error.
|
|
124
|
-
*
|
|
125
|
-
* Use this when you want to opt out of explicit Result handling and
|
|
126
|
-
* let errors propagate as exceptions.
|
|
127
|
-
*
|
|
128
|
-
* @param result - The Result to unwrap
|
|
129
|
-
* @returns The success value
|
|
130
|
-
* @throws The error if the result is not ok
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```ts
|
|
134
|
-
* // Instead of:
|
|
135
|
-
* const result = await echoscu({ host: 'localhost', port: 104 });
|
|
136
|
-
* if (!result.ok) throw result.error;
|
|
137
|
-
* console.log(result.value);
|
|
138
|
-
*
|
|
139
|
-
* // You can write:
|
|
140
|
-
* const value = unwrap(await echoscu({ host: 'localhost', port: 104 }));
|
|
141
|
-
* console.log(value);
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
declare function unwrap<T>(result: Result<T>): T;
|
|
145
122
|
/**
|
|
146
123
|
* Transforms the success value of a Result, passing through errors unchanged.
|
|
147
124
|
*
|
|
@@ -168,4 +145,4 @@ declare function mapResult<T, U>(result: Result<T>, fn: (value: T) => U): Result
|
|
|
168
145
|
*/
|
|
169
146
|
type ResultValue<R> = R extends Result<infer T> ? T : never;
|
|
170
147
|
|
|
171
|
-
export { type DcmtkProcessResult as D, type ExecOptions as E, type LineSource as L, type ProcessLine as P, type Result as R, type SpawnOptions as S, type ResultValue as a, assertUnreachable as b, err as e, mapResult as m, ok as o
|
|
148
|
+
export { type DcmtkProcessResult as D, type ExecOptions as E, type LineSource as L, type ProcessLine as P, type Result as R, type SpawnOptions as S, type ResultValue as a, assertUnreachable as b, err as e, mapResult as m, ok as o };
|
|
@@ -119,29 +119,6 @@ interface ProcessLine {
|
|
|
119
119
|
readonly source: LineSource;
|
|
120
120
|
readonly text: string;
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Extracts the value from a successful Result, or throws the error.
|
|
124
|
-
*
|
|
125
|
-
* Use this when you want to opt out of explicit Result handling and
|
|
126
|
-
* let errors propagate as exceptions.
|
|
127
|
-
*
|
|
128
|
-
* @param result - The Result to unwrap
|
|
129
|
-
* @returns The success value
|
|
130
|
-
* @throws The error if the result is not ok
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```ts
|
|
134
|
-
* // Instead of:
|
|
135
|
-
* const result = await echoscu({ host: 'localhost', port: 104 });
|
|
136
|
-
* if (!result.ok) throw result.error;
|
|
137
|
-
* console.log(result.value);
|
|
138
|
-
*
|
|
139
|
-
* // You can write:
|
|
140
|
-
* const value = unwrap(await echoscu({ host: 'localhost', port: 104 }));
|
|
141
|
-
* console.log(value);
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
declare function unwrap<T>(result: Result<T>): T;
|
|
145
122
|
/**
|
|
146
123
|
* Transforms the success value of a Result, passing through errors unchanged.
|
|
147
124
|
*
|
|
@@ -168,4 +145,4 @@ declare function mapResult<T, U>(result: Result<T>, fn: (value: T) => U): Result
|
|
|
168
145
|
*/
|
|
169
146
|
type ResultValue<R> = R extends Result<infer T> ? T : never;
|
|
170
147
|
|
|
171
|
-
export { type DcmtkProcessResult as D, type ExecOptions as E, type LineSource as L, type ProcessLine as P, type Result as R, type SpawnOptions as S, type ResultValue as a, assertUnreachable as b, err as e, mapResult as m, ok as o
|
|
148
|
+
export { type DcmtkProcessResult as D, type ExecOptions as E, type LineSource as L, type ProcessLine as P, type Result as R, type SpawnOptions as S, type ResultValue as a, assertUnreachable as b, err as e, mapResult as m, ok as o };
|
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 * Extracts the value from a successful Result, or throws the error.\n *\n * Use this when you want to opt out of explicit Result handling and\n * let errors propagate as exceptions.\n *\n * @param result - The Result to unwrap\n * @returns The success value\n * @throws The error if the result is not ok\n *\n * @example\n * ```ts\n * // Instead of:\n * const result = await echoscu({ host: 'localhost', port: 104 });\n * if (!result.ok) throw result.error;\n * console.log(result.value);\n *\n * // You can write:\n * const value = unwrap(await echoscu({ host: 'localhost', port: 104 }));\n * console.log(value);\n * ```\n */\nfunction unwrap<T>(result: Result<T>): T {\n if (result.ok) {\n return result.value;\n }\n throw result.error;\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, unwrap, 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"],"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"]}
|
package/dist/utils.d.cts
CHANGED
package/dist/utils.d.ts
CHANGED
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/utils/batch.ts","../src/utils/retry.ts"],"names":[],"mappings":"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","file":"utils.js","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 * Extracts the value from a successful Result, or throws the error.\n *\n * Use this when you want to opt out of explicit Result handling and\n * let errors propagate as exceptions.\n *\n * @param result - The Result to unwrap\n * @returns The success value\n * @throws The error if the result is not ok\n *\n * @example\n * ```ts\n * // Instead of:\n * const result = await echoscu({ host: 'localhost', port: 104 });\n * if (!result.ok) throw result.error;\n * console.log(result.value);\n *\n * // You can write:\n * const value = unwrap(await echoscu({ host: 'localhost', port: 104 }));\n * console.log(value);\n * ```\n */\nfunction unwrap<T>(result: Result<T>): T {\n if (result.ok) {\n return result.value;\n }\n throw result.error;\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, unwrap, 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"],"names":[],"mappings":"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","file":"utils.js","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubercode/dcmtk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Type-safe Node.js wrapper for DCMTK (DICOM Toolkit) command-line utilities",
|
|
5
5
|
"author": "Michael Lee Hobbs <michael.lee.hobbs@gmail.com> (https://github.com/MichaelLeeHobbs)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -124,23 +124,23 @@
|
|
|
124
124
|
]
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
-
"@eslint/js": "
|
|
128
|
-
"@types/node": "
|
|
129
|
-
"@vitest/coverage-v8": "
|
|
130
|
-
"eslint": "
|
|
127
|
+
"@eslint/js": "10.0.1",
|
|
128
|
+
"@types/node": "25.3.3",
|
|
129
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
130
|
+
"eslint": "10.0.2",
|
|
131
131
|
"eslint-config-prettier": "10.1.8",
|
|
132
132
|
"eslint-plugin-prettier": "5.5.5",
|
|
133
133
|
"fast-check": "4.5.3",
|
|
134
|
-
"globals": "
|
|
134
|
+
"globals": "17.4.0",
|
|
135
135
|
"husky": "9.1.7",
|
|
136
|
-
"lint-staged": "
|
|
136
|
+
"lint-staged": "16.3.1",
|
|
137
137
|
"prettier": "3.8.1",
|
|
138
138
|
"rimraf": "6.1.3",
|
|
139
139
|
"tsup": "8.5.1",
|
|
140
140
|
"tsx": "4.21.0",
|
|
141
141
|
"typescript": "5.9.3",
|
|
142
142
|
"typescript-eslint": "8.56.1",
|
|
143
|
-
"vitest": "
|
|
143
|
+
"vitest": "4.0.18"
|
|
144
144
|
},
|
|
145
145
|
"pnpm": {
|
|
146
146
|
"onlyBuiltDependencies": [
|