Skip to content

Commit 7f87429

Browse files
authored
chore: bump version to 1000.3.3 (#114)
* chore: bump version to 1000.3.3 Patch release covering 8 issues: - #64, #65, #66, #67, #73 — core perf and cleanup - #76, #77 — benchmark script fixes - #112 — handbook root redirect fix * chore(deps): update all dependencies - @biomejs/biome 2.3.14 → 2.4.11 - @types/node 25.2.2 → 25.5.2 - npm 11.9.0 → 11.12.1 - npm-check-updates 19.3.2 → 20.0.0 - typescript 5.9.3 → 6.0.2 Remove deprecated tsconfig options for TS 6.0 compatibility: allowSyntheticDefaultImports, esModuleInterop, importHelpers, strict. Update LTS target/lib from ES2022 to ES2023. * docs(handbook): add v1000.3.3 and rewrite all migration pages Add v1000.3.3 version route with migration page, update latest to point to v1000.3.3, register in versions.ts and navigation.ts. Rewrite all 13 migration pages to consistently document only user-facing changes that affect the installed package. Removed references to CI workflows, tooling config, benchmark scripts, handbook changes, and internal variable names. Applied brand voice across all pages.
1 parent bf47004 commit 7f87429

37 files changed

Lines changed: 1602 additions & 141 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
[![license:mit](https://flat.badgen.net/static/license/MIT/blue)](https://github.com/nerdalytics/beacon/blob/trunk/LICENSE)
66
[![registry:npm:version](https://img.shields.io/npm/v/@nerdalytics/beacon.svg)](https://www.npmjs.com/package/@nerdalytics/beacon)
7-
[![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.0)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.0)
7+
[![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.3)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.3)
88

99
[![tech:nodejs](https://img.shields.io/badge/Node%20js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
1010
[![language:typescript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
33
"assist": {
44
"actions": {
55
"source": {

handbook/src/lib/navigation.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,13 @@ const versionConfigs: [
124124
[
125125
'/latest',
126126
{
127-
migration: 'v1000.3.1 \u2192 v1000.3.2',
127+
migration: 'v1000.3.2 \u2192 v1000.3.3',
128+
},
129+
],
130+
[
131+
'/v1000.3.3',
132+
{
133+
migration: 'v1000.3.2 \u2192 v1000.3.3',
128134
},
129135
],
130136
[

handbook/src/lib/versions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ export const versions: Version[] = [
2121
node: 'https://img.shields.io/badge/node-%3E%3D20.0.0-339933?style=flat-square&logo=nodedotjs&logoColor=white',
2222
},
2323
current: true,
24+
label: 'v1000.3.3',
25+
prefix: '/v1000.3.3',
26+
},
27+
{
28+
badges: {
29+
node: 'https://img.shields.io/badge/node-%3E%3D20.0.0-339933?style=flat-square&logo=nodedotjs&logoColor=white',
30+
},
31+
current: false,
2432
label: 'v1000.3.2',
2533
prefix: '/v1000.3.2',
2634
},

handbook/src/routes/latest/+page.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
<a href="https://pnpm.io/">
2424
<img src="https://img.shields.io/badge/pnpm-F69220?style=flat-square&logo=pnpm&logoColor=white" alt="pnpm" />
2525
</a>
26-
<a href="https://www.npmjs.com/package/@nerdalytics/beacon/v/1000.3.2">
27-
<img src="https://flat.badgen.net/static/npm/v1000.3.2/blue" alt="npm v1000.3.2" />
26+
<a href="https://www.npmjs.com/package/@nerdalytics/beacon/v/1000.3.3">
27+
<img src="https://flat.badgen.net/static/npm/v1000.3.3/blue" alt="npm v1000.3.3" />
2828
</a>
29-
<a href="https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.2">
30-
<img src="https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.2" alt="Socket" />
29+
<a href="https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.3">
30+
<img src="https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.3" alt="Socket" />
3131
</a>
3232
</div>
3333
<div class="mb-5 flex items-center gap-1.5 flex-wrap justify-center">
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
---
2-
title: v1000.3.1 → v1000.3.2
3-
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
44
---
55

66
No API changes. No breaking changes. Drop-in upgrade.
77

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.
99

1010
## What changed
1111

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.
1313

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.
1515

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.
2117

2218
## Upgrade
2319

2420
```bash
25-
npm install @nerdalytics/beacon@1000.3.2 --save-exact
21+
npm install @nerdalytics/beacon@1000.3.3 --save-exact
2622
```
2723

2824
No code changes required. Existing tests pass without modification.

handbook/src/routes/v1000.0.0/migration/+page.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: v1.0.0 → v1000.0.0
33
description: Migrating from Beacon v1.0.0 to v1000.0.0
44
---
55

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.
77

88
## Renamed: `derived``derive`
99

@@ -17,7 +17,7 @@ import { derive } from '@nerdalytics/beacon'
1717
const $doubled = derive(() => $count() * 2)
1818
```
1919

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.
2121

2222
## Renamed types: `Signal<T>``State<T>`
2323

@@ -36,14 +36,14 @@ v1000.0.0 introduces three type levels:
3636
| Type | Description |
3737
|------|-------------|
3838
| `State<T>` | Readable and writable. Has `()`, `.set()`, `.update()` |
39-
| `ReadOnlyState<T>` | Readable only. Just `()` |
39+
| `ReadOnlyState<T>` | Readable only. Has `()` |
4040
| `WriteableState<T>` | Writable only. Has `.set()`, `.update()` |
4141

4242
`derive()` and `select()` return `ReadOnlyState<T>`. `state()` returns `State<T>`.
4343

4444
## New: `select()`
4545

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.
4747

4848
```typescript
4949
import { state, select, effect } from '@nerdalytics/beacon'
@@ -63,11 +63,11 @@ $user.update((u) => ({ ...u, name: 'Grace' }))
6363
// => "Grace"
6464
```
6565

66-
An optional third argument accepts a custom equality function (defaults to `Object.is`).
66+
An optional third argument accepts a custom equality function. Defaults to `Object.is`.
6767

6868
## New: `readonlyState()`
6969

70-
Wraps a `State<T>` to hide its `.set()` and `.update()` methods. Useful when exposing state to consumers that should read but not write.
70+
Wraps a `State<T>` to hide `.set()` and `.update()`. Useful when exposing state to consumers that should read but not write.
7171

7272
```typescript
7373
import { state, readonlyState } from '@nerdalytics/beacon'
@@ -81,7 +81,7 @@ console.log($readOnly()) // => 0
8181

8282
## New: `protectedState()`
8383

84-
Returns a `[ReadOnlyState<T>, WriteableState<T>]` tuple. Separates read and write into distinct references.
84+
Returns a `[ReadOnlyState<T>, WriteableState<T>]` tuple. Separates read and write capabilities into distinct references.
8585

8686
```typescript
8787
import { protectedState } from '@nerdalytics/beacon'
@@ -95,7 +95,7 @@ console.log($get()) // => { name: "Grace" }
9595

9696
## Infinite loop detection
9797

98-
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:
9999

100100
```typescript
101101
const $count = state(0)
@@ -106,11 +106,11 @@ effect(() => {
106106
})
107107
```
108108

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!"`.
110110

111111
## Effect re-entrance prevention
112112

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.
114114

115115
## Deferred effect creation in batches
116116

@@ -128,4 +128,12 @@ In v1.0.0, effects created inside batches ran immediately.
128128

129129
## Internal architecture
130130

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.
132+
133+
## Upgrade
134+
135+
```bash
136+
npm install @nerdalytics/beacon@1000.0.0 --save-exact
137+
```
138+
139+
Search your codebase for `derived(` and replace with `derive(`. Update any `Signal<T>` type annotations to `State<T>`.

handbook/src/routes/v1000.1.0/migration/+page.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ title: v1000.0.0 → v1000.1.0
33
description: Migrating from Beacon v1000.0.0 to v1000.1.0
44
---
55

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.
77

88
## New: `lens()`
99

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()`.
1111

1212
```typescript
1313
import { state, lens, effect } from '@nerdalytics/beacon'
@@ -27,14 +27,7 @@ effect(() => console.log($host()))
2727
// => "0.0.0.0"
2828
```
2929

30-
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.
3831

3932
## Upgrade
4033

handbook/src/routes/v1000.1.1/migration/+page.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ title: v1000.1.0 → v1000.1.1
33
description: Migrating from Beacon v1000.1.0 to v1000.1.1
44
---
55

6-
v1000.1.1 is a patch release with no API changes.
6+
No API changes. No breaking changes. Drop-in upgrade.
77

8-
## Package entry point fix
8+
## What changed
99

1010
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.
1111

handbook/src/routes/v1000.2.0/migration/+page.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ v1000.2.0 is a non-breaking minor release. All existing code works without chang
77

88
## Custom equality functions
99

10-
`state()` and `protectedState()` now accept an optional second argumentan 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()`.
1111

1212
```typescript
1313
state(initialValue, equalityFn?)
1414
protectedState(initialValue, equalityFn?)
1515
```
1616
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).
1818
1919
### Use cases
2020
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
2424
2525
### Example
2626
@@ -39,11 +39,11 @@ effect(() => {
3939
})
4040
// runs = 1
4141

42-
// Structurally identical no notification
42+
// Structurally identical object, no notification fired
4343
$user.set({ name: 'Ada', age: 36 })
4444
// runs = 1 (still)
4545

46-
// Different value — notifies
46+
// Different value, subscribers notified
4747
$user.set({ name: 'Grace', age: 36 })
4848
// runs = 2
4949
```

0 commit comments

Comments
 (0)