Skip to content

chore: release packages - #174

Open
btravers wants to merge 1 commit into
mainfrom
changeset-release/main
Open

chore: release packages#174
btravers wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@btravers

@btravers btravers commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

unthrown@5.1.0

Minor Changes

  • 5ead919: Close a boundary hole where an async function's rejection escaped
    qualification, stop toBeErrTagged counting a message as payload, and name the
    value in NonExhaustiveError.

    fromThrowable / fromSafeThrowable now reject an async fn. These wrap a
    synchronous function, so they only ever see a synchronous throw: an async
    fn rejects long after the boundary has returned, and its rejection could never
    reach qualify. It used to produce Ok(<Promise>) — un-triaged — and the
    rejection then floated as an unhandled rejection, which terminates the process on
    Node by default:

    const f = fromThrowable(
      async () => {
        throw new Error("boom");
      },
      (cause, defect) => defect(cause)
    );
    f(); // before: Ok(<Promise>) + an unhandled rejection
    // now:    Defect(TypeError: … `fn` returned a thenable …)

    The orphaned rejection is adopted and silenced, so it cannot float. Reach for
    fromPromise / fromSafePromise for async work.

    This is caught at runtime rather than by the type system — the one thenable ban
    in the library that is not a compile error. Putting NotThenable on fn's return
    also makes generic functions unassignable, so fromSafeThrowable(structuredClone)
    would stop compiling with T collapsed to unknown; the phantom rest-tuple guard
    fromPromise uses fares worse. The success type is therefore slightly over-stated
    (Result<Promise<T>, E> is spellable but never inhabited) — the mirror of
    recoverErrCases's never under-stating the error channel.

    toBeErrTagged's exact-payload form now ignores every reserved key. The
    documented way to set a TaggedError's message is a subclass field —
    override message = "…" — which lands as an own enumerable property, so it
    leaked into the payload and failed an exact assertion on the very pattern the
    library prescribes:

    class HttpError extends TaggedError("HttpError")<{ status: number }> {
      override message = `http ${this.status}`;
    }
    expect(Err(new HttpError({ status: 500 }))).toBeErrTagged("HttpError", {
      status: 500,
    });
    // before: failed — the payload was seen as { status: 500, message: "http 500" }
    // now:    passes

    The matcher now skips _tag, name, message and stack — exactly the keys
    TaggedErrorInstance omits and the constructor types ?: never, so none of them
    can legitimately be payload. Assertions using expect.objectContaining are
    unaffected (they were already tolerant of the extra key).

    NonExhaustiveError now names the value it could not match. JSON.stringify
    returns undefined — it does not throw — for a function, a symbol, or
    undefined, so the String(input) fallback never fired and the message read
    "no pattern matched the value undefined" for exactly the rogue inputs this error
    exists to describe. It now falls back correctly (… the value function rogueFn() {}).

@unthrown/oxlint@5.1.0

