@signaltree/core 10.2.0 → 10.3.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 +16 -3
- package/dist/lib/entity-signal.js +5 -1
- package/dist/lib/markers/status.js +24 -8
- package/llms-full.txt +19 -3
- package/llms.txt +23 -4
- package/package.json +1 -1
- package/src/lib/markers/status.d.ts +4 -0
- package/src/lib/types.d.ts +1 -0
package/README.md
CHANGED
|
@@ -40,7 +40,20 @@ Every "Wrong pattern" below was actually generated by Claude / GPT-5.4 / Gemini
|
|
|
40
40
|
| `Store.dispatch(action)`, `Store.select(selector)` | **`@ngrx/store` (classic)** | `tree.$.path()` to read, `tree.$.path.set(v)` to write |
|
|
41
41
|
| `.toPromise()` (deprecated RxJS 7+) | RxJS legacy | `firstValueFrom(obs)` — or let `asyncSource` consume directly |
|
|
42
42
|
|
|
43
|
-
###
|
|
43
|
+
### Marker accessor shape — UNIFIED in v10.3
|
|
44
|
+
|
|
45
|
+
**As of v10.3, predicate accessors are bare-named everywhere** — matching `FormControl.dirty` / `.valid` and Angular signals conventions. The old `is`-prefix names on `status` and `entityMap.isEmpty` are kept as **deprecated aliases through v10.x** (return the same Signal instance, no double cost). Removal in v11.0.
|
|
46
|
+
|
|
47
|
+
| Marker | v10.3 canonical (preferred) | Deprecated alias (v10.x only) |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `status` | `.loading`, `.loaded`, `.notLoaded`, `.hasError` | `.isLoading`, `.isLoaded`, `.isNotLoaded`, `.isError` |
|
|
50
|
+
| `entityMap` | `.empty` | `.isEmpty` |
|
|
51
|
+
| `form` | `.dirty`, `.valid`, `.touched`, `.pristine` | (already bare — unchanged) |
|
|
52
|
+
| `asyncSource` / `asyncQuery` | `.loading`, `.error`, `.data` | (already bare — unchanged) |
|
|
53
|
+
|
|
54
|
+
All predicates are **callable `Signal<boolean>`** — invoke them: `tree.$.load.loading()`, `tree.$.users.empty()`.
|
|
55
|
+
|
|
56
|
+
### Status marker — method names (frequently confused)
|
|
44
57
|
|
|
45
58
|
The `status()` marker's canonical methods are **`setLoading` / `setLoaded` / `setError`**. As of **v10.2**, Promise-vocabulary aliases also work (identical semantics):
|
|
46
59
|
|
|
@@ -50,8 +63,8 @@ The `status()` marker's canonical methods are **`setLoading` / `setLoaded` / `se
|
|
|
50
63
|
| `.start()` | `.setLoading()` | Yes — alias |
|
|
51
64
|
| `.succeed()` | `.setLoaded()` | Yes — alias |
|
|
52
65
|
| `.fail(err)` | `.setError(err)` | Yes — alias |
|
|
53
|
-
| `.loading` (bare property) |
|
|
54
|
-
| `.error` (bare property) |
|
|
66
|
+
| `.loading` (bare property — read-only) | call as signal: `.loading()` | Yes — callable Signal |
|
|
67
|
+
| `.error` (bare property — read-only) | call as signal: `.error()` | Yes — returns error value |
|
|
55
68
|
|
|
56
69
|
### Canonical async pattern — use `asyncSource`, NOT `status` + manual try/catch
|
|
57
70
|
|
|
@@ -7,6 +7,7 @@ function createEntitySignal(config, pathNotifier, basePath) {
|
|
|
7
7
|
const idsSignal = signal([]);
|
|
8
8
|
const mapSignal = signal(new Map());
|
|
9
9
|
const nodeCache = new Map();
|
|
10
|
+
let cachedEmpty = null;
|
|
10
11
|
const selectId = config.selectId ?? (entity => entity['id']);
|
|
11
12
|
const tapHandlers = [];
|
|
12
13
|
const interceptHandlers = [];
|
|
@@ -98,8 +99,11 @@ function createEntitySignal(config, pathNotifier, basePath) {
|
|
|
98
99
|
has(id) {
|
|
99
100
|
return computed(() => mapSignal().has(id));
|
|
100
101
|
},
|
|
102
|
+
get empty() {
|
|
103
|
+
return cachedEmpty ??= computed(() => countSignal() === 0);
|
|
104
|
+
},
|
|
101
105
|
get isEmpty() {
|
|
102
|
-
return computed(() => countSignal() === 0);
|
|
106
|
+
return cachedEmpty ??= computed(() => countSignal() === 0);
|
|
103
107
|
},
|
|
104
108
|
where(predicate) {
|
|
105
109
|
const cached = whereCache.get(predicate);
|
|
@@ -26,24 +26,40 @@ function isStatusMarker(value) {
|
|
|
26
26
|
function createStatusSignal(marker) {
|
|
27
27
|
const stateSignal = signal(marker.initialState);
|
|
28
28
|
const errorSignal = signal(null);
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
let
|
|
29
|
+
let _notLoaded = null;
|
|
30
|
+
let _loading = null;
|
|
31
|
+
let _loaded = null;
|
|
32
|
+
let _hasError = null;
|
|
33
|
+
const getNotLoaded = () => _notLoaded ??= computed(() => stateSignal() === LoadingState.NotLoaded);
|
|
34
|
+
const getLoading = () => _loading ??= computed(() => stateSignal() === LoadingState.Loading);
|
|
35
|
+
const getLoaded = () => _loaded ??= computed(() => stateSignal() === LoadingState.Loaded);
|
|
36
|
+
const getHasError = () => _hasError ??= computed(() => stateSignal() === LoadingState.Error);
|
|
33
37
|
return {
|
|
34
38
|
state: stateSignal,
|
|
35
39
|
error: errorSignal,
|
|
40
|
+
get notLoaded() {
|
|
41
|
+
return getNotLoaded();
|
|
42
|
+
},
|
|
43
|
+
get loading() {
|
|
44
|
+
return getLoading();
|
|
45
|
+
},
|
|
46
|
+
get loaded() {
|
|
47
|
+
return getLoaded();
|
|
48
|
+
},
|
|
49
|
+
get hasError() {
|
|
50
|
+
return getHasError();
|
|
51
|
+
},
|
|
36
52
|
get isNotLoaded() {
|
|
37
|
-
return
|
|
53
|
+
return getNotLoaded();
|
|
38
54
|
},
|
|
39
55
|
get isLoading() {
|
|
40
|
-
return
|
|
56
|
+
return getLoading();
|
|
41
57
|
},
|
|
42
58
|
get isLoaded() {
|
|
43
|
-
return
|
|
59
|
+
return getLoaded();
|
|
44
60
|
},
|
|
45
61
|
get isError() {
|
|
46
|
-
return
|
|
62
|
+
return getHasError();
|
|
47
63
|
},
|
|
48
64
|
setNotLoaded() {
|
|
49
65
|
stateSignal.set(LoadingState.NotLoaded);
|
package/llms-full.txt
CHANGED
|
@@ -520,6 +520,23 @@ This is the pattern enforced in production migrations. The `$` access stays read
|
|
|
520
520
|
| `Store.dispatch(action)`, `Store.select(selector)` | **`@ngrx/store` (classic NgRx)** | Direct tree access: `tree.$.path()` to read, `tree.$.path.set(v)` to write |
|
|
521
521
|
| `.toPromise()` on Observables (deprecated RxJS 7+) | RxJS legacy | `firstValueFrom(observable)` — or let `asyncSource` consume the Observable directly |
|
|
522
522
|
|
|
523
|
+
### Marker accessor shape — UNIFIED in v10.3 (bare callable signals)
|
|
524
|
+
|
|
525
|
+
**v10.3 aligns predicate names across all markers** to match `FormControl` / Angular signal conventions. Bare names (`loading`, `loaded`, `empty`, etc.) are now canonical everywhere. The old `is`-prefix names on `status` and `entityMap.isEmpty` are deprecated aliases through v10.x — they return the same Signal instance as the canonical names. Removal in v11.0.
|
|
526
|
+
|
|
527
|
+
| Marker | v10.3 canonical predicate | Deprecated alias (v10.x only) |
|
|
528
|
+
|---|---|---|
|
|
529
|
+
| `status` | `.loading` | `.isLoading` |
|
|
530
|
+
| `status` | `.loaded` | `.isLoaded` |
|
|
531
|
+
| `status` | `.notLoaded` | `.isNotLoaded` |
|
|
532
|
+
| `status` | `.hasError` | `.isError` |
|
|
533
|
+
| `entityMap` | `.empty` | `.isEmpty` |
|
|
534
|
+
| `form` | `.dirty`, `.valid`, `.touched`, `.pristine` | (already bare — unchanged) |
|
|
535
|
+
| `asyncSource` | `.loading`, `.error`, `.data` | (already bare — unchanged) |
|
|
536
|
+
| `asyncQuery` | `.loading`, `.error`, `.data` | (already bare — unchanged) |
|
|
537
|
+
|
|
538
|
+
All predicates are callable `Signal<boolean>` — invoke them: `tree.$.load.loading()`. Both the canonical and deprecated alias return the **same Signal instance**, so there's no double computed cost.
|
|
539
|
+
|
|
523
540
|
### Status marker — exact method names (frequently confused)
|
|
524
541
|
|
|
525
542
|
The `status()` marker's canonical methods are **`setLoading` / `setLoaded` / `setError`**, NOT Promise-vocabulary names (`setSuccess`, `start`, `succeed`, `fail`). However, **as of v10.2 the Promise-vocabulary aliases also work** — they delegate to the canonical methods with identical semantics. Use either; canonical is preferred in new code for searchability.
|
|
@@ -530,9 +547,8 @@ The `status()` marker's canonical methods are **`setLoading` / `setLoaded` / `se
|
|
|
530
547
|
| `.start()` | `.setLoading()` | Yes — alias |
|
|
531
548
|
| `.succeed()` | `.setLoaded()` | Yes — alias |
|
|
532
549
|
| `.fail(err)` | `.setError(err)` | Yes — alias |
|
|
533
|
-
| `.loading` (bare property) | `.
|
|
534
|
-
| `.error` (bare property) | `.error()`
|
|
535
|
-
| `.success` (bare property) | `.isLoaded()` (callable signal) | No — must call as signal |
|
|
550
|
+
| `.loading` (bare property — read-only) | call as signal: `.loading()` | Yes (callable signal) |
|
|
551
|
+
| `.error` (bare property — read-only) | call as signal: `.error()` | Yes (returns error value E \| null) |
|
|
536
552
|
|
|
537
553
|
### Async patterns — prefer `asyncSource` / `asyncQuery` over manual `status` + try/catch
|
|
538
554
|
|
package/llms.txt
CHANGED
|
@@ -120,18 +120,37 @@ This table catches the most common cross-library hallucinations. **Every "Wrong"
|
|
|
120
120
|
| `.upsert(user)` on entity collections | **Akita** | `.upsertOne(user)` (singular suffix) |
|
|
121
121
|
| `BehaviorSubject<T>`, `.next(v)`, `.asObservable()` | **RxJS classic** | A plain leaf in the `signalTree()` literal — no Observable wrapping needed |
|
|
122
122
|
|
|
123
|
+
### Marker accessor shape — UNIFIED in v10.3 (bare callable signals)
|
|
124
|
+
|
|
125
|
+
**As of v10.3, every marker uses the same accessor shape: bare-named callable signals (matching `FormControl.dirty` / `.valid` and Angular signals conventions).** The `is`-prefix names that used to appear on `status` and `entityMap.isEmpty` are kept as deprecated aliases through v10.x and will be removed in v11.0.
|
|
126
|
+
|
|
127
|
+
**Cross-marker predicate naming (v10.3 canonical):**
|
|
128
|
+
|
|
129
|
+
| Marker | Predicate signal (v10.3) | Old name (deprecated alias, v10.x only) |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `status` | `.loading` | `.isLoading` |
|
|
132
|
+
| `status` | `.loaded` | `.isLoaded` |
|
|
133
|
+
| `status` | `.notLoaded` | `.isNotLoaded` |
|
|
134
|
+
| `status` | `.hasError` | `.isError` |
|
|
135
|
+
| `entityMap` | `.empty` | `.isEmpty` |
|
|
136
|
+
| `form` | `.dirty`, `.valid`, `.touched`, `.pristine` | (already bare — unchanged) |
|
|
137
|
+
| `asyncSource` | `.loading`, `.error`, `.data` | (already bare — unchanged) |
|
|
138
|
+
| `asyncQuery` | `.loading`, `.error`, `.data` | (already bare — unchanged) |
|
|
139
|
+
|
|
140
|
+
All predicates are **callable Signals** — invoke them: `tree.$.load.loading()`, `tree.$.users.empty()`. Both `.loading` and `.isLoading` return the same underlying Signal instance; no double allocation.
|
|
141
|
+
|
|
123
142
|
### Status marker — exact method names (frequently confused)
|
|
124
143
|
|
|
125
|
-
The `status()` marker
|
|
144
|
+
The `status()` marker's canonical methods are **`setLoading` / `setLoaded` / `setError` / `setNotLoaded` / `reset`**. Promise-vocabulary aliases also work as of v10.2 (identical semantics):
|
|
126
145
|
|
|
127
|
-
| Wrong (Promise-vocab guess) |
|
|
146
|
+
| Wrong (Promise-vocab guess) | Canonical | Notes |
|
|
128
147
|
|---|---|---|
|
|
129
148
|
| `.setSuccess()` | **`.setLoaded()`** | Alias `.setSuccess` works in v10.2+ |
|
|
130
149
|
| `.start()` | **`.setLoading()`** | Alias `.start` works in v10.2+ |
|
|
131
150
|
| `.succeed()` | **`.setLoaded()`** | Alias `.succeed` works in v10.2+ |
|
|
132
151
|
| `.fail(err)` | **`.setError(err)`** | Alias `.fail` works in v10.2+ |
|
|
133
|
-
| `.loading` (property) | **`.
|
|
134
|
-
| `.error` (property) | **`.error()`** (
|
|
152
|
+
| `.loading` (bare property, can't be assigned) | **`.loading()`** (call as signal) | Read-only derived signal |
|
|
153
|
+
| `.error` (bare property, can't be assigned) | **`.error()`** (call as signal) | Read-only error value |
|
|
135
154
|
|
|
136
155
|
### Async pattern — prefer `asyncSource` / `asyncQuery` over `status` + manual try/catch
|
|
137
156
|
|
package/package.json
CHANGED
|
@@ -17,6 +17,10 @@ export interface StatusMarker<E = Error> {
|
|
|
17
17
|
export interface StatusSignal<E = Error> {
|
|
18
18
|
state: WritableSignal<LoadingState>;
|
|
19
19
|
error: WritableSignal<E | null>;
|
|
20
|
+
notLoaded: Signal<boolean>;
|
|
21
|
+
loading: Signal<boolean>;
|
|
22
|
+
loaded: Signal<boolean>;
|
|
23
|
+
hasError: Signal<boolean>;
|
|
20
24
|
isNotLoaded: Signal<boolean>;
|
|
21
25
|
isLoading: Signal<boolean>;
|
|
22
26
|
isLoaded: Signal<boolean>;
|
package/src/lib/types.d.ts
CHANGED
|
@@ -180,6 +180,7 @@ export interface EntitySignal<E, K extends string | number = string> {
|
|
|
180
180
|
readonly count: Signal<number>;
|
|
181
181
|
readonly ids: Signal<K[]>;
|
|
182
182
|
has(id: K): Signal<boolean>;
|
|
183
|
+
readonly empty: Signal<boolean>;
|
|
183
184
|
readonly isEmpty: Signal<boolean>;
|
|
184
185
|
readonly map: Signal<ReadonlyMap<K, E>>;
|
|
185
186
|
where(predicate: (entity: E) => boolean): Signal<E[]>;
|