From 28efb44bd0a8f9bc060028a0ea49fe396a2c2bb3 Mon Sep 17 00:00:00 2001
From: Denny Trebbin <97166791+nerdalytics@users.noreply.github.com>
Date: Wed, 9 Apr 2025 00:48:11 +0200
Subject: [PATCH 1/7] feat: update API naming and add logo assets
---
README.md | 171 ++++++--
assets/beacon-logo.png | Bin 0 -> 10869 bytes
assets/beacon-logo.svg | 193 ++++++++
assets/beacon-logo@2.png | Bin 0 -> 31638 bytes
package.json | 2 +-
src/index.ts | 533 ++++++++++++++++-------
tsconfig.build.json => tsconfig.lts.json | 0
7 files changed, 696 insertions(+), 203 deletions(-)
create mode 100644 assets/beacon-logo.png
create mode 100644 assets/beacon-logo.svg
create mode 100644 assets/beacon-logo@2.png
rename tsconfig.build.json => tsconfig.lts.json (100%)
diff --git a/README.md b/README.md
index 4f37f0d..27d2068 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,15 @@
# Beacon
-A lightweight reactive signal library for Node.js backends. Enables reactive state management with automatic dependency tracking and efficient updates for server-side applications.
+
+
+ A lightweight reactive state library for Node.js backends. Enables reactive state management with automatic dependency tracking and efficient updates for server-side applications.
+
+
+
## Table of Contents
@@ -8,11 +17,13 @@ A lightweight reactive signal library for Node.js backends. Enables reactive sta
- [Installation](#installation)
- [Usage](#usage)
- [API](#api)
- - [state](#statetinitialvalue-t-signalt)
- - [derived](#derivedfn--t-signalt)
+ - [state](#statetinitialvalue-t-statet)
+ - [derive](#derivefn--t-readonlyt)
- [effect](#effectfn--void--void)
- [batch](#batchfn--t-t)
- - [selector](#selectorsource-signalt-selectorfn-state-t--r-equalityfn-a-r-b-r--boolean-signalr)
+ - [select](#selectsource-readonlyt-selectorfn-state-t--r-equalityfn-a-r-b-r--boolean-readonlyr)
+ - [readonly](#readonlystate-statet-readonlyt)
+ - [protectedState](#protectedstateinitialvalue-t-readonlyt-writemethodst)
- [Development](#development)
- [Node.js LTS Compatibility](#nodejs-lts-compatibility)
- [Key Differences vs TC39 Proposal](#key-differences-between-my-library-and-the-tc39-proposal)
@@ -22,14 +33,15 @@ A lightweight reactive signal library for Node.js backends. Enables reactive sta
## Features
-- ๐ **Reactive signals** - Create reactive values that automatically track dependencies
-- ๐งฎ **Computed values** - Derive values from other signals with automatic updates
-- ๐ **Fine-grained reactivity** - Dependencies are tracked precisely at the signal level
+- ๐ **Reactive state** - Create reactive values that automatically track dependencies
+- ๐งฎ **Computed values** - Derive values from other states with automatic updates
+- ๐ **Fine-grained reactivity** - Dependencies are tracked precisely at the state level
- ๐๏ธ **Efficient updates** - Only recompute values when dependencies change
- ๐ฆ **Batched updates** - Group multiple updates for performance
- ๐ช **Targeted subscriptions** - Select and subscribe to specific parts of state objects
- ๐งน **Automatic cleanup** - Effects and computations automatically clean up dependencies
- ๐ **Cycle handling** - Safely manages cyclic dependencies without crashing
+- ๐จ **Infinite loop detection** - Automatically detects and prevents infinite update loops
- ๐ ๏ธ **TypeScript-first** - Full TypeScript support with generics
- ๐ชถ **Lightweight** - Zero dependencies, < 200 LOC
- โ
**Node.js compatibility** - Works with Node.js LTS v20+ and v22+
@@ -43,11 +55,11 @@ npm install @nerdalytics/beacon
## Usage
```typescript
-import { state, derived, effect, batch, selector } from "@nerdalytics/beacon";
+import { state, derive, effect, batch, select, readonly, protectedState } from "@nerdalytics/beacon";
// Create reactive state
const count = state(0);
-const doubled = derived(() => count() * 2);
+const doubled = derive(() => count() * 2);
// Read values
console.log(count()); // => 0
@@ -75,9 +87,9 @@ batch(() => {
});
// => "Count is 20, doubled is 40" (only once)
-// Using selector to subscribe to specific parts of state
+// Using select to subscribe to specific parts of state
const user = state({ name: "Alice", age: 30, email: "alice@example.com" });
-const nameSelector = selector(user, u => u.name);
+const nameSelector = select(user, u => u.name);
effect(() => {
console.log(`Name changed: ${nameSelector()}`);
@@ -94,17 +106,44 @@ user.update(u => ({ ...u, age: 31 })); // No effect triggered
// Unsubscribe the effect to stop it from running on future updates
// and clean up all its internal subscriptions
unsubscribe();
+
+// Using readonly to create a read-only view
+const counter = state(0);
+const readonlyCounter = readonly(counter);
+// readonlyCounter() works, but readonlyCounter.set() is not available
+
+// Using protectedState to separate read and write capabilities
+const [getUser, setUser] = protectedState({ name: 'Alice' });
+// getUser() works to read the state
+// setUser.set() and setUser.update() work to modify the state
+// but getUser has no mutation methods
+
+// Infinite loop detection example (would throw an error)
+try {
+ effect(() => {
+ const value = counter();
+ // The following would throw an error because it attempts to
+ // update a state that the effect depends on:
+ // "Infinite loop detected: effect() cannot update a state() it depends on!"
+ // counter.set(value + 1);
+
+ // Instead, use a safe pattern with proper dependencies:
+ console.log(`Current counter value: ${value}`);
+ });
+} catch (error) {
+ console.error('Prevented infinite loop:', error.message);
+}
```
## API
-### `state