@signaltree/core 7.1.2 → 7.1.4
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 +138 -6
- package/package.json +2 -2
- package/dist/constants.js +0 -6
- package/dist/deep-equal.js +0 -41
- package/dist/enhancers/batching/batching.js +0 -230
- package/dist/enhancers/devtools/devtools.js +0 -318
- package/dist/enhancers/effects/effects.js +0 -66
- package/dist/enhancers/entities/entities.js +0 -24
- package/dist/enhancers/index.js +0 -72
- package/dist/enhancers/memoization/memoization.js +0 -420
- package/dist/enhancers/presets/lib/presets.js +0 -27
- package/dist/enhancers/serialization/constants.js +0 -15
- package/dist/enhancers/serialization/serialization.js +0 -656
- package/dist/enhancers/time-travel/time-travel.js +0 -283
- package/dist/enhancers/time-travel/utils.js +0 -11
- package/dist/enhancers/utils/copy-tree-properties.js +0 -20
- package/dist/index.js +0 -25
- package/dist/is-built-in-object.js +0 -23
- package/dist/lib/async-helpers.js +0 -77
- package/dist/lib/constants.js +0 -56
- package/dist/lib/edit-session.js +0 -84
- package/dist/lib/entity-signal.js +0 -544
- package/dist/lib/internals/batch-scope.js +0 -8
- package/dist/lib/internals/materialize-markers.js +0 -72
- package/dist/lib/internals/merge-derived.js +0 -59
- package/dist/lib/markers/derived.js +0 -6
- package/dist/lib/markers/entity-map.js +0 -20
- package/dist/lib/markers/status.js +0 -71
- package/dist/lib/markers/stored.js +0 -111
- package/dist/lib/memory/memory-manager.js +0 -164
- package/dist/lib/path-notifier.js +0 -178
- package/dist/lib/presets.js +0 -21
- package/dist/lib/security/security-validator.js +0 -121
- package/dist/lib/signal-tree.js +0 -415
- package/dist/lib/types.js +0 -3
- package/dist/lib/utils.js +0 -264
- package/dist/lru-cache.js +0 -64
- package/dist/parse-path.js +0 -13
- package/src/enhancers/batching/batching.d.ts +0 -10
- package/src/enhancers/batching/batching.types.d.ts +0 -1
- package/src/enhancers/batching/index.d.ts +0 -1
- package/src/enhancers/batching/test-setup.d.ts +0 -3
- package/src/enhancers/devtools/devtools.d.ts +0 -68
- package/src/enhancers/devtools/devtools.types.d.ts +0 -1
- package/src/enhancers/devtools/index.d.ts +0 -1
- package/src/enhancers/devtools/test-setup.d.ts +0 -3
- package/src/enhancers/effects/effects.d.ts +0 -9
- package/src/enhancers/effects/effects.types.d.ts +0 -1
- package/src/enhancers/effects/index.d.ts +0 -1
- package/src/enhancers/entities/entities.d.ts +0 -11
- package/src/enhancers/entities/entities.types.d.ts +0 -1
- package/src/enhancers/entities/index.d.ts +0 -1
- package/src/enhancers/entities/test-setup.d.ts +0 -3
- package/src/enhancers/index.d.ts +0 -3
- package/src/enhancers/memoization/index.d.ts +0 -1
- package/src/enhancers/memoization/memoization.d.ts +0 -54
- package/src/enhancers/memoization/memoization.types.d.ts +0 -1
- package/src/enhancers/memoization/test-setup.d.ts +0 -3
- package/src/enhancers/presets/index.d.ts +0 -1
- package/src/enhancers/presets/lib/presets.d.ts +0 -8
- package/src/enhancers/serialization/constants.d.ts +0 -14
- package/src/enhancers/serialization/index.d.ts +0 -2
- package/src/enhancers/serialization/serialization.d.ts +0 -68
- package/src/enhancers/serialization/test-setup.d.ts +0 -3
- package/src/enhancers/test-helpers/types-equals.d.ts +0 -2
- package/src/enhancers/time-travel/index.d.ts +0 -1
- package/src/enhancers/time-travel/test-setup.d.ts +0 -3
- package/src/enhancers/time-travel/time-travel.d.ts +0 -10
- package/src/enhancers/time-travel/time-travel.types.d.ts +0 -1
- package/src/enhancers/time-travel/utils.d.ts +0 -2
- package/src/enhancers/types.d.ts +0 -1
- package/src/enhancers/typing/helpers-types.d.ts +0 -2
- package/src/enhancers/utils/copy-tree-properties.d.ts +0 -1
- package/src/index.d.ts +0 -25
- package/src/lib/async-helpers.d.ts +0 -8
- package/src/lib/constants.d.ts +0 -41
- package/src/lib/dev-proxy.d.ts +0 -3
- package/src/lib/edit-session.d.ts +0 -21
- package/src/lib/entity-signal.d.ts +0 -1
- package/src/lib/internals/batch-scope.d.ts +0 -3
- package/src/lib/internals/builder-types.d.ts +0 -13
- package/src/lib/internals/derived-types.d.ts +0 -10
- package/src/lib/internals/materialize-markers.d.ts +0 -5
- package/src/lib/internals/merge-derived.d.ts +0 -4
- package/src/lib/markers/derived.d.ts +0 -9
- package/src/lib/markers/entity-map.d.ts +0 -4
- package/src/lib/markers/index.d.ts +0 -3
- package/src/lib/markers/status.d.ts +0 -32
- package/src/lib/markers/stored.d.ts +0 -23
- package/src/lib/memory/memory-manager.d.ts +0 -30
- package/src/lib/path-notifier.d.ts +0 -34
- package/src/lib/performance/diff-engine.d.ts +0 -33
- package/src/lib/performance/path-index.d.ts +0 -25
- package/src/lib/performance/update-engine.d.ts +0 -32
- package/src/lib/presets.d.ts +0 -34
- package/src/lib/security/security-validator.d.ts +0 -33
- package/src/lib/signal-tree.d.ts +0 -6
- package/src/lib/types.d.ts +0 -300
- package/src/lib/utils.d.ts +0 -25
package/README.md
CHANGED
|
@@ -2,19 +2,34 @@
|
|
|
2
2
|
<img src="https://raw.githubusercontent.com/JBorgia/signaltree/main/apps/demo/public/signaltree.svg" alt="SignalTree Logo" width="60" height="60" />
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
|
-
# SignalTree
|
|
5
|
+
# SignalTree: Reactive JSON
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**JSON branches, reactive leaves.**
|
|
8
|
+
|
|
9
|
+
> No actions. No reducers. No selectors.
|
|
8
10
|
|
|
9
11
|
## What is @signaltree/core?
|
|
10
12
|
|
|
11
|
-
SignalTree
|
|
13
|
+
SignalTree treats application state as **reactive JSON** — a typed, dot-notation interface to plain JSON-like objects with fine-grained reactivity layered transparently on top.
|
|
14
|
+
|
|
15
|
+
You don't model state as actions, reducers, selectors, or classes — you model it as **data**.
|
|
16
|
+
|
|
17
|
+
### Core Philosophy
|
|
18
|
+
|
|
19
|
+
| Principle | What It Means |
|
|
20
|
+
| ------------------------ | ---------------------------------------------------------------------------- |
|
|
21
|
+
| **State is Data** | Your state shape looks like JSON. No ceremony, no abstractions. |
|
|
22
|
+
| **Dot-Notation Access** | `tree.$.user.profile.name()` — fully type-safe, IDE-discoverable |
|
|
23
|
+
| **Invisible Reactivity** | You think in data paths, not subscriptions. Reactivity emerges naturally. |
|
|
24
|
+
| **Lazy by Design** | Signals created only where accessed. Types do heavy lifting at compile time. |
|
|
25
|
+
|
|
26
|
+
### Technical Features
|
|
12
27
|
|
|
13
28
|
- Recursive typing with deep nesting and accurate type inference
|
|
14
29
|
- Fast operations with sub‑millisecond measurements at 5–20+ levels
|
|
15
30
|
- Strong TypeScript safety across nested structures
|
|
16
31
|
- Memory efficiency via structural sharing and lazy signals
|
|
17
|
-
- Small API surface with
|
|
32
|
+
- Small API surface with minimal runtime overhead
|
|
18
33
|
- Compact bundle size suited for production
|
|
19
34
|
|
|
20
35
|
## Import guidance (tree-shaking)
|
|
@@ -110,7 +125,7 @@ Follow these principles for idiomatic SignalTree code:
|
|
|
110
125
|
### 1. Expose signals directly (no computed wrappers)
|
|
111
126
|
|
|
112
127
|
```typescript
|
|
113
|
-
const tree = signalTree(initialState).with(entities())
|
|
128
|
+
const tree = signalTree(initialState); // No .with(entities()) needed in v7+ (deprecated in v6, removed in v7)
|
|
114
129
|
const $ = tree.$; // Shorthand for state access
|
|
115
130
|
|
|
116
131
|
// ✅ SignalTree-first: Direct signal exposure
|
|
@@ -135,7 +150,7 @@ export type UserTree = ReturnType<typeof createUserTree>;
|
|
|
135
150
|
|
|
136
151
|
// Factory function - no explicit return type needed
|
|
137
152
|
export function createUserTree() {
|
|
138
|
-
const tree = signalTree(initialState)
|
|
153
|
+
const tree = signalTree(initialState); // entities() not needed in v7+
|
|
139
154
|
return {
|
|
140
155
|
selectedUserId: tree.$.selected.userId, // Type inferred automatically
|
|
141
156
|
// ...
|
|
@@ -897,6 +912,123 @@ tree.log('Tree created');
|
|
|
897
912
|
>
|
|
898
913
|
> 📱 **Interactive demo**: [Demo App](/custom-extensions)
|
|
899
914
|
|
|
915
|
+
### 8) Derived State Tiers
|
|
916
|
+
|
|
917
|
+
SignalTree supports **derived state** via the `.derived()` method, which allows you to add computed signals that build on base state or previous derived tiers.
|
|
918
|
+
|
|
919
|
+
#### Basic Usage (Inline Derived)
|
|
920
|
+
|
|
921
|
+
When derived functions are defined inline, TypeScript automatically infers all types:
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
import { signalTree, entityMap } from '@signaltree/core';
|
|
925
|
+
import { computed } from '@angular/core';
|
|
926
|
+
|
|
927
|
+
const tree = signalTree({
|
|
928
|
+
users: entityMap<User, number>(),
|
|
929
|
+
selectedUserId: null as number | null,
|
|
930
|
+
})
|
|
931
|
+
.derived(($) => ({
|
|
932
|
+
// Tier 1: Entity resolution
|
|
933
|
+
selectedUser: computed(() => {
|
|
934
|
+
const id = $.selectedUserId();
|
|
935
|
+
return id != null ? $.users.byId(id)?.() ?? null : null;
|
|
936
|
+
}),
|
|
937
|
+
}))
|
|
938
|
+
.derived(($) => ({
|
|
939
|
+
// Tier 2: Complex logic (can access $.selectedUser from Tier 1)
|
|
940
|
+
isAdmin: computed(() => $.selectedUser()?.role === 'admin'),
|
|
941
|
+
}));
|
|
942
|
+
|
|
943
|
+
// Usage
|
|
944
|
+
tree.$.selectedUser(); // User | null (computed signal)
|
|
945
|
+
tree.$.isAdmin(); // boolean (computed signal)
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
#### External Derived Functions (Modular Architecture)
|
|
949
|
+
|
|
950
|
+
For larger applications, you may want to organize derived tiers into separate files. **This requires explicit typing** because TypeScript cannot infer types across file boundaries.
|
|
951
|
+
|
|
952
|
+
SignalTree provides two utilities for external derived functions:
|
|
953
|
+
|
|
954
|
+
- **`derivedFrom<TTree>()`** - Curried helper function that provides type context for your derived function
|
|
955
|
+
- **`WithDerived<TTree, TDerivedFn>`** - Type utility to build intermediate tree types
|
|
956
|
+
|
|
957
|
+
```typescript
|
|
958
|
+
// app-tree.ts
|
|
959
|
+
import { signalTree, entityMap, WithDerived } from '@signaltree/core';
|
|
960
|
+
import { entityResolutionDerived } from './derived/tier-entity-resolution';
|
|
961
|
+
import { complexLogicDerived } from './derived/tier-complex-logic';
|
|
962
|
+
|
|
963
|
+
// Define base tree type
|
|
964
|
+
export type AppTreeBase = ReturnType<typeof signalTree<ReturnType<typeof createBaseState>>>;
|
|
965
|
+
|
|
966
|
+
// Build intermediate types using WithDerived
|
|
967
|
+
export type AppTreeWithTier1 = WithDerived<AppTreeBase, typeof entityResolutionDerived>;
|
|
968
|
+
export type AppTreeWithTier2 = WithDerived<AppTreeWithTier1, typeof complexLogicDerived>;
|
|
969
|
+
|
|
970
|
+
function createBaseState() {
|
|
971
|
+
return {
|
|
972
|
+
users: entityMap<User, number>(),
|
|
973
|
+
selectedUserId: null as number | null,
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
export function createAppTree() {
|
|
978
|
+
return signalTree(createBaseState()).derived(entityResolutionDerived).derived(complexLogicDerived);
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
```typescript
|
|
983
|
+
// derived/tier-entity-resolution.ts
|
|
984
|
+
import { computed } from '@angular/core';
|
|
985
|
+
import { derivedFrom } from '@signaltree/core';
|
|
986
|
+
import type { AppTreeBase } from '../app-tree';
|
|
987
|
+
|
|
988
|
+
// derivedFrom provides the type context for $ via curried syntax
|
|
989
|
+
export const entityResolutionDerived = derivedFrom<AppTreeBase>()(($) => ({
|
|
990
|
+
selectedUser: computed(() => {
|
|
991
|
+
const id = $.selectedUserId();
|
|
992
|
+
return id != null ? $.users.byId(id)?.() ?? null : null;
|
|
993
|
+
}),
|
|
994
|
+
}));
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
```typescript
|
|
998
|
+
// derived/tier-complex-logic.ts
|
|
999
|
+
import { computed } from '@angular/core';
|
|
1000
|
+
import { derivedFrom } from '@signaltree/core';
|
|
1001
|
+
import type { AppTreeWithTier1 } from '../app-tree';
|
|
1002
|
+
|
|
1003
|
+
// This tier has access to $.selectedUser from Tier 1
|
|
1004
|
+
export const complexLogicDerived = derivedFrom<AppTreeWithTier1>()(($) => ({
|
|
1005
|
+
isAdmin: computed(() => $.selectedUser()?.role === 'admin'),
|
|
1006
|
+
displayName: computed(() => {
|
|
1007
|
+
const user = $.selectedUser();
|
|
1008
|
+
return user ? `${user.firstName} ${user.lastName}` : 'No user selected';
|
|
1009
|
+
}),
|
|
1010
|
+
}));
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
#### Why External Functions Need Typing
|
|
1014
|
+
|
|
1015
|
+
When a function is defined in a separate file, TypeScript analyzes it **in isolation** before knowing how it will be used. The type inference happens at the **definition site**, not the **call site**:
|
|
1016
|
+
|
|
1017
|
+
```typescript
|
|
1018
|
+
// ❌ TypeScript can't infer $ - this file is compiled before app-tree.ts uses it
|
|
1019
|
+
export function myDerived($) {
|
|
1020
|
+
// $ is 'any'
|
|
1021
|
+
return { foo: computed(() => $.bar()) }; // Error: $ has no properties
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// ✅ derivedFrom provides the type context (curried syntax)
|
|
1025
|
+
export const myDerived = derivedFrom<AppTreeBase>()(($) => ({
|
|
1026
|
+
foo: computed(() => $.bar()), // $ is properly typed
|
|
1027
|
+
}));
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**Key point**: `derivedFrom` is **only needed for functions defined in separate files**. Inline functions automatically inherit types from the chain. Note the curried syntax: `derivedFrom<TreeType>()(fn)` - this allows TypeScript to infer the return type while you specify the tree type.
|
|
1031
|
+
|
|
900
1032
|
## Error handling examples
|
|
901
1033
|
|
|
902
1034
|
### Manual async error handling
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "7.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "7.1.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",
|
|
7
7
|
"sideEffects": false,
|
package/dist/constants.js
DELETED
package/dist/deep-equal.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
function deepEqual(a, b) {
|
|
2
|
-
if (a === b) return true;
|
|
3
|
-
if (a == null || b == null) return a === b;
|
|
4
|
-
const typeA = typeof a;
|
|
5
|
-
const typeB = typeof b;
|
|
6
|
-
if (typeA !== typeB) return false;
|
|
7
|
-
if (typeA !== 'object') return false;
|
|
8
|
-
if (a instanceof Date && b instanceof Date) {
|
|
9
|
-
return a.getTime() === b.getTime();
|
|
10
|
-
}
|
|
11
|
-
if (a instanceof RegExp && b instanceof RegExp) {
|
|
12
|
-
return a.source === b.source && a.flags === b.flags;
|
|
13
|
-
}
|
|
14
|
-
if (a instanceof Map && b instanceof Map) {
|
|
15
|
-
if (a.size !== b.size) return false;
|
|
16
|
-
for (const [key, value] of a) {
|
|
17
|
-
if (!b.has(key) || !deepEqual(value, b.get(key))) return false;
|
|
18
|
-
}
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
if (a instanceof Set && b instanceof Set) {
|
|
22
|
-
if (a.size !== b.size) return false;
|
|
23
|
-
for (const value of a) {
|
|
24
|
-
if (!b.has(value)) return false;
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(a)) {
|
|
29
|
-
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
30
|
-
return a.every((item, index) => deepEqual(item, b[index]));
|
|
31
|
-
}
|
|
32
|
-
if (Array.isArray(b)) return false;
|
|
33
|
-
const objA = a;
|
|
34
|
-
const objB = b;
|
|
35
|
-
const keysA = Object.keys(objA);
|
|
36
|
-
const keysB = Object.keys(objB);
|
|
37
|
-
if (keysA.length !== keysB.length) return false;
|
|
38
|
-
return keysA.every(key => key in objB && deepEqual(objA[key], objB[key]));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export { deepEqual };
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { copyTreeProperties } from '../utils/copy-tree-properties.js';
|
|
2
|
-
|
|
3
|
-
function batching(config = {}) {
|
|
4
|
-
const enabled = config.enabled ?? true;
|
|
5
|
-
const notificationDelayMs = config.notificationDelayMs ?? 0;
|
|
6
|
-
return tree => {
|
|
7
|
-
if (!enabled) {
|
|
8
|
-
const passthrough = {
|
|
9
|
-
batch: fn => fn(),
|
|
10
|
-
coalesce: fn => fn(),
|
|
11
|
-
hasPendingNotifications: () => false,
|
|
12
|
-
flushNotifications: () => {}
|
|
13
|
-
};
|
|
14
|
-
const enhanced = tree;
|
|
15
|
-
Object.assign(enhanced, passthrough);
|
|
16
|
-
enhanced.batchUpdate = updater => {
|
|
17
|
-
if (typeof tree.batchUpdate === 'function') {
|
|
18
|
-
tree.batchUpdate(updater);
|
|
19
|
-
} else {
|
|
20
|
-
updater(tree());
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
return enhanced;
|
|
24
|
-
}
|
|
25
|
-
let notificationPending = false;
|
|
26
|
-
let notificationTimeoutId;
|
|
27
|
-
let inBatch = false;
|
|
28
|
-
let inCoalesce = false;
|
|
29
|
-
const coalescedUpdates = new Map();
|
|
30
|
-
const scheduleNotification = () => {
|
|
31
|
-
if (notificationPending) return;
|
|
32
|
-
notificationPending = true;
|
|
33
|
-
if (notificationDelayMs > 0) {
|
|
34
|
-
notificationTimeoutId = setTimeout(flushNotificationsInternal, notificationDelayMs);
|
|
35
|
-
} else {
|
|
36
|
-
queueMicrotask(flushNotificationsInternal);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const flushNotificationsInternal = () => {
|
|
40
|
-
if (!notificationPending) return;
|
|
41
|
-
notificationPending = false;
|
|
42
|
-
if (notificationTimeoutId !== undefined) {
|
|
43
|
-
clearTimeout(notificationTimeoutId);
|
|
44
|
-
notificationTimeoutId = undefined;
|
|
45
|
-
}
|
|
46
|
-
if (tree.__notifyChangeDetection) {
|
|
47
|
-
tree.__notifyChangeDetection();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const flushCoalescedUpdates = () => {
|
|
51
|
-
const updates = Array.from(coalescedUpdates.values());
|
|
52
|
-
coalescedUpdates.clear();
|
|
53
|
-
updates.forEach(fn => {
|
|
54
|
-
try {
|
|
55
|
-
fn();
|
|
56
|
-
} catch (e) {
|
|
57
|
-
console.error('[SignalTree] Error in coalesced update:', e);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
};
|
|
61
|
-
const wrapSignalSetters = (node, path = '') => {
|
|
62
|
-
if (!node || typeof node !== 'object') return;
|
|
63
|
-
if (typeof node.set === 'function' && !node.__batchingWrapped) {
|
|
64
|
-
const originalSet = node.set.bind(node);
|
|
65
|
-
node.set = value => {
|
|
66
|
-
if (inCoalesce) {
|
|
67
|
-
coalescedUpdates.set(path, () => originalSet(value));
|
|
68
|
-
} else {
|
|
69
|
-
originalSet(value);
|
|
70
|
-
}
|
|
71
|
-
if (!inBatch) {
|
|
72
|
-
scheduleNotification();
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
node.__batchingWrapped = true;
|
|
76
|
-
}
|
|
77
|
-
if (typeof node.update === 'function' && !node.__batchingUpdateWrapped) {
|
|
78
|
-
const originalUpdate = node.update.bind(node);
|
|
79
|
-
node.update = updater => {
|
|
80
|
-
if (inCoalesce) {
|
|
81
|
-
coalescedUpdates.set(`${path}:update:${Date.now()}`, () => originalUpdate(updater));
|
|
82
|
-
} else {
|
|
83
|
-
originalUpdate(updater);
|
|
84
|
-
}
|
|
85
|
-
if (!inBatch) {
|
|
86
|
-
scheduleNotification();
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
node.__batchingUpdateWrapped = true;
|
|
90
|
-
}
|
|
91
|
-
for (const key of Object.keys(node)) {
|
|
92
|
-
if (key.startsWith('_') || key === 'set' || key === 'update') continue;
|
|
93
|
-
const child = node[key];
|
|
94
|
-
if (child && typeof child === 'object') {
|
|
95
|
-
wrapSignalSetters(child, path ? `${path}.${key}` : key);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
if (tree.$) {
|
|
100
|
-
wrapSignalSetters(tree.$);
|
|
101
|
-
}
|
|
102
|
-
const batchingMethods = {
|
|
103
|
-
batch(fn) {
|
|
104
|
-
const wasBatching = inBatch;
|
|
105
|
-
inBatch = true;
|
|
106
|
-
try {
|
|
107
|
-
fn();
|
|
108
|
-
} finally {
|
|
109
|
-
inBatch = wasBatching;
|
|
110
|
-
if (!inBatch) {
|
|
111
|
-
scheduleNotification();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
coalesce(fn) {
|
|
116
|
-
const wasCoalescing = inCoalesce;
|
|
117
|
-
const wasBatching = inBatch;
|
|
118
|
-
inCoalesce = true;
|
|
119
|
-
inBatch = true;
|
|
120
|
-
try {
|
|
121
|
-
fn();
|
|
122
|
-
} finally {
|
|
123
|
-
inCoalesce = wasCoalescing;
|
|
124
|
-
inBatch = wasBatching;
|
|
125
|
-
if (!wasCoalescing) {
|
|
126
|
-
flushCoalescedUpdates();
|
|
127
|
-
}
|
|
128
|
-
if (!inBatch) {
|
|
129
|
-
scheduleNotification();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
hasPendingNotifications() {
|
|
134
|
-
return notificationPending;
|
|
135
|
-
},
|
|
136
|
-
flushNotifications() {
|
|
137
|
-
flushNotificationsInternal();
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const originalTreeCall = tree.bind(tree);
|
|
141
|
-
const enhancedTree = function (...args) {
|
|
142
|
-
if (args.length === 0) {
|
|
143
|
-
return originalTreeCall();
|
|
144
|
-
} else {
|
|
145
|
-
if (args.length === 1) {
|
|
146
|
-
const arg = args[0];
|
|
147
|
-
if (typeof arg === 'function') {
|
|
148
|
-
originalTreeCall(arg);
|
|
149
|
-
} else {
|
|
150
|
-
originalTreeCall(arg);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!inBatch) {
|
|
154
|
-
scheduleNotification();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
|
|
159
|
-
Object.assign(enhancedTree, tree);
|
|
160
|
-
try {
|
|
161
|
-
copyTreeProperties(tree, enhancedTree);
|
|
162
|
-
} catch {}
|
|
163
|
-
Object.defineProperty(enhancedTree, 'with', {
|
|
164
|
-
value: function (enhancer) {
|
|
165
|
-
if (typeof enhancer !== 'function') {
|
|
166
|
-
throw new Error('Enhancer must be a function');
|
|
167
|
-
}
|
|
168
|
-
return enhancer(enhancedTree);
|
|
169
|
-
},
|
|
170
|
-
writable: false,
|
|
171
|
-
enumerable: false,
|
|
172
|
-
configurable: true
|
|
173
|
-
});
|
|
174
|
-
if ('state' in tree) {
|
|
175
|
-
Object.defineProperty(enhancedTree, 'state', {
|
|
176
|
-
value: tree.state,
|
|
177
|
-
enumerable: false,
|
|
178
|
-
configurable: true
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
if ('$' in tree) {
|
|
182
|
-
Object.defineProperty(enhancedTree, '$', {
|
|
183
|
-
value: tree.$,
|
|
184
|
-
enumerable: false,
|
|
185
|
-
configurable: true
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
Object.assign(enhancedTree, batchingMethods);
|
|
189
|
-
enhancedTree.batchUpdate = updater => {
|
|
190
|
-
enhancedTree.batch(() => {
|
|
191
|
-
const current = originalTreeCall();
|
|
192
|
-
const updates = updater(current);
|
|
193
|
-
Object.entries(updates).forEach(([key, value]) => {
|
|
194
|
-
const property = enhancedTree.state[key];
|
|
195
|
-
if (property && typeof property.set === 'function') {
|
|
196
|
-
property.set(value);
|
|
197
|
-
} else if (typeof property === 'function') {
|
|
198
|
-
property(value);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
return enhancedTree;
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
function highPerformanceBatching() {
|
|
207
|
-
return batching({
|
|
208
|
-
enabled: true,
|
|
209
|
-
notificationDelayMs: 0
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
function batchingWithConfig(config = {}) {
|
|
213
|
-
return batching(config);
|
|
214
|
-
}
|
|
215
|
-
function flushBatchedUpdates() {
|
|
216
|
-
console.warn('[SignalTree] flushBatchedUpdates() is deprecated. Use tree.flushNotifications() instead.');
|
|
217
|
-
}
|
|
218
|
-
function hasPendingUpdates() {
|
|
219
|
-
console.warn('[SignalTree] hasPendingUpdates() is deprecated. Use tree.hasPendingNotifications() instead.');
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
function getBatchQueueSize() {
|
|
223
|
-
console.warn('[SignalTree] getBatchQueueSize() is deprecated. Signal writes are now synchronous.');
|
|
224
|
-
return 0;
|
|
225
|
-
}
|
|
226
|
-
Object.assign((config = {}) => batching(config), {
|
|
227
|
-
highPerformance: highPerformanceBatching
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
export { batching, batchingWithConfig, flushBatchedUpdates, getBatchQueueSize, hasPendingUpdates, highPerformanceBatching };
|