Fork of https://github.com/CCherry07/alien-deepsignals ported to alien-signals v2 with support for per-value modification tracking.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
Laurin Weger 58e5e96fa3
major refactor: Use alien-signals v2, support for watching patches
2 weeks ago
src major refactor: Use alien-signals v2, support for watching patches 2 weeks ago
.gitignore major refactor: Use alien-signals v2, support for watching patches 2 weeks ago
README.md major refactor: Use alien-signals v2, support for watching patches 2 weeks ago
package.json major refactor: Use alien-signals v2, support for watching patches 2 weeks ago
pnpm-lock.yaml major refactor: Use alien-signals v2, support for watching patches 2 weeks ago
tsconfig.json feat: update signals version 8 months ago
tsup.config.ts feat: update signals version 8 months ago

README.md

🧶 AlienDeepSignals

Use alien-signals with the interface of a plain JavaScript object.

  • DeepSignal works by wrapping the object with a Proxy that intercepts all property accesses and returns the signal value by default.
  • This allows you to easily create a deep object that can be observed for changes, while still being able to mutate the object normally.
  • Nested objects and arrays are also converted to deep signal objects/arrays, allowing you to create fully reactive data structures.
  • The $ prefix returns the signal instance: state.$prop.

Credits

Features

  • Transparent: deepsignal wraps the object with a proxy that intercepts all property accesses, but does not modify how you interact with the object. This means that you can still use the object as you normally would, and it will behave exactly as you would expect, except that mutating the object also updates the value of the underlying signals.
  • Tiny (less than 1kB): deepsignal is designed to be lightweight and has a minimal footprint, making it easy to include in your projects. It's just a small wrapper around alien-signals.
  • Full array support: deepsignal fully supports arrays, including nested arrays.
  • Deep: deepsignal converts nested objects and arrays to deep signal objects/arrays, allowing you to create fully reactive data structures.
  • Lazy initialization: deepsignal uses lazy initialization, which means that signals and proxies are only created when they are accessed for the first time. This reduces the initialization time to almost zero and improves the overall performance in cases where you only need to observe a small subset of the object's properties.
  • Stable references: deepsignal uses stable references, which means that the same Proxy instances will be returned for the same objects so they can exist in different places of the data structure, just like regular JavaScript objects.
  • Automatic derived state: getters are automatically converted to computeds instead of signals.
  • TypeScript support: deepsignal is written in TypeScript and includes type definitions, so you can use it seamlessly with your TypeScript projects, including access to the signal value through the prefix state.$prop.
  • State management: deepsignal can be used as a state manager, including state and actions in the same object.

The most important feature is that it just works. You don't need to do anything special. Just create an object, mutate it normally and all your components will know when they need to rerender.

Installation

npm install alien-deepsignals

Usage

import { deepSignal } from 'alien-deepsignals';
const state = deepSignal({
  count: 0,
  name: 'John',
  nested: {
    deep: 'value',
  },
  array: [1, 2, 3],
});
state.count++;
state.$nested.value.deep = 'new value';
state.$array.value.push(4);

watch

import { deepSignal, watch } from 'alien-deepsignals';
const state = deepSignal({
  count: 0,
  name: 'John',
  nested: {
    deep: 'value',
  },
  array: [1, 2, 3],
});

watch(state,(value)=>{
  console.log(value);
},{
  deep: true,
  immediate: true,
  // once
})

Advanced watching & patch stream

There are two layers:

  1. watch() (high-level) – subscribe to value changes of signals, deepSignal objects, getters, or arrays of them. Supports options:
    • immediate (fire once right away)
    • deep (traverse nested properties to collect deps)
    • once (auto-dispose after first emission)
    • patchOptimized (when deep + deepSignal, skip full traversal and rely on internal mutation patches).
  2. Patch stream (low-level) – internal subscribeDeepMutations() used by patchOptimized and exposed via new helpers below.

patchOptimized

Deep watches normally trigger a full recursive traversal to register dependencies, which can be expensive for large trees. With patchOptimized: true, a hidden version counter signal is incremented only when a relevant deep mutation patch batch is emitted. That means no repeated deep traversal per change—performance scales with number of actual mutations instead of tree size.

watch(state, (val) => {
  render(val)
}, { deep: true, patchOptimized: true })

New helper APIs (optional sugar)

import { watchPatches, observe } from 'alien-deepsignals'

// 1. watchPatches: directly receive deep mutation patch batches for a deepSignal root
const stop = watchPatches(state, (patches) => {
  for (const p of patches) {
    console.log(p.type, p.path.join('.'), p.value)
  }
})

// 2. observe: unified API for value or patch modes
// value mode (essentially watch())
const offValue = observe(state, (val, old) => {
  console.log('value changed', old, '=>', val)
}, { mode: 'value', deep: true, patchOptimized: true })

// patch mode (delegates to watchPatches)
const offPatches = observe(state, (patches) => {
  console.log('patch batch', patches)
}, { mode: 'patch' })

Modes summary:

API Emits Use-case
watch / observe(..., {mode:'value'}) New value + old value Derive computations, side effects
watchPatches / observe(..., {mode:'patch'}) Array of {root,type,path,value} Sync external stores/UI diff

In patch mode only structural mutations trigger callbacks; reads do not cause traversal.