@signal-tree/vue

Vue observation for SignalTree.
2,269 6
Install
npm install @signal-tree/vue
Latest Version:15.4.4
Vue:3
License:Apache-2.0
Last Updated:Oct 10, 2026
Links: GitHub  ·  npm
Maintainer: counterreset

@signal-tree/vue

Vue-native SignalTree realization. State remains owned by the framework-neutral kernel; terminal leaves are Vue refs and derived values are computed refs.

Install

npm install @signal-tree/vue

Vue 3.5 or newer is required as a peer dependency.

The canonical v15 model and composition guidance ships with this package as llms.txt.

Use

import { signalTree } from '@signal-tree/vue';

const tree = signalTree(
  { profile: { name: 'Ada' } },
  {
    derived: ($) => ({
      greeting: () => `Hello, ${$.profile.name.value}`,
    }),
  }
);

tree.$.profile.name.value = 'Grace';
console.log(tree.$.greeting.value); // Hello, Grace

The ref can be passed directly to watch(), computed(), or v-model. Object branches and the root $ remain callable whole-value accessors. Vue owns dependency tracking; the kernel remains the only state and write authority.

Initial values and external reactivity

Pass plain initial values to signalTree(). Existing Vue refs, computed refs, and reactive or readonly proxies at the root or a nested branch are rejected with a path-specific error. Ref types are also rejected by TypeScript; proxies require runtime checks because their types can look like ordinary objects.

import { ref } from 'vue';
import { leaf, signalTree } from '@signal-tree/vue';

const existing = ref(1);
const tree = signalTree({ count: leaf(existing.value) });

This takes an independent scalar snapshot. For objects, copy the needed values into plain data; toRaw() alone shares the underlying object and is not an independent copy. leaf(existing) explicitly stores a ref or proxy as data. Its inner mutations remain outside tree transactions and restoration.

Validation stops at explicit leaf(...), marker definitions, arrays and built-in terminal values. Their contents remain data, not separately owned tree locations. Ordinary functions remain valid callable data. These checks apply to initial construction, not arbitrary later writes or foreign reactivity from other libraries.

Ownership and disposal

signalTree() constructs a tree; it does not register scope cleanup. A tree owns runtime resources until destroy() is called. Dropping its last reference is not prompt resource reclamation.

For a component- or composable-owned tree, construct it inside the active Vue scope and register its disposal there:

import { onScopeDispose } from 'vue';
import { signalTree } from '@signal-tree/vue';

// Call synchronously during setup() or inside an active effectScope().
function useCounter() {
  const tree = signalTree({ count: 0 });
  onScopeDispose(() => tree.destroy());
  return tree;
}

Stopping the owning scope destroys the tree. A component that receives a tree through props or inject() only borrows it: do not register destruction in that consumer's scope. Shared application stores belong to the application owner, which can use ordinary provide()/inject() to distribute them and call destroy() after unmounting the application.

For SSR, construct a fresh tree for each request, provide it to that request's application, and destroy it in finally after awaited rendering finishes. Do not rely on component unmount hooks for request cleanup or share a module-level tree between requests. With streaming rendering, wait for completion or abort before destroying the request owner. Construct the client tree from the same initial state used for the server output before hydration.

Reads during grouped writes

SignalTree readers, including previously captured collection projections and readonly views, read current state after each write inside a transaction or restoration callback. Observer delivery stays grouped so subscribers see a coherent result. External native computed() values and effects keep their framework timing; use the SignalTree reader itself when an intermediate read must be fresh. Grouping is scoped to an owning tree, not an atomic transaction across independent trees.

Transaction failures

Read Transaction failure policy and the 15.3.1 failure inventory before combining transactions, undo, or persistence. Explicit rollback refusal leaves state unchanged and the handle pending but releases consequences in v15. The 15.3.1 release commits surviving writes on automatic refusal before a handle returns and still throws. An error does not guarantee undo; never blindly retry the entire operation.

15.3.1 containment applies to deferred write subscribers and transaction turn listeners, not all Vue effects. Vue rethrows watcher errors in development but logs them in production, so only the former can trigger automatic rollback at transaction closure.

Independent editors and connections

Keep shared records in an owned tree. Give each independently closable editor or connection its own lifetime; native local form state may be enough for a draft. A declared EntityMap supports dynamic data membership, not runtime installation of composite slices. Separate trees do not share transactions or undo history. Destroy directly created trees at their ownership boundary.

See the owned sessions guide for the Angular reference demo, stale-save policy, same-ID replacement and cleanup tests. Use this package’s own reactive/lifecycle integration when applying the pattern.

Version History

Version Released License
15.4.4 Apache-2.0
15.4.3 Apache-2.0
15.4.2 Apache-2.0
15.4.0 Apache-2.0
15.3.1 Apache-2.0
Pre-releases (3)
Version Released License
15.0.0-rc.16 Apache-2.0
15.0.0-rc.15 Apache-2.0
15.0.0-rc.14 Apache-2.0