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.1
to
1000.2.2
+1
-1
dist/src/index.d.ts

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

type Unsubscribe = () => void;
export type Unsubscribe = () => void;
export type ReadOnlyState<T> = () => T;

@@ -3,0 +3,0 @@ export interface WriteableState<T> {

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

const STATE_ID = Symbol();
const STATE_ID = Symbol('STATE_ID');
export const state = (initialValue, equalityFn = Object.is) => StateImpl.createState(initialValue, equalityFn);

@@ -172,3 +172,5 @@ export const effect = (fn) => StateImpl.createEffect(fn);

if (StateImpl.deferredEffectCreations.length > 0) {
const effectsToRun = [...StateImpl.deferredEffectCreations];
const effectsToRun = [
...StateImpl.deferredEffectCreations,
];
StateImpl.deferredEffectCreations.length = 0;

@@ -186,58 +188,76 @@ for (const effect of effectsToRun) {

static createDerive = (computeFn) => {
const valueState = StateImpl.createState(undefined);
let initialized = false;
let cachedValue;
StateImpl.createEffect(() => {
const newValue = computeFn();
if (!(initialized && Object.is(cachedValue, newValue))) {
cachedValue = newValue;
valueState.set(newValue);
const container = {
cachedValue: undefined,
computeFn,
initialized: false,
valueState: StateImpl.createState(undefined),
};
StateImpl.createEffect(function deriveEffect() {
const newValue = container.computeFn();
if (!(container.initialized && Object.is(container.cachedValue, newValue))) {
container.cachedValue = newValue;
container.valueState.set(newValue);
}
initialized = true;
container.initialized = true;
});
return () => {
if (!initialized) {
cachedValue = computeFn();
initialized = true;
valueState.set(cachedValue);
return function deriveGetter() {
if (!container.initialized) {
container.cachedValue = container.computeFn();
container.initialized = true;
container.valueState.set(container.cachedValue);
}
return valueState();
return container.valueState();
};
};
static createSelect = (source, selectorFn, equalityFn = Object.is) => {
let lastSourceValue;
let lastSelectedValue;
let initialized = false;
const valueState = StateImpl.createState(undefined);
StateImpl.createEffect(() => {
const sourceValue = source();
if (initialized && Object.is(lastSourceValue, sourceValue)) {
const container = {
equalityFn,
initialized: false,
lastSelectedValue: undefined,
lastSourceValue: undefined,
selectorFn,
source,
valueState: StateImpl.createState(undefined),
};
StateImpl.createEffect(function selectEffect() {
const sourceValue = container.source();
if (container.initialized && Object.is(container.lastSourceValue, sourceValue)) {
return;
}
lastSourceValue = sourceValue;
const newSelectedValue = selectorFn(sourceValue);
if (initialized && lastSelectedValue !== undefined && equalityFn(lastSelectedValue, newSelectedValue)) {
container.lastSourceValue = sourceValue;
const newSelectedValue = container.selectorFn(sourceValue);
if (container.initialized &&
container.lastSelectedValue !== undefined &&
container.equalityFn(container.lastSelectedValue, newSelectedValue)) {
return;
}
lastSelectedValue = newSelectedValue;
valueState.set(newSelectedValue);
initialized = true;
container.lastSelectedValue = newSelectedValue;
container.valueState.set(newSelectedValue);
container.initialized = true;
});
return () => {
if (!initialized) {
lastSourceValue = source();
lastSelectedValue = selectorFn(lastSourceValue);
valueState.set(lastSelectedValue);
initialized = true;
return function selectGetter() {
if (!container.initialized) {
container.lastSourceValue = container.source();
container.lastSelectedValue = container.selectorFn(container.lastSourceValue);
container.valueState.set(container.lastSelectedValue);
container.initialized = true;
}
return valueState();
return container.valueState();
};
};
static createLens = (source, accessor) => {
const container = {
accessor,
isUpdating: false,
lensState: null,
originalSet: null,
path: [],
source,
};
const extractPath = () => {
const path = [];
const pathCollector = [];
const proxy = new Proxy({}, {
get: (_, prop) => {
if (typeof prop === 'string' || typeof prop === 'number') {
path.push(prop);
pathCollector.push(prop);
}

@@ -248,41 +268,40 @@ return proxy;

try {
accessor(proxy);
container.accessor(proxy);
}
catch {
}
return path;
return pathCollector;
};
const path = extractPath();
const lensState = StateImpl.createState(accessor(source()));
let isUpdating = false;
StateImpl.createEffect(() => {
if (isUpdating) {
container.path = extractPath();
container.lensState = StateImpl.createState(container.accessor(container.source()));
container.originalSet = container.lensState.set;
StateImpl.createEffect(function lensEffect() {
if (container.isUpdating) {
return;
}
isUpdating = true;
container.isUpdating = true;
try {
lensState.set(accessor(source()));
container.lensState.set(container.accessor(container.source()));
}
finally {
isUpdating = false;
container.isUpdating = false;
}
});
const originalSet = lensState.set;
lensState.set = (value) => {
if (isUpdating) {
container.lensState.set = function lensSet(value) {
if (container.isUpdating) {
return;
}
isUpdating = true;
container.isUpdating = true;
try {
originalSet(value);
source.update((current) => setValueAtPath(current, path, value));
container.originalSet(value);
container.source.update((current) => setValueAtPath(current, container.path, value));
}
finally {
isUpdating = false;
container.isUpdating = false;
}
};
lensState.update = (fn) => {
lensState.set(fn(lensState()));
container.lensState.update = function lensUpdate(fn) {
container.lensState.set(fn(container.lensState()));
};
return lensState;
return container.lensState;
};

@@ -320,3 +339,5 @@ static notifySubscribers = () => {

const updateArrayItem = (arr, index, value) => {
const copy = [...arr];
const copy = [
...arr,
];
copy[index] = value;

@@ -326,3 +347,5 @@ return copy;

const updateShallowProperty = (obj, key, value) => {
const result = { ...obj };
const result = {
...obj,
};
result[key] = value;

@@ -340,3 +363,5 @@ return result;

}
const copy = [...array];
const copy = [
...array,
];
const nextPathSegments = pathSegments.slice(1);

@@ -365,3 +390,5 @@ const nextKey = nextPathSegments[0];

}
const result = { ...obj };
const result = {
...obj,
};
result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value);

@@ -368,0 +395,0 @@ return result;

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

let s=Symbol(),i=(e,t=Object.is)=>u.createState(e,t);var e=e=>u.createEffect(e),t=e=>u.executeBatch(e),r=e=>u.createDerive(e),c=(e,t,r=Object.is)=>u.createSelect(e,t,r);let a=e=>()=>e();var n=(e,t=Object.is)=>{let r=i(e,t);return[()=>a(r)(),{set:e=>r.set(e),update:e=>r.update(e)}]},b=(e,t)=>u.createLens(e,t);class u{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 u(e,t);e=()=>r.get();return e.set=e=>r.set(e),e.update=e=>r.update(e),e[s]=r.stateId,e};get=()=>{var r=u.currentSubscriber;if(r){this.subscribers.add(r);let e=u.subscriberDependencies.get(r),t=(e||(e=new Set,u.subscriberDependencies.set(r,e)),e.add(this.subscribers),u.stateTracking.get(r));t||(t=new Set,u.stateTracking.set(r,t)),t.add(this.stateId)}return this.value};set=e=>{if(!this.equalityFn(this.value,e)){var t=u.currentSubscriber;if(t)if(u.stateTracking.get(t)?.has(this.stateId)&&!u.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)u.pendingSubscribers.add(r);0!==u.batchDepth||u.isNotifying||u.notifySubscribers()}}};update=e=>{this.set(e(this.value))};static createEffect=e=>{let r=()=>{if(!u.activeSubscribers.has(r)){u.activeSubscribers.add(r);var t=u.currentSubscriber;try{if(u.cleanupEffect(r),u.currentSubscriber=r,u.stateTracking.set(r,new Set),t){u.parentSubscriber.set(r,t);let e=u.childSubscribers.get(t);e||(e=new Set,u.childSubscribers.set(t,e)),e.add(r)}e()}finally{u.currentSubscriber=t,u.activeSubscribers.delete(r)}}};if(0===u.batchDepth)r();else{if(u.currentSubscriber){var t=u.currentSubscriber;u.parentSubscriber.set(r,t);let e=u.childSubscribers.get(t);e||(e=new Set,u.childSubscribers.set(t,e)),e.add(r)}u.deferredEffectCreations.push(r)}return()=>{u.cleanupEffect(r),u.pendingSubscribers.delete(r),u.activeSubscribers.delete(r),u.stateTracking.delete(r);var e=u.parentSubscriber.get(r),e=(e&&(e=u.childSubscribers.get(e))&&e.delete(r),u.parentSubscriber.delete(r),u.childSubscribers.get(r));if(e){for(var t of e)u.cleanupEffect(t);e.clear(),u.childSubscribers.delete(r)}}};static executeBatch=e=>{u.batchDepth++;try{return e()}catch(e){throw 1===u.batchDepth&&(u.pendingSubscribers.clear(),u.deferredEffectCreations.length=0),e}finally{if(u.batchDepth--,0===u.batchDepth){if(0<u.deferredEffectCreations.length){var t,e=[...u.deferredEffectCreations];u.deferredEffectCreations.length=0;for(t of e)t()}0<u.pendingSubscribers.size&&!u.isNotifying&&u.notifySubscribers()}}};static createDerive=t=>{let r=u.createState(void 0),s=!1,i;return u.createEffect(()=>{var e=t();s&&Object.is(i,e)||(i=e,r.set(e)),s=!0}),()=>(s||(i=t(),s=!0,r.set(i)),r())};static createSelect=(t,r,s=Object.is)=>{let i,c,a=!1,n=u.createState(void 0);return u.createEffect(()=>{var e=t();a&&Object.is(i,e)||(i=e,e=r(e),a&&void 0!==c&&s(c,e))||(c=e,n.set(e),a=!0)}),()=>(a||(i=t(),c=r(i),n.set(c),a=!0),n())};static createLens=(e,t)=>{let r=(()=>{let r=[],s=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||r.push(t),s)});try{t(s)}catch{}return r})(),s=u.createState(t(e())),i=!1,c=(u.createEffect(()=>{if(!i){i=!0;try{s.set(t(e()))}finally{i=!1}}}),s.set);return s.set=t=>{if(!i){i=!0;try{c(t),e.update(e=>p(e,r,t))}finally{i=!1}}},s.update=e=>{s.set(e(s()))},s};static notifySubscribers=()=>{if(!u.isNotifying){u.isNotifying=!0;try{for(;0<u.pendingSubscribers.size;){var e,t=Array.from(u.pendingSubscribers);u.pendingSubscribers.clear();for(e of t)e()}}finally{u.isNotifying=!1}}};static cleanupEffect=e=>{u.pendingSubscribers.delete(e);var t=u.subscriberDependencies.get(e);if(t){for(var r of t)r.delete(e);t.clear(),u.subscriberDependencies.delete(e)}}}let f=(e,t,r)=>{e=[...e];return e[t]=r,e},l=(e,t,r)=>{e={...e};return e[t]=r,e},d=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},S=(e,t,r)=>{var s=Number(t[0]);if(1===t.length)return f(e,s,r);var i=[...e],t=t.slice(1),c=t[0];let a=e[s];return null==a&&(a=void 0!==c?d(c):{}),i[s]=p(a,t,r),i},h=(e,t,r)=>{var s=t[0];if(void 0===s)return e;if(1===t.length)return l(e,s,r);var t=t.slice(1),i=t[0];let c=e[s];null==c&&(c=void 0!==i?d(i):{});i={...e};return i[s]=p(c,t,r),i},p=(e,t,r)=>0===t.length?r:null==e?p({},t,r):void 0===t[0]?e:(Array.isArray(e)?S:h)(e,t,r);export{i as state,e as effect,t as batch,r as derive,c as select,a as readonlyState,n as protectedState,b as lens};
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.length=0;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=Array.from(l.pendingSubscribers);l.pendingSubscribers.clear();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};
{
"name": "@nerdalytics/beacon",
"version": "1000.2.1",
"author": "Denny Trebbin (nerdalytics)",
"description": "A lightweight reactive state library for Node.js backends. Enables reactive state management with automatic dependency tracking and efficient updates for server-side applications.",
"type": "module",
"main": "dist/src/index.min.js",
"types": "dist/src/index.d.ts",
"files": ["dist/src/index.js", "dist/src/index.d.ts", "src/index.ts", "LICENSE"],
"devDependencies": {
"@biomejs/biome": "2.2.6",
"@types/node": "24.9.1",
"typescript": "5.9.3",
"uglify-js": "3.19.3"
},
"engines": {
"node": ">=20.0.0"
},
"exports": {
".": {
"typescript": "./src/index.ts",
"default": "./dist/src/index.js"
"import": {
"default": "./dist/src/index.min.js",
"types": "./dist/src/index.d.ts"
},
"require": {
"default": "./dist/src/index.min.js"
},
"types": "./dist/src/index.d.ts",
"typescript": "./dist/src/index.ts"
}
},
"files": [
"dist/src/index.js",
"dist/src/index.d.ts",
"dist/src/index.ts",
"LICENSE"
],
"keywords": [
"backend",
"batching",
"computed-values",
"dependency-tracking",
"effects",
"fine-grained",
"lightweight",
"memory-management",
"nodejs",
"performance",
"reactive",
"server-side",
"signals",
"state-management",
"typescript"
],
"license": "MIT",
"main": "dist/src/index.min.js",
"name": "@nerdalytics/beacon",
"packageManager": "npm@11.6.0",
"repository": {
"url": "git+https://github.com/nerdalytics/beacon.git",
"type": "git"
"type": "git",
"url": "git+https://github.com/nerdalytics/beacon.git"
},
"scripts": {
"lint": "npx @biomejs/biome lint --config-path=./biome.json",
"lint:fix": "npx @biomejs/biome lint --fix --config-path=./biome.json",
"lint:fix:unsafe": "npx @biomejs/biome lint --fix --unsafe --config-path=./biome.json",
"format": "npx @biomejs/biome format --write --config-path=./biome.json",
"check": "npx @biomejs/biome check --config-path=./biome.json",
"check:fix": "npx @biomejs/biome format --fix --config-path=./biome.json",
"test": "node --test --test-skip-pattern=\"COMPONENT NAME\" tests/**/*.ts",
"test:coverage": "node --test --experimental-config-file=node.config.json --test-skip-pattern=\"[COMPONENT NAME]\" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info tests/**/*.ts",
"test:unit:state": "node --test tests/state.test.ts",
"test:unit:effect": "node --test tests/effect.test.ts",
"test:unit:batch": "node --test tests/batch.test.ts",
"test:unit:derive": "node --test tests/derive.test.ts",
"test:unit:select": "node --test tests/select.test.ts",
"test:unit:lens": "node --test tests/lens.test.ts",
"test:unit:cleanup": "node --test tests/cleanup.test.ts",
"test:unit:cyclic-dependency": "node --test tests/cyclic-dependency.test.ts",
"test:unit:deep-chain": "node --test tests/deep-chain.test.ts",
"test:unit:infinite-loop": "node --test tests/infinite-loop.test.ts",
"test:unit:custom-equality": "node --test tests/custom-equality.test.ts",
"benchmark": "node scripts/benchmark.ts",
"benchmark:naiv": "node scripts/naiv-benchmark.ts",
"build": "npm run build:lts",
"prebuild:lts": "rm -rf dist/",
"build:lts": "tsc -p tsconfig.lts.json",
"check": "npx @biomejs/biome check",
"check:fix": "npx @biomejs/biome format --fix",
"format": "npx @biomejs/biome format --write",
"lint": "npx @biomejs/biome lint",
"lint:fix": "npx @biomejs/biome lint --fix",
"lint:fix:unsafe": "npx @biomejs/biome lint --fix --unsafe",
"postbuild:lts": "npx uglify-js --compress --mangle --module --toplevel --v8 --warn --source-map \"content='dist/src/index.js.map'\" --output dist/src/index.min.js dist/src/index.js",
"prebuild:lts": "rm -rf dist/",
"prepublishOnly": "npm run build:lts",
"pretest:lts": "node scripts/run-lts-tests.js",
"test": "node --test --test-skip-pattern=\"COMPONENT NAME\" tests/**/*.ts",
"test:behavior": "node --test tests/infinite-loop.test.ts tests/cyclic-dependency.test.ts tests/cleanup.test.ts",
"test:core": "node --test tests/*-core.test.ts",
"test:coverage": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info tests/**/*.test.ts",
"test:integration": "node --test tests/state-*.test.ts tests/batch-integration.test.ts",
"test:lts:20": "node --test dist/tests/**.js",
"test:lts:22": "node --test --test-skip-pattern=\"COMPONENT NAME\" dist/tests/**/*.js",
"test:unit:batch": "node --test tests/batch.test.ts",
"test:unit:cleanup": "node --test tests/cleanup.test.ts",
"test:unit:custom-equality": "node --test tests/custom-equality.test.ts",
"test:unit:cyclic-dependency": "node --test tests/cyclic-dependency.test.ts",
"test:unit:deep-chain": "node --test tests/deep-chain.test.ts",
"test:unit:derive": "node --test tests/derive.test.ts",
"test:unit:effect": "node --test tests/effect.test.ts",
"test:unit:infinite-loop": "node --test tests/infinite-loop.test.ts",
"test:unit:lens": "node --test tests/lens.test.ts",
"test:unit:select": "node --test tests/select.test.ts",
"test:unit:state": "node --test tests/state.test.ts",
"update-performance-docs": "node --experimental-config-file=node.config.json scripts/update-performance-docs.ts"
},
"keywords": [
"state-management",
"effects",
"fine-grained",
"computed-values",
"batching",
"signals",
"reactive",
"lightweight",
"performance",
"dependency-tracking",
"memoization",
"memory-management",
"nodejs",
"server-side",
"backend",
"typescript"
],
"author": "Denny Trebbin (nerdalytics)",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/node": "22.14.1",
"typescript": "5.8.3",
"uglify-js": "3.19.3"
},
"engines": {
"node": ">=20.0.0"
},
"packageManager": "npm@11.3.0"
"type": "module",
"types": "dist/src/index.d.ts",
"version": "1000.2.2"
}
// Core types for reactive primitives
type Subscriber = () => void
type Unsubscribe = () => void
export type ReadOnlyState<T> = () => T
export interface WriteableState<T> {
set(value: T): void
update(fn: (value: T) => T): void
}
// Special symbol used for internal tracking
const STATE_ID = Symbol()
export type State<T> = ReadOnlyState<T> &
WriteableState<T> & {
[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)
/**
* Registers a function to run whenever its reactive dependencies change.
*/
export const effect = (fn: () => void): Unsubscribe => StateImpl.createEffect(fn)
/**
* Groups multiple state updates to trigger effects only once at the end.
*/
export const batch = <T>(fn: () => T): T => StateImpl.executeBatch(fn)
/**
* Creates a read-only computed value that updates when its dependencies change.
*/
export const derive = <T>(computeFn: () => T): ReadOnlyState<T> => StateImpl.createDerive(computeFn)
/**
* 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)
/**
* Creates a read-only view of a state, hiding mutation methods.
*/
export const readonlyState =
<T>(state: State<T>): ReadOnlyState<T> =>
(): T =>
state()
/**
* 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),
},
]
}
/**
* Creates a lens for direct updates to nested properties of a state.
*/
export const lens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> =>
StateImpl.createLens(source, accessor)
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
if (currentEffect) {
// Add this effect to subscribers for future notification
this.subscribers.add(currentEffect)
// Maintain bidirectional dependency tracking to enable precise cleanup
// when effects are unsubscribed, preventing memory leaks
let dependencies = StateImpl.subscriberDependencies.get(currentEffect)
if (!dependencies) {
dependencies = new Set()
StateImpl.subscriberDependencies.set(currentEffect, dependencies)
}
dependencies.add(this.subscribers)
// Track read states to detect direct cyclical dependencies that
// could cause infinite loops
let readStates = StateImpl.stateTracking.get(currentEffect)
if (!readStates) {
readStates = new Set()
StateImpl.stateTracking.set(currentEffect, readStates)
}
readStates.add(this.stateId)
}
return this.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)) {
return
}
// Infinite loop detection prevents direct self-mutation within effects,
// while allowing nested effect patterns that would otherwise appear cyclical
const effect = StateImpl.currentSubscriber
if (effect) {
const states = StateImpl.stateTracking.get(effect)
if (states?.has(this.stateId) && !StateImpl.parentSubscriber.get(effect)) {
throw new Error('Infinite loop detected: effect() cannot update a state() it depends on!')
}
}
this.value = newValue
// Skip updates when there are no subscribers, avoiding unnecessary processing
if (this.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)
}
// Immediate execution outside of batches, deferred execution inside batches
if (StateImpl.batchDepth === 0 && !StateImpl.isNotifying) {
StateImpl.notifySubscribers()
}
}
update = (fn: (currentValue: T) => T): void => {
this.set(fn(this.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
}
StateImpl.activeSubscribers.add(runEffect)
const parentEffect = StateImpl.currentSubscriber
try {
// Clean existing subscriptions before running to ensure only
// currently accessed states are tracked as dependencies
StateImpl.cleanupEffect(runEffect)
// Set current context for automatic dependency tracking
StateImpl.currentSubscriber = runEffect
StateImpl.stateTracking.set(runEffect, new Set())
// 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)
}
// 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 (!children) {
children = new Set()
StateImpl.childSubscribers.set(parent, children)
}
children.add(runEffect)
}
// Queue for execution when batch completes
StateImpl.deferredEffectCreations.push(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)
}
}
StateImpl.parentSubscriber.delete(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)
}
}
}
/**
* 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--
// 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) {
const effectsToRun = [...StateImpl.deferredEffectCreations]
StateImpl.deferredEffectCreations.length = 0
for (const effect of effectsToRun) {
effect()
}
}
// Process state updates that occurred during the batch
if (StateImpl.pendingSubscribers.size > 0 && !StateImpl.isNotifying) {
StateImpl.notifySubscribers()
}
}
}
}
/**
* 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> => {
const valueState = StateImpl.createState<T | undefined>(undefined)
let initialized = false
let cachedValue: T
// Internal effect automatically tracks dependencies and updates the derived value
StateImpl.createEffect((): void => {
const newValue = computeFn()
// Only update if the value actually changed to preserve referential equality
// and prevent unnecessary downstream updates
if (!(initialized && Object.is(cachedValue, newValue))) {
cachedValue = newValue
valueState.set(newValue)
}
initialized = true
})
// Return function with lazy initialization - ensures value is available
// even when accessed before its dependencies have had a chance to update
return (): T => {
if (!initialized) {
cachedValue = computeFn()
initialized = true
valueState.set(cachedValue)
}
return valueState() as T
}
}
/**
* 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> => {
let lastSourceValue: T | undefined
let lastSelectedValue: R | undefined
let initialized = false
const valueState = StateImpl.createState<R | undefined>(undefined)
// Internal effect to track the source and update only when needed
StateImpl.createEffect((): void => {
const sourceValue = source()
// Skip computation if source reference hasn't changed
if (initialized && Object.is(lastSourceValue, sourceValue)) {
return
}
lastSourceValue = sourceValue
const newSelectedValue = selectorFn(sourceValue)
// Use custom equality function to determine if value semantically changed,
// allowing for deep equality comparisons with complex objects
if (initialized && lastSelectedValue !== undefined && equalityFn(lastSelectedValue, newSelectedValue)) {
return
}
// Update cache and notify subscribers due the value has changed
lastSelectedValue = newSelectedValue
valueState.set(newSelectedValue)
initialized = true
})
// Return function with eager initialization capability
return (): R => {
if (!initialized) {
lastSourceValue = source()
lastSelectedValue = selectorFn(lastSourceValue)
valueState.set(lastSelectedValue)
initialized = true
}
return 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> => {
// Extract the property path once during lens creation
const extractPath = (): (string | number)[] => {
const path: (string | number)[] = []
const proxy = new Proxy(
{},
{
get: (_: object, prop: string | symbol): unknown => {
if (typeof prop === 'string' || typeof prop === 'number') {
path.push(prop)
}
return proxy
},
}
)
try {
accessor(proxy as unknown as T)
} catch {
// Ignore errors, we're just collecting the path
}
return path
}
// Capture the path once
const path = extractPath()
// Create a state with the initial value from the source
const lensState = StateImpl.createState<K>(accessor(source()))
// Prevent circular updates
let isUpdating = false
// Set up an effect to sync from source to lens
StateImpl.createEffect((): void => {
if (isUpdating) {
return
}
isUpdating = true
try {
lensState.set(accessor(source()))
} finally {
isUpdating = false
}
})
// Override the lens state's set method to update the source
const originalSet = lensState.set
lensState.set = (value: K): void => {
if (isUpdating) {
return
}
isUpdating = true
try {
// Update lens state
originalSet(value)
// Update source by modifying the value at path
source.update((current: T): T => setValueAtPath(current, path, value))
} finally {
isUpdating = false
}
}
// Add update method for completeness
lensState.update = (fn: (value: K) => K): void => {
lensState.set(fn(lensState()))
}
return lensState
}
// 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) {
return
}
StateImpl.isNotifying = true
try {
// Process all pending effects in batches for better perf,
// ensuring topological execution order is maintained
while (StateImpl.pendingSubscribers.size > 0) {
// Process in snapshot batches to prevent infinite loops
// when effects trigger further state changes
const subscribers = Array.from(StateImpl.pendingSubscribers)
StateImpl.pendingSubscribers.clear()
for (const effect of subscribers) {
effect()
}
}
} finally {
StateImpl.isNotifying = false
}
}
// 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)
// 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)
}
}
}
// Helper for array updates
const updateArrayItem = <V>(arr: unknown[], index: number, value: V): unknown[] => {
const copy = [...arr]
copy[index] = value
return copy
}
// Helper for single-level updates (optimization)
const updateShallowProperty = <V>(
obj: Record<string | number, unknown>,
key: string | number,
value: V
): Record<string | number, unknown> => {
const result = { ...obj }
result[key] = value
return result
}
// Helper to create the appropriate container type
const createContainer = (key: string | number): Record<string | number, unknown> | unknown[] => {
const isArrayKey = typeof key === 'number' || !Number.isNaN(Number(key))
return isArrayKey ? [] : {}
}
// Helper for handling array path updates
const updateArrayPath = <V>(array: unknown[], pathSegments: (string | number)[], value: V): unknown[] => {
const index = Number(pathSegments[0])
if (pathSegments.length === 1) {
// Simple array item update
return updateArrayItem(array, index, value)
}
// Nested path in array
const copy = [...array]
const nextPathSegments = pathSegments.slice(1)
const nextKey = nextPathSegments[0]
// 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) : {}
}
copy[index] = setValueAtPath(nextValue, nextPathSegments, value)
return copy
}
// Helper for handling object path updates
const updateObjectPath = <V>(
obj: Record<string | number, unknown>,
pathSegments: (string | number)[],
value: V
): 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
}
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 = { ...obj }
result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value)
return result
}
// 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) {
return value as unknown as O
}
if (obj === undefined || obj === null) {
return setValueAtPath({} as O, pathSegments, value)
}
const currentKey = pathSegments[0]
if (currentKey === undefined) {
return obj
}
// Delegate to specialized handlers based on data type
if (Array.isArray(obj)) {
return updateArrayPath(obj, pathSegments, value) as unknown as O
}
return updateObjectPath(obj as Record<string | number, unknown>, pathSegments, value) as unknown as O
}