Create a new SignalArray with an optional initial value.
Log every mutation call on a SignalArray at Debug level, tagged
under the stax.signal subsystem, with the call site captured at
the caller's frame.
The write-side counterpart to Readable.debug, for SignalArray.
Wraps every mutation method (push, pop, unshift, shift,
splice, insertAt, removeAt, replaceAt, modifyAt, remove,
move, swap, sort, reverse, clear, and the inherited
Signal.set / Signal.update). Zero cost at the default log level
— the check runs once at construction and returns the underlying
signal unwrapped if Debug isn't enabled.
Unlike Signal.trace (which logs from/to), collection tracing
logs the method name and its arguments — a full snapshot of the
array on every write would be expensive and rarely what you want.
Pair with Readable.debug if you also want a "value changed" line.
Configure a custom equality function for a Signal. The equality function determines when updates are skipped (if values are "equal").
Use an existing Signal if provided, otherwise create a new one with the default value. This enables the controlled/uncontrolled component pattern.
// In a component that supports both controlled and uncontrolled modes:
const value = yield* Signal.fromNullable(props.value, props.defaultValue ?? "");
// If props.value is a Signal, it will be used directly
// If props.value is undefined, a new Signal is created with defaultValue
// With custom equality:
const value = yield* Signal.fromNullable(props.value, defaultUser).pipe(
Signal.equals((a, b) => a.id === b.id)
);
Create a Signal from a reactive value (Signal, Readable, or plain value).
This is useful for controlled/uncontrolled component patterns where a prop can be either a Signal (controlled), a Readable, or a plain value (uncontrolled).
// In a component that accepts flexible input:
interface CheckboxProps {
checked?: Signal<boolean> | Readable<boolean> | boolean;
defaultChecked?: boolean;
}
const Checkbox = (props: CheckboxProps) =>
Effect.gen(function* () {
// Works with Signal (controlled), Readable, or boolean (uncontrolled)
const checked = yield* Signal.fromReactive(
props.checked,
props.defaultChecked ?? false
);
// With custom equality:
const user = yield* Signal.fromReactive(props.user, defaultUser).pipe(
Signal.equals((a, b) => a.id === b.id)
);
});
Check if a value is a Signal.
Create a new Signal with an initial value.
Create a reactive Map with in-place mutation methods.
Check if a value is a SignalMap.
Create a new SignalMap with optional initial entries.
Log every mutation call on a SignalMap at Debug level, tagged under
the stax.signal subsystem, with the call site captured at the
caller's frame.
The write-side counterpart to Readable.debug, for SignalMap. Wraps
set, delete, clear, replace, update, and modifyAt so each
call emits { id, args, callSite }. Zero cost at the default log
level — the check runs once at construction and returns the
underlying signal unwrapped if Debug isn't enabled.
Unlike Signal.trace (which logs from/to), collection tracing
logs the method name and its arguments — a full snapshot of the map
on every write would be expensive and rarely what you want. Pair
with Readable.debug if you also want a "value changed" line.
Create a reactive Set with in-place mutation methods.
Check if a value is a SignalSet.
Create a new SignalSet with optional initial values.
Log every mutation call on a SignalSet at Debug level, tagged under
the stax.signal subsystem, with the call site captured at the
caller's frame.
The write-side counterpart to Readable.debug, for SignalSet. Wraps
add, delete, toggle, clear, replace, and update so each
call emits { id, args, callSite }. Zero cost at the default log
level — the check runs once at construction and returns the
underlying signal unwrapped if Debug isn't enabled.
Create a reactive struct with fixed keys, where each key is accessible as a Signal.
Check if a value is a SignalStruct.
Create a new SignalStruct with an initial value.
Log every struct-level mutation call on a SignalStruct at Debug
level, tagged under the stax.signal subsystem, with the call site
captured at the caller's frame.
The write-side counterpart to Readable.debug, for SignalStruct.
Wraps update and replace — the struct-level mutations. Field
signals (struct.name.set(...) etc.) are plain Signals and can be
traced individually with Signal.trace if needed.
Zero cost at the default log level — the check runs once at construction and returns the underlying signal unwrapped if Debug isn't enabled.
Log every set and update call on a Signal at Debug level, tagged
under the stax.signal subsystem, with the call site captured at the
caller's frame.
A pipeable pass-through observer — mirrors Readable.debug
(which covers reads) for the write side. Zero cost at the default
log level: the pipe reads the current minimum log level once at
construction, and if Debug isn't enabled it returns the underlying
Signal unwrapped — no proxy set/update, no new Error() per
write, no branch.
Each emitted event carries the label, the previous value, the value
being written, and the stack trace at the write's call site (so you
can answer "where in the code did this update come from"). Source
maps make the stack readable in browser devtools automatically; in
Node, run with --enable-source-maps.
const count = yield* Signal.make(0).pipe(Signal.trace("count"));
yield* count.set(1);
// Debug logs:
// stax.signal "write" { id: "count", from: 0, to: 1, callSite: "Error\n at ..." }
update's reducer function runs once (to compute the value we log),
then the result is applied via the underlying set — so non-pure
reducers don't double-fire. Trades the atomic read-modify-write
semantics of signal.update for observability; if you need the
atomicity, don't pipe trace on that signal.
The log-level check is captured at pipe time — a later
Logger.withMinimumLogLevel won't toggle an already-piped signal.
That matches how tracing is usually enabled (once, at startup) and
keeps the fast path branchless.
Create a reactive array with in-place mutation methods.