@signaltree/core 5.1.3 → 5.1.6
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 +6 -8
- package/dist/enhancers/entities/lib/entities.js +4 -31
- package/dist/lib/entity-signal.js +12 -9
- package/dist/lib/signal-tree.js +0 -9
- package/package.json +1 -1
- package/src/enhancers/entities/lib/entities.d.ts +1 -4
- package/src/entities.d.ts +22 -14
- package/src/index.d.ts +1 -1
- package/src/lib/types.d.ts +0 -8
- package/src/types.d.ts +364 -206
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ const selectedUserId = computed(() => $.selected.userId()); // Unnecessary!
|
|
|
141
141
|
```typescript
|
|
142
142
|
// ✅ SignalTree-native
|
|
143
143
|
const user = $.users.byId(123)(); // O(1) lookup
|
|
144
|
-
const allUsers = $.users.all
|
|
144
|
+
const allUsers = $.users.all; // Get all
|
|
145
145
|
$.users.setAll(usersFromApi); // Replace all
|
|
146
146
|
|
|
147
147
|
// ❌ NgRx-style (avoid)
|
|
@@ -493,7 +493,7 @@ const tree = signalTree({
|
|
|
493
493
|
users: [] as User[],
|
|
494
494
|
});
|
|
495
495
|
|
|
496
|
-
//
|
|
496
|
+
// Entity CRUD operations using core methods
|
|
497
497
|
function addUser(user: User) {
|
|
498
498
|
tree.$.users.update((users) => [...users, user]);
|
|
499
499
|
}
|
|
@@ -630,9 +630,7 @@ tree.$.products.addOne(newProduct);
|
|
|
630
630
|
tree.$.products.setAll(productsFromApi);
|
|
631
631
|
|
|
632
632
|
// Entity queries
|
|
633
|
-
const electronics = tree.$.products
|
|
634
|
-
.all()()
|
|
635
|
-
.filter((p) => p.category === 'electronics');
|
|
633
|
+
const electronics = tree.$.products.all.filter((p) => p.category === 'electronics');
|
|
636
634
|
```
|
|
637
635
|
|
|
638
636
|
**Full-Stack Application:**
|
|
@@ -722,7 +720,7 @@ const enhanced = signalTree({
|
|
|
722
720
|
|
|
723
721
|
enhanced.$.users.addOne(newUser); // ✅ Advanced CRUD operations
|
|
724
722
|
enhanced.$.users.byId(123)(); // ✅ O(1) lookups
|
|
725
|
-
enhanced.$.users.all
|
|
723
|
+
enhanced.$.users.all; // ✅ Get all as array
|
|
726
724
|
```
|
|
727
725
|
|
|
728
726
|
Core includes several performance optimizations:
|
|
@@ -945,7 +943,7 @@ const appTree = signalTree({
|
|
|
945
943
|
// Access nested entities using tree.$ accessor
|
|
946
944
|
appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
|
|
947
945
|
appTree.$.app.data.products.selectTotal(); // Count signal
|
|
948
|
-
appTree.$.admin.data.logs.all
|
|
946
|
+
appTree.$.admin.data.logs.all; // All items as array
|
|
949
947
|
appTree.$.admin.data.reports.selectIds(); // ID array signal
|
|
950
948
|
|
|
951
949
|
// For async operations, use manual async or async helpers
|
|
@@ -1370,7 +1368,7 @@ tree.destroy(); // Cleanup resources
|
|
|
1370
1368
|
// Entity helpers (when using entityMap + withEntities)
|
|
1371
1369
|
// tree.$.users.addOne(user); // Add single entity
|
|
1372
1370
|
// tree.$.users.byId(id)(); // O(1) lookup by ID
|
|
1373
|
-
// tree.$.users.all
|
|
1371
|
+
// tree.$.users.all; // Get all as array
|
|
1374
1372
|
// tree.$.users.selectBy(pred); // Filtered signal
|
|
1375
1373
|
```
|
|
1376
1374
|
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEntitySignal } from '../../../lib/entity-signal.js';
|
|
2
2
|
import { getPathNotifier } from '../../../lib/path-notifier.js';
|
|
3
3
|
import { isNodeAccessor } from '../../../lib/utils.js';
|
|
4
4
|
|
|
5
5
|
function isEntityMapMarker(value) {
|
|
6
6
|
return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
|
|
7
7
|
}
|
|
8
|
-
function isEntitySignal(value) {
|
|
9
|
-
return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && 'all' in value;
|
|
10
|
-
}
|
|
11
8
|
function materializeEntities(tree, notifier = getPathNotifier()) {
|
|
12
|
-
const registry = new Map();
|
|
13
9
|
const state = tree.state;
|
|
14
10
|
const visit = (parent, key, value, path) => {
|
|
15
11
|
const nextPath = [...path, key];
|
|
16
12
|
if (isEntityMapMarker(value)) {
|
|
17
13
|
const basePath = nextPath.join('.');
|
|
18
14
|
const config = value.__entityMapConfig ?? {};
|
|
19
|
-
const entitySignal =
|
|
15
|
+
const entitySignal = createEntitySignal(config, notifier, basePath);
|
|
20
16
|
if (parent) {
|
|
21
17
|
try {
|
|
22
18
|
parent[key] = entitySignal;
|
|
@@ -25,7 +21,6 @@ function materializeEntities(tree, notifier = getPathNotifier()) {
|
|
|
25
21
|
try {
|
|
26
22
|
tree[key] = entitySignal;
|
|
27
23
|
} catch {}
|
|
28
|
-
registry.set(basePath, entitySignal);
|
|
29
24
|
return;
|
|
30
25
|
}
|
|
31
26
|
if (isNodeAccessor(value)) {
|
|
@@ -44,23 +39,6 @@ function materializeEntities(tree, notifier = getPathNotifier()) {
|
|
|
44
39
|
for (const key of Object.keys(state)) {
|
|
45
40
|
visit(state, key, state[key], []);
|
|
46
41
|
}
|
|
47
|
-
return registry;
|
|
48
|
-
}
|
|
49
|
-
function resolveEntitySignal(tree, registry, path) {
|
|
50
|
-
const pathStr = String(path);
|
|
51
|
-
const existing = registry.get(pathStr);
|
|
52
|
-
if (existing) return existing;
|
|
53
|
-
const segments = pathStr.split('.');
|
|
54
|
-
let current = tree.state;
|
|
55
|
-
for (const segment of segments) {
|
|
56
|
-
if (!current) break;
|
|
57
|
-
current = current[segment];
|
|
58
|
-
}
|
|
59
|
-
if (isEntitySignal(current)) {
|
|
60
|
-
registry.set(pathStr, current);
|
|
61
|
-
return current;
|
|
62
|
-
}
|
|
63
|
-
throw new Error(`Entity path '${pathStr}' is not configured. Define it with entityMap() in your initial state.`);
|
|
64
42
|
}
|
|
65
43
|
function withEntities(config = {}) {
|
|
66
44
|
const {
|
|
@@ -70,13 +48,8 @@ function withEntities(config = {}) {
|
|
|
70
48
|
if (!enabled) {
|
|
71
49
|
return tree;
|
|
72
50
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
entities(path) {
|
|
76
|
-
return resolveEntitySignal(tree, registry, path);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
return enhancedTree;
|
|
51
|
+
materializeEntities(tree);
|
|
52
|
+
return tree;
|
|
80
53
|
};
|
|
81
54
|
}
|
|
82
55
|
function enableEntities() {
|
|
@@ -20,14 +20,6 @@ class EntitySignalImpl {
|
|
|
20
20
|
this.countSignal = signal(0);
|
|
21
21
|
this.idsSignal = signal([]);
|
|
22
22
|
this.mapSignal = signal(new Map());
|
|
23
|
-
return new Proxy(this, {
|
|
24
|
-
get: (target, prop) => {
|
|
25
|
-
if (typeof prop === 'string' && !isNaN(Number(prop))) {
|
|
26
|
-
return target.byId(Number(prop));
|
|
27
|
-
}
|
|
28
|
-
return target[prop];
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
23
|
}
|
|
32
24
|
byId(id) {
|
|
33
25
|
const entity = this.storage.get(id);
|
|
@@ -276,5 +268,16 @@ class EntitySignalImpl {
|
|
|
276
268
|
return node;
|
|
277
269
|
}
|
|
278
270
|
}
|
|
271
|
+
function createEntitySignal(config, pathNotifier, basePath) {
|
|
272
|
+
const impl = new EntitySignalImpl(config, pathNotifier, basePath);
|
|
273
|
+
return new Proxy(impl, {
|
|
274
|
+
get: (target, prop) => {
|
|
275
|
+
if (typeof prop === 'string' && !isNaN(Number(prop))) {
|
|
276
|
+
return target.byId(Number(prop));
|
|
277
|
+
}
|
|
278
|
+
return target[prop];
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
279
282
|
|
|
280
|
-
export { EntitySignalImpl };
|
|
283
|
+
export { EntitySignalImpl, createEntitySignal };
|
package/dist/lib/signal-tree.js
CHANGED
|
@@ -449,15 +449,6 @@ function addStubMethods(tree, config) {
|
|
|
449
449
|
}
|
|
450
450
|
return engine.update(tree(), updates, options);
|
|
451
451
|
};
|
|
452
|
-
tree.getMetrics = () => {
|
|
453
|
-
return {
|
|
454
|
-
updates: 0,
|
|
455
|
-
computations: 0,
|
|
456
|
-
cacheHits: 0,
|
|
457
|
-
cacheMisses: 0,
|
|
458
|
-
averageUpdateTime: 0
|
|
459
|
-
};
|
|
460
|
-
};
|
|
461
452
|
tree.entities = () => {
|
|
462
453
|
console.warn('[@signaltree/core] tree.entities() is deprecated and will be removed in v6.0. ' + 'Use entityMap<E>() + withEntities() + tree.$.collectionName instead. ' + 'See https://signaltree.dev/docs/migration for migration guide.');
|
|
463
454
|
if (config.debugMode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.6",
|
|
4
4
|
"description": "Lightweight, type-safe signal-based state management for Angular. Core package providing hierarchical signal trees, basic entity management, and async actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SignalTree, EntityAwareTreeNode } from '../../../lib/types';
|
|
2
2
|
interface EntitiesEnhancerConfig {
|
|
3
3
|
enabled?: boolean;
|
|
4
4
|
}
|
|
5
5
|
export declare function withEntities(config?: EntitiesEnhancerConfig): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
|
|
6
6
|
state: EntityAwareTreeNode<T>;
|
|
7
7
|
$: EntityAwareTreeNode<T>;
|
|
8
|
-
entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
|
|
9
8
|
};
|
|
10
9
|
export declare function enableEntities(): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
|
|
11
10
|
state: EntityAwareTreeNode<T>;
|
|
12
11
|
$: EntityAwareTreeNode<T>;
|
|
13
|
-
entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
|
|
14
12
|
};
|
|
15
13
|
export declare function withHighPerformanceEntities(): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
|
|
16
14
|
state: EntityAwareTreeNode<T>;
|
|
17
15
|
$: EntityAwareTreeNode<T>;
|
|
18
|
-
entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
|
|
19
16
|
};
|
|
20
17
|
export {};
|
package/src/entities.d.ts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
EntitySignal,
|
|
3
|
+
SignalTree,
|
|
4
|
+
EntityAwareTreeNode,
|
|
5
|
+
} from '../../../lib/types';
|
|
2
6
|
interface EntitiesEnhancerConfig {
|
|
3
|
-
|
|
7
|
+
enabled?: boolean;
|
|
4
8
|
}
|
|
5
|
-
export declare function withEntities(config?: EntitiesEnhancerConfig): <T>(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
export declare function withEntities(config?: EntitiesEnhancerConfig): <T>(
|
|
10
|
+
tree: SignalTree<T>
|
|
11
|
+
) => Omit<SignalTree<T>, 'state' | '$'> & {
|
|
12
|
+
state: EntityAwareTreeNode<T>;
|
|
13
|
+
$: EntityAwareTreeNode<T>;
|
|
9
14
|
};
|
|
10
|
-
export declare function enableEntities(): <T>(tree: SignalTree<T>) => Omit<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
export declare function enableEntities(): <T>(tree: SignalTree<T>) => Omit<
|
|
16
|
+
SignalTree<T>,
|
|
17
|
+
'state' | '$'
|
|
18
|
+
> & {
|
|
19
|
+
state: EntityAwareTreeNode<T>;
|
|
20
|
+
$: EntityAwareTreeNode<T>;
|
|
14
21
|
};
|
|
15
|
-
export declare function withHighPerformanceEntities(): <T>(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
export declare function withHighPerformanceEntities(): <T>(
|
|
23
|
+
tree: SignalTree<T>
|
|
24
|
+
) => Omit<SignalTree<T>, 'state' | '$'> & {
|
|
25
|
+
state: EntityAwareTreeNode<T>;
|
|
26
|
+
$: EntityAwareTreeNode<T>;
|
|
19
27
|
};
|
|
20
28
|
export {};
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { signalTree } from './lib/signal-tree';
|
|
2
|
-
export type { SignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, RemoveSignalMethods, Primitive, BuiltInObject, NotFn, DeepPath, DeepAccess, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, ChainResult, WithMethod, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions,
|
|
2
|
+
export type { SignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, RemoveSignalMethods, Primitive, BuiltInObject, NotFn, DeepPath, DeepAccess, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, ChainResult, WithMethod, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, EntityHelpers, TimeTravelEntry, } from './lib/types';
|
|
3
3
|
export { entityMap } from './lib/types';
|
|
4
4
|
export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, createLazySignalTree, } from './lib/utils';
|
|
5
5
|
export { getPathNotifier } from './lib/path-notifier';
|
package/src/lib/types.d.ts
CHANGED
|
@@ -89,7 +89,6 @@ export type SignalTree<T> = NodeAccessor<T> & {
|
|
|
89
89
|
batchedUpdates: number;
|
|
90
90
|
};
|
|
91
91
|
};
|
|
92
|
-
getMetrics(): PerformanceMetrics;
|
|
93
92
|
entities<E extends {
|
|
94
93
|
id: string | number;
|
|
95
94
|
}>(entityKey?: keyof T): EntityHelpers<E>;
|
|
@@ -117,13 +116,6 @@ export interface TreeConfig {
|
|
|
117
116
|
useStructuralSharing?: boolean;
|
|
118
117
|
security?: SecurityValidatorConfig;
|
|
119
118
|
}
|
|
120
|
-
export interface PerformanceMetrics {
|
|
121
|
-
updates: number;
|
|
122
|
-
computations: number;
|
|
123
|
-
cacheHits: number;
|
|
124
|
-
cacheMisses: number;
|
|
125
|
-
averageUpdateTime: number;
|
|
126
|
-
}
|
|
127
119
|
export interface EntityConfig<E, K extends string | number = string> {
|
|
128
120
|
selectId?: (entity: E) => K;
|
|
129
121
|
hooks?: {
|
package/src/types.d.ts
CHANGED
|
@@ -1,278 +1,436 @@
|
|
|
1
1
|
import { Signal, WritableSignal } from '@angular/core';
|
|
2
|
+
|
|
2
3
|
import type { SecurityValidatorConfig } from './security/security-validator';
|
|
3
4
|
export type NotFn<T> = T extends (...args: unknown[]) => unknown ? never : T;
|
|
4
5
|
declare module '@angular/core' {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
interface WritableSignal<T> {
|
|
7
|
+
(value: NotFn<T>): void;
|
|
8
|
+
(updater: (current: T) => T): void;
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
|
-
export type Primitive =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export type Primitive =
|
|
12
|
+
| string
|
|
13
|
+
| number
|
|
14
|
+
| boolean
|
|
15
|
+
| null
|
|
16
|
+
| undefined
|
|
17
|
+
| bigint
|
|
18
|
+
| symbol;
|
|
19
|
+
export type BuiltInObject =
|
|
20
|
+
| Date
|
|
21
|
+
| RegExp
|
|
22
|
+
| ((...args: unknown[]) => unknown)
|
|
23
|
+
| Map<unknown, unknown>
|
|
24
|
+
| Set<unknown>
|
|
25
|
+
| WeakMap<object, unknown>
|
|
26
|
+
| WeakSet<object>
|
|
27
|
+
| ArrayBuffer
|
|
28
|
+
| DataView
|
|
29
|
+
| Error
|
|
30
|
+
| Promise<unknown>
|
|
31
|
+
| Uint16Array
|
|
32
|
+
| Int32Array
|
|
33
|
+
| Uint32Array
|
|
34
|
+
| Float32Array
|
|
35
|
+
| Float64Array
|
|
36
|
+
| BigInt64Array
|
|
37
|
+
| BigUint64Array
|
|
38
|
+
| URL
|
|
39
|
+
| URLSearchParams
|
|
40
|
+
| FormData
|
|
41
|
+
| Blob
|
|
42
|
+
| File
|
|
43
|
+
| Headers
|
|
44
|
+
| Request
|
|
45
|
+
| Response
|
|
46
|
+
| AbortController
|
|
47
|
+
| AbortSignal;
|
|
48
|
+
export type Unwrap<T> = [T] extends [WritableSignal<infer U>]
|
|
49
|
+
? U
|
|
50
|
+
: [T] extends [Signal<infer U>]
|
|
51
|
+
? U
|
|
52
|
+
: [T] extends [BuiltInObject]
|
|
53
|
+
? T
|
|
54
|
+
: [T] extends [readonly unknown[]]
|
|
55
|
+
? T
|
|
56
|
+
: [T] extends [EntityMapMarker<infer E, infer K>]
|
|
57
|
+
? EntitySignal<E, K>
|
|
58
|
+
: [T] extends [object]
|
|
59
|
+
? {
|
|
60
|
+
[K in keyof T]: Unwrap<T[K]>;
|
|
61
|
+
}
|
|
62
|
+
: T;
|
|
15
63
|
export interface NodeAccessor<T> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
64
|
+
(): T;
|
|
65
|
+
(value: T): void;
|
|
66
|
+
(updater: (current: T) => T): void;
|
|
19
67
|
}
|
|
20
68
|
export type AccessibleNode<T> = NodeAccessor<T> & TreeNode<T>;
|
|
21
69
|
export type CallableWritableSignal<T> = WritableSignal<T> & {
|
|
22
|
-
|
|
23
|
-
|
|
70
|
+
(value: NotFn<T>): void;
|
|
71
|
+
(updater: (current: T) => T): void;
|
|
24
72
|
};
|
|
25
73
|
export type TreeNode<T> = {
|
|
26
|
-
|
|
74
|
+
[K in keyof T]: [T[K]] extends [EntityMapMarker<infer E, infer Key>]
|
|
75
|
+
? EntitySignal<E, Key>
|
|
76
|
+
: [T[K]] extends [readonly unknown[]]
|
|
77
|
+
? CallableWritableSignal<T[K]>
|
|
78
|
+
: [T[K]] extends [object]
|
|
79
|
+
? [T[K]] extends [Signal<unknown>]
|
|
80
|
+
? T[K]
|
|
81
|
+
: [T[K]] extends [BuiltInObject]
|
|
82
|
+
? CallableWritableSignal<T[K]>
|
|
83
|
+
: [T[K]] extends [(...args: unknown[]) => unknown]
|
|
84
|
+
? CallableWritableSignal<T[K]>
|
|
85
|
+
: AccessibleNode<T[K]>
|
|
86
|
+
: CallableWritableSignal<T[K]>;
|
|
27
87
|
};
|
|
28
88
|
export type RemoveSignalMethods<T> = T extends infer U ? U : never;
|
|
29
|
-
export type DeepPath<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
89
|
+
export type DeepPath<
|
|
90
|
+
T,
|
|
91
|
+
Prefix extends string = '',
|
|
92
|
+
Depth extends readonly number[] = []
|
|
93
|
+
> = Depth['length'] extends 5
|
|
94
|
+
? never
|
|
95
|
+
: {
|
|
96
|
+
[K in keyof T]: K extends string
|
|
97
|
+
? T[K] extends readonly unknown[]
|
|
98
|
+
? `${Prefix}${K}`
|
|
99
|
+
: T[K] extends object
|
|
100
|
+
? T[K] extends Signal<unknown>
|
|
101
|
+
? never
|
|
102
|
+
: T[K] extends BuiltInObject
|
|
103
|
+
? never
|
|
104
|
+
: T[K] extends (...args: unknown[]) => unknown
|
|
105
|
+
? never
|
|
106
|
+
: `${Prefix}${K}` | DeepPath<T[K], `${Prefix}${K}.`, [...Depth, 1]>
|
|
107
|
+
: never
|
|
108
|
+
: never;
|
|
109
|
+
}[keyof T];
|
|
110
|
+
export type DeepAccess<
|
|
111
|
+
T,
|
|
112
|
+
Path extends string
|
|
113
|
+
> = Path extends `${infer First}.${infer Rest}`
|
|
114
|
+
? First extends keyof T
|
|
115
|
+
? DeepAccess<T[First] & object, Rest>
|
|
116
|
+
: never
|
|
117
|
+
: Path extends keyof T
|
|
118
|
+
? T[Path]
|
|
119
|
+
: never;
|
|
33
120
|
export interface EnhancerMeta {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
121
|
+
name?: string;
|
|
122
|
+
requires?: string[];
|
|
123
|
+
provides?: string[];
|
|
37
124
|
}
|
|
38
|
-
export type Enhancer<Input = unknown, Output = unknown> = (
|
|
39
|
-
|
|
40
|
-
|
|
125
|
+
export type Enhancer<Input = unknown, Output = unknown> = (
|
|
126
|
+
input: Input
|
|
127
|
+
) => Output;
|
|
128
|
+
export type EnhancerWithMeta<Input = unknown, Output = unknown> = Enhancer<
|
|
129
|
+
Input,
|
|
130
|
+
Output
|
|
131
|
+
> & {
|
|
132
|
+
metadata?: EnhancerMeta;
|
|
41
133
|
};
|
|
42
134
|
export declare const ENHANCER_META: unique symbol;
|
|
43
|
-
export type ChainResult<
|
|
135
|
+
export type ChainResult<
|
|
136
|
+
Start,
|
|
137
|
+
E extends Array<EnhancerWithMeta<unknown, unknown>>
|
|
138
|
+
> = E extends [infer H, ...infer R]
|
|
139
|
+
? H extends EnhancerWithMeta<SignalTree<unknown>, infer O>
|
|
140
|
+
? R extends Array<EnhancerWithMeta<unknown, unknown>>
|
|
141
|
+
? ChainResult<O, R>
|
|
142
|
+
: O
|
|
143
|
+
: H extends EnhancerWithMeta<infer I, infer O>
|
|
144
|
+
? Start extends I
|
|
145
|
+
? R extends Array<EnhancerWithMeta<unknown, unknown>>
|
|
146
|
+
? ChainResult<O, R>
|
|
147
|
+
: O
|
|
148
|
+
: unknown
|
|
149
|
+
: unknown
|
|
150
|
+
: Start;
|
|
44
151
|
export interface WithMethod<T> {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
152
|
+
(): SignalTree<T>;
|
|
153
|
+
<O>(enhancer: (input: SignalTree<T>) => O): O;
|
|
154
|
+
<O1, O2>(e1: (input: SignalTree<T>) => O1, e2: (input: O1) => O2): O2;
|
|
155
|
+
<O1, O2, O3>(
|
|
156
|
+
e1: (input: SignalTree<T>) => O1,
|
|
157
|
+
e2: (input: O1) => O2,
|
|
158
|
+
e3: (input: O2) => O3
|
|
159
|
+
): O3;
|
|
160
|
+
<O>(enhancer: EnhancerWithMeta<SignalTree<T>, O>): O;
|
|
161
|
+
<O1, O2>(
|
|
162
|
+
e1: EnhancerWithMeta<SignalTree<T>, O1>,
|
|
163
|
+
e2: EnhancerWithMeta<O1, O2>
|
|
164
|
+
): O2;
|
|
165
|
+
<O1, O2, O3>(
|
|
166
|
+
e1: EnhancerWithMeta<SignalTree<T>, O1>,
|
|
167
|
+
e2: EnhancerWithMeta<O1, O2>,
|
|
168
|
+
e3: EnhancerWithMeta<O2, O3>
|
|
169
|
+
): O3;
|
|
52
170
|
}
|
|
53
171
|
export type SignalTree<T> = NodeAccessor<T> & {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
172
|
+
state: TreeNode<T>;
|
|
173
|
+
$: TreeNode<T>;
|
|
174
|
+
with: WithMethod<T>;
|
|
175
|
+
destroy(): void;
|
|
176
|
+
dispose?(): void;
|
|
177
|
+
effect(fn: (tree: T) => void): void;
|
|
178
|
+
subscribe(fn: (tree: T) => void): () => void;
|
|
179
|
+
batch(updater: (tree: T) => void): void;
|
|
180
|
+
batchUpdate(updater: (current: T) => Partial<T>): void;
|
|
181
|
+
memoize<R>(fn: (tree: T) => R, cacheKey?: string): Signal<R>;
|
|
182
|
+
memoizedUpdate(updater: (current: T) => Partial<T>, cacheKey?: string): void;
|
|
183
|
+
clearMemoCache(key?: string): void;
|
|
184
|
+
getCacheStats(): {
|
|
185
|
+
size: number;
|
|
186
|
+
hitRate: number;
|
|
187
|
+
totalHits: number;
|
|
188
|
+
totalMisses: number;
|
|
189
|
+
keys: string[];
|
|
190
|
+
};
|
|
191
|
+
optimize(): void;
|
|
192
|
+
clearCache(): void;
|
|
193
|
+
invalidatePattern(pattern: string): number;
|
|
194
|
+
updateOptimized?(
|
|
195
|
+
updates: Partial<T>,
|
|
196
|
+
options?: {
|
|
197
|
+
batch?: boolean;
|
|
198
|
+
batchSize?: number;
|
|
199
|
+
maxDepth?: number;
|
|
200
|
+
ignoreArrayOrder?: boolean;
|
|
201
|
+
equalityFn?: (a: unknown, b: unknown) => boolean;
|
|
202
|
+
}
|
|
203
|
+
): {
|
|
204
|
+
changed: boolean;
|
|
205
|
+
duration: number;
|
|
206
|
+
changedPaths: string[];
|
|
207
|
+
stats?: {
|
|
208
|
+
totalPaths: number;
|
|
209
|
+
optimizedPaths: number;
|
|
210
|
+
batchedUpdates: number;
|
|
91
211
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
212
|
+
};
|
|
213
|
+
entities<
|
|
214
|
+
E extends {
|
|
215
|
+
id: string | number;
|
|
216
|
+
}
|
|
217
|
+
>(
|
|
218
|
+
entityKey?: keyof T
|
|
219
|
+
): EntityHelpers<E>;
|
|
220
|
+
undo(): void;
|
|
221
|
+
redo(): void;
|
|
222
|
+
getHistory(): TimeTravelEntry<T>[];
|
|
223
|
+
resetHistory(): void;
|
|
224
|
+
jumpTo?: (index: number) => void;
|
|
225
|
+
canUndo?: () => boolean;
|
|
226
|
+
canRedo?: () => boolean;
|
|
227
|
+
getCurrentIndex?: () => number;
|
|
104
228
|
};
|
|
105
229
|
export type TreePreset = 'basic' | 'performance' | 'development' | 'production';
|
|
106
230
|
export interface TreeConfig {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
export interface PerformanceMetrics {
|
|
121
|
-
updates: number;
|
|
122
|
-
computations: number;
|
|
123
|
-
cacheHits: number;
|
|
124
|
-
cacheMisses: number;
|
|
125
|
-
averageUpdateTime: number;
|
|
231
|
+
batchUpdates?: boolean;
|
|
232
|
+
useMemoization?: boolean;
|
|
233
|
+
enableTimeTravel?: boolean;
|
|
234
|
+
useLazySignals?: boolean;
|
|
235
|
+
useShallowComparison?: boolean;
|
|
236
|
+
maxCacheSize?: number;
|
|
237
|
+
trackPerformance?: boolean;
|
|
238
|
+
treeName?: string;
|
|
239
|
+
enableDevTools?: boolean;
|
|
240
|
+
debugMode?: boolean;
|
|
241
|
+
useStructuralSharing?: boolean;
|
|
242
|
+
security?: SecurityValidatorConfig;
|
|
126
243
|
}
|
|
127
244
|
export interface EntityConfig<E, K extends string | number = string> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
245
|
+
selectId?: (entity: E) => K;
|
|
246
|
+
hooks?: {
|
|
247
|
+
beforeAdd?: (entity: E) => E | false;
|
|
248
|
+
beforeUpdate?: (id: K, changes: Partial<E>) => Partial<E> | false;
|
|
249
|
+
beforeRemove?: (id: K, entity: E) => boolean;
|
|
250
|
+
};
|
|
134
251
|
}
|
|
135
252
|
declare const ENTITY_MAP_BRAND: unique symbol;
|
|
136
253
|
export interface EntityMapMarker<E, K extends string | number> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
254
|
+
readonly [ENTITY_MAP_BRAND]: {
|
|
255
|
+
__entity: E;
|
|
256
|
+
__key: K;
|
|
257
|
+
};
|
|
258
|
+
readonly __isEntityMap: true;
|
|
259
|
+
readonly __entityMapConfig?: EntityConfig<E, K>;
|
|
143
260
|
}
|
|
144
|
-
export declare function entityMap<
|
|
261
|
+
export declare function entityMap<
|
|
262
|
+
E,
|
|
263
|
+
K extends string | number = E extends {
|
|
145
264
|
id: infer I extends string | number;
|
|
146
|
-
}
|
|
265
|
+
}
|
|
266
|
+
? I
|
|
267
|
+
: string
|
|
268
|
+
>(config?: EntityConfig<E, K>): EntityMapMarker<E, K>;
|
|
147
269
|
export interface MutationOptions {
|
|
148
|
-
|
|
270
|
+
onError?: (error: Error) => void;
|
|
149
271
|
}
|
|
150
272
|
export interface AddOptions<E, K> extends MutationOptions {
|
|
151
|
-
|
|
273
|
+
selectId?: (entity: E) => K;
|
|
152
274
|
}
|
|
153
275
|
export interface AddManyOptions<E, K> extends AddOptions<E, K> {
|
|
154
|
-
|
|
276
|
+
mode?: 'strict' | 'skip' | 'overwrite';
|
|
155
277
|
}
|
|
156
278
|
export interface TapHandlers<E, K extends string | number> {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
279
|
+
onAdd?: (entity: E, id: K) => void;
|
|
280
|
+
onUpdate?: (id: K, changes: Partial<E>, entity: E) => void;
|
|
281
|
+
onRemove?: (id: K, entity: E) => void;
|
|
282
|
+
onChange?: () => void;
|
|
161
283
|
}
|
|
162
284
|
export interface InterceptContext<T> {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
285
|
+
block(reason?: string): void;
|
|
286
|
+
transform(value: T): void;
|
|
287
|
+
readonly blocked: boolean;
|
|
288
|
+
readonly blockReason: string | undefined;
|
|
167
289
|
}
|
|
168
290
|
export interface InterceptHandlers<E, K extends string | number> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
291
|
+
onAdd?: (entity: E, ctx: InterceptContext<E>) => void | Promise<void>;
|
|
292
|
+
onUpdate?: (
|
|
293
|
+
id: K,
|
|
294
|
+
changes: Partial<E>,
|
|
295
|
+
ctx: InterceptContext<Partial<E>>
|
|
296
|
+
) => void | Promise<void>;
|
|
297
|
+
onRemove?: (
|
|
298
|
+
id: K,
|
|
299
|
+
entity: E,
|
|
300
|
+
ctx: InterceptContext<void>
|
|
301
|
+
) => void | Promise<void>;
|
|
172
302
|
}
|
|
173
303
|
export type EntityNode<E> = {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
304
|
+
(): E;
|
|
305
|
+
(value: E): void;
|
|
306
|
+
(updater: (current: E) => E): void;
|
|
177
307
|
} & {
|
|
178
|
-
|
|
308
|
+
[P in keyof E]: E[P] extends object
|
|
309
|
+
? E[P] extends readonly unknown[]
|
|
310
|
+
? CallableWritableSignal<E[P]>
|
|
311
|
+
: EntityNode<E[P]>
|
|
312
|
+
: CallableWritableSignal<E[P]>;
|
|
179
313
|
};
|
|
180
314
|
export interface EntitySignal<E, K extends string | number = string> {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
315
|
+
byId(id: K): EntityNode<E> | undefined;
|
|
316
|
+
byIdOrFail(id: K): EntityNode<E>;
|
|
317
|
+
readonly all: Signal<E[]>;
|
|
318
|
+
readonly count: Signal<number>;
|
|
319
|
+
readonly ids: Signal<K[]>;
|
|
320
|
+
has(id: K): Signal<boolean>;
|
|
321
|
+
readonly isEmpty: Signal<boolean>;
|
|
322
|
+
readonly map: Signal<ReadonlyMap<K, E>>;
|
|
323
|
+
where(predicate: (entity: E) => boolean): Signal<E[]>;
|
|
324
|
+
find(predicate: (entity: E) => boolean): Signal<E | undefined>;
|
|
325
|
+
addOne(entity: E, opts?: AddOptions<E, K>): K;
|
|
326
|
+
addMany(entities: E[], opts?: AddManyOptions<E, K>): K[];
|
|
327
|
+
updateOne(id: K, changes: Partial<E>, opts?: MutationOptions): void;
|
|
328
|
+
updateMany(ids: K[], changes: Partial<E>, opts?: MutationOptions): void;
|
|
329
|
+
updateWhere(predicate: (entity: E) => boolean, changes: Partial<E>): number;
|
|
330
|
+
upsertOne(entity: E, opts?: AddOptions<E, K>): K;
|
|
331
|
+
upsertMany(entities: E[], opts?: AddOptions<E, K>): K[];
|
|
332
|
+
removeOne(id: K, opts?: MutationOptions): void;
|
|
333
|
+
removeMany(ids: K[], opts?: MutationOptions): void;
|
|
334
|
+
removeWhere(predicate: (entity: E) => boolean): number;
|
|
335
|
+
clear(): void;
|
|
336
|
+
removeAll(): void;
|
|
337
|
+
setAll(entities: E[], opts?: AddOptions<E, K>): void;
|
|
338
|
+
tap(handlers: TapHandlers<E, K>): () => void;
|
|
339
|
+
intercept(handlers: InterceptHandlers<E, K>): () => void;
|
|
206
340
|
}
|
|
207
|
-
export interface EntityHelpers<
|
|
341
|
+
export interface EntityHelpers<
|
|
342
|
+
E extends {
|
|
208
343
|
id: string | number;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
344
|
+
}
|
|
345
|
+
> {
|
|
346
|
+
add(entity: E): void;
|
|
347
|
+
update(id: E['id'], updates: Partial<E>): void;
|
|
348
|
+
remove(id: E['id']): void;
|
|
349
|
+
upsert(entity: E): void;
|
|
350
|
+
selectById(id: E['id']): Signal<E | undefined>;
|
|
351
|
+
selectBy(predicate: (entity: E) => boolean): Signal<E[]>;
|
|
352
|
+
selectIds(): Signal<Array<string | number>>;
|
|
353
|
+
selectAll(): Signal<E[]>;
|
|
354
|
+
selectTotal(): Signal<number>;
|
|
355
|
+
clear(): void;
|
|
220
356
|
}
|
|
221
357
|
export interface LoggingConfig {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
358
|
+
name?: string;
|
|
359
|
+
filter?: (path: string) => boolean;
|
|
360
|
+
collapsed?: boolean;
|
|
361
|
+
onLog?: (entry: LogEntry) => void;
|
|
226
362
|
}
|
|
227
363
|
export interface LogEntry {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
364
|
+
path: string;
|
|
365
|
+
prev: unknown;
|
|
366
|
+
value: unknown;
|
|
367
|
+
timestamp: number;
|
|
232
368
|
}
|
|
233
369
|
export interface ValidationConfig<T> {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
370
|
+
validators: Array<{
|
|
371
|
+
match: (path: string) => boolean;
|
|
372
|
+
validate: (value: T, path: string) => void | never;
|
|
373
|
+
}>;
|
|
374
|
+
onError?: (error: Error, path: string) => void;
|
|
239
375
|
}
|
|
240
376
|
export interface PersistenceConfig {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
377
|
+
key: string;
|
|
378
|
+
storage?: Storage;
|
|
379
|
+
debounceMs?: number;
|
|
380
|
+
filter?: (path: string) => boolean;
|
|
381
|
+
serialize?: (state: unknown) => string;
|
|
382
|
+
deserialize?: (json: string) => unknown;
|
|
247
383
|
}
|
|
248
384
|
export interface DevToolsConfig {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
385
|
+
name?: string;
|
|
386
|
+
maxAge?: number;
|
|
387
|
+
features?: {
|
|
388
|
+
jump?: boolean;
|
|
389
|
+
skip?: boolean;
|
|
390
|
+
reorder?: boolean;
|
|
391
|
+
};
|
|
256
392
|
}
|
|
257
|
-
export type EntityType<T> = T extends EntitySignal<
|
|
258
|
-
|
|
259
|
-
|
|
393
|
+
export type EntityType<T> = T extends EntitySignal<
|
|
394
|
+
infer E,
|
|
395
|
+
infer K extends string | number
|
|
396
|
+
>
|
|
397
|
+
? E
|
|
398
|
+
: never;
|
|
399
|
+
export type EntityKeyType<T> = T extends EntitySignal<
|
|
400
|
+
unknown,
|
|
401
|
+
infer K extends string | number
|
|
402
|
+
>
|
|
403
|
+
? K
|
|
404
|
+
: never;
|
|
405
|
+
export type IsEntityMap<T> = T extends EntityMapMarker<
|
|
406
|
+
unknown,
|
|
407
|
+
infer K extends string | number
|
|
408
|
+
>
|
|
409
|
+
? true
|
|
410
|
+
: false;
|
|
260
411
|
export type EntityAwareTreeNode<T> = {
|
|
261
|
-
|
|
412
|
+
[K in keyof T]: T[K] extends EntityMapMarker<infer E, infer Key>
|
|
413
|
+
? EntitySignal<E, Key>
|
|
414
|
+
: T[K] extends object
|
|
415
|
+
? EntityAwareTreeNode<T[K]>
|
|
416
|
+
: CallableWritableSignal<T[K]>;
|
|
262
417
|
};
|
|
263
418
|
export type PathHandler = (value: unknown, prev: unknown, path: string) => void;
|
|
264
|
-
export type PathInterceptor = (
|
|
419
|
+
export type PathInterceptor = (
|
|
420
|
+
ctx: {
|
|
265
421
|
path: string;
|
|
266
422
|
value: unknown;
|
|
267
423
|
prev: unknown;
|
|
268
424
|
blocked: boolean;
|
|
269
425
|
blockReason?: string;
|
|
270
|
-
},
|
|
426
|
+
},
|
|
427
|
+
next: () => void
|
|
428
|
+
) => void | Promise<void>;
|
|
271
429
|
export interface TimeTravelEntry<T> {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
430
|
+
action: string;
|
|
431
|
+
timestamp: number;
|
|
432
|
+
state: T;
|
|
433
|
+
payload?: unknown;
|
|
276
434
|
}
|
|
277
435
|
export declare function isSignalTree<T>(value: unknown): value is SignalTree<T>;
|
|
278
436
|
export {};
|