Skip to content

feat(core): add flatTapErr and isResult#40

Merged
btravers merged 1 commit into
mainfrom
feat/result-flat-tap-err-is-result
Jun 29, 2026
Merged

feat(core): add flatTapErr and isResult#40
btravers merged 1 commit into
mainfrom
feat/result-flat-tap-err-is-result

Conversation

@btravers

Copy link
Copy Markdown
Collaborator

Closes the only two structural gaps surfaced by comparing unthrown's surface against boxed, neverthrow, and byethrow. (The functions that comparison initially flagged as "missing" — flatMapError, mapOkToResult, mapErrorToResult — already exist under standard names: orElse and flatMap.)

flatTapErr — failable tap on the error channel

The error-side mirror of flatTap. Runs a Result-returning effect on the error; on the effect's Ok the original error flows through, otherwise the effect's error is threaded (Result<T, E | E2>). A throw becomes a Defect like every other combinator. Added on both Result and AsyncResult.

findUser(id)
  .flatTapErr((e) => auditLog(e)) // auditLog may itself fail
  .match({ ok, err, defect });

Use it for a failable effect during error handling — e.g. writing the error to an audit log that may itself fail. (byethrow ships this as orThrough; unthrown previously had only the pure tapErr, an asymmetry vs. tap/flatTap on the success side.)

isResult(x) — guard from unknown

A standalone type guard (and Result.isResult) narrowing an unknown to Result<unknown, unknown>, for untyped interop boundaries. It checks the value carries the Result prototype, so a plain { tag: "Ok" } look-alike is not matched, and an AsyncResult returns false. (boxed and byethrow both ship an isResult.)

Deliberately still excluded

Everything else the peers have conflicts with a stated thesis or the "small / done-able" goal: error accumulation (combineWithAllErrors/collect), Option, generator do-notation (safeTry), and neverthrow's asyncMap/asyncAndThen (which would bypass boundary qualification, Thesis #3).

Coverage

Full gate green: format · lint · typecheck (15/15, incl. type-level tests for flatTapErr channel widening and isResult narrowing) · knip · test (165 tests, functions/lines 100%) · build. New behavior is guarded 1:1 in invariants.spec.ts (throw→Defect, Defect-passthrough) plus result/async-result/constructors/facade specs.

🤖 Generated with Claude Code

flatTapErr is the error-channel mirror of flatTap: it runs a
Result-returning effect on the error, keeps the original error when the
effect succeeds, and threads the effect's error otherwise
(Result<T, E | E2>). A throw becomes a Defect like every other
combinator. Use it for a failable effect during error handling (e.g.
writing the error to an audit log that may itself fail). Added on both
Result and AsyncResult.

isResult(x) is a standalone guard narrowing an unknown to
Result<unknown, unknown> (and Result.isResult). It checks the Result
prototype, so a { tag: "Ok" } look-alike is not matched and an
AsyncResult returns false. For untyped interop boundaries.

Closes the only structural gaps found comparing the surface to boxed /
neverthrow / byethrow; everything else they have (error accumulation,
Option, generator do-notation, asyncMap) stays deliberately excluded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 29, 2026 15:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes two missing surface-area gaps in @unthrown/core by adding an error-channel “failable tap” combinator and a safe unknown-to-Result type guard, aligning the library’s Result API with peer libraries while preserving unthrown’s defect/throw invariants.

Changes:

  • Add flatTapErr to both Result and AsyncResult to run a Result/AsyncResult-returning effect on Err, preserving the original error on effect success and threading effect failures.
  • Add isResult(x) (and Result.isResult) implemented as a prototype/instanceof check to safely narrow from unknown.
  • Add/extend runtime tests, type-level tests, docs, and a changeset entry for the new API.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/core/src/types.ts Adds flatTapErr to the public ResultMethods and AsyncResult type surfaces.
packages/core/src/types.test-d.ts Adds type-level assertions for flatTapErr error widening and isResult narrowing.
packages/core/src/core.ts Implements Res.flatTapErr, AsyncRes.flatTapErr, and exports isResult.
packages/core/src/index.ts Re-exports isResult from the package entrypoint.
packages/core/src/facade.ts Exposes Result.isResult on the facade object and updates docs link list.
packages/core/src/result.spec.ts Adds behavioral tests for Result.flatTapErr (success keeps original error; failures thread).
packages/core/src/async-result.spec.ts Adds behavioral tests for AsyncResult.flatTapErr (sync/async effects, passthrough behavior).
packages/core/src/invariants.spec.ts Extends invariants coverage (throw→Defect, Defect passthrough) to include flatTapErr.
packages/core/src/facade.spec.ts Verifies facade wiring for Result.isResult and basic behavior.
packages/core/src/constructors.spec.ts Adds runtime tests for isResult true/false cases including look-alikes and AsyncResult.
docs/guide/core-concepts.md Documents flatTapErr in the error channel list and introduces isResult for unknown.
docs/guide/choosing-a-combinator.md Adds flatTapErr to the combinator selection table and guidance section.
CLAUDE.md Updates the internal “surface”/invariants documentation to include flatTapErr and isResult.
.changeset/flat-tap-err-is-result.md Declares a minor release and summarizes the added API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@btravers btravers merged commit 6eeb19d into main Jun 29, 2026
12 checks passed
@btravers btravers deleted the feat/result-flat-tap-err-is-result branch June 29, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants