pure-effect 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  All notable changes to pure-effect are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and from 1.0.0 the project follows [Semantic Versioning](https://semver.org/). Before 1.0.0 a minor version could change behaviour; each such change is marked below.
4
4
 
5
+ ## [0.17.0] - 2026-10-06
6
+
7
+ ### Changed
8
+
9
+ - **Inside a `Parallel`, a Command's function gets the `AbortSignal` only if it takes a parameter.** Every function was handed the signal, so one passed by name whose first parameter has a default value read the signal in its place: `Command(nanoid)` returned an empty ID inside a `Parallel` and a full one outside, and a helper with a default page size got the signal as its page size. The signal now goes only to a function that declares a parameter, counted as `Function.length` counts them, which leaves out parameters with default values, so such a function keeps its default everywhere. A function that reads the signal through a parameter with a default value or through `...args`, or that a wrapper hides behind a `length` of 0, is no longer given it, and runs to completion when its branch is cancelled, as a function that ignores the signal does; write it as `(signal) => ...`. A plain first parameter that a function treats as optional still takes the signal, as before.
10
+ - **An `options` argument refuses a name it does not read.** `Retry`, `Parallel`, `recorder`, `recordEffect`, `replayEffect` and `timeTravel` ignored any other name, so a misspelt or borrowed one ran with the default and nothing said so: `Parallel(batch, { concurrency: 4 })`, the name p-limit uses, ran with no limit, `{ settle: true }` ran fail-fast so one bad record cancelled the batch, and `{ attemps: 5 }` got 3 attempts. Such a name now throws, naming it and the options the function reads; `Retry` and `Parallel` throw an `EffectTypeError` as the flow is built, the others a `TypeError`. TypeScript already refused most of them in an object literal written in the call. An option set to `undefined` is still accepted.
11
+ - **The message for a step a trace lacks says what `onMissing: 'execute'` would run.** It advised `'execute'` where the Commands only read, so a replay that met a newly added lookup could run it that way, and every step after it, which had moved to a new path, ran live too. The message now says so, and advises `'execute'` only where every Command the flow can still reach goes to a test double or only reads.
12
+
13
+ ### Fixed
14
+
15
+ - **An `onStep` hook that calls `op()` without awaiting it can no longer replace a Command's result with `undefined`.** A hook that returned `undefined` in place of the Command's value made the run reject, but the check ran as soon as the hook returned, while a Command that took any time was still running, so a found user still reached `next` as `undefined` and the flow took the branch for a missing one. The interpreter now waits for a Command the hook left running before it judges the step: the run rejects if the Command returned a value, a throw from it is an I/O fault, as it is for a hook that awaited `op`, and nothing is left running unobserved.
16
+ - **`timeTravel` warns about unreached steps after a flow's own error named like a replay error.** It recognized its own replay errors by name, so a flow that ended on an error of its own named `ReplayError` or `TimeParadox` lost the warning about recorded steps it never reached. It now recognizes only the errors the library raises.
17
+
18
+ ## [0.16.0] - 2026-10-01
19
+
20
+ ### Added
21
+
22
+ - **`commandName` gives a Command's name as a trace records it:** `meta.name`, else the function's name, else `'anonymous'`. A test that walks a flow checks each step with it. Reading `step.cmd.name`, as the README's tests did, could not check a Command named through `meta.name` whose function is an inline arrow, since such a function has no name. Anything but a Command throws an `EffectTypeError`.
23
+ - **`timeTravel` warns about anonymous steps.** A trace whose steps include any named `'anonymous'`, usually inline arrow Commands, gets a warning in the narration, since a replay tells steps apart by name and two such steps that trade places after a refactor replay without complaint.
24
+ - **`recordEffect`'s trace keeps the input and context types.** `TraceLog` takes the input and context as type parameters, and `recordEffect` and `recorder().toTrace` fill them in, so `replayEffect(flow(trace.initialInput), trace)` needs no cast straight after recording. A trace read back from storage is typed as before.
25
+ - **`EffectValue`, `EffectError` and `EffectContext` read the value, error and context of an Effect**, as in `EffectError<ReturnType<typeof checkoutFlow>>`. `Parallel`'s declaration is built on them, alongside `ParallelBranches`, `ParallelValues` and `ParallelContext`.
26
+
27
+ ### Changed
28
+
29
+ - **`replayEffect` refuses `onMissing: 'execute'` on a trace that dropped entries.** A trace whose `dropped` is above 0 now rejects with a `ReplayError` before anything runs; replay it without the option to stop at the first step it lacks, or record the flow with a higher `maxEntries`. The message for a missing step now says whether the cap may have dropped it, and `'execute'` is documented for Commands that reach test doubles or only read.
30
+ - **An error union survives a pipeline without return annotations.** A step that cannot return a `Failure`, such as a Command with the default `next`, a Command whose `next` only succeeds, or a pure step that only returns `Success`, declared its error as `unknown`, which absorbed every other step's. So the Quick Start written in TypeScript typed `result.error` as `unknown`, and so did every pipeline that retried a Command in the recommended shape. Such a step now declares `never`, and the union holds the `Failure`s the steps can return: `Retry(Command(fn))` declares `RetryExhaustedError` alone, and a `Retry` whose fallback cannot fail declares nothing of its own. A `Command` call that gives some type arguments, such as `Command<User | null>(fetchJson, next)`, keeps the types it had, since TypeScript infers none of the others in such a call. A `Command` without a `next` takes only its result's type as a type argument. `Ask` and `Retry` given only some type arguments, as in `Ask<Product>(...)`, now refuse a step that can fail; give every type argument or none. A variable that holds the results of two different flows, which both used to type as `FailureState<unknown>`, may need a wider annotation. A Command whose function throws still ends the run with a `Failure` the union does not name, so where a flow's declared error is now only strings, or nothing, `result.error instanceof Error` stops compiling: copy the error into a variable typed `unknown` first, as in `const error: unknown = result.error`.
31
+ - **A `Parallel` declares its branches' errors.** Its branches were typed with one shared error that nothing inferred, so every `Parallel` declared `unknown`, and so did every pipeline holding one, including every loop written as the README writes it. A `Parallel` now declares each branch's error together with what `next` can return, and each outcome a settled `Parallel` hands `next` carries its own branch's error. A settled `Parallel` declares only `next`'s error, since a branch's failure reaches `next` as an outcome rather than escaping. `ParallelOutcomes` takes the branches instead of the values and one error, so `ParallelOutcomes<[User, Order], MyError>` is now written `ParallelOutcomes<[Effect<User, MyError>, Effect<Order, MyError>]>`, and `ParallelState` takes a sixth, optional parameter for the branches' error. `Parallel`'s type parameter is now the branches rather than their values, so an explicit `Parallel<[number]>(...)` stops compiling; leave it to be inferred.
32
+ - **The type declarations need TypeScript 5.1 or later.** The new `Parallel` declaration relies on 5.1, the first version that treats a mapped type over the branches as an array. TypeScript 5.1 is now checked on every change.
33
+ - **`Failure` keeps the literal type of its error.** Its type parameter is `const`, so `Failure('invalid_email')` is typed `'invalid_email'` rather than `string`, and an inferred error union stays exact with no `as const`. An object error is typed as readonly with literal properties, which is still assignable to a mutable object type, and an array error as a readonly tuple, which a mutable array type does not accept. A value typed `string` stays `string`.
34
+ - **A `next` that can only fail no longer widens a step's value.** `Command` with an explicit `next` declared its function's result as its value when `next` could only fail, and `Ask`, `Parallel` and `effectPipe` declared `unknown`, so a step that either succeeds or compensates and then fails, as in `ok ? Success(order) : Command(refund, () => Failure('declined'))`, did not compile in a pipeline, and a compensation written as a sub-pipeline failed the same way. Such a `next` now declares `never`, which adds nothing to the step's value.
35
+ - **`Parallel` refuses a third argument after its options.** Nothing reads a third argument once the options come second, so `Parallel(effects, { limit: 1 }, next)` ran without its `next` and handed the branch values on as they were, which only TypeScript refused. It now throws an `EffectTypeError` that gives the order, and so does a second options object passed there. `undefined` or `null` in that place still counts as absent.
36
+ - **An error for a `next` that returned something other than an Effect names whose `next` it was.** It said only that the flow or a continuation had returned it, so finding the culprit meant reading every `next`. It now reads, for example, `The next of Command 'cmdChargeCard' returned the number 2`, naming a Command by its name and an `Ask` or `Parallel` by its type.
37
+ - **Node 22 or later.** `engines` says `>=22`, where it said `>=18`: Node 18 and 20 have reached their end of life, and the tests run on 22, 24 and 26. Nothing in the library needs a newer Node, so code on an older one keeps running, but it is no longer tested there.
38
+
39
+ ### Fixed
40
+
41
+ - **A `Parallel` requires the context its branches read.** The same shared type made its context `unknown`, so `runEffect` accepted a flow with no context, or the wrong one, whenever only a `Parallel`'s branches read it, and `Ask` then got an empty object: a query filtered on `ctx.tenant` received `undefined`. A `Parallel` now needs every context its branches and its `next` read, as a pipeline needs its steps', whether the branches are listed, built with `map`, settled, or inside a pipeline.
42
+ - **A function that returns `Failure`s of different shapes compiles without an annotation.** A pipeline step, a `next` given to `Command`, `Ask` or `Parallel`, or an `onExhausted` fallback that could fail with, say, `Failure('cart_empty')` and `Failure({ code: 'invalid_quantity', sku })` did not compile unless its return type was written out. Such a function is now typed from its whole return, so its errors join into a union, each kept as written, and its values join the same way. Explicit type arguments type a call as before. A step that returns something other than an Effect is now reported as not assignable to `AnyEffect`, the type every step returns.
43
+ - **A stored trace imported as a JSON module type-checks.** `TraceEntry.threw` was declared `true`, and a JSON import widens `true` to `boolean`, so `replayEffect(flow(incident.initialInput), incident)` with `incident` imported from a trace file was refused whenever the trace held a failed step, which is how the README turns an incident into a regression test. It is declared `boolean` now; the recorder still writes only `true`.
44
+ - **A typed context accepts `flowName`.** `runEffect`, `recordEffect`, `replayEffect` and `timeTravel` refused `{ tenant, flowName }` for a flow whose context type does not declare `flowName`, though that key is how a run is named in traces and spans, so the README's `recordEffect` example did not compile against a typed flow. The context they take is now `RunContext`, the flow's context with an optional `flowName`.
45
+ - **A test walking a flow through a `Retry` can hand its `next` what the retried Command returned.** A `Retry` that starts a pipeline typed its `next` as taking the pipeline's final value, although it receives the retried Command's, so such a walk did not compile without a cast. `RetryState` takes a fourth, optional parameter for the value `next` receives, which defaults to the first.
46
+
5
47
  ## [0.15.0] - 2026-09-27
6
48
 
7
49
  ### Added
@@ -194,6 +236,8 @@ All notable changes to pure-effect are recorded here. The format follows [Keep a
194
236
 
195
237
  - Initial release: `Success`, `Failure`, `Command`, `effectPipe`, and `runEffect`.
196
238
 
239
+ [0.17.0]: https://github.com/aycangulez/pure-effect/compare/v0.16.0...v0.17.0
240
+ [0.16.0]: https://github.com/aycangulez/pure-effect/compare/v0.15.0...v0.16.0
197
241
  [0.15.0]: https://github.com/aycangulez/pure-effect/compare/v0.14.0...v0.15.0
198
242
  [0.14.0]: https://github.com/aycangulez/pure-effect/compare/v0.13.0...v0.14.0
199
243
  [0.13.0]: https://github.com/aycangulez/pure-effect/compare/v0.12.0...v0.13.0