stax-monorepo
    Preparing search index...

    Variable Signal

    Signal: {
        Array: {
            make: <T>(
                initial?: readonly T[],
            ) => Effect<SignalArray<T>, never, Scope>;
            OutOfBoundsError: typeof OutOfBoundsError;
            trace: (
                id: string,
            ) => <T>(
                self: Effect<SignalArray<T>, never, Scope>,
            ) => Effect<SignalArray<T>, never, Scope>;
        };
        equals: {
            <A>(
                f: (a: A, b: A) => boolean,
            ): (
                self: Effect<Signal<A>, never, Scope>,
            ) => Effect<Signal<A>, never, Scope>;
            <A>(
                self: Effect<Signal<A>, never, Scope>,
                f: (a: A, b: A) => boolean,
            ): Effect<Signal<A>, never, Scope>;
        };
        fromNullable: <A>(
            existing: Signal<A> | undefined,
            defaultValue: A,
        ) => Effect<Signal<A>, never, Scope>;
        fromReactive: <A>(
            value: A | Signal<A> | Readable.Readable<A> | undefined,
            defaultValue: A,
        ) => Effect<Signal<A>, never, Scope>;
        isSignal: (value: unknown) => value is Signal<unknown>;
        make: <A>(initial: A) => Effect<Signal<A>, never, Scope>;
        Map: {
            isSignalMap: (value: unknown) => value is SignalMap<unknown, unknown>;
            KeyNotFoundError: typeof KeyNotFoundError;
            make: <K, V>(
                initial?: ReadonlyMap<K, V> | Iterable<readonly [K, V], any, any>,
            ) => Effect<SignalMap<K, V>, never, Scope>;
            SignalMapTypeId: symbol;
            trace: (
                id: string,
            ) => <K, V>(
                self: Effect<SignalMap<K, V>, never, Scope>,
            ) => Effect<SignalMap<K, V>, never, Scope>;
        };
        Set: {
            isSignalSet: (value: unknown) => value is SignalSet<unknown>;
            make: <T>(
                initial?: ReadonlySet<T> | Iterable<T, any, any>,
            ) => Effect<SignalSet<T>, never, Scope>;
            SignalSetTypeId: symbol;
            trace: (
                id: string,
            ) => <T>(
                self: Effect<SignalSet<T>, never, Scope>,
            ) => Effect<SignalSet<T>, never, Scope>;
        };
        SignalRegistry: typeof SignalRegistry;
        SignalTypeId: symbol;
        Struct: {
            isSignalStruct: (
                value: unknown,
            ) => value is SignalStruct<Record<string, unknown>>;
            make: <T extends Record<string, unknown>>(
                initial: T,
            ) => Effect<SignalStruct<T>, never, Scope>;
            SignalStructTypeId: symbol;
            trace: (
                id: string,
            ) => <T extends Record<string, unknown>>(
                self: Effect<SignalStruct<T>, never, Scope>,
            ) => Effect<SignalStruct<T>, never, Scope>;
        };
        trace: (
            id: string,
        ) => <A>(
            self: Effect<Signal<A>, never, Scope>,
        ) => Effect<Signal<A>, never, Scope>;
    }

    Type Declaration

    • Array: {
          make: <T>(
              initial?: readonly T[],
          ) => Effect<SignalArray<T>, never, Scope>;
          OutOfBoundsError: typeof OutOfBoundsError;
          trace: (
              id: string,
          ) => <T>(
              self: Effect<SignalArray<T>, never, Scope>,
          ) => Effect<SignalArray<T>, never, Scope>;
      }

      Create a reactive array with in-place mutation methods.

      SignalArray

      • make: <T>(initial?: readonly T[]) => Effect<SignalArray<T>, never, Scope>

        Create a new SignalArray with an optional initial value.

      • OutOfBoundsError: typeof OutOfBoundsError
      • trace: (
            id: string,
        ) => <T>(
            self: Effect<SignalArray<T>, never, Scope>,
        ) => Effect<SignalArray<T>, never, Scope>

        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.

        const todos = yield* Signal.Array.make<Todo>().pipe(
        Signal.Array.trace("todos"),
        );
        yield* todos.push({ id: 1, text: "buy milk" });
        // Debug logs:
        // stax.signal "push" { id: "todos", args: [{...}], callSite: "..." }
    • equals: {
          <A>(
              f: (a: A, b: A) => boolean,
          ): (
              self: Effect<Signal<A>, never, Scope>,
          ) => Effect<Signal<A>, never, Scope>;
          <A>(
              self: Effect<Signal<A>, never, Scope>,
              f: (a: A, b: A) => boolean,
          ): Effect<Signal<A>, never, Scope>;
      }

      Configure a custom equality function for a Signal. The equality function determines when updates are skipped (if values are "equal").

      // Compare users by id only
      const user = yield* Signal.make({ id: 1, name: "John" }).pipe(
      Signal.equals((a, b) => a.id === b.id)
      );

      // This won't trigger an update (same id)
      yield* user.set({ id: 1, name: "Johnny" });
    • fromNullable: <A>(
          existing: Signal<A> | undefined,
          defaultValue: A,
      ) => Effect<Signal<A>, never, Scope>

      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)
      );
    • fromReactive: <A>(
          value: A | Signal<A> | Readable.Readable<A> | undefined,
          defaultValue: A,
      ) => Effect<Signal<A>, never, Scope>

      Create a Signal from a reactive value (Signal, Readable, or plain value).

      • If input is already a Signal, returns it as-is
      • If input is a Readable, creates a new Signal initialized with the Readable's current value
      • If input is a plain value, creates a new Signal with that 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)
      );
      });
    • isSignal: (value: unknown) => value is Signal<unknown>

      Check if a value is a Signal.

    • make: <A>(initial: A) => Effect<Signal<A>, never, Scope>

      Create a new Signal with an initial value.

      // Basic usage
      const counter = yield* Signal.make(0);

      // With custom equality (pipeable)
      const user = yield* Signal.make({ id: 1, name: "John" }).pipe(
      Signal.equals((a, b) => a.id === b.id)
      );
    • Map: {
          isSignalMap: (value: unknown) => value is SignalMap<unknown, unknown>;
          KeyNotFoundError: typeof KeyNotFoundError;
          make: <K, V>(
              initial?: ReadonlyMap<K, V> | Iterable<readonly [K, V], any, any>,
          ) => Effect<SignalMap<K, V>, never, Scope>;
          SignalMapTypeId: symbol;
          trace: (
              id: string,
          ) => <K, V>(
              self: Effect<SignalMap<K, V>, never, Scope>,
          ) => Effect<SignalMap<K, V>, never, Scope>;
      }

      Create a reactive Map with in-place mutation methods.

      SignalMap

      • isSignalMap: (value: unknown) => value is SignalMap<unknown, unknown>

        Check if a value is a SignalMap.

      • KeyNotFoundError: typeof KeyNotFoundError
      • make: <K, V>(
            initial?: ReadonlyMap<K, V> | Iterable<readonly [K, V], any, any>,
        ) => Effect<SignalMap<K, V>, never, Scope>

        Create a new SignalMap with optional initial entries.

      • SignalMapTypeId: symbol
      • trace: (
            id: string,
        ) => <K, V>(
            self: Effect<SignalMap<K, V>, never, Scope>,
        ) => Effect<SignalMap<K, V>, never, Scope>

        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.

        const users = yield* Signal.Map.make<string, User>().pipe(
        Signal.Map.trace("users"),
        );
        yield* users.set("ada", { name: "Ada" });
        // Debug logs:
        // stax.signal "set" { id: "users", args: ["ada", {...}], callSite: "..." }
    • Set: {
          isSignalSet: (value: unknown) => value is SignalSet<unknown>;
          make: <T>(
              initial?: ReadonlySet<T> | Iterable<T, any, any>,
          ) => Effect<SignalSet<T>, never, Scope>;
          SignalSetTypeId: symbol;
          trace: (
              id: string,
          ) => <T>(
              self: Effect<SignalSet<T>, never, Scope>,
          ) => Effect<SignalSet<T>, never, Scope>;
      }

      Create a reactive Set with in-place mutation methods.

      SignalSet

      • isSignalSet: (value: unknown) => value is SignalSet<unknown>

        Check if a value is a SignalSet.

      • make: <T>(
            initial?: ReadonlySet<T> | Iterable<T, any, any>,
        ) => Effect<SignalSet<T>, never, Scope>

        Create a new SignalSet with optional initial values.

      • SignalSetTypeId: symbol
      • trace: (
            id: string,
        ) => <T>(
            self: Effect<SignalSet<T>, never, Scope>,
        ) => Effect<SignalSet<T>, never, Scope>

        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.

        const tags = yield* Signal.Set.make<string>().pipe(Signal.Set.trace("tags"));
        yield* tags.add("urgent");
        // Debug logs:
        // stax.signal "add" { id: "tags", args: ["urgent"], callSite: "..." }
    • SignalRegistry: typeof SignalRegistry
    • SignalTypeId: symbol
    • Struct: {
          isSignalStruct: (
              value: unknown,
          ) => value is SignalStruct<Record<string, unknown>>;
          make: <T extends Record<string, unknown>>(
              initial: T,
          ) => Effect<SignalStruct<T>, never, Scope>;
          SignalStructTypeId: symbol;
          trace: (
              id: string,
          ) => <T extends Record<string, unknown>>(
              self: Effect<SignalStruct<T>, never, Scope>,
          ) => Effect<SignalStruct<T>, never, Scope>;
      }

      Create a reactive struct with fixed keys, where each key is accessible as a Signal.

      SignalStruct

      • isSignalStruct: (value: unknown) => value is SignalStruct<Record<string, unknown>>

        Check if a value is a SignalStruct.

      • make: <T extends Record<string, unknown>>(
            initial: T,
        ) => Effect<SignalStruct<T>, never, Scope>

        Create a new SignalStruct with an initial value.

      • SignalStructTypeId: symbol
      • trace: (
            id: string,
        ) => <T extends Record<string, unknown>>(
            self: Effect<SignalStruct<T>, never, Scope>,
        ) => Effect<SignalStruct<T>, never, Scope>

        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.

        const user = yield* Signal.Struct.make({
        name: "Ada",
        email: "a@b.co",
        }).pipe(Signal.Struct.trace("user"));
        yield* user.update({ name: "Ada L." });
        // Debug logs:
        // stax.signal "update" { id: "user", args: [{...}], callSite: "..." }
    • trace: (
          id: string,
      ) => <A>(
          self: Effect<Signal<A>, never, Scope>,
      ) => Effect<Signal<A>, never, Scope>

      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.