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 CHANGED
@@ -29,18 +29,37 @@ interface ProductStoreConfig {
29
29
  export const ProductStore = Store.for<ProductStoreConfig>().build();
30
30
 
31
31
  // Facade
32
- @SkipIfCached('LIST', (i) => i.store)
33
- @Loading('LIST', (i) => i.store)
34
- loadProducts() {
35
- this.http.get<Product[]>('/api/products')
36
- .pipe(syncToStore(this.store, 'LIST'))
37
- .subscribe();
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 template just read the signal
41
- @if (facade.list().isLoading) { <spinner /> }
42
- @for (product of facade.list().data; track product.id) {
43
- <product-card [product]="product" />
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({ providedIn: "root" })
197
+ @Injectable()
178
198
  export class ProductFacade {
179
199
  private readonly http = inject(HttpClient);
180
200
  readonly store = inject(ProductStore);
181
201
 
182
- // Expose signals for templates
183
- readonly list = this.store.get('LIST');
184
- readonly detail = this.store.get('DETAIL');
202
+ getProducts() {
203
+ return this.store.get("LIST");
204
+ }
205
+
206
+ getProductDetail() {
207
+ return this.store.get("DETAIL");
208
+ }
185
209
 
186
- @SkipIfCached('LIST', (i: ProductFacade) => i.store)
187
- @Loading('LIST', (i: ProductFacade) => i.store)
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, 'LIST'))
215
+ .pipe(syncToStore(this.store, "LIST"))
192
216
  .subscribe();
193
217
  }
194
218
 
195
- @Loading('DETAIL', (i: ProductFacade) => i.store)
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, 'DETAIL'))
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 (facade.list().isLoading) {
234
+ @if (productsState().isLoading) {
211
235
  <spinner />
212
- } @if (facade.list().status === 'Success') { @for (product of
213
- facade.list().data; track product.id) {
236
+ } @if (productsState().status === 'Success') { @for (product of
237
+ productsState().data; track product.id) {
214
238
  <product-card [product]="product" />
215
- } } @if (facade.list().status === 'Error') {
216
- <error-banner [errors]="facade.list().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,
@@ -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 { BaseStore, Store, mirrorKey, collectKeyed } 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;AASA,kBAMO;AAGP,mBAA0D;AAI1D,gBAMO;","names":[]}
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 { BaseStore, Store, mirrorKey, collectKeyed } from "@flurryx/store";
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 { BaseStore, Store, mirrorKey, collectKeyed } 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,SAAS,WAAW,OAAO,WAAW,oBAAoB;AAI1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
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.7.6",
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.7.6",
42
- "@flurryx/store": "0.7.6",
43
- "@flurryx/rx": "0.7.6"
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",