Minor Changes

  • 829b9d3: Add the no-unused-matcher rule (Add an @unthrown/oxlint rule: no-unused-matcher (…Cases callbacks that ignore the injected matcher) #171) — and enable it in the recommended
    preset.

    A …Cases callback (the five error combinators, and match's errCases
    handler) that never uses the matcher it was handed sources its exhaustiveness
    from a builder bound to some other value. The type checker cannot see it —
    the ExhaustiveMatch constraint is structural, and noUnusedParameters never
    fires because the parameter is not unused, it is simply never declared — and
    the runtime runs the borrowed builder against whatever value it closed over:
    the wrong branch is chosen silently (a plausible wrong value, with the Err
    channel typing as fully handled), or nothing matches and the modeled error
    becomes a Defect.

    The rule reports a callback whose matcher parameter is absent (() => …) or
    never read, and — separately, to catch a trivial void matcher reference
    fronting for a foreign builder — any second unthrown / ts-pattern match(...)
    built in the callback's own body. Branch handlers (nested functions) stay free
    to match their payload. There is no escape hatch: a …Cases callback that
    does not use its matcher is never what you meant.

    Enabling the rule in recommended can surface new lint errors in an existing
    codebase; each one is a real bug of the shape above.

@unthrown/prisma@0.2.0

Minor Changes

  • ab7194f: Make E domain outcomes only: infrastructure failures become defects, and fix
    an unsound error channel on tryCreate / tryUpsert.

    E now carries only what you would actually branch on. Three P-codes
    describe a domain outcome and stay modeled — UniqueConstraintViolation (P2002,
    409), ForeignKeyViolation (P2003, 400), RecordNotFound (P2025/P2018, 404).
    Everything infrastructural is now a defect: a dropped connection, a pool
    timeout, a deadlock, an unmapped P-code, a malformed query, a client that could
    not start, an engine panic.

    Nobody writes domain logic for a severed TCP connection — you log it and return a
    500, which is exactly what match's defect arm already does. Modelling those
    failures only forced every call site to carry an arm that duplicated its own
    defect arm:

    // before — the same handling, written twice, at every call site:
    errCases: (matcher) => matcher
      .with(P.tag("UniqueConstraintViolation"), (e) => resp.conflict(e.fields))
      .with(P.tag("DriverError"), (e) => resp.serverError(e)),
    defect: (cause) => resp.serverError(cause),

    A defect is not a crash: it flows through the pipeline untouched and is folded at
    the edge like any other unmodeled failure.

    Consequences:

    • The DriverError class is removed. Nothing routes to it any more; the
      original Prisma error reaches your defect arm unwrapped, with its code,
      meta and stack intact.
    • A read has no modeled failure: tryFindMany is
      AsyncResult<User[], never>. Absence is still null.
    • Pagination gains InvalidCursor, the one carve-out. A cursor is an opaque
      string from a client, so a parseCursor that rejects it — or a query Prisma
      refuses to validate — is anticipated input you answer with a 400. A throw out
      of getCursor, which reads rows you just fetched, is a bug and stays a defect.
    • Retrying a deadlock (P2034) or pool timeout (P2024) now goes through
      recoverDefect rather than a tag match — one place in a codebase, versus an
      arm at every call site.

    tryCreate and tryUpsert now carry RecordNotFound. A nested connect
    pointing at a record that does not exist raises P2025, even though neither
    operation has a row of its own to miss:

    db.post.tryCreate({ data: { title, author: { connect: { id: 999 } } } });
    // Err(RecordNotFound) — P2025

    Their unions previously excluded it, so the runtime produced an error the type
    said was impossible. An exhaustive mapErrCases over the declared union then had
    no arm for the value that arrived, the matcher threw NonExhaustiveError, and
    the throw-to-defect net turned a modeled database failure into a Defect. P2018
    — the same failure from the to-many side of a nested write — now maps to
    RecordNotFound too. The batch mutations (tryCreateMany / tryUpdateMany and
    their *AndReturn twins) accept no nested writes, so they remain free of it.

    withCursor's after and before are now mutually exclusive. Passing both
    type-checked and silently ignored after.

    Breaking, shipped as a minor: qualifyPrismaError takes the injected defect
    helper as a second argument (it is a qualify, so passing it to a boundary is
    unchanged — only direct invocation needs updating), the DriverError class is
    gone, and every error union changed. Each of those is a compile error at the call
    site rather than a silent behaviour change — which is the point.

Patch Changes

  • Updated dependencies [5ead919]
    • unthrown@5.1.0

@unthrown/vitest@5.1.0

Minor Changes

  • 5ead919: Close a boundary hole where an async function's rejection escaped
    qualification, stop toBeErrTagged counting a message as payload, and name the
    value in NonExhaustiveError.

    fromThrowable / fromSafeThrowable now reject an async fn. These wrap a
    synchronous function, so they only ever see a synchronous throw: an async
    fn rejects long after the boundary has returned, and its rejection could never
    reach qualify. It used to produce Ok(<Promise>) — un-triaged — and the
    rejection then floated as an unhandled rejection, which terminates the process on
    Node by default:

    const f = fromThrowable(
      async () => {
        throw new Error("boom");
      },
      (cause, defect) => defect(cause)
    );
    f(); // before: Ok(<Promise>) + an unhandled rejection
    // now:    Defect(TypeError: … `fn` returned a thenable …)

    The orphaned rejection is adopted and silenced, so it cannot float. Reach for
    fromPromise / fromSafePromise for async work.

    This is caught at runtime rather than by the type system — the one thenable ban
    in the library that is not a compile error. Putting NotThenable on fn's return
    also makes generic functions unassignable, so fromSafeThrowable(structuredClone)
    would stop compiling with T collapsed to unknown; the phantom rest-tuple guard
    fromPromise uses fares worse. The success type is therefore slightly over-stated
    (Result<Promise<T>, E> is spellable but never inhabited) — the mirror of
    recoverErrCases's never under-stating the error channel.

    toBeErrTagged's exact-payload form now ignores every reserved key. The
    documented way to set a TaggedError's message is a subclass field —
    override message = "…" — which lands as an own enumerable property, so it
    leaked into the payload and failed an exact assertion on the very pattern the
    library prescribes:

    class HttpError extends TaggedError("HttpError")<{ status: number }> {
      override message = `http ${this.status}`;
    }
    expect(Err(new HttpError({ status: 500 }))).toBeErrTagged("HttpError", {
      status: 500,
    });
    // before: failed — the payload was seen as { status: 500, message: "http 500" }
    // now:    passes

    The matcher now skips _tag, name, message and stack — exactly the keys
    TaggedErrorInstance omits and the constructor types ?: never, so none of them
    can legitimately be payload. Assertions using expect.objectContaining are
    unaffected (they were already tolerant of the extra key).

    NonExhaustiveError now names the value it could not match. JSON.stringify
    returns undefined — it does not throw — for a function, a symbol, or
    undefined, so the String(input) fallback never fired and the message read
    "no pattern matched the value undefined" for exactly the rogue inputs this error
    exists to describe. It now falls back correctly (… the value function rogueFn() {}).

@unthrown/boxed@5.1.0

Patch Changes

  • Updated dependencies [5ead919]
    • unthrown@5.1.0

@unthrown/effect@5.1.0

Patch Changes

  • Updated dependencies [5ead919]
    • unthrown@5.1.0

@unthrown/neverthrow@5.1.0

Patch Changes

  • Updated dependencies [5ead919]
    • unthrown@5.1.0

@unthrown/standard-schema@5.1.0

Patch Changes

  • Updated dependencies [5ead919]
    • unthrown@5.1.0

Copilot AI review requested due to automatic review settings July 30, 2026 21:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This is an automated Changesets release PR to publish the @unthrown/* fixed version group at 5.1.0, including a minor release of @unthrown/oxlint adding the new no-unused-matcher rule (enabled in the recommended preset).

Changes:

  • Bump package versions from 5.0.05.1.0 across the fixed release group.
  • Update package changelogs for 5.1.0 (notably @unthrown/oxlint with the new rule notes).
  • Remove the applied changeset file after generating changelogs.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/vitest/package.json Version bump to 5.1.0.
packages/vitest/CHANGELOG.md Add 5.1.0 section header.
packages/standard-schema/package.json Version bump to 5.1.0.
packages/standard-schema/CHANGELOG.md Add 5.1.0 patch entry referencing unthrown@5.1.0.
packages/oxlint/package.json Version bump to 5.1.0.
packages/oxlint/CHANGELOG.md Document the no-unused-matcher rule release notes for 5.1.0.
packages/neverthrow/package.json Version bump to 5.1.0.
packages/neverthrow/CHANGELOG.md Add 5.1.0 patch entry referencing unthrown@5.1.0.
packages/effect/package.json Version bump to 5.1.0.
packages/effect/CHANGELOG.md Add 5.1.0 patch entry referencing unthrown@5.1.0.
packages/core/package.json Version bump to 5.1.0.
packages/core/CHANGELOG.md Add 5.1.0 section header.
packages/boxed/package.json Version bump to 5.1.0.
packages/boxed/CHANGELOG.md Add 5.1.0 patch entry referencing unthrown@5.1.0.
.changeset/seven-rules-no-unused-matcher.md Remove the consumed changeset after release generation.

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

Comment thread packages/vitest/CHANGELOG.md
Comment thread packages/core/CHANGELOG.md
@btravers
btravers force-pushed the changeset-release/main branch 2 times, most recently from 4244701 to 42689a3 Compare July 31, 2026 22:37
@btravers
btravers force-pushed the changeset-release/main branch from 42689a3 to 4a36859 Compare July 31, 2026 23:02
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