@signaltree/core 7.6.0 β†’ 8.0.0

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
@@ -118,7 +118,7 @@ Performance and bundle size vary by app shape, build tooling, device, and runtim
118
118
 
119
119
  ## Best Practices (SignalTree-First)
120
120
 
121
- > πŸ“– **Full guide**: [Implementation Patterns](https://github.com/JBorgia/signaltree/blob/main/docs/IMPLEMENTATION_PATTERNS.md)
121
+ > πŸ“– **Full guide**: [Architecture Guide](https://github.com/JBorgia/signaltree/blob/main/docs/architecture/signaltree-architecture-guide.md)
122
122
 
123
123
  Follow these principles for idiomatic SignalTree code:
124
124
 
@@ -164,7 +164,7 @@ export function createUserTree() {
164
164
  // βœ… Correct: Derived from multiple signals
165
165
  const selectedUser = computed(() => {
166
166
  const id = $.selected.userId();
167
- return id ? $.users.byId(id)() : null;
167
+ return id ? $.users.byId(id)?.() ?? null : null;
168
168
  });
169
169
 
170
170
  // ❌ Wrong: Wrapping an existing signal
@@ -175,8 +175,8 @@ const selectedUserId = computed(() => $.selected.userId()); // Unnecessary!
175
175
 
176
176
  ```typescript
177
177
  // βœ… SignalTree-native
178
- const user = $.users.byId(123)(); // O(1) lookup
179
- const allUsers = $.users.all; // Get all
178
+ const user = $.users.byId(123)?.(); // O(1) lookup
179
+ const allUsers = $.users.all(); // Get all
180
180
  $.users.setAll(usersFromApi); // Replace all
181
181
 
182
182
  // ❌ NgRx-style (avoid)
@@ -650,7 +650,7 @@ All enhancers are exported directly from `@signaltree/core`:
650
650
 
651
651
  **Data Management:**
652
652
 
653
- - `entities()` - Advanced CRUD operations for collections
653
+ - `entities()` - Legacy helper (not required when using `entityMap()` in v7)
654
654
  - `createAsyncOperation()` - Async operation management with loading/error states
655
655
  - `trackAsync()` - Track async operations in your state
656
656
  - `serialization()` - State persistence and SSR support
@@ -658,9 +658,15 @@ All enhancers are exported directly from `@signaltree/core`:
658
658
 
659
659
  **Development Tools:**
660
660
 
661
- - `devTools()` - Redux DevTools integration
661
+ - `devTools()` - Redux DevTools auto-connect, path actions, and time-travel dispatch
662
662
  - `withTimeTravel()` - Undo/redo functionality
663
663
 
664
+ **DevTools connection model**
665
+
666
+ `devTools()` connects to the Redux DevTools browser extension via `window.__REDUX_DEVTOOLS_EXTENSION__.connect(...)`. That means **each SignalTree instance you enhance with `devTools()` will show up as its own DevTools β€œinstance”.**
667
+
668
+ If you want a single unified DevTools view, prefer a **single global tree** composed from domain slices, and apply `devTools()` once at the root. See the Architecture Guide (Category C: Feature-scoped / composed sub-trees) for patterns that keep code modular without fragmenting runtime state.
669
+
664
670
  **Presets:**
665
671
 
666
672
  - `createDevTree()` - Pre-configured development setup
@@ -691,14 +697,12 @@ const tree = signalTree({ count: 0 }).with(
691
697
  **Performance-Focused Stack:**
692
698
 
693
699
  ```typescript
694
- import { signalTree, batching, memoization, entities } from '@signaltree/core';
700
+ import { signalTree, batching, memoization, entityMap } from '@signaltree/core';
695
701
 
696
702
  const tree = signalTree({
697
703
  products: entityMap<Product>(),
698
704
  ui: { loading: false },
699
- })
700
- .with(entities()) // Efficient CRUD operations (auto-detects entityMap)
701
- .with(batching()); // Batch updates for optimal rendering
705
+ }).with(batching()); // Batch updates for optimal rendering
702
706
 
703
707
  // Entity CRUD operations
704
708
  tree.$.products.addOne(newProduct);
@@ -748,6 +752,8 @@ tree.undo(); // Revert changes
748
752
 
749
753
  #### Enhancer Metadata & Ordering
750
754
 
755
+ Derived computed signals are preserved across `.with()` chaining, so enhancer composition does not recreate signal identities.
756
+
751
757
  Enhancers can declare metadata for automatic dependency resolution:
752
758
 
753
759
  ```typescript
@@ -782,20 +788,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
782
788
  SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
783
789
 
784
790
  ```typescript
785
- import { signalTree, entityMap, entities } from '@signaltree/core';
791
+ import { signalTree, entityMap } from '@signaltree/core';
786
792
 
787
793
  // Without entityMap - use manual array updates
788
794
  const basic = signalTree({ users: [] as User[] });
789
795
  basic.$.users.update((users) => [...users, newUser]);
790
796
 
791
- // With entityMap + entities - use entity helpers
797
+ // With entityMap (v7+ auto-processed) - use entity helpers
792
798
  const enhanced = signalTree({
793
799
  users: entityMap<User>(),
794
- }).with(entities());
800
+ });
795
801
 
