@signal-tree/angular 15.0.0-rc.12 → 15.0.0-rc.15

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 CHANGED
@@ -1,8 +1,15 @@
1
- # @signal-tree/angular
1
+ # `@signal-tree/angular`
2
2
 
3
- Angular realization for SignalTree. It uses native Angular `Signal` and
4
- `WritableSignal` carriers while keeping state, entity, and causal semantics in
5
- `@signal-tree/kernel`.
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 = computed(() => {
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: computed(() => selected()?.name ?? 'None'),
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 compose through ordinary
51
- local `computed` references inside that factory.
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
- Use `defineStore` for Angular dependency injection and `toWritableSignal` when a
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{ANGULAR_TREE_REALIZATION}from"./lib/angular-realization.js";import{createSignalTreeFactory}from"@signal-tree/kernel/adapter";import{SignalTreeRollbackError,asReadonly,batching,devTools,entityMap,external,link,onTreeError,restoration,transactions,undoable}from"@signal-tree/kernel";import{defineStore}from"./lib/define-store.js";import{toWritableSignal}from"./lib/to-writable-signal.js";const signalTree=createSignalTreeFactory(ANGULAR_TREE_REALIZATION);export{SignalTreeRollbackError,asReadonly,batching,defineStore,devTools,entityMap,external,link,onTreeError,restoration,signalTree,toWritableSignal,transactions,undoable};
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.")}}}sig.set=value=>{if(options?.undoable){withRestorationDesignation(()=>{node(value)})}else{node(value)}originalSet(value)};sig.update=updater=>{sig.set(updater(sig()))};return sig}export{toWritableSignal};
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.12",
3
+ "version": "15.0.0-rc.15",
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.12"
28
+ "@signal-tree/kernel": "15.0.0-rc.15"
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, ReadonlyStoreOf, ISignalTreeOf, LeafOf, SignalTreeFactoryOf, TreeNodeOf } from '@signal-tree/kernel/adapter';
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 EntitySignal<E, K extends string | number = string> = EntitySignalOf<E, K, 'angular'>;
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 ReadonlyStore<TSource, TAccum = TreeNode<TSource>> = ReadonlyStoreOf<TSource, TAccum, 'angular'>;
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
- * The Angular tree contract. Same one parameter users always wrote; the carrier
33
- * differs because THIS package installed the Angular realization.
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';
@@ -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, TreeNodeOf } from '@signal-tree/kernel/adapter';
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/kernel';
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.update((n) => n + 1); }
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.set(1); // ❌ type error — not on ReadonlyStore
80
+ * // this.store.$.count(1); // ❌ type error — not on ReadonlyStore
83
81
  * }
84
82
  * ```
85
83
  */
86
- export declare function defineStore<T, A>(factory: () => AngularSignalTree<T, A>, config: DefineStoreConfig & {
84
+ export declare function defineStore<T, A>(factory: () => ISignalTreeOf<T, 'angular', A>, config: DefineStoreConfig & {
87
85
  expose: 'readonly';
88
- }): Type<ReadonlyStore<T, A>>;
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 {};
@@ -0,0 +1,3 @@
1
+ import type { ObservationAdapter } from '@signal-tree/kernel/adapter';
2
+ /** Angular dependency tracking for kernel-owned locations. */
3
+ export declare const ANGULAR_OBSERVATION_ADAPTER: ObservationAdapter;
@@ -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 NodeAccessor (SignalTree slice or whole tree) into a WritableSignal
5
- * for use with any API that expects a `WritableSignal` — e.g. as an Angular
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
- * Creates a two-way binding between the NodeAccessor and a WritableSignal:
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**: This function uses `effect()` internally for synchronization, which requires
15
- * an injection context. It can be called in:
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 NodeAccessor to convert (can be a slice or whole tree)
22
- * @returns A WritableSignal that stays in sync with the NodeAccessor
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
- * // Leaves are already WritableSignal - no conversion needed
34
- * const nameSignal = tree.$.user.name; // ✅ Already a WritableSignal
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;