Affect 0.0 — release constant

Unreliable TypeScript for fun

Build systems your team can observe, nobody has to run, and we spent about an afternoon on.

Affect.all
Combine multiple affects into one. The result is already there.
observing
There is no play button.Click a source to set it. Everything downstream follows.
Inspired by Effect, which runs the other way
What Affect solves

Built-in answers for the hard questions

Try-catch blocks everywhere but you still do not know how things can fail.
ETyped faults
  • Faults in the type signature
  • Caught while present, not after
  • Clear on their own, no retry
Another decorator. Another magic string. Another runtime error.
RLive services
  • Type-safe service definitions
  • Services that change under you, on purpose
  • Swap a layer, keep the program
That Promise.all call? One failure and everything crashes.
Fine-grained dependency
  • Subscribes to what it actually read
  • Evaluates once per change, in order
  • Lets go of what it stopped reading
Network failed? Try again later. But when, and how many times?
No scheduling
  • Nothing is retried
  • When the source changes, so does the value
  • No backoff. No jitter. No cron
No observability. Production is on fire and you do not know why.
Observation
  • observe is the only way in
  • Every change notifies exactly once
  • Stop watching with one call
Validation logic is duplicated across every layer of your application.
Structural equality
  • Equal writes do not propagate
  • Equal faults do not re-notify
  • Data classes compare by value
The mental model

Track values, faults, sources in one type

  • No more useEffect. Values are derived, not synchronized
  • Faults are conditions, not events. They leave when their cause does
  • Sources are explicit. Nothing is hidden, nothing is run
Affect<Value, Fault, Sources>
const program = Affect.gen(function* () { const config = yield* Config const value = yield* someOtherAffect return `${config.name}: ${value}` })
- yield* subscribes to the Value
- Faults and sources are tracked in the parent Affect
- The body re-runs when either changes
Why Affect was designed this way ↗
FAQ

Questions you probably have

Most of them are about the other library.

Is this Effect?

No.

Is it compatible with Effect?

The API is. The behavior is not. Code written for one will typecheck against the other after a rename, which is either a feature or the whole problem, depending on which one you meant to install.

Which one should I use?

If your program is a thing that happens, Effect. If your program is a thing that is true, Affect.

Why does gen run my body more than once?

Because something it yielded changed. That is what gen is for. If you want a body that runs once, do not yield anything that changes, or use Effect.

Where is retry?

Nowhere. There is nothing to retry. When the sources change, the value re-evaluates. If you want to try again without changing anything, the answer will be the same.

Why does runPromise not reject on a fault?

A rejected promise is a computation that ended badly. A fault is a value that is currently unavailable. The promise waits. If the fault never clears, the promise never settles, which is an accurate account of the situation.

import { Affect } from "affect"

Stop running a program for every question

Describe what is true. Observe it. Change a source. It is still true.