flurryx 0.7.6 → 0.8.1
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 +91 -26
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -29,18 +29,37 @@ interface ProductStoreConfig {
|
|
|
29
29
|
export const ProductStore = Store.for<ProductStoreConfig>().build();
|
|
30
30
|
|
|
31
31
|
// Facade
|
|
32
|
-
@
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.
|
|
32
|
+
@Injectable()
|
|
33
|
+
export class ProductFacade {
|
|
34
|
+
@SkipIfCached("LIST", (i) => i.store)
|
|
35
|
+
@Loading("LIST", (i) => i.store)
|
|
36
|
+
loadProducts() {
|
|
37
|
+
this.http
|
|
38
|
+
.get<Product[]>("/api/products")
|
|
39
|
+
.pipe(syncToStore(this.store, "LIST"))
|
|
40
|
+
.subscribe();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Read signals from the facade
|
|
44
|
+
getProducts() {
|
|
45
|
+
return this.store.get("LIST");
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
|
|
40
|
-
// Component
|
|
41
|
-
@
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
// Component — read the facade signal once, use it in the template
|
|
50
|
+
@Component({
|
|
51
|
+
selector: "app-product-list",
|
|
52
|
+
template: `
|
|
53
|
+
@if (productsState().isLoading) {
|
|
54
|
+
<spinner />
|
|
55
|
+
} @for (product of productsState().data; track product.id) {
|
|
56
|
+
<product-card [product]="product" />
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
})
|
|
60
|
+
export class ProductListComponent {
|
|
61
|
+
private readonly facade = inject(ProductFacade);
|
|
62
|
+
readonly productsState = this.facade.getProducts();
|
|
44
63
|
}
|
|
45
64
|
```
|
|
46
65
|
|
|
@@ -67,6 +86,7 @@ No `async` pipe. No `subscribe` in templates. No manual unsubscription.
|
|
|
67
86
|
- [Keyed Resources](#keyed-resources)
|
|
68
87
|
- [Store Mirroring](#store-mirroring)
|
|
69
88
|
- [Builder .mirror()](#builder-mirror)
|
|
89
|
+
- [Builder .mirrorSelf()](#builder-mirrorself)
|
|
70
90
|
- [Builder .mirrorKeyed()](#builder-mirrorkeyed)
|
|
71
91
|
- [mirrorKey](#mirrorkey)
|
|
72
92
|
- [collectKeyed](#collectkeyed)
|
|
@@ -174,29 +194,33 @@ import { Injectable, inject } from "@angular/core";
|
|
|
174
194
|
import { HttpClient } from "@angular/common/http";
|
|
175
195
|
import { syncToStore, SkipIfCached, Loading } from "flurryx";
|
|
176
196
|
|
|
177
|
-
@Injectable(
|
|
197
|
+
@Injectable()
|
|
178
198
|
export class ProductFacade {
|
|
179
199
|
private readonly http = inject(HttpClient);
|
|
180
200
|
readonly store = inject(ProductStore);
|
|
181
201
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
202
|
+
getProducts() {
|
|
203
|
+
return this.store.get("LIST");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getProductDetail() {
|
|
207
|
+
return this.store.get("DETAIL");
|
|
208
|
+
}
|
|
185
209
|
|
|
186
|
-
@SkipIfCached(
|
|
187
|
-
@Loading(
|
|
210
|
+
@SkipIfCached("LIST", (i: ProductFacade) => i.store)
|
|
211
|
+
@Loading("LIST", (i: ProductFacade) => i.store)
|
|
188
212
|
loadProducts() {
|
|
189
213
|
this.http
|
|
190
214
|
.get<Product[]>("/api/products")
|
|
191
|
-
.pipe(syncToStore(this.store,
|
|
215
|
+
.pipe(syncToStore(this.store, "LIST"))
|
|
192
216
|
.subscribe();
|
|
193
217
|
}
|
|
194
218
|
|
|
195
|
-
@Loading(
|
|
219
|
+
@Loading("DETAIL", (i: ProductFacade) => i.store)
|
|
196
220
|
loadProduct(id: string) {
|
|
197
221
|
this.http
|
|
198
222
|
.get<Product>(`/api/products/${id}`)
|
|
199
|
-
.pipe(syncToStore(this.store,
|
|
223
|
+
.pipe(syncToStore(this.store, "DETAIL"))
|
|
200
224
|
.subscribe();
|
|
201
225
|
}
|
|
202
226
|
}
|
|
@@ -207,18 +231,19 @@ export class ProductFacade {
|
|
|
207
231
|
```typescript
|
|
208
232
|
@Component({
|
|
209
233
|
template: `
|
|
210
|
-
@if (
|
|
234
|
+
@if (productsState().isLoading) {
|
|
211
235
|
<spinner />
|
|
212
|
-
} @if (
|
|
213
|
-
|
|
236
|
+
} @if (productsState().status === 'Success') { @for (product of
|
|
237
|
+
productsState().data; track product.id) {
|
|
214
238
|
<product-card [product]="product" />
|
|
215
|
-
} } @if (
|
|
216
|
-
<error-banner [errors]="
|
|
239
|
+
} } @if (productsState().status === 'Error') {
|
|
240
|
+
<error-banner [errors]="productsState().errors" />
|
|
217
241
|
}
|
|
218
242
|
`,
|
|
219
243
|
})
|
|
220
244
|
export class ProductListComponent {
|
|
221
|
-
readonly facade = inject(ProductFacade);
|
|
245
|
+
private readonly facade = inject(ProductFacade);
|
|
246
|
+
readonly productsState = this.facade.getProducts();
|
|
222
247
|
|
|
223
248
|
constructor() {
|
|
224
249
|
this.facade.loadProducts();
|
|
@@ -291,7 +316,8 @@ Once injected, the store exposes these methods:
|
|
|
291
316
|
| `get(key)` | Returns the `Signal` for a slot |
|
|
292
317
|
| `update(key, partial)` | Merges partial state (immutable spread) |
|
|
293
318
|
| `clear(key)` | Resets a slot to its initial empty state |
|
|
294
|
-
| `clearAll()` | Resets every slot
|
|
319
|
+
| `clearAll()` | Resets every slot in one store |
|
|
320
|
+
| `clearAllStores()` | Resets every tracked store instance |
|
|
295
321
|
| `startLoading(key)` | Sets `isLoading: true`, clears `status` and `errors` |
|
|
296
322
|
| `stopLoading(key)` | Sets `isLoading: false`, clears `status` and `errors` |
|
|
297
323
|
| `onUpdate(key, callback)` | Registers a listener fired after `update` or `clear`. Returns an unsubscribe function |
|
|
@@ -696,6 +722,45 @@ export const SessionStore = Store.for<{ ARTICLES: Item[] }>()
|
|
|
696
722
|
|
|
697
723
|
The builder calls `inject()` under the hood, so source stores are resolved through Angular's DI. Everything — data, loading, status, errors — is mirrored automatically. No manual cleanup needed; the mirrors live as long as the store.
|
|
698
724
|
|
|
725
|
+
### Builder .mirrorSelf()
|
|
726
|
+
|
|
727
|
+
Use `.mirrorSelf()` when one slot in a store should mirror another slot in the same store. It is useful for aliases, local snapshots, or secondary slots that should stay in sync with a primary slot without wiring `onUpdate` manually.
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
interface SessionStoreConfig {
|
|
731
|
+
CUSTOMER_DETAILS: Customer;
|
|
732
|
+
CUSTOMER_SNAPSHOT: Customer;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export const SessionStore = Store.for<SessionStoreConfig>()
|
|
736
|
+
.mirrorSelf('CUSTOMER_DETAILS', 'CUSTOMER_SNAPSHOT')
|
|
737
|
+
.build();
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
It mirrors the full resource state one way — `data`, `isLoading`, `status`, and `errors` all flow from the source key to the target key. The target key must be different from the source key.
|
|
741
|
+
|
|
742
|
+
Because it listens to updates on the built store itself, `.mirrorSelf()` also reacts when the source key is updated by another mirror:
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
interface CustomerStoreConfig {
|
|
746
|
+
CUSTOMERS: Customer[];
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
interface SessionStoreConfig {
|
|
750
|
+
CUSTOMERS: Customer[];
|
|
751
|
+
CUSTOMER_COPY: Customer[];
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export const CustomerStore = Store.for<CustomerStoreConfig>().build();
|
|
755
|
+
|
|
756
|
+
export const SessionStore = Store.for<SessionStoreConfig>()
|
|
757
|
+
.mirror(CustomerStore, 'CUSTOMERS')
|
|
758
|
+
.mirrorSelf('CUSTOMERS', 'CUSTOMER_COPY')
|
|
759
|
+
.build();
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
`.mirrorSelf()` is available on all builder styles. For fluent builders, declare both slots first, then chain `.mirrorSelf(sourceKey, targetKey)` before `.build()`.
|
|
763
|
+
|
|
699
764
|
### Builder .mirrorKeyed()
|
|
700
765
|
|
|
701
766
|
When the source store holds a single-entity slot (e.g. `CUSTOMER_DETAILS: Customer`) and you want to accumulate those fetches into a `KeyedResourceData` cache on the target, use `.mirrorKeyed()`. It is the builder equivalent of [`collectKeyed`](#collectkeyed).
|
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
Loading: () => import_rx.Loading,
|
|
27
27
|
SkipIfCached: () => import_rx.SkipIfCached,
|
|
28
28
|
Store: () => import_store.Store,
|
|
29
|
+
clearAllStores: () => import_store.clearAllStores,
|
|
29
30
|
collectKeyed: () => import_store.collectKeyed,
|
|
30
31
|
createKeyedResourceData: () => import_core.createKeyedResourceData,
|
|
31
32
|
defaultErrorNormalizer: () => import_rx.defaultErrorNormalizer,
|
|
@@ -47,6 +48,7 @@ var import_rx = require("@flurryx/rx");
|
|
|
47
48
|
Loading,
|
|
48
49
|
SkipIfCached,
|
|
49
50
|
Store,
|
|
51
|
+
clearAllStores,
|
|
50
52
|
collectKeyed,
|
|
51
53
|
createKeyedResourceData,
|
|
52
54
|
defaultErrorNormalizer,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @flurryx/core\nexport type {\n ResourceState,\n StoreEnum,\n KeyedResourceData,\n KeyedResourceKey,\n ResourceStatus,\n ResourceErrors,\n} from \"@flurryx/core\";\nexport {\n isKeyedResourceData,\n createKeyedResourceData,\n isAnyKeyLoading,\n CACHE_NO_TIMEOUT,\n DEFAULT_CACHE_TTL_MS,\n} from \"@flurryx/core\";\n\n// @flurryx/store\nexport {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @flurryx/core\nexport type {\n ResourceState,\n StoreEnum,\n KeyedResourceData,\n KeyedResourceKey,\n ResourceStatus,\n ResourceErrors,\n} from \"@flurryx/core\";\nexport {\n isKeyedResourceData,\n createKeyedResourceData,\n isAnyKeyLoading,\n CACHE_NO_TIMEOUT,\n DEFAULT_CACHE_TTL_MS,\n} from \"@flurryx/core\";\n\n// @flurryx/store\nexport {\n BaseStore,\n Store,\n clearAllStores,\n mirrorKey,\n collectKeyed,\n} from \"@flurryx/store\";\nexport type { MirrorOptions, CollectKeyedOptions } from \"@flurryx/store\";\n\n// @flurryx/rx\nexport {\n syncToStore,\n syncToKeyedStore,\n SkipIfCached,\n Loading,\n defaultErrorNormalizer,\n} from \"@flurryx/rx\";\nexport type {\n SyncToStoreOptions,\n SyncToKeyedStoreOptions,\n ErrorNormalizer,\n} from \"@flurryx/rx\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,kBAMO;AAGP,mBAMO;AAIP,gBAMO;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { CACHE_NO_TIMEOUT, DEFAULT_CACHE_TTL_MS, KeyedResourceData, KeyedResourceKey, ResourceErrors, ResourceState, ResourceStatus, StoreEnum, createKeyedResourceData, isAnyKeyLoading, isKeyedResourceData } from '@flurryx/core';
|
|
2
|
-
export { BaseStore, CollectKeyedOptions, MirrorOptions, Store, collectKeyed, mirrorKey } from '@flurryx/store';
|
|
2
|
+
export { BaseStore, CollectKeyedOptions, MirrorOptions, Store, clearAllStores, collectKeyed, mirrorKey } from '@flurryx/store';
|
|
3
3
|
export { ErrorNormalizer, Loading, SkipIfCached, SyncToKeyedStoreOptions, SyncToStoreOptions, defaultErrorNormalizer, syncToKeyedStore, syncToStore } from '@flurryx/rx';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { CACHE_NO_TIMEOUT, DEFAULT_CACHE_TTL_MS, KeyedResourceData, KeyedResourceKey, ResourceErrors, ResourceState, ResourceStatus, StoreEnum, createKeyedResourceData, isAnyKeyLoading, isKeyedResourceData } from '@flurryx/core';
|
|
2
|
-
export { BaseStore, CollectKeyedOptions, MirrorOptions, Store, collectKeyed, mirrorKey } from '@flurryx/store';
|
|
2
|
+
export { BaseStore, CollectKeyedOptions, MirrorOptions, Store, clearAllStores, collectKeyed, mirrorKey } from '@flurryx/store';
|
|
3
3
|
export { ErrorNormalizer, Loading, SkipIfCached, SyncToKeyedStoreOptions, SyncToStoreOptions, defaultErrorNormalizer, syncToKeyedStore, syncToStore } from '@flurryx/rx';
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
CACHE_NO_TIMEOUT,
|
|
7
7
|
DEFAULT_CACHE_TTL_MS
|
|
8
8
|
} from "@flurryx/core";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
BaseStore,
|
|
11
|
+
Store,
|
|
12
|
+
clearAllStores,
|
|
13
|
+
mirrorKey,
|
|
14
|
+
collectKeyed
|
|
15
|
+
} from "@flurryx/store";
|
|
10
16
|
import {
|
|
11
17
|
syncToStore,
|
|
12
18
|
syncToKeyedStore,
|
|
@@ -21,6 +27,7 @@ export {
|
|
|
21
27
|
Loading,
|
|
22
28
|
SkipIfCached,
|
|
23
29
|
Store,
|
|
30
|
+
clearAllStores,
|
|
24
31
|
collectKeyed,
|
|
25
32
|
createKeyedResourceData,
|
|
26
33
|
defaultErrorNormalizer,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @flurryx/core\nexport type {\n ResourceState,\n StoreEnum,\n KeyedResourceData,\n KeyedResourceKey,\n ResourceStatus,\n ResourceErrors,\n} from \"@flurryx/core\";\nexport {\n isKeyedResourceData,\n createKeyedResourceData,\n isAnyKeyLoading,\n CACHE_NO_TIMEOUT,\n DEFAULT_CACHE_TTL_MS,\n} from \"@flurryx/core\";\n\n// @flurryx/store\nexport {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @flurryx/core\nexport type {\n ResourceState,\n StoreEnum,\n KeyedResourceData,\n KeyedResourceKey,\n ResourceStatus,\n ResourceErrors,\n} from \"@flurryx/core\";\nexport {\n isKeyedResourceData,\n createKeyedResourceData,\n isAnyKeyLoading,\n CACHE_NO_TIMEOUT,\n DEFAULT_CACHE_TTL_MS,\n} from \"@flurryx/core\";\n\n// @flurryx/store\nexport {\n BaseStore,\n Store,\n clearAllStores,\n mirrorKey,\n collectKeyed,\n} from \"@flurryx/store\";\nexport type { MirrorOptions, CollectKeyedOptions } from \"@flurryx/store\";\n\n// @flurryx/rx\nexport {\n syncToStore,\n syncToKeyedStore,\n SkipIfCached,\n Loading,\n defaultErrorNormalizer,\n} from \"@flurryx/rx\";\nexport type {\n SyncToStoreOptions,\n SyncToKeyedStoreOptions,\n ErrorNormalizer,\n} from \"@flurryx/rx\";\n"],"mappings":";AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flurryx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Signal-first reactive state management for Angular",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"sideEffects": false,
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flurryx/core": "0.
|
|
42
|
-
"@flurryx/store": "0.
|
|
43
|
-
"@flurryx/rx": "0.
|
|
41
|
+
"@flurryx/core": "0.8.0",
|
|
42
|
+
"@flurryx/store": "0.8.1",
|
|
43
|
+
"@flurryx/rx": "0.8.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@angular/core": ">=17.0.0",
|