fix(lsp): align operations and harden lifecycle
This commit is contained in:
48
packages/lsp/lsp-local/src/abort.ts
Normal file
48
packages/lsp/lsp-local/src/abort.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Shared cancellation helpers for the local LSP provider's host-I/O, queue, and protocol phases.
|
||||
* @module @deepseek-ai/dsh-lsp-local/abort
|
||||
*/
|
||||
|
||||
import { timeoutOf } from '@deepseek-ai/dsh-timeout'
|
||||
|
||||
/**
|
||||
* Build an abort Error carrying the signal's reason and preserving timeout classification.
|
||||
* @param signal - the aborted signal whose reason to surface.
|
||||
* @returns the timeout reason if present, else the Error reason, else a generic aborted Error.
|
||||
*/
|
||||
export function abortError(signal: AbortSignal): Error {
|
||||
const timeout = timeoutOf(signal)
|
||||
if (timeout !== undefined) return timeout
|
||||
const reason: unknown = signal.reason
|
||||
if (reason instanceof Error) return reason
|
||||
return new Error('LSP query aborted')
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw the signal's classified abort error when it has already fired.
|
||||
* @param signal - the optional query cancellation signal.
|
||||
*/
|
||||
export function throwIfAborted(signal?: AbortSignal): void {
|
||||
if (signal?.aborted) throw abortError(signal)
|
||||
}
|
||||
|
||||
/**
|
||||
* Await work while allowing a query signal to abandon its wait; the underlying work keeps its own
|
||||
* handlers and continues to its owner-defined quiescence boundary.
|
||||
* @param work - the owned asynchronous work.
|
||||
* @param signal - optional query cancellation.
|
||||
* @returns the work result, or a rejection carrying the classified abort reason.
|
||||
*/
|
||||
export function abortable<T>(work: Promise<T>, signal?: AbortSignal): Promise<T> {
|
||||
if (signal === undefined) return work
|
||||
if (signal.aborted) return Promise.reject(abortError(signal))
|
||||
const canceled = Promise.withResolvers<never>()
|
||||
const onAbort = (): void => { canceled.reject(abortError(signal)) }
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
const normalized = work.catch((error: unknown) => {
|
||||
/* v8 ignore next -- owned LSP promises reject with Error; coercion defends the generic helper. */
|
||||
throw error instanceof Error ? error : new Error(String(error))
|
||||
})
|
||||
return Promise.race([normalized, canceled.promise])
|
||||
.finally(() => { signal.removeEventListener('abort', onAbort) })
|
||||
}
|
||||
Reference in New Issue
Block a user