Size / Priority
- Size: Trivial (~50 call-sites consolidated)
- Category: C.2 Simplifications & DRY.
- Risk: low.
Affected files
- Across
src/cache/*, src/io/broker/*, src/persistence/* — every try { … } catch (e) { throw new XxxError(...) } pattern.
Background
The same shape repeats many times:
try {
await client.doSomething();
} catch (e) {
if (e instanceof XxxError) throw e;
throw new XxxError(`XxxBackend.doSomething failed for key '${key}'`, e);
}
Two annoyances:
- The if-check
instanceof XxxError — prevents double-wrapping when an inner call already wrapped. Easy to forget.
- Bespoke message format —
'XxxBackend.method failed for key X' repeated with manual variation.
Target code
// src/util/WrapError.ts (new)
/**
* Re-throw if already typed; otherwise wrap with a context message.
*
* try { ... } catch (e) { throw wrapError(e, MyError, 'context: foo failed for key', { key }); }
*/
export function wrapError<E extends Error>(
e: unknown,
ErrorClass: new (msg: string, cause?: unknown) => E,
message: string,
context?: Record<string, unknown>,
): E {
if (e instanceof ErrorClass) return e;
const ctxStr = context ? ` (${Object.entries(context).map(([k, v]) => `${k}=${String(v)}`).join(', ')})` : '';
return new ErrorClass(`${message}${ctxStr}`, e);
}
Per-site usage:
try {
await client.doSomething();
} catch (e) {
throw wrapError(e, CacheError, `${this.name}.doSomething failed`, { key });
}
Integration / risk
Test plan
- Regression — error-throwing tests still report correct error class + message.
- Double-wrap prevention — nested wrap calls don't re-wrap.
- Context formatting — verify
{ key: 'foo' } becomes (key=foo).
Acceptance criteria
Size / Priority
Affected files
src/cache/*,src/io/broker/*,src/persistence/*— everytry { … } catch (e) { throw new XxxError(...) }pattern.Background
The same shape repeats many times:
Two annoyances:
instanceof XxxError— prevents double-wrapping when an inner call already wrapped. Easy to forget.'XxxBackend.method failed for key X'repeated with manual variation.Target code
Per-site usage:
Integration / risk
Test plan
{ key: 'foo' }becomes(key=foo).Acceptance criteria
wrapError(e, Cls, msg, ctx?)exported.