fix(eslint): identity + no-swallow error law; enhance shared NestedError#49
Merged
Conversation
Ports the identity + error-preservation eslint law from wire-tools-ts (the two
repos share the ban set) and enhances the shared error primitive it depends on:
- @wireio/shared NestedError: takes a new message AND preserves the full cause
chain — the real Error objects survive in `.causes` and native `Error.cause`
(each cause keeps its own stack trace + message), a nested-exception
`Caused by:` section per cause is appended to `.stack`, and structured
`context` is folded into the message. + unit tests.
- eslint.config.mjs: `preserve-caught-error` ON (was off); `BanErrorMessageRestring`
+ `BanBareErrorInHandler` (a bare Error in an error handler that RECEIVES the
error — Either.mapLeft/ifLeft/recoverWith, Promise.catch, Future.onFailure —
NOT Option.orElse/ifNone); a `wire-local/no-identity-arrow` custom rule.
- Sweep: `x => x` identity arrows -> lodash `identity` (serializer builtins via
`identity<T>` to keep the exact signature); caught-error rewrites ->
`NestedError({ cause })` in `api/Client` and `serializer/Decoder`; plus the
pre-existing `BanNullUnionReturn` / `BanInlineTypeLiteral` violations the
now-enforced lint surfaces in the new `sysio.authex` / `sysio.reserv` code
(`ci.yaml` runs test:ci, not lint) — dropped the unenforced `| null` return
unions (strictNullChecks is off) and named the inline object types.
- CLAUDE.md: correct the stale "No ESLint configured" note (eslint IS configured,
copied from wire-tools-ts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change-Id: I50bf25b03fda4ca66f7b3a8ffc9d11f4716f08ba
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Ports the identity + error-preservation eslint law from
wire-tools-ts(the tworepos share the ban set) and enhances the
@wireio/sharedNestedErrorprimitiveit depends on, then sweeps the codebase into compliance. Also corrects a stale
CLAUDE.mdnote.Why (business reasoning)
Errors were being swallowed. Caught errors were rewritten into bare
new Error(...)(or restrung via`${err.message}`), which discards theoriginal error object — its stack trace, its structured fields, its
causechain. In production a swallowed cause turns a precise, localized failure into a
confusing far-away symptom, and the diagnosis cost is paid over and over. The fix
is to ALWAYS preserve the caught error as a
cause.Identity arrows re-implement a library primitive.
x => xIS lodashidentity— hand-writing it adds an anonymous closure that reads as if it mightdo something and isn't greppable.
Both are now mechanically enforced so they can't regress, matching the law
already live in
wire-tools-ts.Changes
@wireio/sharedNestedError — the vehicleErrorobjects survive in
.causesand the native ES2022Error.cause, so eachcause keeps its own stack trace and message. A nested-exception
Caused by:section per cause is appended to
.stack, so the whole chain is visiblewherever the error prints — not only via the programmatic accessors.
.context(the failing input, ids, amounts) — both a property andfolded (bigint-/cycle-safe, truncated) into the message.
NestedErrorhad zero consumers, so the API change breaksnothing. + unit tests.
eslint enforcement (
eslint.config.mjs)preserve-caught-errorON (was deliberately off) — acatchthat rethrowswithout
{ cause }is an error.BanErrorMessageRestring— a caught error's.message/.stack(ortoMessage(err)) interpolated into anew Error.BanBareErrorInHandler— a bare (no-cause) nativeErrorinside an errorhandler that RECEIVES the error:
Either.mapLeft/ifLeft/recoverWith,Promise.catch,Future.onFailure(NOTOption.orElse/ifNone— thosehandle ABSENCE; a
Noneis not an error to preserve).wire-local/no-identity-arrow— a small custom rule (esquery cannot assertbody-name === param-name) flagging
x => x.Sweep — the rules applied everywhere
identity(api/Client,serializer/Builtinsvia thetyped
identity<T>so the serializer's exact signature is preserved).NestedError({ cause })inapi/Clientandserializer/Decoder.BanNullUnionReturn/BanInlineTypeLiteralviolations thatthe now-enforced lint surfaces in the new
sysio.authex/sysio.reservcode —they slipped in because
ci.yamlrunstest:ci, notlint. Dropped theunenforced
| nullreturn unions (strictNullChecksis off, so the union isdead ceremony) and named the inline object types (
SignCreateLinkOptions,Stringifiable).Docs
CLAUDE.md: corrected the stale "No ESLint configured" line — eslint ISconfigured (copied from
wire-tools-ts, same ban set + ratchet discipline).Verification
pnpm buildclean;eslint .→ 0 errors (only pre-existingno-explicit-anywarnings remain); tests green — NestedError 11, sdk-core 70(incl.
authex/reserv/serializer/Client/api).🤖 Generated with Claude Code