Retree - v1.0.0
    Preparing search index...

    Main entry point for use with Retree package. Exposes utility functions for observing to changes to an object and its children.

    Index

    Constructors

    Methods

    • Clear all listeners for a given node.

      Parameters

      • node: object

        node to clear all listeners for

      • shallow: boolean = true

        when false, will unsubscribe to all child nodes as well.

      Returns void

      Equivalent to calling each unsubscribe function returned by Retree.on.

      Prefer storing and calling the unsubscribe returned from Retree.on when you own a single subscription. Use clearListeners when you own every listener for a node, such as during teardown of a Retree-managed integration.

      const root = Retree.root({ child: { count: 0 } });
      Retree.on(root, "nodeChanged", () => {});
      Retree.on(root.child, "nodeChanged", () => {});

      Retree.clearListeners(root, false); // clears root and child listeners
    • Clone a Retree-managed node into a detached object that can be assigned somewhere else as a new structural child.

      Type Parameters

      • TNode extends object

      Parameters

      • node: TNode

        Existing Retree-managed node to copy.

      Returns TNode

      A detached copy of the node's current raw data.

      Use this when two places need independent state initialized from the same current data. The clone is detached until you assign it into a Retree tree. Mutating the clone after assignment emits for the clone's new structural location, not the source node.

      Do not use clone when the original object should simply move; use Retree.move. Do not use clone for a selected-item pointer; use Retree.link or @link.

      const project = Retree.root({ tasks: [{ title: "Draft" }] });
      const copy = Retree.clone(project.tasks[0]);

      project.tasks.push(copy); // ✅ copy becomes a new child
      project.tasks[1].title = "Published"; // source task is unchanged
    • Create a reactive pointer to an existing Retree-managed node.

      Type Parameters

      • TNode extends object

      Parameters

      • node: TNode

        Existing Retree-managed node to point at.

      Returns RetreeLink<TNode>

      A Retree-managed link object whose current points at node.

      The returned link can be stored in a Retree tree without reparenting the linked target. Replacing link.current emits for the link; mutating the linked target emits from its structural location.

      Use this for selected items, cross-references, and pointers into another part of the same tree. Do not use a link when ownership should move; use Retree.move instead. Do not use a link when the two locations should diverge independently; use Retree.clone instead.

      const root = Retree.root({
      tasks: [{ title: "Docs" }],
      selected: null as RetreeLink<{ title: string }> | null,
      });

      root.selected = Retree.link(root.tasks[0]); // ✅ emits on root
      root.selected.current.title = "Better docs"; // ✅ emits where task is owned
      Retree.parent(root.selected.current) === root.tasks; // true
    • Move an existing Retree-managed node from its current parent to a new parent.

      Type Parameters

      • TNode extends object
      • TValue extends object = TNode

      Parameters

      • node: TNode extends TValue ? TNode : never

        Existing Retree-managed node to move.

      • destination: TValue[]

        Retree-managed array, map, set, or object destination.

      • Optionalkey: number

        Insertion index for arrays, map key for maps, or property key for objects.

      Returns TNode

      The latest reproxy for the moved node.

      Retree is a pure tree: each node has one structural parent. Use move when ownership should transfer from the old parent to the destination. Retree finds the current parent with Retree.parent and removes the node safely before inserting it into the destination.

      Arrays accept an optional numeric insertion index. Maps and plain objects require a key. Sets ignore the key. Do not manually delete the node from its old parent before calling move.

      const workspace = Retree.root({
      todo: [{ title: "Docs" }],
      done: [] as { title: string }[],
      });

      const moved = Retree.move(workspace.todo[0], workspace.done);
      workspace.todo.length; // 0
      workspace.done[0] === moved; // true
    • Move an existing Retree-managed node from its current parent to a new parent.

      Type Parameters

      • TNode extends object
      • TKey = unknown
      • TValue extends object = TNode

      Parameters

      • node: TNode extends TValue ? TNode : never

        Existing Retree-managed node to move.

      • destination: Map<TKey, TValue>

        Retree-managed array, map, set, or object destination.

      • key: TKey

        Insertion index for arrays, map key for maps, or property key for objects.

      Returns TNode

      The latest reproxy for the moved node.

      Retree is a pure tree: each node has one structural parent. Use move when ownership should transfer from the old parent to the destination. Retree finds the current parent with Retree.parent and removes the node safely before inserting it into the destination.

      Arrays accept an optional numeric insertion index. Maps and plain objects require a key. Sets ignore the key. Do not manually delete the node from its old parent before calling move.

      const workspace = Retree.root({
      todo: [{ title: "Docs" }],
      done: [] as { title: string }[],
      });

      const moved = Retree.move(workspace.todo[0], workspace.done);
      workspace.todo.length; // 0
      workspace.done[0] === moved; // true
    • Move an existing Retree-managed node from its current parent to a new parent.

      Type Parameters

      • TNode extends object
      • TValue extends object = TNode

      Parameters

      • node: TNode extends TValue ? TNode : never

        Existing Retree-managed node to move.

      • destination: Set<TValue>

        Retree-managed array, map, set, or object destination.

      Returns TNode

      The latest reproxy for the moved node.

      Retree is a pure tree: each node has one structural parent. Use move when ownership should transfer from the old parent to the destination. Retree finds the current parent with Retree.parent and removes the node safely before inserting it into the destination.

      Arrays accept an optional numeric insertion index. Maps and plain objects require a key. Sets ignore the key. Do not manually delete the node from its old parent before calling move.

      const workspace = Retree.root({
      todo: [{ title: "Docs" }],
      done: [] as { title: string }[],
      });

      const moved = Retree.move(workspace.todo[0], workspace.done);
      workspace.todo.length; // 0
      workspace.done[0] === moved; // true
    • Move an existing Retree-managed node from its current parent to a new parent.

      Type Parameters

      • TNode extends object
      • TDestination extends object = object

      Parameters

      Returns TNode

      The latest reproxy for the moved node.

      Retree is a pure tree: each node has one structural parent. Use move when ownership should transfer from the old parent to the destination. Retree finds the current parent with Retree.parent and removes the node safely before inserting it into the destination.

      Arrays accept an optional numeric insertion index. Maps and plain objects require a key. Sets ignore the key. Do not manually delete the node from its old parent before calling move.

      const workspace = Retree.root({
      todo: [{ title: "Docs" }],
      done: [] as { title: string }[],
      });

      const moved = Retree.move(workspace.todo[0], workspace.done);
      workspace.todo.length; // 0
      workspace.done[0] === moved; // true
    • Listen for changes to a node.

      Type Parameters

      Parameters

      • node: T

        the object to listen for changes to.

      • listenerType: TEvent

        the type of TRetreeEvents change events to listen to.

      • callback: TEvent extends TRetreeChangedEvents ? (reproxiedNode: T) => void : () => void

        the callback function for your listener.

      Returns () => void

      an unsubscribe function to clean up your listeners.

      Use nodeChanged for changes directly owned by the node. Use treeChanged for changes to the node or descendants. Use nodeRemoved for when this node is removed from its parent.

      // Create the root node
      const counter = Retree.root({ count: 0 });
      // Listen for changes to values of the node
      const unsubscribe = Retree.on(counter, "nodeChanged", (reproxy) => {
      console.log(reproxy !== counter); // output: false
      console.log(reproxy.count === counter.count); // output: true
      });
      // Make a change
      counter.count = counter.count + 1;
      // Stop listening for changes
      unsubscribe();
    • Get a parent node for a given child node, if it exists

      Parameters

      • node: object

        a child node to get the parent of

      Returns object | null

      the parent node if it exists, otherwise null

      const tree = Retree.root({
      count: 0,
      child: {
      count: 0,
      child: {
      count: 0,
      },
      },
      });
      function recursiveLog(node) {
      console.log(node.count);
      // Get the parent of node, if it exists
      const parent = Retree.parent(node);
      if (!parent) return; // at top of tree
      recursiveLog(parent);
      }
      Retree.on(tree.child.child, "nodeChanged", (child) => {
      // Recursively log the count of this node and all its parents
      recursiveLog(child);
      });
      tree.child.child.count = 1;
    • Builds a Retree compatible root node for the root object of your tree.

      Type Parameters

      • T extends object = object

      Parameters

      • object: T

        a root TreeNode for your tree

      Returns T

      a Retree compatible object of type T

      Use this once where plain state enters Retree. The returned proxy is compatible with Retree.on, Retree.parent, Retree.move, Retree.link, and React hooks from @retreejs/react.

      Do mutate the returned tree directly with normal JavaScript assignment and collection methods. Do not call Retree.root(...) on every child you assign into the tree; Retree prepares children as they are attached or read.

      const counter = Retree.root({ count: 0 });
      Retree.on(counter, "nodeChanged", () => console.log(counter.count));
      counter.count = counter.count + 1;
    • Run a synchronous transaction that will not cause `Retree.on listeners to emit.

      Parameters

      • transaction: () => void

        transaction function to run

      • skipReproxy: boolean = true

        skip reproxying nodes such that subsequent comparisons are equal. defaults to true.

      Returns void

      Use runSilent for non-rendered bookkeeping or integration state that should update without notifying Retree listeners. By default it also skips reproxying, so old and new object identities stay equal for later comparison checks.

      Pass skipReproxy = false when you want to suppress listener emission but still refresh reproxy identities.

      const state = Retree.root({ rendered: 0, telemetry: 0 });
      Retree.on(state, "nodeChanged", () => console.log("render"));

      Retree.runSilent(() => {
      state.telemetry += 1;
      }); // ❌ no listener emit

      state.rendered += 1; // ✅ emits
    • Run a synchronous transaction that will not cause Retree.on listeners for changed nodes to emit multiple times.

      Parameters

      • transaction: () => void

        transaction function to run

      Returns void

      If multiple nodes changed during the transaction, Retree.on events will be emitted for each node that changed. If using React, this should still be flattened to a single render, but it is not guaranteed. It may be reasonable to combine this with React.startTransition if this is a concern.

      const counter = Retree.root({ count: 0 });
      Retree.on(counter, "nodeChanged", () => console.log(counter.count));
      // Will only emit "nodeChanged" once
      Retree.runTransaction(() => {
      counter.count = counter.count + 1;
      counter.count = counter.count * 2;
      });
    • Subscribe to a derived value from any Retree-managed node.

      Type Parameters

      • TNode extends object
      • TSelected

      Parameters

      • node: TNode

        Retree-managed node to observe.

      • selector: RetreeSelectSelector<TNode, TSelected>

        Function that reads a selected value or dependency list from the latest reproxy.

      • callback: (next: TSelected, previous: TSelected) => void

        Called only when the selected value or dependency list changes.

      • Optionaloptions: RetreeSelectOptions<TSelected>

        Optional listener type and equality comparison for the whole selected value or tuple.

      Returns () => void

      Unsubscribe function.

      select recomputes the selected value when the observed node or selected reactive dependencies emit, then calls callback only when the selection changes. Selectors may return one value or an ordered dependency list. Reactive entries in a dependency list are subscribed to; primitive and plain entries are compared by identity.

      Dependency-list subscriptions are observational: if a selected dependency emits, select calls your callback when the selection changes, but it does not force the node passed to select to receive a fresh reproxy. Use @select when a ReactiveNode owner should emit nodeChanged. This is a subscription primitive, not a memo cache: use memo / fnMemo to cache computation, and select to narrow notifications.

      By default select listens to nodeChanged, which is correct when the selector reads fields directly owned by node. Pass listenerType: "treeChanged" when the selector intentionally reads descendants that are not included as reactive entries in a dependency list.

      You can also call Retree.select(() => value, callback) without a node. That form traps reads automatically. Whole Retree-managed values are subscribed to broadly. Property reads subscribe to the owner node but compare the specific property value, so task.done can react to task replacement or done changes without reacting to unrelated task fields. Primitive reads are kept as comparison values, so the callback only runs when the trapped reads make the selected value or dependency set change.

      const project = Retree.root({
      tasks: [{ done: false }, { done: true }],
      });

      const unsubscribe = Retree.select(
      project.tasks,
      (tasks) => tasks.filter((task) => task.done).length,
      (next, previous) => console.log({ next, previous }),
      { listenerType: "treeChanged" }
      );

      project.tasks[0].done = true; // ✅ callback: 1 -> 2
      project.tasks[0].done = true; // ❌ selected value did not change
      unsubscribe();
      Retree.select(
      row,
      (self) => [self.attributes, self.attributeId, self.attribute],
      ([, , attribute]) => console.log(attribute)
      );
      Retree.select(
      () => project.tasks.filter((task) => task.done).length,
      (doneCount) => console.log(doneCount)
      );
    • Subscribe to a derived value from any Retree-managed node.

      Type Parameters

      • TSelector extends RetreeTrackedSelectSelector<unknown>

      Parameters

      • selector: TSelector

        Function that reads a selected value or dependency list from the latest reproxy.

      • callback: (next: ReturnType<TSelector>, previous: ReturnType<TSelector>) => void

        Called only when the selected value or dependency list changes.

      • Optionaloptions: RetreeSelectOptions<ReturnType<TSelector>>

        Optional listener type and equality comparison for the whole selected value or tuple.

      Returns () => void

      Unsubscribe function.

      select recomputes the selected value when the observed node or selected reactive dependencies emit, then calls callback only when the selection changes. Selectors may return one value or an ordered dependency list. Reactive entries in a dependency list are subscribed to; primitive and plain entries are compared by identity.

      Dependency-list subscriptions are observational: if a selected dependency emits, select calls your callback when the selection changes, but it does not force the node passed to select to receive a fresh reproxy. Use @select when a ReactiveNode owner should emit nodeChanged. This is a subscription primitive, not a memo cache: use memo / fnMemo to cache computation, and select to narrow notifications.

      By default select listens to nodeChanged, which is correct when the selector reads fields directly owned by node. Pass listenerType: "treeChanged" when the selector intentionally reads descendants that are not included as reactive entries in a dependency list.

      You can also call Retree.select(() => value, callback) without a node. That form traps reads automatically. Whole Retree-managed values are subscribed to broadly. Property reads subscribe to the owner node but compare the specific property value, so task.done can react to task replacement or done changes without reacting to unrelated task fields. Primitive reads are kept as comparison values, so the callback only runs when the trapped reads make the selected value or dependency set change.

      const project = Retree.root({
      tasks: [{ done: false }, { done: true }],
      });

      const unsubscribe = Retree.select(
      project.tasks,
      (tasks) => tasks.filter((task) => task.done).length,
      (next, previous) => console.log({ next, previous }),
      { listenerType: "treeChanged" }
      );

      project.tasks[0].done = true; // ✅ callback: 1 -> 2
      project.tasks[0].done = true; // ❌ selected value did not change
      unsubscribe();
      Retree.select(
      row,
      (self) => [self.attributes, self.attributeId, self.attribute],
      ([, , attribute]) => console.log(attribute)
      );
      Retree.select(
      () => project.tasks.filter((task) => task.done).length,
      (doneCount) => console.log(doneCount)
      );
    • Type Parameters

      • T extends object = object

      Parameters

      • object: T

      Returns T

      Use root instead.

      const state = Retree.use({ count: 0 }); // deprecated
      const state = Retree.root({ count: 0 }); // preferred