@signal-tree/angular 15.0.0-rc.12 → 15.0.0-rc.14
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 +40 -13
- package/dist/index.js +1 -1
- package/dist/lib/observation-adapter.js +1 -0
- package/dist/lib/to-writable-signal.js +1 -1
- package/llms.txt +177 -0
- package/package.json +4 -3
- package/src/index.d.ts +10 -29
- package/src/lib/carrier.d.ts +8 -19
- package/src/lib/define-store.d.ts +6 -9
- package/src/lib/observation-adapter.d.ts +3 -0
- package/src/lib/to-writable-signal.d.ts +15 -11
- package/dist/lib/angular-realization.js +0 -1
- package/dist/lib/scalar-leaf-realization.js +0 -1
- package/src/lib/angular-realization.d.ts +0 -7
- package/src/lib/scalar-leaf-realization.d.ts +0 -15
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@signal-tree/angular`
|
|
2
2
|
|
|
3
|
-
Angular realization
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Angular-native SignalTree realization. State, identity, entity behavior, and
|
|
4
|
+
causal semantics remain in `@signal-tree/kernel`; terminal leaves are native
|
|
5
|
+
Angular signals and work directly in templates and `computed()`.
|
|
6
|
+
|
|
7
|
+
## Semantic Guidance
|
|
8
|
+
|
|
9
|
+
The canonical v15 model and composition guidance ships with this package as
|
|
10
|
+
[llms.txt](llms.txt). It explains the Angular facade rule, `link()`
|
|
11
|
+
relationships, persistence composition, and causal explanations as projections
|
|
12
|
+
rather than retained kernel facts.
|
|
6
13
|
|
|
7
14
|
## Install
|
|
8
15
|
|
|
@@ -11,13 +18,15 @@ npm install @signal-tree/angular
|
|
|
11
18
|
```
|
|
12
19
|
|
|
13
20
|
`@signal-tree/angular` installs `@signal-tree/kernel` as an exact dependency.
|
|
14
|
-
Angular itself remains a peer dependency supplied by the application.
|
|
21
|
+
Angular itself remains a peer dependency supplied by the application. This is
|
|
22
|
+
the complete SignalTree facade for Angular applications: import `signalTree`,
|
|
23
|
+
markers, enhancers, and types from this package rather than mixing kernel
|
|
24
|
+
imports into Angular application code.
|
|
15
25
|
|
|
16
26
|
Angular applications should construct state through this package, not through
|
|
17
27
|
the neutral kernel package:
|
|
18
28
|
|
|
19
29
|
```ts
|
|
20
|
-
import { computed } from '@angular/core';
|
|
21
30
|
import { asReadonly, batching, entityMap, signalTree } from '@signal-tree/angular';
|
|
22
31
|
|
|
23
32
|
type User = { id: number; name: string };
|
|
@@ -30,13 +39,13 @@ const tree = signalTree(
|
|
|
30
39
|
{
|
|
31
40
|
enhancers: [batching()],
|
|
32
41
|
derived: ($) => {
|
|
33
|
-
const selected =
|
|
42
|
+
const selected = () => {
|
|
34
43
|
const id = $.selectedId();
|
|
35
44
|
return id === null ? null : $.users.byId(id)?.() ?? null;
|
|
36
|
-
}
|
|
45
|
+
};
|
|
37
46
|
return {
|
|
38
47
|
selected,
|
|
39
|
-
selectedName:
|
|
48
|
+
selectedName: () => selected()?.name ?? 'None',
|
|
40
49
|
};
|
|
41
50
|
},
|
|
42
51
|
}
|
|
@@ -47,10 +56,28 @@ reader.$.selectedName();
|
|
|
47
56
|
```
|
|
48
57
|
|
|
49
58
|
There is one construction grammar: state, enhancers, and one derived factory are
|
|
50
|
-
declared together in `signalTree(...)`. Derived values
|
|
51
|
-
|
|
59
|
+
declared together in `signalTree(...)`. Derived values are zero-argument recipes
|
|
60
|
+
that SignalTree memoizes as native readonly Angular signals.
|
|
61
|
+
|
|
62
|
+
Use `defineStore` for Angular dependency injection. State leaves already have
|
|
63
|
+
native Angular signal identity and methods:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
tree.$.selectedId.set(42);
|
|
67
|
+
tree.$.selectedId.update((id) => (id ?? 0) + 1);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`toWritableSignal()` remains useful for adapting a callable root or object
|
|
71
|
+
branch to APIs such as Signal Forms. Passing an ordinary leaf without options
|
|
72
|
+
returns that same `WritableSignal`; `{ undoable: true }` creates a distinct
|
|
73
|
+
ingress that designates writes for restoration:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const profileModel = toWritableSignal(tree.$.profile, injector, {
|
|
77
|
+
undoable: true,
|
|
78
|
+
});
|
|
79
|
+
```
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
branch must cross an Angular writable-signal boundary. Application components
|
|
81
|
+
Application components
|
|
55
82
|
should normally receive a read-only `$` plus explicit operation services for
|
|
56
83
|
writes and asynchronous work.
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ANGULAR_OBSERVATION_ADAPTER}from"./lib/observation-adapter.js";import{createSignalTreeFactory}from"@signal-tree/kernel/adapter";import{asReadonly as asReadonly$1}from"@signal-tree/kernel";export*from"@signal-tree/kernel";import{defineStore}from"./lib/define-store.js";import{toWritableSignal}from"./lib/to-writable-signal.js";const signalTree=createSignalTreeFactory(ANGULAR_OBSERVATION_ADAPTER);const asReadonly=asReadonly$1;export{asReadonly,defineStore,signalTree,toWritableSignal};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{linkedSignal,untracked,computed,signal}from"@angular/core";const ANGULAR_OBSERVATION_ADAPTER={createToken(){const revision=signal(0);return{observe:()=>void revision(),invalidate:()=>revision.update(value=>value+1)}},createWritableCell:read=>{const cell=signal(read());const publish=cell.set.bind(cell);return{cell,peek:read,token:{observe:()=>void cell(),invalidate:()=>publish(read())}}},createReadonlyCell:compute=>computed(compute),createWritableProjection:compute=>{const cell=linkedSignal(compute);return{cell,peek:()=>untracked(cell)}},runInvalidationGroup(run){run()}};export{ANGULAR_OBSERVATION_ADAPTER};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{signal,runInInjectionContext,effect}from"@angular/core";import{withRestorationDesignation}from"@signal-tree/kernel/adapter";function toWritableSignal(node,injector,options){const sig=signal(node());const originalSet=sig.set.bind(sig);const runner=()=>{originalSet(node())};if(injector){runInInjectionContext(injector,()=>effect(runner))}else{try{effect(runner)}catch{if(typeof ngDevMode==="undefined"||ngDevMode){console.warn("[SignalTree] toWritableSignal called without injection context; pass Injector for reactivity.")}}}
|
|
1
|
+
import{isSignal,signal,runInInjectionContext,effect}from"@angular/core";import{withRestorationDesignation,isNodeAccessor,replaceLocation}from"@signal-tree/kernel/adapter";function toWritableSignal(node,injector,options){if(isSignal(node)&&!options?.undoable){return node}const sig=signal(node());const originalSet=sig.set.bind(sig);const runner=()=>{originalSet(node())};if(injector){runInInjectionContext(injector,()=>effect(runner))}else{try{effect(runner)}catch{if(typeof ngDevMode==="undefined"||ngDevMode){console.warn("[SignalTree] toWritableSignal called without injection context; pass Injector for reactivity.")}}}const applyWrite=write=>{if(options?.undoable){withRestorationDesignation(write)}else{write()}originalSet(node())};sig.set=value=>{applyWrite(()=>{if(isSignal(node))node.set(value);else if(isNodeAccessor(node))node(value);else replaceLocation(node,value)})};sig.update=updater=>{applyWrite(()=>{if(isSignal(node))node.update(updater);else if(isNodeAccessor(node))node(updater);else node(updater)})};return sig}export{toWritableSignal};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
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 pre-15
|
|
27
|
+
`@signaltree/*` scope, no hyphen, stopped at 14.1.1):
|
|
28
|
+
|
|
29
|
+
- `@signal-tree/kernel` — framework-neutral tree, `entityMap()`, causal turns,
|
|
30
|
+
links, and the `restoration()` / `transactions()` / `batching()` / `devTools()`
|
|
31
|
+
enhancers. Also `@signal-tree/kernel/adapter`, the realization SDK.
|
|
32
|
+
- `@signal-tree/angular` — the complete Angular application facade. **Angular
|
|
33
|
+
code imports `signalTree` and all other SignalTree APIs from here**. Terminal
|
|
34
|
+
state leaves are native `WritableSignal<T>` values and derived leaves are
|
|
35
|
+
native `Signal<T>` values. Adds `defineStore()` and `toWritableSignal()` for
|
|
36
|
+
adapting callable object branches or designating form ingress.
|
|
37
|
+
- `@signal-tree/react` — the complete React application facade. **React code
|
|
38
|
+
imports `signalTree`, markers, enhancers, and `useSignalTree(owner, selector)`
|
|
39
|
+
from here**; React observes the canonical kernel tree without copying it.
|
|
40
|
+
- `@signal-tree/vue` — the complete Vue application facade. **Vue code imports
|
|
41
|
+
`signalTree` and all other SignalTree APIs from here**. Terminal state leaves
|
|
42
|
+
are native `Ref<T>` values and derived leaves are `ComputedRef<T>` values.
|
|
43
|
+
|
|
44
|
+
Use `@signal-tree/kernel` directly only for framework-neutral TypeScript,
|
|
45
|
+
including reusable domain libraries. Framework facades forward the neutral
|
|
46
|
+
kernel surface by canonical identity; they do not duplicate semantic authority.
|
|
47
|
+
|
|
48
|
+
## Accessor grammar and terminal values
|
|
49
|
+
|
|
50
|
+
The neutral kernel exposes callable locations:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
location(); // read
|
|
54
|
+
location(nextValue); // replace the complete value
|
|
55
|
+
location((current) => nextValue); // derive the next complete value
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The root (`tree.$`) and object branches keep this callable whole-value grammar
|
|
59
|
+
in every facade. Terminal values use the framework's native carrier:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
angularTree.$.count();
|
|
63
|
+
angularTree.$.count.set(5);
|
|
64
|
+
angularTree.$.count.update((count) => count + 1);
|
|
65
|
+
|
|
66
|
+
vueTree.$.count.value;
|
|
67
|
+
vueTree.$.count.value = 5;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
React has no persistent signal primitive, so `@signal-tree/react` keeps neutral
|
|
71
|
+
locations and observes selected state through `useSignalTree(owner, selector)`.
|
|
72
|
+
EntityMap query and field leaves follow the same carrier rule; EntityMap command
|
|
73
|
+
methods such as `setAll()` and `updateOne()` do not change.
|
|
74
|
+
|
|
75
|
+
Plain objects normally become traversable branches. `leaf(value)` explicitly
|
|
76
|
+
ends topology so an object remains one atomic location. Callable values always
|
|
77
|
+
use `leaf()` because a bare function argument is the updater syntax:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const tree = signalTree({
|
|
81
|
+
range: leaf({ start: 0, end: 10 }),
|
|
82
|
+
callback: leaf((value: number) => console.log(value)),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
angularTree.$.range.set({ start: 5, end: 15 });
|
|
86
|
+
angularTree.$.callback.set((value) => persist(value));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The wrapper is consumed at construction or invocation and never enters state,
|
|
90
|
+
snapshots, persistence, restoration, links, or causal payloads.
|
|
91
|
+
|
|
92
|
+
There is no v15 forms, persistence, validation, events, or realtime package;
|
|
93
|
+
those are application-owned. `.with()`, positional `derived`, and the
|
|
94
|
+
`stored`/`asyncSource`/`asyncQuery`/`form`/`status` markers were all removed.
|
|
95
|
+
|
|
96
|
+
## Migration Rule
|
|
97
|
+
|
|
98
|
+
Never design SignalTree around a legacy application's intermediate state.
|
|
99
|
+
|
|
100
|
+
1. Determine the canonical greenfield v15 architecture.
|
|
101
|
+
2. Implement and validate that architecture independently.
|
|
102
|
+
3. Migrate applications toward that target.
|
|
103
|
+
4. Prefer deleting obsolete concepts over adapting them.
|
|
104
|
+
5. Never add compatibility APIs merely to reduce migration work.
|
|
105
|
+
6. A migration may falsify the target architecture, but legacy usage does not
|
|
106
|
+
define it.
|
|
107
|
+
|
|
108
|
+
Do not create intermediate APIs intended to be removed later. Do not add
|
|
109
|
+
compatibility layers because a migration is large. Do not preserve old
|
|
110
|
+
ownership because moving it is inconvenient. Do not design framework adapters
|
|
111
|
+
from legacy application idioms. Do not optimize for minimal migration diff.
|
|
112
|
+
Optimize for the architecture applications should use five years from now.
|
|
113
|
+
|
|
114
|
+
`@signal-tree/kernel/adapter` is the SDK for realization ownership, not a
|
|
115
|
+
compatibility layer. New exports must be framework-neutral semantic facts owned
|
|
116
|
+
by the kernel and required by correct realizations.
|
|
117
|
+
|
|
118
|
+
## Framework Realization Rule
|
|
119
|
+
|
|
120
|
+
Framework packages may realize SignalTree truth for their runtime. They must
|
|
121
|
+
not create another state authority.
|
|
122
|
+
|
|
123
|
+
Never use process-global mutable framework installation merely to make a legacy
|
|
124
|
+
integration work if construction-bound ownership can express the long-term
|
|
125
|
+
architecture. A migration cannot determine realization ownership.
|
|
126
|
+
|
|
127
|
+
## Framework Ownership Ratchet
|
|
128
|
+
|
|
129
|
+
`@signal-tree/kernel` owns framework-independent SignalTree semantics.
|
|
130
|
+
`@signal-tree/kernel/adapter` owns only neutral ports for semantic questions
|
|
131
|
+
the kernel owns. Framework packages own their implementations, lifecycle,
|
|
132
|
+
diagnostics, schedulers, rendering behavior, primitive identity rules, and
|
|
133
|
+
quirks. Neutral naming does not establish neutral ownership.
|
|
134
|
+
|
|
135
|
+
Every new realization contract must state its SignalTree semantic job, provide
|
|
136
|
+
a neutral implementation, be implementable by a tiny framework-free fake, and
|
|
137
|
+
name the kernel authority deciding when and why it runs. Reject contracts that
|
|
138
|
+
exist only for one framework. If Angular, React, and Vue disappeared, the
|
|
139
|
+
contract must remain meaningful to SignalTree or another reactive runtime.
|
|
140
|
+
|
|
141
|
+
## Composition patterns
|
|
142
|
+
|
|
143
|
+
Several capabilities that look like missing features are compositions of
|
|
144
|
+
primitives that already ship — see `docs/guides/composition-recipes.md` for
|
|
145
|
+
the full recipes with executable-spec citations. Do not propose a new marker
|
|
146
|
+
or kernel API for any of these before reading it:
|
|
147
|
+
|
|
148
|
+
- a standard enhancer policy, a reusable entity-CRUD Ops base, a selection
|
|
149
|
+
read-model
|
|
150
|
+
- optimistic writes with server reconciliation (`transactions()`'s
|
|
151
|
+
pending/confirm/rollback lifecycle)
|
|
152
|
+
- staged/draft editing (an application-owned draft, one authored commit — no
|
|
153
|
+
`beginStage()` session API)
|
|
154
|
+
- one-shot loading (`external()`, no `link()` needed for a single fetch) versus
|
|
155
|
+
a persistent relationship with an external authority (`link()`'s three
|
|
156
|
+
composable directions: PULL/PUSH-IN/PUSH-OUT)
|
|
157
|
+
- accepted external truth is distinct from authored application work, so an
|
|
158
|
+
external write is not automatically a retained causal-history turn
|
|
159
|
+
- a human-readable explanation projected from the causal record (the
|
|
160
|
+
explanation is a PROJECTION of causal truth — the kernel does not store
|
|
161
|
+
prose, actor names, or timestamps merely to make one convenient)
|
|
162
|
+
|
|
163
|
+
`docs/guides/persistence-guide.md` is the `link()`-as-storage specialization
|
|
164
|
+
of the same model.
|
|
165
|
+
|
|
166
|
+
## Canonical Sources
|
|
167
|
+
|
|
168
|
+
- `AGENTS.md` — contributor and consumer rules
|
|
169
|
+
- `RELEASE-1.0.md` — v15 release invariants and current release state
|
|
170
|
+
- `README.md` — public package overview
|
|
171
|
+
- `packages/kernel/README.md` — kernel API and examples
|
|
172
|
+
- `packages/angular/README.md` — Angular realization
|
|
173
|
+
- `packages/react/README.md` — React observation
|
|
174
|
+
- `docs/guides/composition-recipes.md` — patterns built from existing primitives, no new API
|
|
175
|
+
- `docs/guides/persistence-guide.md` — the `link()`-as-storage recipe
|
|
176
|
+
- `docs/guides/migration-v14-v15.md` — `@signaltree/*` → `@signal-tree/*` migration (rename, consolidation, removed APIs)
|
|
177
|
+
- `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/angular",
|
|
3
|
-
"version": "15.0.0-rc.
|
|
3
|
+
"version": "15.0.0-rc.14",
|
|
4
4
|
"description": "Angular realization 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.0.0-rc.
|
|
28
|
+
"@signal-tree/kernel": "15.0.0-rc.14"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
|
package/src/index.d.ts
CHANGED
|
@@ -1,37 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@signal-tree/angular` — SignalTree realized with native Angular signals.
|
|
3
|
-
*
|
|
4
|
-
* REALIZATION IS CONSTRUCTION-BOUND. This package exports the same semantic
|
|
5
|
-
* factory grammar as the kernel, bound once to Angular's native mechanisms.
|
|
6
|
-
*
|
|
7
|
-
* That ordering is the fix for a MEASURED defect: entity APIs used without
|
|
8
|
-
* first calling `signalTree()` silently received neutral kernel cells — no
|
|
9
|
-
* `isSignal`, no `asReadonly`, no dependency tracking. The old monolith
|
|
10
|
-
* installed Angular as a side effect of importing `signal-tree.ts`, so the
|
|
11
|
-
* guarantee depended on which module a consumer happened to touch first.
|
|
12
|
-
*
|
|
13
|
-
* REALIZATION IS SELECTED AT CONSTRUCTION AND OWNED FOR THE TREE LIFETIME.
|
|
14
|
-
*/
|
|
1
|
+
/** Angular observation plus the complete SignalTree application surface. */
|
|
15
2
|
import './lib/carrier.js';
|
|
16
|
-
import type { EntityNodeOf, EntitySignalOf, EntitySignalWithSlicesOf,
|
|
17
|
-
/**
|
|
18
|
-
* The kernel's `signalTree`, DECLARED with Angular's carrier.
|
|
19
|
-
*
|
|
20
|
-
* Same kernel construction authority, package-bound to Angular mechanisms.
|
|
21
|
-
*/
|
|
3
|
+
import type { AccessibleNodeOf, EntityNodeOf, EntitySignalOf, EntitySignalWithSlicesOf, ISignalTreeOf, LeafOf, ReadonlyStoreOf, ReadonlyViewOf, SignalTreeFactoryOf, TreeNodeOf } from '@signal-tree/kernel/adapter';
|
|
22
4
|
export declare const signalTree: SignalTreeFactoryOf<"angular">;
|
|
23
|
-
export { entityMap, link, restoration, undoable, external, asReadonly, batching, devTools, transactions, onTreeError, SignalTreeRollbackError, } from '@signal-tree/kernel';
|
|
24
|
-
export type { TreeConfig, NodeAccessor, AccessibleNode, Primitive, Enhancer, EnhancerCleanup, Link, LinkEndpoint, TreeId, TreeErrorEvent, EntityMapMarker, EntityMapBuilder, EntityMapComputedSlices, EntityMapMarkerWithSlices, ComputedSliceConfig, AddOptions, AddManyOptions, DefaultKey, RestorationMethods, RestorationHistoryEntry, BatchingConfig, BatchingMethods, DevToolsMethods, DevToolsLogEntry, TransactionMethods, } from '@signal-tree/kernel';
|
|
25
5
|
export type TreeNode<T> = TreeNodeOf<T, 'angular'>;
|
|
26
6
|
export type WritableLeaf<T> = LeafOf<T, 'angular'>;
|
|
27
|
-
export type
|
|
7
|
+
export type AccessibleNode<T> = AccessibleNodeOf<T, 'angular'>;
|
|
8
|
+
export type ISignalTree<T, TAccum = TreeNode<T>> = ISignalTreeOf<T, 'angular', TAccum>;
|
|
9
|
+
export type SignalTree<T> = ISignalTree<T>;
|
|
28
10
|
export type EntityNode<E> = EntityNodeOf<E, 'angular'>;
|
|
29
|
-
export type
|
|
11
|
+
export type EntitySignal<E, K extends string | number = string> = EntitySignalOf<E, K, 'angular'>;
|
|
30
12
|
export type EntitySignalWithSlices<E, K extends string | number, Slices extends Record<string, unknown>> = EntitySignalWithSlicesOf<E, K, Slices, 'angular'>;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export type SignalTree<T> = ISignalTreeOf<T, 'angular'>;
|
|
13
|
+
export type ReadonlyView<T> = ReadonlyViewOf<T, 'angular'>;
|
|
14
|
+
export type ReadonlyStore<TSource, TAccum = TreeNode<TSource>> = ReadonlyStoreOf<TSource, TAccum, 'angular'>;
|
|
15
|
+
export declare const asReadonly: <TSource, TAccum>(tree: ISignalTreeOf<TSource, "angular", TAccum>) => ReadonlyStoreOf<TSource, TAccum, "angular">;
|
|
16
|
+
export * from '@signal-tree/kernel';
|
|
36
17
|
export { defineStore, type DefineStoreConfig } from './lib/define-store.js';
|
|
37
18
|
export { toWritableSignal } from './lib/to-writable-signal.js';
|
package/src/lib/carrier.d.ts
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
|
-
import type { WritableSignal } from '@angular/core';
|
|
2
|
-
/**
|
|
3
|
-
* Angular's leaf carrier — the shape `@signal-tree/angular` promises consumers.
|
|
4
|
-
*
|
|
5
|
-
* This lived in the kernel until TYPE-A-PACKAGE-BINDING-0 (TA-B). It cannot: it
|
|
6
|
-
* extends `WritableSignal`, whose private `[SIGNAL]` / `[ɵWRITABLE_SIGNAL]`
|
|
7
|
-
* brands are precisely what make the Angular carrier truthful — and what make a
|
|
8
|
-
* neutral cell correctly NOT assignable to it. Describing it structurally in the
|
|
9
|
-
* kernel would be a lie, and importing Angular to describe it would keep
|
|
10
|
-
* `@angular/core` in kernel declarations forever.
|
|
11
|
-
*/
|
|
1
|
+
import type { Signal, WritableSignal } from '@angular/core';
|
|
12
2
|
export interface AngularLeaf<T> extends WritableSignal<T> {
|
|
13
3
|
(): T;
|
|
4
|
+
asReadonly(): Signal<T>;
|
|
14
5
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Register the carrier with the kernel's canonical registry.
|
|
17
|
-
*
|
|
18
|
-
* Declaration merging against the module that DECLARES `LeafCarriers`, so
|
|
19
|
-
* `LeafOf<T,'angular'>` and `TreeNodeOf<T,'angular'>` resolve to `AngularLeaf`.
|
|
20
|
-
* Augmenting a re-export would create a second, unused interface and silently
|
|
21
|
-
* leave the real registry unchanged.
|
|
22
|
-
*/
|
|
23
6
|
declare module '@signal-tree/kernel/adapter' {
|
|
24
7
|
interface LeafCarriers<T> {
|
|
25
8
|
angular: AngularLeaf<T>;
|
|
26
9
|
}
|
|
10
|
+
interface ReadonlyLeafCarriers<T> {
|
|
11
|
+
angular: Signal<T>;
|
|
12
|
+
}
|
|
13
|
+
interface ReadonlyViewLeafCarriers<T> {
|
|
14
|
+
angular: Signal<T>;
|
|
15
|
+
}
|
|
27
16
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type Type } from '@angular/core';
|
|
2
|
-
import type { ISignalTreeOf, ReadonlyStoreOf
|
|
3
|
-
type ReadonlyStore<T, TAccum = TreeNodeOf<T, 'angular'>> = ReadonlyStoreOf<T, TAccum, 'angular'>;
|
|
4
|
-
type AngularSignalTree<T, TAccum = TreeNodeOf<T, 'angular'>> = ISignalTreeOf<T, 'angular', TAccum>;
|
|
2
|
+
import type { ISignalTreeOf, ReadonlyStoreOf } from '@signal-tree/kernel/adapter';
|
|
5
3
|
/**
|
|
6
4
|
* Config for {@link defineStore}.
|
|
7
5
|
*/
|
|
@@ -49,7 +47,7 @@ export interface DefineStoreConfig {
|
|
|
49
47
|
*
|
|
50
48
|
* @example
|
|
51
49
|
* ```ts
|
|
52
|
-
* import { signalTree, defineStore } from '@signal-tree/
|
|
50
|
+
* import { signalTree, defineStore } from '@signal-tree/angular';
|
|
53
51
|
*
|
|
54
52
|
* export const CounterStore = defineStore(() =>
|
|
55
53
|
* signalTree({ count: 0 })
|
|
@@ -64,7 +62,7 @@ export interface DefineStoreConfig {
|
|
|
64
62
|
* @Component({ providers: [CounterStore] })
|
|
65
63
|
* export class Counter {
|
|
66
64
|
* readonly store = inject(CounterStore);
|
|
67
|
-
* inc() { this.store.$.count
|
|
65
|
+
* inc() { this.store.$.count((n) => n + 1); }
|
|
68
66
|
* }
|
|
69
67
|
* ```
|
|
70
68
|
*
|
|
@@ -79,13 +77,13 @@ export interface DefineStoreConfig {
|
|
|
79
77
|
* export class Display {
|
|
80
78
|
* readonly store = inject(CounterStore);
|
|
81
79
|
* read() { return this.store.$.count(); } // ✅ read-only
|
|
82
|
-
* // this.store.$.count
|
|
80
|
+
* // this.store.$.count(1); // ❌ type error — not on ReadonlyStore
|
|
83
81
|
* }
|
|
84
82
|
* ```
|
|
85
83
|
*/
|
|
86
|
-
export declare function defineStore<T, A>(factory: () =>
|
|
84
|
+
export declare function defineStore<T, A>(factory: () => ISignalTreeOf<T, 'angular', A>, config: DefineStoreConfig & {
|
|
87
85
|
expose: 'readonly';
|
|
88
|
-
}): Type<
|
|
86
|
+
}): Type<ReadonlyStoreOf<T, A, 'angular'>>;
|
|
89
87
|
/**
|
|
90
88
|
* Fallback overload. `expose?: undefined` means a config variable WIDENED to
|
|
91
89
|
* {@link DefineStoreConfig} matches neither overload — pass a config object
|
|
@@ -95,4 +93,3 @@ export declare function defineStore<T, A>(factory: () => AngularSignalTree<T, A>
|
|
|
95
93
|
export declare function defineStore<R>(factory: () => R, config?: DefineStoreConfig & {
|
|
96
94
|
expose?: undefined;
|
|
97
95
|
}): Type<R>;
|
|
98
|
-
export {};
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { type WritableSignal } from '@angular/core';
|
|
2
|
-
import type { NodeAccessor } from '@signal-tree/kernel';
|
|
2
|
+
import type { Location, NodeAccessor } from '@signal-tree/kernel';
|
|
3
3
|
/**
|
|
4
|
-
* Converts a
|
|
5
|
-
* for use with any API that expects a
|
|
4
|
+
* Converts a writable SignalTree branch into a WritableSignal, or returns an
|
|
5
|
+
* Angular-native leaf unchanged, for use with any API that expects a
|
|
6
|
+
* `WritableSignal` — e.g. as an Angular
|
|
6
7
|
* Signal Forms model, or the value fed to `SignalFormControl`. (Note: Angular
|
|
7
8
|
* has no `FormControl.connect(signal)` API — see `signalForm()` for the
|
|
8
9
|
* signal-native forms bridge.)
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
+
* Branch adaptation creates a two-way binding between the NodeAccessor and a
|
|
12
|
+
* WritableSignal:
|
|
11
13
|
* - Reads all leaf values from the NodeAccessor and exposes them as a signal
|
|
12
14
|
* - Writes to the WritableSignal update the underlying NodeAccessor
|
|
13
15
|
*
|
|
14
|
-
* **Important**:
|
|
15
|
-
*
|
|
16
|
+
* **Important**: Branch adaptation and `{ undoable: true }` wrappers use
|
|
17
|
+
* `effect()` internally for synchronization, which requires an injection
|
|
18
|
+
* context. Passing an ordinary native leaf without options returns it directly
|
|
19
|
+
* and needs no injection context. This function can be called in:
|
|
16
20
|
* - Component/directive/pipe class field initializers
|
|
17
21
|
* - Component/directive/pipe constructors
|
|
18
22
|
* - Functions called from within an injection context
|
|
19
23
|
*
|
|
20
24
|
* @template T - The type of the node value
|
|
21
|
-
* @param node - The
|
|
22
|
-
* @returns
|
|
25
|
+
* @param node - The branch accessor or writable leaf to expose
|
|
26
|
+
* @returns The native leaf or a WritableSignal synchronized with the branch
|
|
23
27
|
*
|
|
24
28
|
* @example
|
|
25
29
|
* ```typescript
|
|
@@ -30,11 +34,11 @@ import type { NodeAccessor } from '@signal-tree/kernel';
|
|
|
30
34
|
* // Convert a slice to a WritableSignal (e.g. a Signal Forms model)
|
|
31
35
|
* const userSignal = toWritableSignal(tree.$.user);
|
|
32
36
|
*
|
|
33
|
-
* //
|
|
34
|
-
* const nameSignal = tree.$.user.name;
|
|
37
|
+
* // A leaf is already native; this returns the same object.
|
|
38
|
+
* const nameSignal = toWritableSignal(tree.$.user.name);
|
|
35
39
|
* ```
|
|
36
40
|
*/
|
|
37
|
-
export declare function toWritableSignal<T>(node: NodeAccessor<T>, injector?: unknown, options?: {
|
|
41
|
+
export declare function toWritableSignal<T>(node: NodeAccessor<T> | Location<T> | WritableSignal<T>, injector?: unknown, options?: {
|
|
38
42
|
/**
|
|
39
43
|
* Mark writes ENTERING through this adapter as designating their authored
|
|
40
44
|
* causal turn undoable — the same designation {@link undoable} applies, for
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{signal,computed,untracked,isSignal}from"@angular/core";import{ANGULAR_SCALAR_LEAF_REALIZATION}from"./scalar-leaf-realization.js";const ANGULAR_TREE_REALIZATION=Object.freeze({materialization:{isReactiveNode:node=>isSignal(node)},suppressTracking:fn=>untracked(fn),scalarLeaf:ANGULAR_SCALAR_LEAF_REALIZATION,derived:{createDerived:compute=>computed(compute)},cell:{createCell:(initial,equal)=>signal(initial,equal?{equal}:void 0)}});export{ANGULAR_TREE_REALIZATION};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{linkedSignal,signal}from"@angular/core";const ANGULAR_SCALAR_LEAF_REALIZATION={createToken(){const token=signal(0);return{observe:()=>void token(),invalidate:()=>token.update(value=>value+1)}},createLeaf(compute){return linkedSignal(compute)},runInvalidationGroup(run){run()}};export{ANGULAR_SCALAR_LEAF_REALIZATION};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type TreeRealization } from '@signal-tree/kernel/adapter';
|
|
2
|
-
/**
|
|
3
|
-
* Immutable Angular mechanisms captured by this package's bound tree factory.
|
|
4
|
-
* Selection occurs at construction; tree-owned lazy allocation retains only
|
|
5
|
-
* the capabilities it needs for that tree's lifetime.
|
|
6
|
-
*/
|
|
7
|
-
export declare const ANGULAR_TREE_REALIZATION: TreeRealization;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ScalarLeafRealization } from '@signal-tree/kernel/adapter';
|
|
2
|
-
/**
|
|
3
|
-
* The ANGULAR realization of scalar leaves. Framework mechanism only.
|
|
4
|
-
*
|
|
5
|
-
* SCALAR-REALIZATION-SEAM-0 = SR-A. What used to be a 200-line module owning
|
|
6
|
-
* slot bookkeeping, publication rules, production accounting, membership
|
|
7
|
-
* dormancy and reactivation is now this: a dependency token and a read-through
|
|
8
|
-
* leaf. It imports NOTHING from the kernel but two type declarations — no
|
|
9
|
-
* member-membership, no PhysicalCommitClock, no production stats, no physical
|
|
10
|
-
* slot runtime, no position ids.
|
|
11
|
-
*
|
|
12
|
-
* That is the discriminator: a hypothetical `@signal-tree/fake-reactive` could
|
|
13
|
-
* be written against this same contract with a counter and a closure.
|
|
14
|
-
*/
|
|
15
|
-
export declare const ANGULAR_SCALAR_LEAF_REALIZATION: ScalarLeafRealization;
|