@signal-tree/react 15.0.0-rc.7 → 15.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -8
- package/dist/index.js +1 -0
- package/dist/use-signal-tree.js +1 -1
- package/llms.txt +180 -0
- package/package.json +9 -3
- package/src/index.d.ts +10 -0
package/README.md
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@signal-tree/react`
|
|
2
2
|
|
|
3
3
|
React observation for SignalTree. It connects React's external-store lifecycle
|
|
4
4
|
to canonical SignalTree reads without copying state into React.
|
|
5
5
|
|
|
6
|
+
## Semantic Guidance
|
|
7
|
+
|
|
8
|
+
The canonical v15 model and composition guidance ships with this package as
|
|
9
|
+
[llms.txt](llms.txt). It explains the React facade rule, `link()`
|
|
10
|
+
relationships, persistence composition, and causal explanations as projections
|
|
11
|
+
rather than retained kernel facts.
|
|
12
|
+
|
|
6
13
|
## Install
|
|
7
14
|
|
|
8
15
|
```bash
|
|
9
|
-
npm install @signal-tree/react
|
|
16
|
+
npm install @signal-tree/react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
React 18 or 19 is required as a peer dependency. `@signal-tree/react` installs
|
|
20
|
+
the framework-neutral kernel as its dependency, so React applications should
|
|
21
|
+
construct and enhance trees through this package:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { entityMap, signalTree, useSignalTree } from '@signal-tree/react';
|
|
25
|
+
|
|
26
|
+
const tree = signalTree({ orders: entityMap<{ id: string; status: string }>() });
|
|
10
27
|
```
|
|
11
28
|
|
|
12
|
-
|
|
29
|
+
Framework-neutral libraries may import from `@signal-tree/kernel` directly.
|
|
13
30
|
|
|
14
31
|
## Observe A Projection
|
|
15
32
|
|
|
16
33
|
```tsx
|
|
17
|
-
import { useSignalTree } from '@signal-tree/react';
|
|
18
|
-
|
|
19
34
|
function OrderStatus({ tree, orderId }) {
|
|
20
35
|
const status = useSignalTree(tree, ($) => $.orders.byIdOrFail(orderId).status());
|
|
21
36
|
|
|
@@ -35,6 +50,13 @@ const state = useSignalTree(tree, ($) => $());
|
|
|
35
50
|
|
|
36
51
|
Use whole-root projection only when the component needs whole-root truth.
|
|
37
52
|
|
|
53
|
+
## Server Rendering
|
|
54
|
+
|
|
55
|
+
`useSignalTree()` reads the same canonical selector snapshot during server
|
|
56
|
+
rendering, so React SSR does not require a mirrored store or a separate server
|
|
57
|
+
adapter. Hydration should construct the tree from the same application state
|
|
58
|
+
used by the server render before mounting the client tree.
|
|
59
|
+
|
|
38
60
|
## Selector Contract
|
|
39
61
|
|
|
40
62
|
Selectors read synchronously from the supplied root location and must return an
|
|
@@ -67,6 +89,5 @@ SignalTree remains the only state authority. This package owns subscription,
|
|
|
67
89
|
cleanup, and React snapshot observation. It does not mirror state, expose write
|
|
68
90
|
APIs, own the tree lifecycle, or change SignalTree's causal semantics.
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
of the initial surface.
|
|
92
|
+
React Native validation, custom equality, shared cross-component subscriptions,
|
|
93
|
+
and first-party memoized selectors are not part of the initial surface.
|
package/dist/index.js
CHANGED
package/dist/use-signal-tree.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback,useSyncExternalStore}from"react";import{observeOwnerInvalidation}from"@signal-tree/kernel/adapter";function useSignalTree(owner,selector){const subscribe=useCallback(notify=>observeOwnerInvalidation(owner,notify),[owner]);const getSnapshot=useCallback(()=>selector(owner.$),[owner,selector]);return useSyncExternalStore(subscribe,getSnapshot)}export{useSignalTree};
|
|
1
|
+
import{useCallback,useSyncExternalStore}from"react";import{observeOwnerInvalidation}from"@signal-tree/kernel/adapter";function useSignalTree(owner,selector){const subscribe=useCallback(notify=>observeOwnerInvalidation(owner,notify),[owner]);const getSnapshot=useCallback(()=>selector(owner.$),[owner,selector]);return useSyncExternalStore(subscribe,getSnapshot,getSnapshot)}export{useSignalTree};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# SignalTree
|
|
2
|
+
|
|
3
|
+
SignalTree is framework-neutral reactive application state with causal
|
|
4
|
+
semantics. The public v15 construction model is
|
|
5
|
+
`signalTree(initialState, { derived, enhancers })`; state is read through
|
|
6
|
+
`tree.$`.
|
|
7
|
+
|
|
8
|
+
## What SignalTree is — and is not
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
IS: framework-independent consequential application state, stable subject
|
|
12
|
+
identity, authored versus external authority, coherent operation
|
|
13
|
+
boundaries, causal/restoration semantics, and link-based relationships
|
|
14
|
+
|
|
15
|
+
IS NOT: primarily an undo library, event log, query library, sync engine, or
|
|
16
|
+
persistence framework
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Undo/redo, event replay, query composition, live synchronization, and
|
|
20
|
+
persistence are all things applications correctly BUILD on top of SignalTree's
|
|
21
|
+
causal model — see "Composition patterns" below — but none of them is what
|
|
22
|
+
SignalTree is FOR. Describe SignalTree by its primary job (consequential state
|
|
23
|
+
with a causal model an application can trust), not by one thing that model
|
|
24
|
+
happens to make easy.
|
|
25
|
+
|
|
26
|
+
v15 ships four packages under the hyphenated `@signal-tree/*` scope. The
|
|
27
|
+
no-hyphen `@signaltree/*` scope is the separate v14 line: still maintained for
|
|
28
|
+
bug and security fixes (currently `14.1.3`), frozen on features, and not an
|
|
29
|
+
alias for anything under `@signal-tree/*`. Generate `@signal-tree/*` names for
|
|
30
|
+
new v15 code:
|
|
31
|
+
|
|
32
|
+
- `@signal-tree/kernel` — framework-neutral tree, `entityMap()`, causal turns,
|
|
33
|
+
links, and the `restoration()` / `transactions()` / `batching()` / `devTools()`
|
|
34
|
+
enhancers. Also `@signal-tree/kernel/adapter`, the realization SDK.
|
|
35
|
+
- `@signal-tree/angular` — the complete Angular application facade. **Angular
|
|
36
|
+
code imports `signalTree` and all other SignalTree APIs from here**. Terminal
|
|
37
|
+
state leaves are native `WritableSignal<T>` values and derived leaves are
|
|
38
|
+
native `Signal<T>` values. Adds `defineStore()` and `toWritableSignal()` for
|
|
39
|
+
adapting callable object branches or designating form ingress.
|
|
40
|
+
- `@signal-tree/react` — the complete React application facade. **React code
|
|
41
|
+
imports `signalTree`, markers, enhancers, and `useSignalTree(owner, selector)`
|
|
42
|
+
from here**; React observes the canonical kernel tree without copying it.
|
|
43
|
+
- `@signal-tree/vue` — the complete Vue application facade. **Vue code imports
|
|
44
|
+
`signalTree` and all other SignalTree APIs from here**. Terminal state leaves
|
|
45
|
+
are native `Ref<T>` values and derived leaves are `ComputedRef<T>` values.
|
|
46
|
+
|
|
47
|
+
Use `@signal-tree/kernel` directly only for framework-neutral TypeScript,
|
|
48
|
+
including reusable domain libraries. Framework facades forward the neutral
|
|
49
|
+
kernel surface by canonical identity; they do not duplicate semantic authority.
|
|
50
|
+
|
|
51
|
+
## Accessor grammar and terminal values
|
|
52
|
+
|
|
53
|
+
The neutral kernel exposes callable locations:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
location(); // read
|
|
57
|
+
location(nextValue); // replace the complete value
|
|
58
|
+
location((current) => nextValue); // derive the next complete value
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The root (`tree.$`) and object branches keep this callable whole-value grammar
|
|
62
|
+
in every facade. Terminal values use the framework's native carrier:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
angularTree.$.count();
|
|
66
|
+
angularTree.$.count.set(5);
|
|
67
|
+
angularTree.$.count.update((count) => count + 1);
|
|
68
|
+
|
|
69
|
+
vueTree.$.count.value;
|
|
70
|
+
vueTree.$.count.value = 5;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
React has no persistent signal primitive, so `@signal-tree/react` keeps neutral
|
|
74
|
+
locations and observes selected state through `useSignalTree(owner, selector)`.
|
|
75
|
+
EntityMap query and field leaves follow the same carrier rule; EntityMap command
|
|
76
|
+
methods such as `setAll()` and `updateOne()` do not change.
|
|
77
|
+
|
|
78
|
+
Plain objects normally become traversable branches. `leaf(value)` explicitly
|
|
79
|
+
ends topology so an object remains one atomic location. Callable values always
|
|
80
|
+
use `leaf()` because a bare function argument is the updater syntax:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const tree = signalTree({
|
|
84
|
+
range: leaf({ start: 0, end: 10 }),
|
|
85
|
+
callback: leaf((value: number) => console.log(value)),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
angularTree.$.range.set({ start: 5, end: 15 });
|
|
89
|
+
angularTree.$.callback.set((value) => persist(value));
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The wrapper is consumed at construction or invocation and never enters state,
|
|
93
|
+
snapshots, persistence, restoration, links, or causal payloads.
|
|
94
|
+
|
|
95
|
+
There is no v15 forms, persistence, validation, events, or realtime package;
|
|
96
|
+
those are application-owned. `.with()`, positional `derived`, and the
|
|
97
|
+
`stored`/`asyncSource`/`asyncQuery`/`form`/`status` markers were all removed.
|
|
98
|
+
|
|
99
|
+
## Migration Rule
|
|
100
|
+
|
|
101
|
+
Never design SignalTree around a legacy application's intermediate state.
|
|
102
|
+
|
|
103
|
+
1. Determine the canonical greenfield v15 architecture.
|
|
104
|
+
2. Implement and validate that architecture independently.
|
|
105
|
+
3. Migrate applications toward that target.
|
|
106
|
+
4. Prefer deleting obsolete concepts over adapting them.
|
|
107
|
+
5. Never add compatibility APIs merely to reduce migration work.
|
|
108
|
+
6. A migration may falsify the target architecture, but legacy usage does not
|
|
109
|
+
define it.
|
|
110
|
+
|
|
111
|
+
Do not create intermediate APIs intended to be removed later. Do not add
|
|
112
|
+
compatibility layers because a migration is large. Do not preserve old
|
|
113
|
+
ownership because moving it is inconvenient. Do not design framework adapters
|
|
114
|
+
from legacy application idioms. Do not optimize for minimal migration diff.
|
|
115
|
+
Optimize for the architecture applications should use five years from now.
|
|
116
|
+
|
|
117
|
+
`@signal-tree/kernel/adapter` is the SDK for realization ownership, not a
|
|
118
|
+
compatibility layer. New exports must be framework-neutral semantic facts owned
|
|
119
|
+
by the kernel and required by correct realizations.
|
|
120
|
+
|
|
121
|
+
## Framework Realization Rule
|
|
122
|
+
|
|
123
|
+
Framework packages may realize SignalTree truth for their runtime. They must
|
|
124
|
+
not create another state authority.
|
|
125
|
+
|
|
126
|
+
Never use process-global mutable framework installation merely to make a legacy
|
|
127
|
+
integration work if construction-bound ownership can express the long-term
|
|
128
|
+
architecture. A migration cannot determine realization ownership.
|
|
129
|
+
|
|
130
|
+
## Framework Ownership Ratchet
|
|
131
|
+
|
|
132
|
+
`@signal-tree/kernel` owns framework-independent SignalTree semantics.
|
|
133
|
+
`@signal-tree/kernel/adapter` owns only neutral ports for semantic questions
|
|
134
|
+
the kernel owns. Framework packages own their implementations, lifecycle,
|
|
135
|
+
diagnostics, schedulers, rendering behavior, primitive identity rules, and
|
|
136
|
+
quirks. Neutral naming does not establish neutral ownership.
|
|
137
|
+
|
|
138
|
+
Every new realization contract must state its SignalTree semantic job, provide
|
|
139
|
+
a neutral implementation, be implementable by a tiny framework-free fake, and
|
|
140
|
+
name the kernel authority deciding when and why it runs. Reject contracts that
|
|
141
|
+
exist only for one framework. If Angular, React, and Vue disappeared, the
|
|
142
|
+
contract must remain meaningful to SignalTree or another reactive runtime.
|
|
143
|
+
|
|
144
|
+
## Composition patterns
|
|
145
|
+
|
|
146
|
+
Several capabilities that look like missing features are compositions of
|
|
147
|
+
primitives that already ship — see `docs/guides/composition-recipes.md` for
|
|
148
|
+
the full recipes with executable-spec citations. Do not propose a new marker
|
|
149
|
+
or kernel API for any of these before reading it:
|
|
150
|
+
|
|
151
|
+
- a standard enhancer policy, a reusable entity-CRUD Ops base, a selection
|
|
152
|
+
read-model
|
|
153
|
+
- optimistic writes with server reconciliation (`transactions()`'s
|
|
154
|
+
pending/confirm/rollback lifecycle)
|
|
155
|
+
- staged/draft editing (an application-owned draft, one authored commit — no
|
|
156
|
+
`beginStage()` session API)
|
|
157
|
+
- one-shot loading (`external()`, no `link()` needed for a single fetch) versus
|
|
158
|
+
a persistent relationship with an external authority (`link()`'s three
|
|
159
|
+
composable directions: PULL/PUSH-IN/PUSH-OUT)
|
|
160
|
+
- accepted external truth is distinct from authored application work, so an
|
|
161
|
+
external write is not automatically a retained causal-history turn
|
|
162
|
+
- a human-readable explanation projected from the causal record (the
|
|
163
|
+
explanation is a PROJECTION of causal truth — the kernel does not store
|
|
164
|
+
prose, actor names, or timestamps merely to make one convenient)
|
|
165
|
+
|
|
166
|
+
`docs/guides/persistence-guide.md` is the `link()`-as-storage specialization
|
|
167
|
+
of the same model.
|
|
168
|
+
|
|
169
|
+
## Canonical Sources
|
|
170
|
+
|
|
171
|
+
- `AGENTS.md` — contributor and consumer rules
|
|
172
|
+
- `RELEASE-1.0.md` — v15 release invariants and current release state
|
|
173
|
+
- `README.md` — public package overview
|
|
174
|
+
- `packages/kernel/README.md` — kernel API and examples
|
|
175
|
+
- `packages/angular/README.md` — Angular realization
|
|
176
|
+
- `packages/react/README.md` — React observation
|
|
177
|
+
- `docs/guides/composition-recipes.md` — patterns built from existing primitives, no new API
|
|
178
|
+
- `docs/guides/persistence-guide.md` — the `link()`-as-storage recipe
|
|
179
|
+
- `docs/guides/migration-v14-v15.md` — `@signaltree/*` → `@signal-tree/*` migration (rename, consolidation, removed APIs)
|
|
180
|
+
- `docs/migration/post-rc1-workstream.md` — greenfield-first post-RC program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signal-tree/react",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.1.1",
|
|
4
4
|
"description": "React observation for SignalTree.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"src/**/*.d.ts",
|
|
22
22
|
"README.md",
|
|
23
23
|
"LICENSE",
|
|
24
|
-
"NOTICE"
|
|
24
|
+
"NOTICE",
|
|
25
|
+
"llms.txt"
|
|
25
26
|
],
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@signal-tree/kernel": "15.
|
|
28
|
+
"@signal-tree/kernel": "15.1.1"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -32,5 +33,10 @@
|
|
|
32
33
|
},
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/JBorgia/signal-tree.git",
|
|
40
|
+
"directory": "packages/react"
|
|
35
41
|
}
|
|
36
42
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@signal-tree/react` - React observation plus the complete SignalTree
|
|
3
|
+
* application surface.
|
|
4
|
+
*
|
|
5
|
+
* React observes framework-neutral SignalTree truth through
|
|
6
|
+
* `useSyncExternalStore`; it does not replace the kernel's tree carrier. React
|
|
7
|
+
* applications therefore construct, synchronize, and enhance trees through
|
|
8
|
+
* this package, then observe them with `useSignalTree`.
|
|
9
|
+
*/
|
|
10
|
+
export * from '@signal-tree/kernel';
|
|
1
11
|
export { useSignalTree } from './use-signal-tree.js';
|