@nerdalytics/beacon
Advanced tools
@@ -7,6 +7,3 @@ type Unsubscribe = () => void; | ||
| } | ||
| declare const STATE_ID: unique symbol; | ||
| type State<T> = ReadOnlyState<T> & WriteableState<T> & { | ||
| [STATE_ID]?: symbol; | ||
| }; | ||
| type State<T> = ReadOnlyState<T> & WriteableState<T>; | ||
| declare const createState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => State<T>; | ||
@@ -13,0 +10,0 @@ declare const createEffect: (fn: () => void) => Unsubscribe; |
@@ -1,1 +0,1 @@ | ||
| let r=Symbol("STATE_ID"),f=null,o=new Set,s=!1,u=0,a=[],l=new Set,d=new WeakMap,c=new WeakMap,v=new WeakMap,i=new WeakMap,p=new Set(["__proto__","constructor","prototype"]),y=(e,t,r)=>{let n=e.get(t);return n||(n=r(),e.set(t,n)),n},w=()=>{if(!s){s=!0;try{for(;0<o.size;){var e,t=o;o=new Set;for(e of t)e()}}finally{s=!1}}},g=e=>{o.delete(e);var t=c.get(e);if(t){for(var r of t)r.delete(e);t.clear(),c.delete(e)}},h=e=>{g(e),l.delete(e),d.delete(e);var t=v.get(e),t=(t&&(t=i.get(t))&&t.delete(e),v.delete(e),i.get(e));if(t){for(var r of t)h(r);t.clear(),i.delete(e)}},S=(e,n=Object.is)=>{let a=e,l=new Set,i=Symbol(),t=()=>{var e=f;return e&&(l.add(e),y(c,e,()=>new Set).add(l),y(d,e,()=>new Set).add(i)),a};return t.set=e=>{if(!n(a,e)){var t=f;if(t)if(d.get(t)?.has(i)&&!v.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(a=e,0!==l.size){for(var r of l)o.add(r);0!==u||s||w()}}},t.update=e=>{t.set(e(a))},t[r]=i,t},b=r=>{let n=()=>{if(!l.has(n)){l.add(n);var e=f;try{g(n),f=n;var t=d.get(n);t?t.clear():d.set(n,new Set),e&&(v.set(n,e),y(i,e,()=>new Set).add(n)),r()}finally{f=e,l.delete(n)}}};var e;return 0===u?n():(f&&(e=f,v.set(n,e),y(i,e,()=>new Set).add(n)),a.push(n)),()=>{h(n)}};var e=e=>{u++;try{return e()}catch(e){throw 1===u&&(o.clear(),a.length=0),e}finally{if(0===--u){if(0<a.length){var t,e=a;a=[];for(t of e)t()}0<o.size&&!s&&w()}}},t=t=>{let r=void 0,n=!1,a=S(void 0);return b(function(){var e=t();n&&Object.is(r,e)||(r=e,a.set(e)),n=!0}),function(){return n||(r=t(),n=!0,a.set(r)),a()}},n=(t,r,n=Object.is)=>{let a=!1,l,i,f=S(void 0);return b(function(){var e=t();a&&Object.is(i,e)||(i=e,e=r(e),a&&void 0!==l&&n(l,e))||(l=e,f.set(e),a=!0)}),function(){return a||(i=t(),l=r(i),f.set(l),a=!0),f()}};let m=(e,t,r)=>{e=[...e];return e[t]=r,e},j=(e,t,r)=>{e={...e};return e[t]=r,e},N=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},O=(n,a,l,i)=>{if(l>=a.length)return i;if(null==n)return O({},a,l,i);var f=a[l];if(void 0===f)return n;if(Array.isArray(n)){var o=Number(f);if(l===a.length-1)return m(n,o,i);var s=[...n];let e=l+1,t=a[e],r=n[o];return null==r&&(r=void 0===t?{}:N(t)),s[o]=O(r,a,e,i),s}o=n;if(l===a.length-1)return j(o,f,i);let e=l+1,t=a[e],r=o[f];null==r&&(r=void 0===t?{}:N(t));s={...o};return s[f]=O(r,a,e,i),s};var _=(e,t)=>{let r=!1;let n=(()=>{let r=[],n=!1,a=new Proxy({},{get:(e,t)=>(n||"string"!=typeof t&&"number"!=typeof t||(p.has(String(t))?n=!0:r.push(t)),a)});try{t(a)}catch{}return n?[]:r})(),a=S(t(e())),l=a.set;return b(function(){if(!r){r=!0;try{a.set(t(e()))}finally{r=!1}}}),a.set=function(t){if(!r&&0!==n.length){r=!0;try{l(t),e.update(e=>O(e,n,0,t))}finally{r=!1}}},a.update=function(e){a.set(e(a()))},a};let k=e=>()=>e();var M=(e,t=Object.is)=>{let r=S(e,t);return[k(r),{set:e=>r.set(e),update:e=>r.update(e)}]};export{t as derive,b as effect,_ as lens,M as protectedState,k as readonlyState,n as select,S as state,e as batch}; | ||
| let o=null,r=new Set,a=new Set,s=a,u=!1,c=0,n=[],f=new Set,i=new WeakMap,v=new WeakMap,d=new WeakMap,l=new WeakMap,w=new Set(["__proto__","constructor","prototype"]),p=(e,t,r)=>{let a=e.get(t);return a||(a=r(),e.set(t,a)),a},y=()=>{if(!u){u=!0;try{for(;0<s.size;){var e,t=s;s=t===a?r:a;for(e of t)e();t.clear()}}finally{u=!1}}},S=e=>{s.delete(e);var t=v.get(e);if(t){for(var r of t)r.delete(e);t.clear(),v.delete(e)}},g=e=>{S(e),f.delete(e),i.delete(e);var t=d.get(e),t=(t&&(t=l.get(t))&&t.delete(e),d.delete(e),l.get(e));if(t){for(var r of t)g(r);t.clear(),l.delete(e)}},h=(e,a=Object.is)=>{let n=e,f=new Set,l=Symbol(),t=()=>{var e=o;return e&&(f.add(e),p(v,e,()=>new Set).add(f),p(i,e,()=>new Set).add(l)),n};return t.set=e=>{if(!a(n,e)){var t=o;if(t)if(i.get(t)?.has(l)&&!d.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(n=e,0!==f.size){for(var r of f)s.add(r);0!==c||u||y()}}},t.update=e=>{t.set(e(n))},t},b=r=>{let a=()=>{if(!f.has(a)){f.add(a);var e=o;try{S(a),o=a;var t=i.get(a);t?t.clear():i.set(a,new Set),e&&(d.set(a,e),p(l,e,()=>new Set).add(a)),r()}finally{o=e,f.delete(a)}}};var e;return 0===c?a():(o&&(e=o,d.set(a,e),p(l,e,()=>new Set).add(a)),n.push(a)),()=>{g(a)}};var e=e=>{c++;try{return e()}catch(e){throw 1===c&&(s.clear(),n.length=0),e}finally{if(0===--c){if(0<n.length){var t,e=n;n=[];for(t of e)t()}0<s.size&&!u&&y()}}},t=r=>{let a=void 0,n=!1,f=new Set;return b(function(){var e=r();if(!n||!Object.is(a,e)){a=e;for(var t of f)s.add(t);0!==c||u||y()}n=!0}),function(){var e=o;return e&&(f.add(e),p(v,e,()=>new Set).add(f)),n||(a=r(),n=!0),a}},j=(r,a,n=Object.is)=>{let f=!1,l,i,d=new Set;return b(function(){var e=r();if(!f||!Object.is(i,e)){i=e;e=a(e);if(!f||void 0===l||!n(l,e)){l=e,f=!0;for(var t of d)s.add(t);0!==c||u||y()}}}),function(){var e=o;return e&&(d.add(e),p(v,e,()=>new Set).add(d)),f||(i=r(),l=a(i),f=!0),l}};let N=(e,t)=>null!=e?e:void 0===t||Number.isNaN(Number(t))?{}:[],O=(t,e,r,a)=>{if(r>=e.length)return a;if(null==t)return O({},e,r,a);var n=e[r];if(void 0===n)return t;var f=Array.isArray(t),n=f?Number(n):n;if(r===e.length-1){if(f)return(l=[...t])[n]=a,l;let e={...t};return e[n]=a,e}var l=r+1,r=e[l],i=t[n],i=N(i,r);if(f)return(r=[...t])[n]=O(i,e,l,a),r;let d={...t};return d[n]=O(i,e,l,a),d};var k=(e,t)=>{let r=!1;let a=(()=>{let r=[],a=!1,n=new Proxy({},{get:(e,t)=>(a||"string"!=typeof t||(w.has(String(t))?a=!0:r.push(t)),n)});try{t(n)}catch{}return a?[]:r})(),n=h(t(e())),f=n.set;return b(function(){if(!r){r=!0;try{n.set(t(e()))}finally{r=!1}}}),n.set=function(t){if(!r&&0!==a.length){r=!0;try{f(t),e.update(e=>O(e,a,0,t))}finally{r=!1}}},n.update=function(e){n.set(e(n()))},n};let m=e=>()=>e();var M=(e,t=Object.is)=>{let r=h(e,t);return[m(r),{set:e=>r.set(e),update:e=>r.update(e)}]};export{t as derive,b as effect,k as lens,M as protectedState,m as readonlyState,j as select,h as state,e as batch}; |
+12
-34
@@ -5,6 +5,6 @@ { | ||
| "devDependencies": { | ||
| "@biomejs/biome": "2.3.14", | ||
| "@types/node": "25.2.2", | ||
| "npm-check-updates": "19.3.2", | ||
| "typescript": "5.9.3", | ||
| "@biomejs/biome": "2.4.11", | ||
| "@types/node": "25.5.2", | ||
| "npm-check-updates": "20.0.0", | ||
| "typescript": "6.0.2", | ||
| "uglify-js": "3.19.3" | ||
@@ -36,16 +36,9 @@ }, | ||
| "backend", | ||
| "batching", | ||
| "computed-values", | ||
| "dependency-tracking", | ||
| "effects", | ||
| "fine-grained", | ||
| "lightweight", | ||
| "memory-management", | ||
| "batch", | ||
| "dependency-graph", | ||
| "effect", | ||
| "nodejs", | ||
| "performance", | ||
| "reactive", | ||
| "server-side", | ||
| "signals", | ||
| "state-management", | ||
| "typescript" | ||
| "state" | ||
| ], | ||
@@ -55,3 +48,3 @@ "license": "MIT", | ||
| "name": "@nerdalytics/beacon", | ||
| "packageManager": "npm@11.9.0", | ||
| "packageManager": "npm@11.12.1", | ||
| "repository": { | ||
@@ -63,3 +56,3 @@ "type": "git", | ||
| "benchmark": "node --expose-gc scripts/benchmark.ts -R 3", | ||
| "benchmark:naiv": "node scripts/naiv-benchmark.ts", | ||
| "build": "npm run build:lts", | ||
@@ -78,21 +71,6 @@ "build:lts": "tsc -p tsconfig.lts.json", | ||
| "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-dependencies": "npx npm-check-updates --interactive --upgrade --removeRange", | ||
| "update-performance-docs": "node --experimental-config-file=node.config.json scripts/update-performance-docs.ts" | ||
| "update-dependencies": "npx npm-check-updates --interactive --upgrade --removeRange" | ||
| }, | ||
@@ -102,3 +80,3 @@ "sideEffects": false, | ||
| "types": "dist/src/index.d.ts", | ||
| "version": "1000.3.2" | ||
| "version": "1000.3.3" | ||
| } |
+1
-1
@@ -7,3 +7,3 @@ # 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"/> | ||
| [](https://www.npmjs.com/package/@nerdalytics/beacon) | ||
| [](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.0) | ||
| [](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.3) | ||
@@ -10,0 +10,0 @@ [](https://nodejs.org/) |
+70
-77
@@ -10,13 +10,9 @@ // Core types for reactive primitives | ||
| // Special symbol used for internal tracking | ||
| const STATE_ID: unique symbol = Symbol('STATE_ID') | ||
| type State<T> = ReadOnlyState<T> & WriteableState<T> | ||
| type State<T> = ReadOnlyState<T> & | ||
| WriteableState<T> & { | ||
| [STATE_ID]?: symbol | ||
| } | ||
| // Module-level reactive state | ||
| let currentSubscriber: Subscriber | null = null | ||
| let pendingSubscribers: Set<Subscriber> = new Set<Subscriber>() | ||
| const flushing: Set<Subscriber> = new Set<Subscriber>() | ||
| const queued: Set<Subscriber> = new Set<Subscriber>() | ||
| let pendingSubscribers: Set<Subscriber> = queued | ||
| let isNotifying = false | ||
@@ -58,3 +54,3 @@ let batchDepth = 0 | ||
| const subscribers = pendingSubscribers | ||
| pendingSubscribers = new Set() | ||
| pendingSubscribers = subscribers === queued ? flushing : queued | ||
@@ -64,2 +60,3 @@ for (const effect of subscribers) { | ||
| } | ||
| subscribers.clear() | ||
| } | ||
@@ -157,3 +154,2 @@ } finally { | ||
| get[STATE_ID] = stateId | ||
| return get as State<T> | ||
@@ -243,3 +239,3 @@ } | ||
| let initialized = false | ||
| const valueState = createState<T | undefined>(undefined) | ||
| const subscribers = new Set<Subscriber>() | ||
@@ -251,3 +247,9 @@ createEffect(function deriveEffect(): void { | ||
| cachedValue = newValue | ||
| valueState.set(newValue) | ||
| for (const sub of subscribers) { | ||
| pendingSubscribers.add(sub) | ||
| } | ||
| if (batchDepth === 0 && !isNotifying) { | ||
| notifySubscribers() | ||
| } | ||
| } | ||
@@ -259,8 +261,13 @@ | ||
| return function deriveGetter(): T { | ||
| const currentEffect = currentSubscriber | ||
| if (currentEffect) { | ||
| subscribers.add(currentEffect) | ||
| getOrCreate(subscriberDependencies, currentEffect, () => new Set()).add(subscribers) | ||
| } | ||
| if (!initialized) { | ||
| cachedValue = computeFn() | ||
| initialized = true | ||
| valueState.set(cachedValue) | ||
| } | ||
| return valueState() as T | ||
| return cachedValue | ||
| } | ||
@@ -277,3 +284,3 @@ } | ||
| let lastSourceValue: T | undefined | ||
| const valueState = createState<R | undefined>(undefined) | ||
| const subscribers = new Set<Subscriber>() | ||
@@ -295,46 +302,36 @@ createEffect(function selectEffect(): void { | ||
| lastSelectedValue = newSelectedValue | ||
| valueState.set(newSelectedValue) | ||
| initialized = true | ||
| for (const sub of subscribers) { | ||
| pendingSubscribers.add(sub) | ||
| } | ||
| if (batchDepth === 0 && !isNotifying) { | ||
| notifySubscribers() | ||
| } | ||
| }) | ||
| return function selectGetter(): R { | ||
| const currentEffect = currentSubscriber | ||
| if (currentEffect) { | ||
| subscribers.add(currentEffect) | ||
| getOrCreate(subscriberDependencies, currentEffect, () => new Set()).add(subscribers) | ||
| } | ||
| if (!initialized) { | ||
| lastSourceValue = source() | ||
| lastSelectedValue = selectorFn(lastSourceValue) | ||
| valueState.set(lastSelectedValue) | ||
| initialized = true | ||
| } | ||
| return valueState() as R | ||
| return lastSelectedValue as R | ||
| } | ||
| } | ||
| // Helper for array updates | ||
| const updateArrayItem = <V>(arr: unknown[], index: number, value: V): unknown[] => { | ||
| const copy = [ | ||
| ...arr, | ||
| ] | ||
| copy[index] = value | ||
| return copy | ||
| // Returns the value if non-nullish, otherwise creates the appropriate container type | ||
| const ensureContainer = (value: unknown, nextKey: string | undefined): unknown => { | ||
| if (value != null) return value | ||
| if (nextKey !== undefined && !Number.isNaN(Number(nextKey))) return [] | ||
| return {} | ||
| } | ||
| // 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 ? [] : {} | ||
| } | ||
| const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], depth: number, value: V): O => { | ||
| const setValueAtPath = <V, O>(obj: O, pathSegments: string[], depth: number, value: V): O => { | ||
| if (depth >= pathSegments.length) { | ||
@@ -344,3 +341,3 @@ return value as unknown as O | ||
| if (obj === undefined || obj === null) { | ||
| if (obj == null) { | ||
| return setValueAtPath({} as O, pathSegments, depth, value) | ||
@@ -354,42 +351,38 @@ } | ||
| if (Array.isArray(obj)) { | ||
| const index = Number(currentKey) | ||
| const isArray = Array.isArray(obj) | ||
| const key = isArray ? Number(currentKey) : currentKey | ||
| if (depth === pathSegments.length - 1) { | ||
| return updateArrayItem(obj, index, value) as unknown as O | ||
| if (depth === pathSegments.length - 1) { | ||
| if (isArray) { | ||
| const copy = [ | ||
| ...(obj as unknown[]), | ||
| ] | ||
| copy[key as number] = value | ||
| return copy as unknown as O | ||
| } | ||
| const copy = [ | ||
| ...obj, | ||
| ] | ||
| const nextDepth = depth + 1 | ||
| const nextKey = pathSegments[nextDepth] | ||
| let nextValue = obj[index] | ||
| if (nextValue === undefined || nextValue === null) { | ||
| nextValue = nextKey === undefined ? {} : createContainer(nextKey) | ||
| const result = { | ||
| ...(obj as Record<string, unknown>), | ||
| } | ||
| copy[index] = setValueAtPath(nextValue, pathSegments, nextDepth, value) | ||
| return copy as unknown as O | ||
| result[key] = value | ||
| return result as unknown as O | ||
| } | ||
| const record = obj as Record<string | number, unknown> | ||
| if (depth === pathSegments.length - 1) { | ||
| return updateShallowProperty(record, currentKey, value) as unknown as O | ||
| } | ||
| const nextDepth = depth + 1 | ||
| const nextKey = pathSegments[nextDepth] | ||
| const source = isArray ? (obj as unknown[])[key as number] : (obj as Record<string | number, unknown>)[key] | ||
| let currentValue = record[currentKey] | ||
| if (currentValue === undefined || currentValue === null) { | ||
| currentValue = nextKey === undefined ? {} : createContainer(nextKey) | ||
| const nextValue = ensureContainer(source, nextKey) | ||
| if (isArray) { | ||
| const copy = [ | ||
| ...(obj as unknown[]), | ||
| ] | ||
| copy[key as number] = setValueAtPath(nextValue, pathSegments, nextDepth, value) | ||
| return copy as unknown as O | ||
| } | ||
| const result = { | ||
| ...record, | ||
| ...(obj as Record<string | number, unknown>), | ||
| } | ||
| result[currentKey] = setValueAtPath(currentValue, pathSegments, nextDepth, value) | ||
| result[key] = setValueAtPath(nextValue, pathSegments, nextDepth, value) | ||
| return result as unknown as O | ||
@@ -401,4 +394,4 @@ } | ||
| const extractPath = (): (string | number)[] => { | ||
| const pathCollector: (string | number)[] = [] | ||
| const extractPath = (): string[] => { | ||
| const pathCollector: string[] = [] | ||
| let tainted = false | ||
@@ -409,3 +402,3 @@ const proxy = new Proxy( | ||
| get: (_: object, prop: string | symbol): unknown => { | ||
| if (!tainted && (typeof prop === 'string' || typeof prop === 'number')) { | ||
| if (!tainted && typeof prop === 'string') { | ||
| if (DANGEROUS_KEYS.has(String(prop))) { | ||
@@ -412,0 +405,0 @@ tainted = true |
21320
-6.54%423
-1.63%