You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: Migrating from Beacon v1000.3.1 to v1000.3.2
2
+
title: v1000.3.2 → v1000.3.3
3
+
description: Migrating from Beacon v1000.3.2 to v1000.3.3
4
4
---
5
5
6
6
No API changes. No breaking changes. Drop-in upgrade.
7
7
8
-
This release adds a proto-key denylist to lens path extraction. The public interface, behavior, and type signatures are identical to v1000.3.1.
8
+
This release removes internal allocation overhead in two areas. The public interface, behavior, and type signatures are identical to v1000.3.2.
9
9
10
10
## What changed
11
11
12
-
`lens()` now rejects `__proto__`, `constructor`, and `prototype` as path segments. If an accessor traverses one of these keys, the lens treats the path as invalid and silently ignores writes. Reads still reflect the source value.
12
+
`derive` and `select` previously created an internal `state` object to hold their computed value. They now use direct variable storage, eliminating the backing state allocation and its associated subscription machinery.
13
13
14
-
The guard runs once during path extraction via a `tainted` flag in the Proxy trap. When a dangerous key appears at any depth in the path, the entire path is discarded, not just the offending segment. This prevents orphaned child segments from writing to unintended properties.
14
+
The notification loop previously allocated a new `Set` on every flush cycle to track which subscribers had been notified. It now clears and reuses a single `Set` instance across flushes.
15
15
16
-
`lensSet` checks for an empty path and returns early, making the write a no-op. `lensUpdate` delegates to `lensSet`, so both write methods are covered.
17
-
18
-
## Why
19
-
20
-
Prototype pollution through `constructor.prototype` is a known attack vector with multiple CVEs in libraries like lodash, protobuf.js, and tree-kit. Beacon's spread-then-assign pattern in `setValueAtPath` is safe on current V8, but that safety comes from engine behavior, not explicit guards. The denylist makes the safety explicit.
16
+
Both changes reduce GC pressure in applications with many derived values or frequent state updates.
Copy file name to clipboardExpand all lines: handbook/src/routes/v1000.0.0/migration/+page.md
+19-11Lines changed: 19 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@ title: v1.0.0 → v1000.0.0
3
3
description: Migrating from Beacon v1.0.0 to v1000.0.0
4
4
---
5
5
6
-
v1000.0.0 is a complete rewrite. The core API pattern stays the same — callable signals with `.set()` and `.update()` — but types, naming, and internals changed. This page covers every breaking change and new addition.
6
+
v1000.0.0 is a complete rewrite. The core API pattern stays the same (callable signals with `.set()` and `.update()`), but types, naming, and runtime behavior changed. This page covers every breaking change and new addition.
7
7
8
8
## Renamed: `derived` → `derive`
9
9
@@ -17,7 +17,7 @@ import { derive } from '@nerdalytics/beacon'
17
17
const $doubled =derive(() =>$count() *2)
18
18
```
19
19
20
-
`derive()`also returns `ReadOnlyState<T>` instead of `Signal<T>`. You can no longer call `.set()` or `.update()` on a derived value.
20
+
`derive()` returns `ReadOnlyState<T>` instead of `Signal<T>`. Calling `.set()` or `.update()` on a derived value is no longer possible.
21
21
22
22
## Renamed types: `Signal<T>` → `State<T>`
23
23
@@ -36,14 +36,14 @@ v1000.0.0 introduces three type levels:
36
36
| Type | Description |
37
37
|------|-------------|
38
38
|`State<T>`| Readable and writable. Has `()`, `.set()`, `.update()`|
39
-
|`ReadOnlyState<T>`| Readable only. Just`()`|
39
+
|`ReadOnlyState<T>`| Readable only. Has`()`|
40
40
|`WriteableState<T>`| Writable only. Has `.set()`, `.update()`|
41
41
42
42
`derive()` and `select()` return `ReadOnlyState<T>`. `state()` returns `State<T>`.
43
43
44
44
## New: `select()`
45
45
46
-
Subscribe to a computed slice of state. The effect only re-runs when the selected value changes, not when other properties on the source change.
46
+
Subscribe to a computed slice of state. The subscriber only re-runs when the selected value changes, not when other properties on the source change.
v1000.0.0 detects when an effect writes to a state it reads and throws immediately:
98
+
Effects that write to a state they read now throw immediately instead of looping:
99
99
100
100
```typescript
101
101
const $count =state(0)
@@ -106,11 +106,11 @@ effect(() => {
106
106
})
107
107
```
108
108
109
-
In v1.0.0 this would loop until the `processEffects` queue drained. In v1000.0.0 it throws `"Infinite loop detected: effect() cannot update a state() it depends on!"`.
109
+
In v1.0.0 this would loop until the effect queue drained. In v1000.0.0 it throws `"Infinite loop detected: effect() cannot update a state() it depends on!"`.
110
110
111
111
## Effect re-entrance prevention
112
112
113
-
If an effect is already executing, re-entry is silently skipped. In v1.0.0, concurrent effect execution was prevented only by the `updateInProgress` flag on `processEffects`. In v1000.0.0, each effect tracks its own active state via an `activeSubscribers` set.
113
+
If an effect is already executing, re-entry is silently skipped. Each effect tracks its own active state, so two distinct effects can run concurrently without interference.
114
114
115
115
## Deferred effect creation in batches
116
116
@@ -128,4 +128,12 @@ In v1.0.0, effects created inside batches ran immediately.
128
128
129
129
## Internal architecture
130
130
131
-
The implementation moved from module-level closures (~200 LOC) to a `StateImpl` class with static methods (~427 LOC). The public API is unchanged — `state()`, `derive()`, `effect()`, `batch()` are still top-level function exports that delegate to `StateImpl`.
131
+
The internals were restructured from module-level closures to a class-based implementation. The public API surface is unchanged: `state()`, `derive()`, `effect()`, `batch()` remain top-level function exports.
Copy file name to clipboardExpand all lines: handbook/src/routes/v1000.1.0/migration/+page.md
+3-10Lines changed: 3 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,11 +3,11 @@ title: v1000.0.0 → v1000.1.0
3
3
description: Migrating from Beacon v1000.0.0 to v1000.1.0
4
4
---
5
5
6
-
v1000.1.0 is a non-breaking minor release. All existing v1000.0.0 code works without modification.
6
+
No breaking changes. All existing v1000.0.0 code works without modification.
7
7
8
8
## New: `lens()`
9
9
10
-
Creates a writable two-way binding to a nested property of a state object. Unlike `select()`, which returns a read-only `ReadOnlyState<R>`, `lens()` returns a writable`State<K>`.
10
+
Creates a writable two-way binding to a nested property of a state object. Unlike `select()`, which returns `ReadOnlyState<R>`, `lens()` returns a full`State<K>` with `.set()` and `.update()`.
Internally, `lens()` extracts the property path from the accessor via a Proxy trap at creation time, syncs to the source via an effect, and overrides `.set()` to immutably update the source at that path.
31
-
32
-
## No breaking changes
33
-
34
-
- All existing exports remain unchanged
35
-
- No renamed or removed APIs
36
-
- No behavioral changes to existing primitives
37
-
- JSDoc comment improvements in internals (no public impact)
30
+
The property path is extracted from the accessor via a Proxy trap at creation time. Writes propagate back to the source as immutable updates.
Copy file name to clipboardExpand all lines: handbook/src/routes/v1000.1.1/migration/+page.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,9 +3,9 @@ title: v1000.1.0 → v1000.1.1
3
3
description: Migrating from Beacon v1000.1.0 to v1000.1.1
4
4
---
5
5
6
-
v1000.1.1 is a patch release with no API changes.
6
+
No API changes. No breaking changes. Drop-in upgrade.
7
7
8
-
## Package entry point fix
8
+
## What changed
9
9
10
10
The `main` and `types` fields in `package.json` were corrected from `dist/index.js` to `dist/src/index.js`. This fixes module resolution for consumers that rely on these fields rather than the `exports` map.
Copy file name to clipboardExpand all lines: handbook/src/routes/v1000.2.0/migration/+page.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,20 +7,20 @@ v1000.2.0 is a non-breaking minor release. All existing code works without chang
7
7
8
8
## Custom equality functions
9
9
10
-
`state()` and `protectedState()` now accept an optional second argument — an equality function that replaces `Object.is` for the same-value check in `.set()`:
10
+
`state()` and `protectedState()` now accept an optional second argument: an equality function that replaces `Object.is` for the same-value check in `.set()`.
11
11
12
12
```typescript
13
13
state(initialValue, equalityFn?)
14
14
protectedState(initialValue, equalityFn?)
15
15
```
16
16
17
-
If omitted, behavior is identical to v1000.1.1 (`Object.is` comparison). The equality function receives the current value and the incoming value, and returns `true` if they should be considered equal (i.e., skip notification).
17
+
If omitted, behavior is identical to v1000.1.1 (`Object.is` comparison). The equality function receives the current value and the incoming value, and returns `true` if they should be considered equal (skip notification).
18
18
19
19
### Use cases
20
20
21
-
- **Deep equality for objects** — avoid notifications when a structurally identical object is set
22
-
- **Structural comparison for arrays** — compare by contents rather than reference
23
-
- **Domain-specific equivalence** — ignore irrelevant fields when deciding whether state changed
21
+
- **Deep equality for objects**: avoid notifications when a structurally identical object is set
22
+
- **Structural comparison for arrays**: compare by contents rather than reference
23
+
- **Domain-specific equivalence**: ignore irrelevant fields when deciding whether state changed
24
24
25
25
### Example
26
26
@@ -39,11 +39,11 @@ effect(() => {
39
39
})
40
40
// runs = 1
41
41
42
-
// Structurally identical — no notification
42
+
// Structurally identical object, no notification fired
0 commit comments