@signaltree/core 5.1.3 → 5.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 +6 -8
- package/dist/enhancers/entities/lib/entities.js +3 -3
- package/dist/lib/entity-signal.js +12 -9
- package/package.json +1 -1
- package/src/types.d.ts +370 -204
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,4 +1,4 @@
|
|
|
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
|
|
|
@@ -6,7 +6,7 @@ function isEntityMapMarker(value) {
|
|
|
6
6
|
return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
|
|
7
7
|
}
|
|
8
8
|
function isEntitySignal(value) {
|
|
9
|
-
return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && 'all'
|
|
9
|
+
return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && typeof value['all'] !== 'undefined';
|
|
10
10
|
}
|
|
11
11
|
function materializeEntities(tree, notifier = getPathNotifier()) {
|
|
12
12
|
const registry = new Map();
|
|
@@ -16,7 +16,7 @@ function materializeEntities(tree, notifier = getPathNotifier()) {
|
|
|
16
16
|
if (isEntityMapMarker(value)) {
|
|
17
17
|
const basePath = nextPath.join('.');
|
|
18
18
|
const config = value.__entityMapConfig ?? {};
|
|
19
|
-
const entitySignal =
|
|
19
|
+
const entitySignal = createEntitySignal(config, notifier, basePath);
|
|
20
20
|
if (parent) {
|
|
21
21
|
try {
|
|
22
22
|
parent[key] = entitySignal;
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.4",
|
|
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,
|
package/src/types.d.ts
CHANGED
|
@@ -1,278 +1,444 @@
|
|
|
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
|
+
getMetrics(): PerformanceMetrics;
|
|
214
|
+
entities<
|
|
215
|
+
E extends {
|
|
216
|
+
id: string | number;
|
|
217
|
+
}
|
|
218
|
+
>(
|
|
219
|
+
entityKey?: keyof T
|
|
220
|
+
): EntityHelpers<E>;
|
|
221
|
+
undo(): void;
|
|
222
|
+
redo(): void;
|
|
223
|
+
getHistory(): TimeTravelEntry<T>[];
|
|
224
|
+
resetHistory(): void;
|
|
225
|
+
jumpTo?: (index: number) => void;
|
|
226
|
+
canUndo?: () => boolean;
|
|
227
|
+
canRedo?: () => boolean;
|
|
228
|
+
getCurrentIndex?: () => number;
|
|
104
229
|
};
|
|
105
230
|
export type TreePreset = 'basic' | 'performance' | 'development' | 'production';
|
|
106
231
|
export interface TreeConfig {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
232
|
+
batchUpdates?: boolean;
|
|
233
|
+
useMemoization?: boolean;
|
|
234
|
+
enableTimeTravel?: boolean;
|
|
235
|
+
useLazySignals?: boolean;
|
|
236
|
+
useShallowComparison?: boolean;
|
|
237
|
+
maxCacheSize?: number;
|
|
238
|
+
trackPerformance?: boolean;
|
|
239
|
+
treeName?: string;
|
|
240
|
+
enableDevTools?: boolean;
|
|
241
|
+
debugMode?: boolean;
|
|
242
|
+
useStructuralSharing?: boolean;
|
|
243
|
+
security?: SecurityValidatorConfig;
|
|
119
244
|
}
|
|
120
245
|
export interface PerformanceMetrics {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
246
|
+
updates: number;
|
|
247
|
+
computations: number;
|
|
248
|
+
cacheHits: number;
|
|
249
|
+
cacheMisses: number;
|
|
250
|
+
averageUpdateTime: number;
|
|
126
251
|
}
|
|
127
252
|
export interface EntityConfig<E, K extends string | number = string> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
253
|
+
selectId?: (entity: E) => K;
|
|
254
|
+
hooks?: {
|
|
255
|
+
beforeAdd?: (entity: E) => E | false;
|
|
256
|
+
beforeUpdate?: (id: K, changes: Partial<E>) => Partial<E> | false;
|
|
257
|
+
beforeRemove?: (id: K, entity: E) => boolean;
|
|
258
|
+
};
|
|
134
259
|
}
|
|
135
260
|
declare const ENTITY_MAP_BRAND: unique symbol;
|
|
136
261
|
export interface EntityMapMarker<E, K extends string | number> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
262
|
+
readonly [ENTITY_MAP_BRAND]: {
|
|
263
|
+
__entity: E;
|
|
264
|
+
__key: K;
|
|
265
|
+
};
|
|
266
|
+
readonly __isEntityMap: true;
|
|
267
|
+
readonly __entityMapConfig?: EntityConfig<E, K>;
|
|
143
268
|
}
|
|
144
|
-
export declare function entityMap<
|
|
269
|
+
export declare function entityMap<
|
|
270
|
+
E,
|
|
271
|
+
K extends string | number = E extends {
|
|
145
272
|
id: infer I extends string | number;
|
|
146
|
-
}
|
|
273
|
+
}
|
|
274
|
+
? I
|
|
275
|
+
: string
|
|
276
|
+
>(config?: EntityConfig<E, K>): EntityMapMarker<E, K>;
|
|
147
277
|
export interface MutationOptions {
|
|
148
|
-
|
|
278
|
+
onError?: (error: Error) => void;
|
|
149
279
|
}
|
|
150
280
|
export interface AddOptions<E, K> extends MutationOptions {
|
|
151
|
-
|
|
281
|
+
selectId?: (entity: E) => K;
|
|
152
282
|
}
|
|
153
283
|
export interface AddManyOptions<E, K> extends AddOptions<E, K> {
|
|
154
|
-
|
|
284
|
+
mode?: 'strict' | 'skip' | 'overwrite';
|
|
155
285
|
}
|
|
156
286
|
export interface TapHandlers<E, K extends string | number> {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
287
|
+
onAdd?: (entity: E, id: K) => void;
|
|
288
|
+
onUpdate?: (id: K, changes: Partial<E>, entity: E) => void;
|
|
289
|
+
onRemove?: (id: K, entity: E) => void;
|
|
290
|
+
onChange?: () => void;
|
|
161
291
|
}
|
|
162
292
|
export interface InterceptContext<T> {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
293
|
+
block(reason?: string): void;
|
|
294
|
+
transform(value: T): void;
|
|
295
|
+
readonly blocked: boolean;
|
|
296
|
+
readonly blockReason: string | undefined;
|
|
167
297
|
}
|
|
168
298
|
export interface InterceptHandlers<E, K extends string | number> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
299
|
+
onAdd?: (entity: E, ctx: InterceptContext<E>) => void | Promise<void>;
|
|
300
|
+
onUpdate?: (
|
|
301
|
+
id: K,
|
|
302
|
+
changes: Partial<E>,
|
|
303
|
+
ctx: InterceptContext<Partial<E>>
|
|
304
|
+
) => void | Promise<void>;
|
|
305
|
+
onRemove?: (
|
|
306
|
+
id: K,
|
|
307
|
+
entity: E,
|
|
308
|
+
ctx: InterceptContext<void>
|
|
309
|
+
) => void | Promise<void>;
|
|
172
310
|
}
|
|
173
311
|
export type EntityNode<E> = {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
312
|
+
(): E;
|
|
313
|
+
(value: E): void;
|
|
314
|
+
(updater: (current: E) => E): void;
|
|
177
315
|
} & {
|
|
178
|
-
|
|
316
|
+
[P in keyof E]: E[P] extends object
|
|
317
|
+
? E[P] extends readonly unknown[]
|
|
318
|
+
? CallableWritableSignal<E[P]>
|
|
319
|
+
: EntityNode<E[P]>
|
|
320
|
+
: CallableWritableSignal<E[P]>;
|
|
179
321
|
};
|
|
180
322
|
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
|
-
|
|
323
|
+
byId(id: K): EntityNode<E> | undefined;
|
|
324
|
+
byIdOrFail(id: K): EntityNode<E>;
|
|
325
|
+
readonly all: Signal<E[]>;
|
|
326
|
+
readonly count: Signal<number>;
|
|
327
|
+
readonly ids: Signal<K[]>;
|
|
328
|
+
has(id: K): Signal<boolean>;
|
|
329
|
+
readonly isEmpty: Signal<boolean>;
|
|
330
|
+
readonly map: Signal<ReadonlyMap<K, E>>;
|
|
331
|
+
where(predicate: (entity: E) => boolean): Signal<E[]>;
|
|
332
|
+
find(predicate: (entity: E) => boolean): Signal<E | undefined>;
|
|
333
|
+
addOne(entity: E, opts?: AddOptions<E, K>): K;
|
|
334
|
+
addMany(entities: E[], opts?: AddManyOptions<E, K>): K[];
|
|
335
|
+
updateOne(id: K, changes: Partial<E>, opts?: MutationOptions): void;
|
|
336
|
+
updateMany(ids: K[], changes: Partial<E>, opts?: MutationOptions): void;
|
|
337
|
+
updateWhere(predicate: (entity: E) => boolean, changes: Partial<E>): number;
|
|
338
|
+
upsertOne(entity: E, opts?: AddOptions<E, K>): K;
|
|
339
|
+
upsertMany(entities: E[], opts?: AddOptions<E, K>): K[];
|
|
340
|
+
removeOne(id: K, opts?: MutationOptions): void;
|
|
341
|
+
removeMany(ids: K[], opts?: MutationOptions): void;
|
|
342
|
+
removeWhere(predicate: (entity: E) => boolean): number;
|
|
343
|
+
clear(): void;
|
|
344
|
+
removeAll(): void;
|
|
345
|
+
setAll(entities: E[], opts?: AddOptions<E, K>): void;
|
|
346
|
+
tap(handlers: TapHandlers<E, K>): () => void;
|
|
347
|
+
intercept(handlers: InterceptHandlers<E, K>): () => void;
|
|
206
348
|
}
|
|
207
|
-
export interface EntityHelpers<
|
|
349
|
+
export interface EntityHelpers<
|
|
350
|
+
E extends {
|
|
208
351
|
id: string | number;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
352
|
+
}
|
|
353
|
+
> {
|
|
354
|
+
add(entity: E): void;
|
|
355
|
+
update(id: E['id'], updates: Partial<E>): void;
|
|
356
|
+
remove(id: E['id']): void;
|
|
357
|
+
upsert(entity: E): void;
|
|
358
|
+
selectById(id: E['id']): Signal<E | undefined>;
|
|
359
|
+
selectBy(predicate: (entity: E) => boolean): Signal<E[]>;
|
|
360
|
+
selectIds(): Signal<Array<string | number>>;
|
|
361
|
+
selectAll(): Signal<E[]>;
|
|
362
|
+
selectTotal(): Signal<number>;
|
|
363
|
+
clear(): void;
|
|
220
364
|
}
|
|
221
365
|
export interface LoggingConfig {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
366
|
+
name?: string;
|
|
367
|
+
filter?: (path: string) => boolean;
|
|
368
|
+
collapsed?: boolean;
|
|
369
|
+
onLog?: (entry: LogEntry) => void;
|
|
226
370
|
}
|
|
227
371
|
export interface LogEntry {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
372
|
+
path: string;
|
|
373
|
+
prev: unknown;
|
|
374
|
+
value: unknown;
|
|
375
|
+
timestamp: number;
|
|
232
376
|
}
|
|
233
377
|
export interface ValidationConfig<T> {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
378
|
+
validators: Array<{
|
|
379
|
+
match: (path: string) => boolean;
|
|
380
|
+
validate: (value: T, path: string) => void | never;
|
|
381
|
+
}>;
|
|
382
|
+
onError?: (error: Error, path: string) => void;
|
|
239
383
|
}
|
|
240
384
|
export interface PersistenceConfig {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
385
|
+
key: string;
|
|
386
|
+
storage?: Storage;
|
|
387
|
+
debounceMs?: number;
|
|
388
|
+
filter?: (path: string) => boolean;
|
|
389
|
+
serialize?: (state: unknown) => string;
|
|
390
|
+
deserialize?: (json: string) => unknown;
|
|
247
391
|
}
|
|
248
392
|
export interface DevToolsConfig {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
393
|
+
name?: string;
|
|
394
|
+
maxAge?: number;
|
|
395
|
+
features?: {
|
|
396
|
+
jump?: boolean;
|
|
397
|
+
skip?: boolean;
|
|
398
|
+
reorder?: boolean;
|
|
399
|
+
};
|
|
256
400
|
}
|
|
257
|
-
export type EntityType<T> = T extends EntitySignal<
|
|
258
|
-
|
|
259
|
-
|
|
401
|
+
export type EntityType<T> = T extends EntitySignal<
|
|
402
|
+
infer E,
|
|
403
|
+
infer K extends string | number
|
|
404
|
+
>
|
|
405
|
+
? E
|
|
406
|
+
: never;
|
|
407
|
+
export type EntityKeyType<T> = T extends EntitySignal<
|
|
408
|
+
unknown,
|
|
409
|
+
infer K extends string | number
|
|
410
|
+
>
|
|
411
|
+
? K
|
|
412
|
+
: never;
|
|
413
|
+
export type IsEntityMap<T> = T extends EntityMapMarker<
|
|
414
|
+
unknown,
|
|
415
|
+
infer K extends string | number
|
|
416
|
+
>
|
|
417
|
+
? true
|
|
418
|
+
: false;
|
|
260
419
|
export type EntityAwareTreeNode<T> = {
|
|
261
|
-
|
|
420
|
+
[K in keyof T]: T[K] extends EntityMapMarker<infer E, infer Key>
|
|
421
|
+
? EntitySignal<E, Key>
|
|
422
|
+
: T[K] extends object
|
|
423
|
+
? EntityAwareTreeNode<T[K]>
|
|
424
|
+
: CallableWritableSignal<T[K]>;
|
|
262
425
|
};
|
|
263
426
|
export type PathHandler = (value: unknown, prev: unknown, path: string) => void;
|
|
264
|
-
export type PathInterceptor = (
|
|
427
|
+
export type PathInterceptor = (
|
|
428
|
+
ctx: {
|
|
265
429
|
path: string;
|
|
266
430
|
value: unknown;
|
|
267
431
|
prev: unknown;
|
|
268
432
|
blocked: boolean;
|
|
269
433
|
blockReason?: string;
|
|
270
|
-
},
|
|
434
|
+
},
|
|
435
|
+
next: () => void
|
|
436
|
+
) => void | Promise<void>;
|
|
271
437
|
export interface TimeTravelEntry<T> {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
438
|
+
action: string;
|
|
439
|
+
timestamp: number;
|
|
440
|
+
state: T;
|
|
441
|
+
payload?: unknown;
|
|
276
442
|
}
|
|
277
443
|
export declare function isSignalTree<T>(value: unknown): value is SignalTree<T>;
|
|
278
444
|
export {};
|