Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@nerdalytics/beacon

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nerdalytics/beacon - npm Package Compare versions

Comparing version
1000.2.5
to
1000.3.0
+14
-13
dist/src/index.d.ts

@@ -1,4 +0,4 @@

export type Unsubscribe = () => void;
export type ReadOnlyState<T> = () => T;
export interface WriteableState<T> {
type Unsubscribe = () => void;
type ReadOnlyState<T> = () => T;
interface WriteableState<T> {
set(value: T): void;

@@ -8,13 +8,14 @@ update(fn: (value: T) => T): void;

declare const STATE_ID: unique symbol;
export type State<T> = ReadOnlyState<T> & WriteableState<T> & {
type State<T> = ReadOnlyState<T> & WriteableState<T> & {
[STATE_ID]?: symbol;
};
export declare const state: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => State<T>;
export declare const effect: (fn: () => void) => Unsubscribe;
export declare const batch: <T>(fn: () => T) => T;
export declare const derive: <T>(computeFn: () => T) => ReadOnlyState<T>;
export declare const select: <T, R>(source: ReadOnlyState<T>, selectorFn: (state: T) => R, equalityFn?: (a: R, b: R) => boolean) => ReadOnlyState<R>;
export declare const readonlyState: <T>(state: State<T>) => ReadOnlyState<T>;
export declare const protectedState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => [ReadOnlyState<T>, WriteableState<T>];
export declare const lens: <T, K>(source: State<T>, accessor: (state: T) => K) => State<K>;
export {};
declare const createState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => State<T>;
declare const createEffect: (fn: () => void) => Unsubscribe;
declare const executeBatch: <T>(fn: () => T) => T;
declare const createDerive: <T>(computeFn: () => T) => ReadOnlyState<T>;
declare const createSelect: <T, R>(source: ReadOnlyState<T>, selectorFn: (state: T) => R, equalityFn?: (a: R, b: R) => boolean) => ReadOnlyState<R>;
declare const createLens: <T, K>(source: State<T>, accessor: (state: T) => K) => State<K>;
declare const createReadonlyState: <T>(source: State<T>) => ReadOnlyState<T>;
declare const createProtectedState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => [ReadOnlyState<T>, WriteableState<T>];
export { createDerive as derive, createEffect as effect, createLens as lens, createProtectedState as protectedState, createReadonlyState as readonlyState, createSelect as select, createState as state, executeBatch as batch, };
export type { ReadOnlyState, State, Unsubscribe, WriteableState };

@@ -1,1 +0,1 @@

let i=Symbol("STATE_ID"),a=(e,t=Object.is)=>l.createState(e,t);var e=e=>l.createEffect(e),t=e=>l.executeBatch(e),r=e=>l.createDerive(e),s=(e,t,r=Object.is)=>l.createSelect(e,t,r);let c=e=>()=>e();var n=(e,t=Object.is)=>{let r=a(e,t);return[()=>c(r)(),{set:e=>r.set(e),update:e=>r.update(e)}]},u=(e,t)=>l.createLens(e,t);class l{static currentSubscriber=null;static pendingSubscribers=new Set;static isNotifying=!1;static batchDepth=0;static deferredEffectCreations=[];static activeSubscribers=new Set;static stateTracking=new WeakMap;static subscriberDependencies=new WeakMap;static parentSubscriber=new WeakMap;static childSubscribers=new WeakMap;value;subscribers=new Set;stateId=Symbol();equalityFn;constructor(e,t=Object.is){this.value=e,this.equalityFn=t}static createState=(e,t=Object.is)=>{let r=new l(e,t);e=()=>r.get();return e.set=e=>r.set(e),e.update=e=>r.update(e),e[i]=r.stateId,e};get=()=>{var r=l.currentSubscriber;if(r){this.subscribers.add(r);let e=l.subscriberDependencies.get(r),t=(e||(e=new Set,l.subscriberDependencies.set(r,e)),e.add(this.subscribers),l.stateTracking.get(r));t||(t=new Set,l.stateTracking.set(r,t)),t.add(this.stateId)}return this.value};set=e=>{if(!this.equalityFn(this.value,e)){var t=l.currentSubscriber;if(t)if(l.stateTracking.get(t)?.has(this.stateId)&&!l.parentSubscriber.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(this.value=e,0!==this.subscribers.size){for(var r of this.subscribers)l.pendingSubscribers.add(r);0!==l.batchDepth||l.isNotifying||l.notifySubscribers()}}};update=e=>{this.set(e(this.value))};static createEffect=e=>{let r=()=>{if(!l.activeSubscribers.has(r)){l.activeSubscribers.add(r);var t=l.currentSubscriber;try{if(l.cleanupEffect(r),l.currentSubscriber=r,l.stateTracking.set(r,new Set),t){l.parentSubscriber.set(r,t);let e=l.childSubscribers.get(t);e||(e=new Set,l.childSubscribers.set(t,e)),e.add(r)}e()}finally{l.currentSubscriber=t,l.activeSubscribers.delete(r)}}};if(0===l.batchDepth)r();else{if(l.currentSubscriber){var t=l.currentSubscriber;l.parentSubscriber.set(r,t);let e=l.childSubscribers.get(t);e||(e=new Set,l.childSubscribers.set(t,e)),e.add(r)}l.deferredEffectCreations.push(r)}return()=>{l.cleanupEffect(r),l.pendingSubscribers.delete(r),l.activeSubscribers.delete(r),l.stateTracking.delete(r);var e=l.parentSubscriber.get(r),e=(e&&(e=l.childSubscribers.get(e))&&e.delete(r),l.parentSubscriber.delete(r),l.childSubscribers.get(r));if(e){for(var t of e)l.cleanupEffect(t);e.clear(),l.childSubscribers.delete(r)}}};static executeBatch=e=>{l.batchDepth++;try{return e()}catch(e){throw 1===l.batchDepth&&(l.pendingSubscribers.clear(),l.deferredEffectCreations.length=0),e}finally{if(l.batchDepth--,0===l.batchDepth){if(0<l.deferredEffectCreations.length){var t,e=l.deferredEffectCreations;l.deferredEffectCreations=[];for(t of e)t()}0<l.pendingSubscribers.size&&!l.isNotifying&&l.notifySubscribers()}}};static createDerive=e=>{let t={cachedValue:void 0,computeFn:e,initialized:!1,valueState:l.createState(void 0)};return l.createEffect(function(){var e=t.computeFn();t.initialized&&Object.is(t.cachedValue,e)||(t.cachedValue=e,t.valueState.set(e)),t.initialized=!0}),function(){return t.initialized||(t.cachedValue=t.computeFn(),t.initialized=!0,t.valueState.set(t.cachedValue)),t.valueState()}};static createSelect=(e,t,r=Object.is)=>{let i={equalityFn:r,initialized:!1,lastSelectedValue:void 0,lastSourceValue:void 0,selectorFn:t,source:e,valueState:l.createState(void 0)};return l.createEffect(function(){var e=i.source();i.initialized&&Object.is(i.lastSourceValue,e)||(i.lastSourceValue=e,e=i.selectorFn(e),i.initialized&&void 0!==i.lastSelectedValue&&i.equalityFn(i.lastSelectedValue,e))||(i.lastSelectedValue=e,i.valueState.set(e),i.initialized=!0)}),function(){return i.initialized||(i.lastSourceValue=i.source(),i.lastSelectedValue=i.selectorFn(i.lastSourceValue),i.valueState.set(i.lastSelectedValue),i.initialized=!0),i.valueState()}};static createLens=(e,t)=>{let a={accessor:t,isUpdating:!1,lensState:null,originalSet:null,path:[],source:e};return a.path=(()=>{let r=[],i=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||r.push(t),i)});try{a.accessor(i)}catch{}return r})(),a.lensState=l.createState(a.accessor(a.source())),a.originalSet=a.lensState.set,l.createEffect(function(){if(!a.isUpdating){a.isUpdating=!0;try{a.lensState.set(a.accessor(a.source()))}finally{a.isUpdating=!1}}}),a.lensState.set=function(t){if(!a.isUpdating){a.isUpdating=!0;try{a.originalSet(t),a.source.update(e=>p(e,a.path,t))}finally{a.isUpdating=!1}}},a.lensState.update=function(e){a.lensState.set(e(a.lensState()))},a.lensState};static notifySubscribers=()=>{if(!l.isNotifying){l.isNotifying=!0;try{for(;0<l.pendingSubscribers.size;){var e,t=l.pendingSubscribers;l.pendingSubscribers=new Set;for(e of t)e()}}finally{l.isNotifying=!1}}};static cleanupEffect=e=>{l.pendingSubscribers.delete(e);var t=l.subscriberDependencies.get(e);if(t){for(var r of t)r.delete(e);t.clear(),l.subscriberDependencies.delete(e)}}}let b=(e,t,r)=>{e=[...e];return e[t]=r,e},d=(e,t,r)=>{e={...e};return e[t]=r,e},f=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},S=(e,t,r)=>{var i=Number(t[0]);if(1===t.length)return b(e,i,r);var a=[...e],t=t.slice(1),s=t[0];let c=e[i];return null==c&&(c=void 0!==s?f(s):{}),a[i]=p(c,t,r),a},o=(e,t,r)=>{var i=t[0];if(void 0===i)return e;if(1===t.length)return d(e,i,r);var t=t.slice(1),a=t[0];let s=e[i];null==s&&(s=void 0!==a?f(a):{});a={...e};return a[i]=p(s,t,r),a},p=(e,t,r)=>0===t.length?r:null==e?p({},t,r):void 0===t[0]?e:(Array.isArray(e)?S:o)(e,t,r);export{a as state,e as effect,t as batch,r as derive,s as select,c as readonlyState,n as protectedState,u as lens};
let a=Symbol("STATE_ID"),s=null,u=new Set,d=!1,c=0,l=[],i=new Set,o=new WeakMap,f=new WeakMap,S=new WeakMap,r=new WeakMap,v=()=>{if(!d){d=!0;try{for(;0<u.size;){var e,t=u;u=new Set;for(e of t)e()}}finally{d=!1}}},n=e=>{u.delete(e);var t=f.get(e);if(t){for(var a of t)a.delete(e);t.clear(),f.delete(e)}},p=(e,l=Object.is)=>{let i=e,r=new Set,n=Symbol(),t=()=>{var a=s;if(a){r.add(a);let e=f.get(a),t=(e||(e=new Set,f.set(a,e)),e.add(r),o.get(a));t||(t=new Set,o.set(a,t)),t.add(n)}return i};return t.set=e=>{if(!l(i,e)){var t=s;if(t)if(o.get(t)?.has(n)&&!S.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(i=e,0!==r.size){for(var a of r)u.add(a);0!==c||d||v()}}},t.update=e=>{t.set(e(i))},t[a]=n,t},g=e=>{let a=()=>{if(!i.has(a)){i.add(a);var t=s;try{if(n(a),s=a,o.set(a,new Set),t){S.set(a,t);let e=r.get(t);e||(e=new Set,r.set(t,e)),e.add(a)}e()}finally{s=t,i.delete(a)}}};if(0===c)a();else{if(s){var t=s;S.set(a,t);let e=r.get(t);e||(e=new Set,r.set(t,e)),e.add(a)}l.push(a)}return()=>{n(a),u.delete(a),i.delete(a),o.delete(a);var e=S.get(a),e=(e&&(e=r.get(e))&&e.delete(a),S.delete(a),r.get(a));if(e){for(var t of e)n(t);e.clear(),r.delete(a)}}};var e=e=>{c++;try{return e()}catch(e){throw 1===c&&(u.clear(),l.length=0),e}finally{if(0===--c){if(0<l.length){var t,e=l;l=[];for(t of e)t()}0<u.size&&!d&&v()}}},t=e=>{let t={cachedValue:void 0,computeFn:e,initialized:!1,valueState:p(void 0)};return g(function(){var e=t.computeFn();t.initialized&&Object.is(t.cachedValue,e)||(t.cachedValue=e,t.valueState.set(e)),t.initialized=!0}),function(){return t.initialized||(t.cachedValue=t.computeFn(),t.initialized=!0,t.valueState.set(t.cachedValue)),t.valueState()}},h=(e,t,a=Object.is)=>{let l={equalityFn:a,initialized:!1,lastSelectedValue:void 0,lastSourceValue:void 0,selectorFn:t,source:e,valueState:p(void 0)};return g(function(){var e=l.source();l.initialized&&Object.is(l.lastSourceValue,e)||(l.lastSourceValue=e,e=l.selectorFn(e),l.initialized&&void 0!==l.lastSelectedValue&&l.equalityFn(l.lastSelectedValue,e))||(l.lastSelectedValue=e,l.valueState.set(e),l.initialized=!0)}),function(){return l.initialized||(l.lastSourceValue=l.source(),l.lastSelectedValue=l.selectorFn(l.lastSourceValue),l.valueState.set(l.lastSelectedValue),l.initialized=!0),l.valueState()}};let y=(e,t,a)=>{e=[...e];return e[t]=a,e},w=(e,t,a)=>{e={...e};return e[t]=a,e},V=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},z=(e,t,a)=>{var l=Number(t[0]);if(1===t.length)return y(e,l,a);var i=[...e],t=t.slice(1),r=t[0];let n=e[l];return null==n&&(n=void 0!==r?V(r):{}),i[l]=m(n,t,a),i},b=(e,t,a)=>{var l=t[0];if(void 0===l)return e;if(1===t.length)return w(e,l,a);var t=t.slice(1),i=t[0];let r=e[l];null==r&&(r=void 0!==i?V(i):{});i={...e};return i[l]=m(r,t,a),i},m=(e,t,a)=>0===t.length?a:null==e?m({},t,a):void 0===t[0]?e:(Array.isArray(e)?z:b)(e,t,a);var F=(e,t)=>{let i={accessor:t,isUpdating:!1,lensState:null,originalSet:null,path:[],source:e};return i.path=(()=>{let a=[],l=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||a.push(t),l)});try{i.accessor(l)}catch{}return a})(),i.lensState=p(i.accessor(i.source())),i.originalSet=i.lensState.set,g(function(){if(!i.isUpdating){i.isUpdating=!0;try{i.lensState.set(i.accessor(i.source()))}finally{i.isUpdating=!1}}}),i.lensState.set=function(t){if(!i.isUpdating){i.isUpdating=!0;try{i.originalSet(t),i.source.update(e=>m(e,i.path,t))}finally{i.isUpdating=!1}}},i.lensState.update=function(e){i.lensState.set(e(i.lensState()))},i.lensState};let U=e=>()=>e();var j=(e,t=Object.is)=>{let a=p(e,t);return[()=>U(a)(),{set:e=>a.set(e),update:e=>a.update(e)}]};export{t as derive,g as effect,F as lens,j as protectedState,U as readonlyState,h as select,p as state,e as batch};