796
802
  enhanced.$.users.addOne(newUser); // βœ… Advanced CRUD operations
797
- enhanced.$.users.byId(123)(); // βœ… O(1) lookups
798
- enhanced.$.users.all; // βœ… Get all as array
803
+ enhanced.$.users.byId(123)?.(); // βœ… O(1) lookups
804
+ enhanced.$.users.all(); // βœ… Get all as array
799
805
  ```
800
806
 
801
807
  Core includes several performance optimizations:
@@ -908,7 +914,7 @@ const tree = signalTree({ count: 0 }).with(withLogger());
908
914
  tree.log('Tree created');
909
915
  ```
910
916
 
911
- > πŸ“– **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/custom-markers-enhancers.md)
917
+ > πŸ“– **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/guides/custom-markers-enhancers.md)
912
918
  >
913
919
  > πŸ“± **Interactive demo**: [Demo App](/custom-extensions)
914
920
 
@@ -1574,19 +1580,19 @@ const filteredProducts = computed(() => {
1574
1580
  ### Data Management Composition
1575
1581
 
1576
1582
  ```typescript
1577
- import { signalTree, entityMap, entities } from '@signaltree/core';
1583
+ import { signalTree, entityMap } from '@signaltree/core';
1578
1584
 
1579
- // Add data management capabilities (+2.77KB total)
1585
+ // Add data management capabilities (entityMap is auto-processed)
1580
1586
  const tree = signalTree({
1581
1587
  users: entityMap<User>(),
1582
1588
  posts: entityMap<Post>(),
1583
1589
  ui: { loading: false, error: null as string | null },
1584
- }).with(entities());
1590
+ });
1585
1591
 
1586
1592
  // Advanced entity operations via tree.$ accessor
1587
1593
  tree.$.users.addOne(newUser);
1588
- tree.$.users.selectBy((u) => u.active);
1589
- tree.$.users.updateMany([{ id: '1', changes: { status: 'active' } }]);
1594
+ tree.$.users.where((u) => u.active); // Filtered signal
1595
+ tree.$.users.updateMany(['1'], { status: 'active' });
1590
1596
 
1591
1597
  // Entity helpers work with nested structures
1592
1598
  // Example: deeply nested entities in a domain-driven design pattern
@@ -1603,13 +1609,13 @@ const appTree = signalTree({
1603
1609
  reports: entityMap<Report>(),
1604
1610
  },
1605
1611
  },
1606
- }).with(entities());
1612
+ });
1607
1613
 
1608
1614
  // Access nested entities using tree.$ accessor
1609
- appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
1610
- appTree.$.app.data.products.selectTotal(); // Count signal
1611
- appTree.$.admin.data.logs.all; // All items as array
1612
- appTree.$.admin.data.reports.selectIds(); // ID array signal
1615
+ appTree.$.app.data.users.where((u) => u.isAdmin); // Filtered signal
1616
+ appTree.$.app.data.products.count(); // Count
1617
+ appTree.$.admin.data.logs.all(); // All items as array
1618
+ appTree.$.admin.data.reports.ids(); // ID array
1613
1619
 
1614
1620
  // For async operations, use manual async or async helpers
1615
1621
  async function fetchUsers() {
@@ -1628,7 +1634,7 @@ async function fetchUsers() {
1628
1634
  ### Full-Featured Development Composition
1629
1635
 
1630
1636
  ```typescript
1631
- import { signalTree, batching, entities, serialization, withTimeTravel, devTools } from '@signaltree/core';
1637
+ import { signalTree, batching, serialization, withTimeTravel, devTools } from '@signaltree/core';
1632
1638
 
1633
1639
  // Full development stack (example)
1634
1640
  const tree = signalTree({
@@ -1639,7 +1645,6 @@ const tree = signalTree({
1639
1645
  },
1640
1646
  }).with(
1641
1647
  batching(), // Performance
1642
- entities(), // Data management
1643
1648
  // withAsync removed β€” use async helpers for API integration
1644
1649
  serialization({
1645
1650
  // State persistence
@@ -1653,7 +1658,9 @@ const tree = signalTree({
1653
1658
  devTools({
1654
1659
  // Debug tools (dev only)
1655
1660
  name: 'MyApp',
1656
- trace: true,
1661
+ enableTimeTravel: true,
1662
+ includePaths: ['app.*', 'ui.*'],
1663
+ formatPath: (path) => path.replace(/\.(\d+)/g, '[$1]'),
1657
1664
  })
1658
1665
  );
1659
1666
 
@@ -1661,7 +1668,7 @@ const tree = signalTree({
1661
1668
  async function fetchUser(id: string) {
1662
1669
  return await api.getUser(id);
1663
1670
  }
1664
- tree.$.app.data.users.byId(userId)(); // O(1) lookup
1671
+ tree.$.app.data.users.byId(userId)?.(); // O(1) lookup
1665
1672
  tree.undo(); // Time travel
1666
1673
  tree.save(); // Persistence
1667
1674
  ```
@@ -1669,12 +1676,11 @@ tree.save(); // Persistence
1669
1676
  ### Production-Ready Composition
1670
1677
 
1671
1678
  ```typescript
1672
- import { signalTree, batching, entities, serialization } from '@signaltree/core';
1679
+ import { signalTree, batching, serialization } from '@signaltree/core';
1673
1680
 
1674
1681
  // Production build (no dev tools)
1675
1682
  const tree = signalTree(initialState).with(
1676
1683
  batching(), // Performance optimization
1677
- entities(), // Data management
1678
1684
  // withAsync removed β€” use async helpers for API integration
1679
1685
  serialization({
1680
1686
  // User preferences
@@ -1690,14 +1696,13 @@ const tree = signalTree(initialState).with(
1690
1696
  ### Conditional Enhancement
1691
1697
 
1692
1698
  ```typescript
1693
- import { signalTree, batching, entities, devTools, withTimeTravel } from '@signaltree/core';
1699
+ import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
1694
1700
 
1695
1701
  const isDevelopment = process.env['NODE_ENV'] === 'development';
1696
1702
 
1697
1703
  // Conditional enhancement based on environment
1698
1704
  const tree = signalTree(state).with(
1699
1705
  batching(), // Always include performance
1700
- entities(), // Always include data management
1701
1706
  ...(isDevelopment
1702
1707
  ? [
1703
1708
  // Development-only features
@@ -1742,7 +1747,7 @@ const tree = signalTree(state);
1742
1747
  const tree2 = tree.with(batching());
1743
1748
 
1744
1749
  // Phase 3: Add data management for collections
1745
- const tree3 = tree2.with(entities());
1750
+ const tree3 = tree2; // entityMap is auto-processed in v7
1746
1751
 
1747
1752
  // Phase 4: Add async for API integration
1748
1753
  // withAsync removed β€” no explicit async enhancer; use async helpers instead
@@ -1820,7 +1825,7 @@ For fair, reproducible measurements that reflect your app and hardware, use the
1820
1825
  {{ userTree.$.error() }}
1821
1826
  <button (click)="loadUsers()">Retry</button>
1822
1827
  </div>
1823
- } @else { @for (user of users.selectAll()(); track user.id) {
1828
+ } @else { @for (user of users(); track user.id) {
1824
1829
  <div class="user-card">
1825
1830
  <h3>{{ user.name }}</h3>
1826
1831
  <p>{{ user.email }}</p>
@@ -1848,6 +1853,8 @@ class UserManagerComponent implements OnInit {
1848
1853
  form: { id: '', name: '', email: '' },
1849
1854
  });
1850
1855
 
1856
+ readonly users = this.userTree.$.users;
1857
+
1851
1858
  constructor(private userService: UserService) {}
1852
1859
 
1853
1860
  ngOnInit() {
@@ -2032,9 +2039,9 @@ tree.destroy(); // Cleanup resources
2032
2039
 
2033
2040
  // Entity helpers (when using entityMap + entities)
2034
2041
  // tree.$.users.addOne(user); // Add single entity
2035
- // tree.$.users.byId(id)(); // O(1) lookup by ID
2036
- // tree.$.users.all; // Get all as array
2037
- // tree.$.users.selectBy(pred); // Filtered signal
2042
+ // tree.$.users.byId(id)?.(); // O(1) lookup by ID
2043
+ // tree.$.users.all(); // Get all as array
2044
+ // tree.$.users.where(pred)(); // Filtered array
2038
2045
  ```
2039
2046
 
2040
2047
  ## Extending with enhancers
@@ -2054,8 +2061,8 @@ All enhancers are included in `@signaltree/core`:
2054
2061
 
2055
2062
  - **batching()** - Batch multiple updates for better performance
2056
2063
  - **memoization()** - Intelligent caching & performance optimization
2057
- - **entities()** - Advanced entity management & CRUD operations
2058
- - **devTools()** - Redux DevTools integration for debugging
2064
+ - **entities()** - Legacy helper (not required when using `entityMap()` in v7)
2065
+ - **devTools()** - Auto-connect, path actions, and time-travel dispatch
2059
2066
  - **withTimeTravel()** - Undo/redo functionality & state history
2060
2067
  - **serialization()** - State persistence & SSR support
2061
2068
  - **createDevTree()** - Pre-configured development setup
@@ -2210,11 +2217,11 @@ All enhancers are now consolidated in the core package. The following features a
2210
2217
 
2211
2218
  ### Advanced Features
2212
2219
 
2213
- - **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
2220
+ - **entities()** (+0.97KB gzipped) - Legacy helper (not required when using `entityMap()` in v7)
2214
2221
 
2215
2222
  ### Development Tools
2216
2223
 
2217
- - **devTools()** (+2.49KB gzipped) - Development tools & Redux DevTools integration
2224
+ - **devTools()** (+2.49KB gzipped) - Auto-connect, path actions, and time-travel dispatch
2218
2225
  - **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
2219
2226
 
2220
2227
  ### Integration & Convenience
@@ -216,7 +216,7 @@ function sanitizeState(value, options, depth = 0, seen = new WeakSet()) {
216
216
  if (typeof value === 'number' || typeof value === 'boolean') return value;
217
217
  if (typeof value === 'bigint') return `${value.toString()}n`;
218
218
  if (typeof value === 'symbol') return String(value);
219
- if (typeof value === 'function') return '[Function]';
219
+ if (typeof value === 'function') return undefined;
220
220
  if (depth >= maxDepth) return '[MaxDepth]';
221
221
  if (value instanceof Date) return value.toISOString();
222
222
  if (value instanceof RegExp) return value.toString();
@@ -245,6 +245,7 @@ function sanitizeState(value, options, depth = 0, seen = new WeakSet()) {
245
245
  }
246
246
  const result = {};
247
247
  for (const [key, val] of Object.entries(obj)) {
248
+ if (typeof val === 'function') continue;
248
249
  result[key] = sanitizeState(val, options, depth + 1, seen);
249
250
  }
250
251
  return result;
@@ -299,6 +300,7 @@ function devTools(config = {}) {
299
300
  logger.logComposition(modules, 'with');
300
301
  };
301
302
  const activeProfiles = new Map();
303
+ let browserDevToolsConnection = null;
302
304
  let browserDevTools = null;
303
305
  let isConnected = false;
304
306
  let isApplyingExternalState = false;
@@ -374,7 +376,7 @@ function devTools(config = {}) {
374
376
  lastSnapshot = currentSnapshot;
375
377
  return;
376
378
  }
377
- const effectiveAction = pendingExplicitAction ? pendingAction : formattedPaths.length === 1 ? buildAction(`SignalTree/${formattedPaths[0]}`, formattedPaths[0]) : formattedPaths.length > 1 ? buildAction('SignalTree/batch', formattedPaths) : buildAction('SignalTree/update');
379
+ const effectiveAction = pendingExplicitAction ? pendingAction : formattedPaths.length === 1 ? buildAction(`SignalTree/${formattedPaths[0]}`, formattedPaths[0]) : formattedPaths.length > 1 ? buildAction('SignalTree/update', formattedPaths) : buildAction('SignalTree/update');
378
380
  const actionMeta = {
379
381
  timestamp: Date.now(),
380
382
  ...(pendingSource && {
@@ -521,15 +523,20 @@ function devTools(config = {}) {
521
523
  skip: true
522
524
  }
523
525
  });
526
+ browserDevToolsConnection = connection;
524
527
  browserDevTools = {
525
528
  send: connection.send,
526
529
  subscribe: connection.subscribe
527
530
  };
528
531
  if (browserDevTools.subscribe && !unsubscribeDevTools) {
529
- browserDevTools.subscribe(handleDevToolsMessage);
530
- unsubscribeDevTools = () => {
531
- browserDevTools?.subscribe?.(() => void 0);
532
- };
532
+ const maybeUnsubscribe = browserDevTools.subscribe(handleDevToolsMessage);
533
+ if (typeof maybeUnsubscribe === 'function') {
534
+ unsubscribeDevTools = maybeUnsubscribe;
535
+ } else {
536
+ unsubscribeDevTools = () => {
537
+ browserDevTools?.subscribe?.(() => void 0);
538
+ };
539
+ }
533
540
  }
534
541
  const rawSnapshot = readSnapshot();
535
542
  const sanitized = buildSerializedState(rawSnapshot);
@@ -652,7 +659,17 @@ function devTools(config = {}) {
652
659
  initBrowserDevTools();
653
660
  },
654
661
  disconnectDevTools() {
662
+ try {
663
+ unsubscribeDevTools?.();
664
+ } catch {}
665
+ try {
666
+ browserDevToolsConnection?.unsubscribe?.();
667
+ } catch {}
668
+ try {
669
+ browserDevToolsConnection?.disconnect?.();
670
+ } catch {}
655
671
  browserDevTools = null;
672
+ browserDevToolsConnection = null;
656
673
  isConnected = false;
657
674
  if (unsubscribeNotifier) {
658
675
  unsubscribeNotifier();
@@ -662,10 +679,7 @@ function devTools(config = {}) {
662
679
  unsubscribeFlush();
663
680
  unsubscribeFlush = null;
664
681
  }
665
- if (unsubscribeDevTools) {
666
- unsubscribeDevTools();
667
- unsubscribeDevTools = null;
668
- }
682
+ unsubscribeDevTools = null;
669
683
  if (effectRef) {
670
684
  effectRef.destroy();
671
685
  effectRef = null;
package/dist/lib/utils.js CHANGED
@@ -180,6 +180,8 @@ function unwrap(node) {
180
180
  } else {
181
181
  result[key] = unwrappedValue;
182
182
  }
183
+ } else if (typeof value === 'function') {
184
+ continue;
183
185
  } else if (typeof value === 'object' && value !== null && !Array.isArray(value) && !isBuiltInObject(value)) {
184
186
  result[key] = unwrap(value);
185
187
  } else {
@@ -212,6 +214,9 @@ function unwrap(node) {
212
214
  if (typeof v === 'function') continue;
213
215
  }
214
216
  const value = node[key];
217
+ if (typeof value === 'function' && !isNodeAccessor(value) && !isSignal(value)) {
218
+ continue;
219
+ }
215
220
  if (isNodeAccessor(value)) {
216
221
  const unwrappedValue = value();
217
222
  if (typeof unwrappedValue === 'object' && unwrappedValue !== null && !Array.isArray(unwrappedValue) && !isBuiltInObject(unwrappedValue)) {
@@ -235,6 +240,9 @@ function unwrap(node) {
235
240
  const symbols = Object.getOwnPropertySymbols(node);
236
241
  for (const sym of symbols) {
237
242
  const value = node[sym];
243
+ if (typeof value === 'function' && !isNodeAccessor(value) && !isSignal(value)) {
244
+ continue;
245
+ }
238
246
  if (isNodeAccessor(value)) {
239
247
  const unwrappedValue = value();
240
248
  if (typeof unwrappedValue === 'object' && unwrappedValue !== null && !Array.isArray(unwrappedValue) && !isBuiltInObject(unwrappedValue)) {
@@ -263,6 +271,12 @@ function snapshotState(state) {
263
271
  function applyState(stateNode, snapshot) {
264
272
  if (snapshot === null || snapshot === undefined) return;
265
273
  if (typeof snapshot !== 'object') return;
274
+ if (stateNode && typeof stateNode === 'object' && typeof stateNode.setAll === 'function' && snapshot && typeof snapshot === 'object' && Array.isArray(snapshot.all)) {
275
+ try {
276
+ stateNode.setAll(snapshot.all);
277
+ return;
278
+ } catch {}
279
+ }
266
280
  for (const key of Object.keys(snapshot)) {
267
281
  const val = snapshot[key];
268
282
  const target = stateNode[key];
@@ -288,6 +302,14 @@ function applyState(stateNode, snapshot) {
288
302
  target(val);
289
303
  } catch {}
290
304
  }
305
+ } else if (target && typeof target === 'object' && val && typeof val === 'object' && !Array.isArray(target) && !Array.isArray(val)) {
306
+ try {
307
+ applyState(target, val);
308
+ } catch {
309
+ try {
310
+ stateNode[key] = val;
311
+ } catch {}
312
+ }
291
313
  } else {
292
314
  try {
293
315
  stateNode[key] = val;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "7.6.0",
3
+ "version": "8.0.0",
4
4
  "description": "Reactive JSON for Angular. JSON branches, reactive leaves. No actions. No reducers. No selectors.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,4 @@
1
1
  import { WritableSignal } from '@angular/core';
2
- import { deepEqual, isBuiltInObject, parsePath } from '@signaltree/shared';
3
2
  export { deepEqual };
4
3
  export { deepEqual as equal };
5
4
  export { isBuiltInObject };
@@ -11,6 +10,26 @@ export interface MemoryManager {
11
10
  dispose(): void;
12
11
  }
13
12
  import type { NodeAccessor, TreeNode } from './types';
13
+
14
+ // Inlined from @signaltree/shared (internal package)
15
+ declare function deepEqual<T>(a: T, b: T): boolean;
16
+ declare function deepClone<T>(obj: T): T;
17
+ declare function isBuiltInObject(value: unknown): boolean;
18
+ declare function parsePath(path: string): string[];
19
+ declare function matchPath(path: string[], pattern: string[]): boolean;
20
+ declare function mergeDeep<T>(target: T, source: unknown): T;
21
+ declare function snapshotsEqual<T>(a: T, b: T): boolean;
22
+ declare function getChanges<T>(prev: T, next: T): unknown;
23
+ declare class LRUCache<K, V> {
24
+ constructor(maxSize: number);
25
+ get(key: K): V | undefined;
26
+ set(key: K, value: V): void;
27
+ has(key: K): boolean;
28
+ delete(key: K): boolean;
29
+ clear(): void;
30
+ get size(): number;
31
+ }
32
+ declare const DEFAULT_PATH_CACHE_SIZE: number;
14
33
  export declare function isNodeAccessor(value: unknown): value is NodeAccessor<unknown>;
15
34
  export declare function isAnySignal(value: unknown): boolean;
16
35
  export declare function toWritableSignal<T>(node: NodeAccessor<T>, injector?: unknown): WritableSignal<T>;