Faisceau is a tiny opinionated wrapper around alien-signals that exposes class-based helpers for signals and computed values. It keeps the ergonomics of alien-signals while providing:
SignalandComputedclasses withget,peek, andsethelpers.- Type guards (
isSignal,isComputed) for narrowing. - A safe
peekanduntrackedhelpers implemented viasetActiveSubso side reads do not create subscriptions. - A simple
batchhelper that pairsstartBatch/endBatch. - Full re-export of the underlying alien-signals API so you do not lose low-level access.
pnpm add faisceauimport { batch, computed, effect, signal } from 'faisceau'
const count = signal(0)
const double = computed(() => count.get() * 2)
const status = signal<'idle' | 'dirty'>('idle')
effect(() => {
console.log(
'count is',
count.get(),
'double is',
double.get(),
'status is',
status.peek(), // peek avoids subscribing the effect to status changes
)
})
count.set(1)
console.log(double.get()) // 2
// Update several signals in one go without retriggering the effect for `status`
batch(() => {
status.set('dirty')
count.set(count.get() + 1)
status.set('idle')
})Because Faisceau re-exports everything from alien-signals, you can still import granular primitives when you need them:
import { effect, effectScope, endBatch, setActiveSub, startBatch } from 'faisceau'Creates a Signal instance backed by alien-signals.
get()returns the current value and tracks.peek()returns the current value without tracking (temporarily clears the active subscriber).set(value: T)updates the value.
Creates a Computed instance backed by alien-signals.
get()returns the derived value and tracks dependencies.peek()reads the current value without tracking.
Runtime guards that let TypeScript narrow when working with mixed values.
Runs fn between startBatch and endBatch, letting alien-signals flush subscribers once.
Temporarily clears the active subscriber while running fn, so reads performed
inside the callback do not become dependencies of the currently active
subscriber. Effects created inside the callback are not parented to the active
subscriber.
Example:
import { effect, signal, untracked } from 'faisceau'
const count = signal(0)
effect(() => {
// This effect will not be subscribed to `count` reads done inside `untracked`.
console.log('effect ran')
untracked(() => {
// reading here won't make the outer effect depend on `count`
console.log('peek-like read', count.get())
})
})
count.set(1) // only triggers the inner reads' effects if they were createdReturns the underlying value of a reactive instance, or the value itself if it is not reactive.
Useful when you want to work with the raw value regardless of whether it’s reactive.
Returns a promise that resolves once a reactive-aware condition becomes true. The condition re-evaluates whenever its reactive dependencies change.
Example:
import { signal, when } from 'faisceau'
const ready = signal(false)
when(() => ready.get()).then(() => {
console.log('Ready is true!')
})
// Later
ready.set(true)Returns a promise that resolves when two values become strictly equal (===). Works with reactive and non-reactive values. Throws CannotBecomeEqualError if both values are non-reactive and already different.
Example:
import { signal, when } from 'faisceau'
const ready = signal(false)
when(() => ready.get()).then(() => {
console.log('Ready is true!')
})
// Later
ready.set(true)MIT