···2233All notable changes to this project will be documented in this file.
4455+## [0.1.0-alpha.11] - 2026-02-28
66+77+### Added
88+99+- **`tapEffect`** — Effectful success tap: observe a success value with an effect-returning side-effect, discard the result, return the original value. Errors from the side-effect propagate into the error union.
1010+- **`tapError`** — Effectful error tap: observe an error with an effect-returning side-effect, discard the result, re-raise the original error.
1111+- **`tapBoth`** — Effectful dual tap: observe both success and failure with independent effectful handlers. Each handler can have its own error and requirement types.
1212+1313+### Changed
1414+1515+- **`tapBoth` handlers have independent type params** — `onSuccess` and `onFailure` can use different error (`E2`/`E3`) and requirement (`R2`/`R3`) types, enabling different effect types per branch.
1616+1717+## [0.1.0-alpha.10] - 2026-02-28
1818+1919+### Fixed
2020+2121+- Type errors in examples (stories, task-queue, user-registration)
2222+- `isSet` / `isMap` guards now return `ReadonlySet` / `ReadonlyMap` for stricter type safety
2323+- `and` guard combinator returns intersection type (`B & C`) instead of requiring `C extends B`
2424+2525+### Changed
2626+2727+- Expanded guard tests (137+ assertions covering edge cases)
2828+- Added `tsconfig.test.json` path mappings
2929+530## [0.1.0-alpha.9] - 2026-02-07
631732### Added
+9-8
src/effect/combinators.ts
···147147/**
148148 * Observe both success and failure with effectful side-effects.
149149 * The original value or error is always preserved after the side-effect runs.
150150+ * Each handler can have independent error and requirement types.
150151 *
151152 * @example
152153 * ```typescript
···160161 * ```
161162 */
162163export const tapBoth =
163163- <A, E, E2, R2>(handlers: {
164164+ <A, E, E2, R2, E3, R3>(handlers: {
164165 readonly onSuccess: (a: A) => Eff<unknown, E2, R2>
165165- readonly onFailure: (e: E) => Eff<unknown, E2, R2>
166166+ readonly onFailure: (e: E) => Eff<unknown, E3, R3>
166167 }) =>
167167- <R>(eff: Eff<A, E, R>): Eff<A, E | E2, R | R2> =>
168168+ <R>(eff: Eff<A, E, R>): Eff<A, E | E2 | E3, R | R2 | R3> =>
168169 pipe(
169169- eff as Eff<A, E | E2, R | R2>,
170170+ eff as Eff<A, E | E2 | E3, R | R2 | R3>,
170171 foldEff(
171171- (e: E | E2) =>
172172+ (e: E | E2 | E3) =>
172173 pipe(
173173- handlers.onFailure(e as E) as Eff<unknown, E | E2, R | R2>,
174174- flatMap(() => fail(e) as Eff<A, E | E2, R | R2>),
174174+ handlers.onFailure(e as E) as Eff<unknown, E | E2 | E3, R | R2 | R3>,
175175+ flatMap(() => fail(e) as Eff<A, E | E2 | E3, R | R2 | R3>),
175176 ),
176177 (a: A) =>
177178 pipe(
178178- handlers.onSuccess(a) as Eff<unknown, E | E2, R | R2>,
179179+ handlers.onSuccess(a) as Eff<unknown, E | E2 | E3, R | R2 | R3>,
179180 mapEff(() => a),
180181 ),
181182 ),