@@ -5,4 +5,4 @@ {

"devDependencies": {
"@biomejs/biome": "2.3.13",
"@types/node": "25.0.10",
"@biomejs/biome": "2.3.14",
"@types/node": "25.2.2",
"npm-check-updates": "19.3.2",

@@ -54,3 +54,3 @@ "typescript": "5.9.3",

"name": "@nerdalytics/beacon",
"packageManager": "npm@11.8.0",
"packageManager": "npm@11.9.0",
"repository": {

@@ -61,3 +61,3 @@ "type": "git",

"scripts": {
"benchmark": "node scripts/benchmark.ts",
"benchmark": "node --expose-gc scripts/benchmark.ts",
"benchmark:naiv": "node scripts/naiv-benchmark.ts",

@@ -97,5 +97,6 @@ "build": "npm run build:lts",

},
"sideEffects": false,
"type": "module",
"types": "dist/src/index.d.ts",
"version": "1000.2.5"
"version": "1000.3.0"
}

@@ -1,2 +0,2 @@

# Beacon <img align="right" src="https://raw.githubusercontent.com/nerdalytics/beacon/refs/heads/trunk/assets/beacon-logo.svg" width="128px" alt="A stylized lighthouse beacon with golden light against a dark blue background, representing the reactive state library"/>
# Beacon <img align="right" src="https://raw.githubusercontent.com/nerdalytics/beacon/refs/heads/trunk/assets/beacon-logo-v2.svg" width="128px" alt="A stylized lighthouse beacon with golden light against a dark blue background, representing the reactive state library"/>

@@ -7,3 +7,3 @@ > Lightweight reactive state management for Node.js backends

[![registry:npm:version](https://img.shields.io/npm/v/@nerdalytics/beacon.svg)](https://www.npmjs.com/package/@nerdalytics/beacon)
[![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.2.4)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.2.4)
[![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.0)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.0)

@@ -10,0 +10,0 @@ [![tech:nodejs](https://img.shields.io/badge/Node%20js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org/)

+336
-463
// Core types for reactive primitives
type Subscriber = () => void
export type Unsubscribe = () => void
export type ReadOnlyState<T> = () => T
export interface WriteableState<T> {
type Unsubscribe = () => void
type ReadOnlyState<T> = () => T
interface WriteableState<T> {
set(value: T): void

@@ -13,3 +13,3 @@ update(fn: (value: T) => T): void

export type State<T> = ReadOnlyState<T> &
type State<T> = ReadOnlyState<T> &
WriteableState<T> & {

@@ -19,149 +19,90 @@ [STATE_ID]?: symbol

/**
* Creates a reactive state container with the provided initial value.
*/
export const state = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = Object.is): State<T> =>
StateImpl.createState(initialValue, equalityFn)
// Module-level reactive state
let currentSubscriber: Subscriber | null = null
let pendingSubscribers: Set<Subscriber> = new Set<Subscriber>()
let isNotifying = false
let batchDepth = 0
let deferredEffectCreations: Subscriber[] = []
const activeSubscribers: Set<Subscriber> = new Set<Subscriber>()
const stateTracking: WeakMap<Subscriber, Set<symbol>> = new WeakMap<Subscriber, Set<symbol>>()
const subscriberDependencies: WeakMap<Subscriber, Set<Set<Subscriber>>> = new WeakMap<
Subscriber,
Set<Set<Subscriber>>
>()
const parentSubscriber: WeakMap<Subscriber, Subscriber> = new WeakMap<Subscriber, Subscriber>()
const childSubscribers: WeakMap<Subscriber, Set<Subscriber>> = new WeakMap<Subscriber, Set<Subscriber>>()
/**
* Registers a function to run whenever its reactive dependencies change.
*/
export const effect = (fn: () => void): Unsubscribe => StateImpl.createEffect(fn)
const notifySubscribers = (): void => {
if (isNotifying) {
return
}
/**
* Groups multiple state updates to trigger effects only once at the end.
*/
export const batch = <T>(fn: () => T): T => StateImpl.executeBatch(fn)
isNotifying = true
/**
* Creates a read-only computed value that updates when its dependencies change.
*/
export const derive = <T>(computeFn: () => T): ReadOnlyState<T> => StateImpl.createDerive(computeFn)
try {
while (pendingSubscribers.size > 0) {
const subscribers = pendingSubscribers
pendingSubscribers = new Set()
/**
* Creates an efficient subscription to a subset of a state value.
*/
export const select = <T, R>(
source: ReadOnlyState<T>,
selectorFn: (state: T) => R,
equalityFn: (a: R, b: R) => boolean = Object.is
): ReadOnlyState<R> => StateImpl.createSelect(source, selectorFn, equalityFn)
for (const effect of subscribers) {
effect()
}
}
} finally {
isNotifying = false
}
}
/**
* Creates a read-only view of a state, hiding mutation methods.
*/
export const readonlyState =
<T>(state: State<T>): ReadOnlyState<T> =>
(): T =>
state()
const cleanupEffect = (effect: Subscriber): void => {
pendingSubscribers.delete(effect)
/**
* Creates a state with access control, returning a tuple of reader and writer.
*/
export const protectedState = <T>(
initialValue: T,
equalityFn: (a: T, b: T) => boolean = Object.is
): [
ReadOnlyState<T>,
WriteableState<T>,
] => {
const fullState = state(initialValue, equalityFn)
return [
(): T => readonlyState(fullState)(),
{
set: (value: T): void => fullState.set(value),
update: (fn: (value: T) => T): void => fullState.update(fn),
},
]
const deps = subscriberDependencies.get(effect)
if (deps) {
for (const subscribers of deps) {
subscribers.delete(effect)
}
deps.clear()
subscriberDependencies.delete(effect)
}
}
/**
* Creates a lens for direct updates to nested properties of a state.
* Creates a reactive state container with the provided initial value.
*/
export const lens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> =>
StateImpl.createLens(source, accessor)
const createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = Object.is): State<T> => {
let value = initialValue
const subscribers = new Set<Subscriber>()
const stateId = Symbol()
class StateImpl<T> {
// Static fields track global reactivity state - this centralized approach allows
// for coordinated updates while maintaining individual state isolation
private static currentSubscriber: Subscriber | null = null
private static pendingSubscribers = new Set<Subscriber>()
private static isNotifying = false
private static batchDepth = 0
private static deferredEffectCreations: Subscriber[] = []
private static activeSubscribers = new Set<Subscriber>()
// WeakMaps enable automatic garbage collection when subscribers are no
// longer referenced, preventing memory leaks in long-running applications
private static stateTracking = new WeakMap<Subscriber, Set<symbol>>()
private static subscriberDependencies = new WeakMap<Subscriber, Set<Set<Subscriber>>>()
private static parentSubscriber = new WeakMap<Subscriber, Subscriber>()
private static childSubscribers = new WeakMap<Subscriber, Set<Subscriber>>()
// Instance state - each state has unique subscribers and ID
private value: T
private subscribers = new Set<Subscriber>()
private stateId = Symbol()
private equalityFn: (a: T, b: T) => boolean
constructor(initialValue: T, equalityFn: (a: T, b: T) => boolean = Object.is) {
this.value = initialValue
this.equalityFn = equalityFn
}
/**
* Creates a reactive state container with the provided initial value.
* Implementation of the public 'state' function.
*/
static createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = Object.is): State<T> => {
const instance = new StateImpl<T>(initialValue, equalityFn)
const get = (): T => instance.get()
get.set = (value: T): void => instance.set(value)
get.update = (fn: (currentValue: T) => T): void => instance.update(fn)
get[STATE_ID] = instance.stateId
return get as State<T>
}
// Auto-tracks dependencies when called within effects, creating a fine-grained
// reactivity graph that only updates affected components
get = (): T => {
const currentEffect = StateImpl.currentSubscriber
const get = (): T => {
const currentEffect = currentSubscriber
if (currentEffect) {
// Add this effect to subscribers for future notification
this.subscribers.add(currentEffect)
subscribers.add(currentEffect)
// Maintain bidirectional dependency tracking to enable precise cleanup
// when effects are unsubscribed, preventing memory leaks
let dependencies = StateImpl.subscriberDependencies.get(currentEffect)
let dependencies = subscriberDependencies.get(currentEffect)
if (!dependencies) {
dependencies = new Set()
StateImpl.subscriberDependencies.set(currentEffect, dependencies)
subscriberDependencies.set(currentEffect, dependencies)
}
dependencies.add(this.subscribers)
dependencies.add(subscribers)
// Track read states to detect direct cyclical dependencies that
// could cause infinite loops
let readStates = StateImpl.stateTracking.get(currentEffect)
let readStates = stateTracking.get(currentEffect)
if (!readStates) {
readStates = new Set()
StateImpl.stateTracking.set(currentEffect, readStates)
stateTracking.set(currentEffect, readStates)
}
readStates.add(this.stateId)
readStates.add(stateId)
}
return this.value
return value
}
// Handles value updates with built-in optimizations and safeguards
set = (newValue: T): void => {
// Skip updates for unchanged values to prevent redundant effect executions
if (this.equalityFn(this.value, newValue)) {
get.set = (newValue: T): void => {
if (equalityFn(value, newValue)) {
return
}
// Infinite loop detection prevents direct self-mutation within effects,
// while allowing nested effect patterns that would otherwise appear cyclical
const effect = StateImpl.currentSubscriber
const effect = currentSubscriber
if (effect) {
const states = StateImpl.stateTracking.get(effect)
if (states?.has(this.stateId) && !StateImpl.parentSubscriber.get(effect)) {
const states = stateTracking.get(effect)
if (states?.has(stateId) && !parentSubscriber.get(effect)) {
throw new Error('Infinite loop detected: effect() cannot update a state() it depends on!')

@@ -171,82 +112,49 @@ }

this.value = newValue
value = newValue
// Skip updates when there are no subscribers, avoiding unnecessary processing
if (this.subscribers.size === 0) {
if (subscribers.size === 0) {
return
}
// Queue notifications instead of executing immediately to support batch operations
// and prevent redundant effect runs
for (const sub of this.subscribers) {
StateImpl.pendingSubscribers.add(sub)
for (const sub of subscribers) {
pendingSubscribers.add(sub)
}
// Immediate execution outside of batches, deferred execution inside batches
if (StateImpl.batchDepth === 0 && !StateImpl.isNotifying) {
StateImpl.notifySubscribers()
if (batchDepth === 0 && !isNotifying) {
notifySubscribers()
}
}
update = (fn: (currentValue: T) => T): void => {
this.set(fn(this.value))
get.update = (fn: (currentValue: T) => T): void => {
get.set(fn(value))
}
/**
* Registers a function to run whenever its reactive dependencies change.
* Implementation of the public 'effect' function.
*/
static createEffect = (fn: () => void): Unsubscribe => {
const runEffect = (): void => {
// Prevent re-entrance to avoid cascade updates during effect execution
if (StateImpl.activeSubscribers.has(runEffect)) {
return
}
get[STATE_ID] = stateId
return get as State<T>
}
StateImpl.activeSubscribers.add(runEffect)
const parentEffect = StateImpl.currentSubscriber
/**
* Registers a function to run whenever its reactive dependencies change.
*/
const createEffect = (fn: () => void): Unsubscribe => {
const runEffect = (): void => {
if (activeSubscribers.has(runEffect)) {
return
}
try {
// Clean existing subscriptions before running to ensure only
// currently accessed states are tracked as dependencies
StateImpl.cleanupEffect(runEffect)
activeSubscribers.add(runEffect)
const parentEffect = currentSubscriber
// Set current context for automatic dependency tracking
StateImpl.currentSubscriber = runEffect
StateImpl.stateTracking.set(runEffect, new Set())
try {
cleanupEffect(runEffect)
// Track parent-child relationships to handle nested effects correctly
// and enable hierarchical cleanup later
if (parentEffect) {
StateImpl.parentSubscriber.set(runEffect, parentEffect)
let children = StateImpl.childSubscribers.get(parentEffect)
if (!children) {
children = new Set()
StateImpl.childSubscribers.set(parentEffect, children)
}
children.add(runEffect)
}
currentSubscriber = runEffect
stateTracking.set(runEffect, new Set())
// Execute the effect function, which will auto-track dependencies
fn()
} finally {
// Restore previous context when done
StateImpl.currentSubscriber = parentEffect
StateImpl.activeSubscribers.delete(runEffect)
}
}
// Run immediately unless we're in a batch operation
if (StateImpl.batchDepth === 0) {
runEffect()
} else {
// Still track parent-child relationship even when deferred,
// ensuring proper hierarchical cleanup later
if (StateImpl.currentSubscriber) {
const parent = StateImpl.currentSubscriber
StateImpl.parentSubscriber.set(runEffect, parent)
let children = StateImpl.childSubscribers.get(parent)
if (parentEffect) {
parentSubscriber.set(runEffect, parentEffect)
let children = childSubscribers.get(parentEffect)
if (!children) {
children = new Set()
StateImpl.childSubscribers.set(parent, children)
childSubscribers.set(parentEffect, children)
}

@@ -256,306 +164,168 @@ children.add(runEffect)

// Queue for execution when batch completes
StateImpl.deferredEffectCreations.push(runEffect)
fn()
} finally {
currentSubscriber = parentEffect
activeSubscribers.delete(runEffect)
}
}
// Return cleanup function to properly disconnect from reactivity graph
return (): void => {
// Remove from dependency tracking to stop future notifications
StateImpl.cleanupEffect(runEffect)
StateImpl.pendingSubscribers.delete(runEffect)
StateImpl.activeSubscribers.delete(runEffect)
StateImpl.stateTracking.delete(runEffect)
// Clean up parent-child relationship bidirectionally
const parent = StateImpl.parentSubscriber.get(runEffect)
if (parent) {
const siblings = StateImpl.childSubscribers.get(parent)
if (siblings) {
siblings.delete(runEffect)
}
if (batchDepth === 0) {
runEffect()
} else {
if (currentSubscriber) {
const parent = currentSubscriber
parentSubscriber.set(runEffect, parent)
let children = childSubscribers.get(parent)
if (!children) {
children = new Set()
childSubscribers.set(parent, children)
}
StateImpl.parentSubscriber.delete(runEffect)
children.add(runEffect)
}
// Recursively clean up child effects to prevent memory leaks in
// nested effect scenarios
const children = StateImpl.childSubscribers.get(runEffect)
if (children) {
for (const child of children) {
StateImpl.cleanupEffect(child)
}
children.clear()
StateImpl.childSubscribers.delete(runEffect)
}
}
deferredEffectCreations.push(runEffect)
}
/**
* Groups multiple state updates to trigger effects only once at the end.
* Implementation of the public 'batch' function.
*/
static executeBatch = <T>(fn: () => T): T => {
// Increment depth counter to handle nested batches correctly
StateImpl.batchDepth++
try {
return fn()
} catch (error: unknown) {
// Clean up on error to prevent stale subscribers from executing
// and potentially causing cascading errors
if (StateImpl.batchDepth === 1) {
StateImpl.pendingSubscribers.clear()
StateImpl.deferredEffectCreations.length = 0
}
throw error
} finally {
StateImpl.batchDepth--
return (): void => {
cleanupEffect(runEffect)
pendingSubscribers.delete(runEffect)
activeSubscribers.delete(runEffect)
stateTracking.delete(runEffect)
// Only process effects when exiting the outermost batch,
// maintaining proper execution order while avoiding redundant runs
if (StateImpl.batchDepth === 0) {
// Process effects created during the batch
if (StateImpl.deferredEffectCreations.length > 0) {
// Swap reference instead of spread copy to avoid array allocation
const effectsToRun = StateImpl.deferredEffectCreations
StateImpl.deferredEffectCreations = []
for (const effect of effectsToRun) {
effect()
}
}
// Process state updates that occurred during the batch
if (StateImpl.pendingSubscribers.size > 0 && !StateImpl.isNotifying) {
StateImpl.notifySubscribers()
}
const parent = parentSubscriber.get(runEffect)
if (parent) {
const siblings = childSubscribers.get(parent)
if (siblings) {
siblings.delete(runEffect)
}
}
}
parentSubscriber.delete(runEffect)
/**
* Creates a read-only computed value that updates when its dependencies change.
* Implementation of the public 'derive' function.
*/
static createDerive = <T>(computeFn: () => T): ReadOnlyState<T> => {
// Create a container to hold state and minimize closure captures
const container = {
cachedValue: undefined as unknown as T,
computeFn,
initialized: false,
valueState: StateImpl.createState<T | undefined>(undefined),
}
// Internal effect automatically tracks dependencies and updates the derived value
StateImpl.createEffect(function deriveEffect(): void {
const newValue = container.computeFn()
// Only update if the value actually changed to preserve referential equality
// and prevent unnecessary downstream updates
if (!(container.initialized && Object.is(container.cachedValue, newValue))) {
container.cachedValue = newValue
container.valueState.set(newValue)
const children = childSubscribers.get(runEffect)
if (children) {
for (const child of children) {
cleanupEffect(child)
}
container.initialized = true
})
// Return function with lazy initialization - ensures value is available
// even when accessed before its dependencies have had a chance to update
return function deriveGetter(): T {
if (!container.initialized) {
container.cachedValue = container.computeFn()
container.initialized = true
container.valueState.set(container.cachedValue)
}
return container.valueState() as T
children.clear()
childSubscribers.delete(runEffect)
}
}
}
/**
* Creates an efficient subscription to a subset of a state value.
* Implementation of the public 'select' function.
*/
static createSelect = <T, R>(
source: ReadOnlyState<T>,
selectorFn: (state: T) => R,
equalityFn: (a: R, b: R) => boolean = Object.is
): ReadOnlyState<R> => {
// Create a container to hold state and minimize closure captures
const container = {
equalityFn,
initialized: false,
lastSelectedValue: undefined as R | undefined,
lastSourceValue: undefined as T | undefined,
selectorFn,
source,
valueState: StateImpl.createState<R | undefined>(undefined),
/**
* Groups multiple state updates to trigger effects only once at the end.
*/
const executeBatch = <T>(fn: () => T): T => {
batchDepth++
try {
return fn()
} catch (error: unknown) {
if (batchDepth === 1) {
pendingSubscribers.clear()
deferredEffectCreations.length = 0
}
throw error
} finally {
batchDepth--
// Internal effect to track the source and update only when needed
StateImpl.createEffect(function selectEffect(): void {
const sourceValue = container.source()
// Skip computation if source reference hasn't changed
if (container.initialized && Object.is(container.lastSourceValue, sourceValue)) {
return
if (batchDepth === 0) {
if (deferredEffectCreations.length > 0) {
const effectsToRun = deferredEffectCreations
deferredEffectCreations = []
for (const effect of effectsToRun) {
effect()
}
}
container.lastSourceValue = sourceValue
const newSelectedValue = container.selectorFn(sourceValue)
// Use custom equality function to determine if value semantically changed,
// allowing for deep equality comparisons with complex objects
if (
container.initialized &&
container.lastSelectedValue !== undefined &&
container.equalityFn(container.lastSelectedValue, newSelectedValue)
) {
return
if (pendingSubscribers.size > 0 && !isNotifying) {
notifySubscribers()
}
// Update cache and notify subscribers due the value has changed
container.lastSelectedValue = newSelectedValue
container.valueState.set(newSelectedValue)
container.initialized = true
})
// Return function with eager initialization capability
return function selectGetter(): R {
if (!container.initialized) {
container.lastSourceValue = container.source()
container.lastSelectedValue = container.selectorFn(container.lastSourceValue)
container.valueState.set(container.lastSelectedValue)
container.initialized = true
}
return container.valueState() as R
}
}
}
/**
* Creates a lens for direct updates to nested properties of a state.
* Implementation of the public 'lens' function.
*/
static createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> => {
// Create a container to hold lens state and minimize closure captures
const container = {
accessor,
isUpdating: false,
lensState: null as unknown as State<K>,
originalSet: null as unknown as (value: K) => void,
path: [] as (string | number)[],
source,
}
/**
* Creates a read-only computed value that updates when its dependencies change.
*/
const createDerive = <T>(computeFn: () => T): ReadOnlyState<T> => {
const container = {
cachedValue: undefined as unknown as T,
computeFn,
initialized: false,
valueState: createState<T | undefined>(undefined),
}
// Extract the property path once during lens creation
const extractPath = (): (string | number)[] => {
const pathCollector: (string | number)[] = []
const proxy = new Proxy(
{},
{
get: (_: object, prop: string | symbol): unknown => {
if (typeof prop === 'string' || typeof prop === 'number') {
pathCollector.push(prop)
}
return proxy
},
}
)
createEffect(function deriveEffect(): void {
const newValue = container.computeFn()
try {
container.accessor(proxy as unknown as T)
} catch {
// Ignore errors, we're just collecting the path
}
return pathCollector
if (!(container.initialized && Object.is(container.cachedValue, newValue))) {
container.cachedValue = newValue
container.valueState.set(newValue)
}
// Capture the path once
container.path = extractPath()
container.initialized = true
})
// Create a state with the initial value from the source
container.lensState = StateImpl.createState<K>(container.accessor(container.source()))
container.originalSet = container.lensState.set
// Set up an effect to sync from source to lens
StateImpl.createEffect(function lensEffect(): void {
if (container.isUpdating) {
return
}
container.isUpdating = true
try {
container.lensState.set(container.accessor(container.source()))
} finally {
container.isUpdating = false
}
})
// Override the lens state's set method to update the source
container.lensState.set = function lensSet(value: K): void {
if (container.isUpdating) {
return
}
container.isUpdating = true
try {
// Update lens state
container.originalSet(value)
// Update source by modifying the value at path
container.source.update((current: T): T => setValueAtPath(current, container.path, value))
} finally {
container.isUpdating = false
}
return function deriveGetter(): T {
if (!container.initialized) {
container.cachedValue = container.computeFn()
container.initialized = true
container.valueState.set(container.cachedValue)
}
return container.valueState() as T
}
}
// Add update method for completeness
container.lensState.update = function lensUpdate(fn: (value: K) => K): void {
container.lensState.set(fn(container.lensState()))
}
return container.lensState
/**
* Creates an efficient subscription to a subset of a state value.
*/
const createSelect = <T, R>(
source: ReadOnlyState<T>,
selectorFn: (state: T) => R,
equalityFn: (a: R, b: R) => boolean = Object.is
): ReadOnlyState<R> => {
const container = {
equalityFn,
initialized: false,
lastSelectedValue: undefined as R | undefined,
lastSourceValue: undefined as T | undefined,
selectorFn,
source,
valueState: createState<R | undefined>(undefined),
}
// Processes queued subscriber notifications in a controlled, non-reentrant way
private static notifySubscribers = (): void => {
// Prevent reentrance to avoid cascading notification loops when
// effects trigger further state changes
if (StateImpl.isNotifying) {
createEffect(function selectEffect(): void {
const sourceValue = container.source()
if (container.initialized && Object.is(container.lastSourceValue, sourceValue)) {
return
}
StateImpl.isNotifying = true
container.lastSourceValue = sourceValue
const newSelectedValue = container.selectorFn(sourceValue)
try {
// Process all pending effects in batches for better perf,
// ensuring topological execution order is maintained
while (StateImpl.pendingSubscribers.size > 0) {
// Swap with empty Set instead of Array.from() to avoid array allocation
const subscribers = StateImpl.pendingSubscribers
StateImpl.pendingSubscribers = new Set()
for (const effect of subscribers) {
effect()
}
}
} finally {
StateImpl.isNotifying = false
if (
container.initialized &&
container.lastSelectedValue !== undefined &&
container.equalityFn(container.lastSelectedValue, newSelectedValue)
) {
return
}
}
// Removes effect from dependency tracking to prevent memory leaks
private static cleanupEffect = (effect: Subscriber): void => {
// Remove from execution queue to prevent stale updates
StateImpl.pendingSubscribers.delete(effect)
container.lastSelectedValue = newSelectedValue
container.valueState.set(newSelectedValue)
container.initialized = true
})
// Remove bidirectional dependency references to prevent memory leaks
const deps = StateImpl.subscriberDependencies.get(effect)
if (deps) {
for (const subscribers of deps) {
subscribers.delete(effect)
}
deps.clear()
StateImpl.subscriberDependencies.delete(effect)
return function selectGetter(): R {
if (!container.initialized) {
container.lastSourceValue = container.source()
container.lastSelectedValue = container.selectorFn(container.lastSourceValue)
container.valueState.set(container.lastSelectedValue)
container.initialized = true
}
return container.valueState() as R
}
}
// Helper for array updates

@@ -594,7 +364,5 @@ const updateArrayItem = <V>(arr: unknown[], index: number, value: V): unknown[] => {

if (pathSegments.length === 1) {
// Simple array item update
return updateArrayItem(array, index, value)
}
// Nested path in array
const copy = [

@@ -606,6 +374,4 @@ ...array,

// For null/undefined values in arrays, create appropriate containers
let nextValue = array[index]
if (nextValue === undefined || nextValue === null) {
// Use empty object as default if nextKey is undefined
nextValue = nextKey !== undefined ? createContainer(nextKey) : {}

@@ -624,6 +390,4 @@ }

): Record<string | number, unknown> => {
// Ensure we have a valid key
const currentKey = pathSegments[0]
if (currentKey === undefined) {
// This shouldn't happen given our checks in the main function
return obj

@@ -633,18 +397,13 @@ }

if (pathSegments.length === 1) {
// Simple object property update
return updateShallowProperty(obj, currentKey, value)
}
// Nested path in object
const nextPathSegments = pathSegments.slice(1)
const nextKey = nextPathSegments[0]
// For null/undefined values, create appropriate containers
let currentValue = obj[currentKey]
if (currentValue === undefined || currentValue === null) {
// Use empty object as default if nextKey is undefined
currentValue = nextKey !== undefined ? createContainer(nextKey) : {}
}
// Create new object with updated property
const result = {

@@ -657,5 +416,3 @@ ...obj,

// Simplified function to update a nested value at a path
const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], value: V): O => {
// Handle base cases
if (pathSegments.length === 0) {

@@ -674,3 +431,2 @@ return value as unknown as O

// Delegate to specialized handlers based on data type
if (Array.isArray(obj)) {

@@ -682,1 +438,118 @@ return updateArrayPath(obj, pathSegments, value) as unknown as O

}
/**
* Creates a lens for direct updates to nested properties of a state.
*/
const createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> => {
const container = {
accessor,
isUpdating: false,
lensState: null as unknown as State<K>,
originalSet: null as unknown as (value: K) => void,
path: [] as (string | number)[],
source,
}
const extractPath = (): (string | number)[] => {
const pathCollector: (string | number)[] = []
const proxy = new Proxy(
{},
{
get: (_: object, prop: string | symbol): unknown => {
if (typeof prop === 'string' || typeof prop === 'number') {
pathCollector.push(prop)
}
return proxy
},
}
)
try {
container.accessor(proxy as unknown as T)
} catch {
// Ignore errors, we're just collecting the path
}
return pathCollector
}
container.path = extractPath()
container.lensState = createState<K>(container.accessor(container.source()))
container.originalSet = container.lensState.set
createEffect(function lensEffect(): void {
if (container.isUpdating) {
return
}
container.isUpdating = true
try {
container.lensState.set(container.accessor(container.source()))
} finally {
container.isUpdating = false
}
})
container.lensState.set = function lensSet(value: K): void {
if (container.isUpdating) {
return
}
container.isUpdating = true
try {
container.originalSet(value)
container.source.update((current: T): T => setValueAtPath(current, container.path, value))
} finally {
container.isUpdating = false
}
}
container.lensState.update = function lensUpdate(fn: (value: K) => K): void {
container.lensState.set(fn(container.lensState()))
}
return container.lensState
}
/**
* Creates a read-only view of a state, hiding mutation methods.
*/
const createReadonlyState =
<T>(source: State<T>): ReadOnlyState<T> =>
(): T =>
source()
/**
* Creates a state with access control, returning a tuple of reader and writer.
*/
const createProtectedState = <T>(
initialValue: T,
equalityFn: (a: T, b: T) => boolean = Object.is
): [
ReadOnlyState<T>,
WriteableState<T>,
] => {
const fullState = createState(initialValue, equalityFn)
return [
(): T => createReadonlyState(fullState)(),
{
set: (value: T): void => fullState.set(value),
update: (fn: (value: T) => T): void => fullState.update(fn),
},
]
}
export {
createDerive as derive,
createEffect as effect,
createLens as lens,
createProtectedState as protectedState,
createReadonlyState as readonlyState,
createSelect as select,
createState as state,
executeBatch as batch,
}
export type { ReadOnlyState, State, Unsubscribe, WriteableState }