flurryx 0.7.1 → 0.7.3

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.
Files changed (2) hide show
  1. package/README.md +142 -31
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -66,6 +66,7 @@ No `async` pipe. No `subscribe` in templates. No manual unsubscription.
66
66
  - [Constants](#constants)
67
67
  - [Keyed Resources](#keyed-resources)
68
68
  - [Store Mirroring](#store-mirroring)
69
+ - [Builder .mirror()](#builder-mirror)
69
70
  - [mirrorKey](#mirrorkey)
70
71
  - [collectKeyed](#collectkeyed)
71
72
  - [Design Decisions](#design-decisions)
@@ -599,14 +600,114 @@ import {
599
600
 
600
601
  When building session or aggregation stores that combine state from multiple feature stores, you typically need `onUpdate` listeners, cleanup arrays, and `DestroyRef` wiring. The `mirrorKey` and `collectKeyed` utilities reduce that to a single call.
601
602
 
603
+ ```
604
+ +--------------------+ +--------------------+
605
+ | Feature Store A | | |
606
+ | (CUSTOMERS) |-- mirrorKey ------>| |
607
+ +--------------------+ | |
608
+ | Session Store |
609
+ +--------------------+ | (aggregated) |
610
+ | Feature Store B | | |
611
+ | (ORDERS) |-- mirrorKey ------>| CUSTOMERS + |
612
+ +--------------------+ | ORDERS + |
613
+ | CUSTOMER_CACHE + |
614
+ +--------------------+ | |
615
+ | Feature Store C | | |
616
+ | (CUSTOMER_DETAIL) |-- collectKeyed --->| |
617
+ +--------------------+ +--------------------+
618
+ ```
619
+
602
620
  ```typescript
603
- import { mirrorKey, collectKeyed } from "flurryx";
621
+ import { Store, mirrorKey, collectKeyed } from "flurryx";
622
+ ```
623
+
624
+ ### Builder .mirror()
625
+
626
+ The simplest way to set up mirroring is directly in the store builder. Chain `.mirror()` to declare which source stores to mirror from — the wiring happens automatically when Angular creates the store.
627
+
628
+ ```typescript
629
+ // Feature stores
630
+ interface CustomerStoreConfig {
631
+ CUSTOMERS: Customer[];
632
+ }
633
+ export const CustomerStore = Store.for<CustomerStoreConfig>().build();
634
+
635
+ interface OrderStoreConfig {
636
+ ORDERS: Order[];
637
+ }
638
+ export const OrderStore = Store.for<OrderStoreConfig>().build();
604
639
  ```
605
640
 
641
+ **Interface-based builder** (recommended):
642
+
643
+ ```typescript
644
+ interface SessionStoreConfig {
645
+ CUSTOMERS: Customer[];
646
+ ORDERS: Order[];
647
+ }
648
+
649
+ export const SessionStore = Store.for<SessionStoreConfig>()
650
+ .mirror(CustomerStore, 'CUSTOMERS')
651
+ .mirror(OrderStore, 'ORDERS')
652
+ .build();
653
+ ```
654
+
655
+ **Fluent chaining:**
656
+
657
+ ```typescript
658
+ export const SessionStore = Store
659
+ .resource('CUSTOMERS').as<Customer[]>()
660
+ .resource('ORDERS').as<Order[]>()
661
+ .mirror(CustomerStore, 'CUSTOMERS')
662
+ .mirror(OrderStore, 'ORDERS')
663
+ .build();
664
+ ```
665
+
666
+ **Enum-constrained:**
667
+
668
+ ```typescript
669
+ const SessionEnum = { CUSTOMERS: 'CUSTOMERS', ORDERS: 'ORDERS' } as const;
670
+
671
+ export const SessionStore = Store.for(SessionEnum)
672
+ .resource('CUSTOMERS').as<Customer[]>()
673
+ .resource('ORDERS').as<Order[]>()
674
+ .mirror(CustomerStore, 'CUSTOMERS')
675
+ .mirror(OrderStore, 'ORDERS')
676
+ .build();
677
+ ```
678
+
679
+ **Different source and target keys:**
680
+
681
+ ```typescript
682
+ export const SessionStore = Store.for<{ ARTICLES: Item[] }>()
683
+ .mirror(ItemStore, 'ITEMS', 'ARTICLES')
684
+ .build();
685
+ ```
686
+
687
+ 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.
688
+
606
689
  ### mirrorKey
607
690
 
608
691
  Mirrors a resource key from one store to another. When the source updates, the target is updated with the same state.
609
692
 
693
+ ```
694
+ +------------------+--------------------------------+------------------+
695
+ | CustomerStore | mirrorKey | SessionStore |
696
+ | | | |
697
+ | CUSTOMERS -------|--- onUpdate --> update ------->| CUSTOMERS |
698
+ | | (same key or different) | |
699
+ | { data, | | { data, |
700
+ | status, | | status, |
701
+ | isLoading } | | isLoading } |
702
+ +------------------+--------------------------------+------------------+
703
+
704
+ source.update('CUSTOMERS', { data: [...], status: 'Success' })
705
+ |
706
+ '--> target is automatically updated with the same state
707
+ ```
708
+
709
+ You wire it once. Every future update — data, loading, errors — flows automatically. Call the cleanup function or use `destroyRef` to stop.
710
+
610
711
  ```typescript
611
712
  // Same key on both stores (default)
612
713
  mirrorKey(customersStore, 'CUSTOMERS', sessionStore);
@@ -623,40 +724,24 @@ mirrorKey(customersStore, 'CUSTOMERS', sessionStore, { destroyRef });
623
724
  mirrorKey(customersStore, 'ITEMS', sessionStore, 'ARTICLES', { destroyRef });
624
725
  ```
625
726
 
626
- **Full example — session facade that aggregates feature stores:**
627
-
628
- ```typescript
629
- // Feature stores
630
- interface CustomerStoreConfig {
631
- CUSTOMERS: Customer[];
632
- }
633
- export const CustomerStore = Store.for<CustomerStoreConfig>().build();
634
-
635
- interface OrderStoreConfig {
636
- ORDERS: Order[];
637
- }
638
- export const OrderStore = Store.for<OrderStoreConfig>().build();
727
+ **Full example — session store that aggregates feature stores:**
639
728
 
640
- // Session storemirrors state from feature stores
641
- interface SessionStoreConfig {
642
- CUSTOMERS: Customer[];
643
- ORDERS: Order[];
644
- }
645
- export const SessionStore = Store.for<SessionStoreConfig>().build();
729
+ For simple aggregation, prefer the [builder `.mirror()` approach](#builder-mirror). Use `mirrorKey` when you need imperative control e.g. conditional mirroring, late setup, or `DestroyRef`-based cleanup:
646
730
 
731
+ ```typescript
647
732
  @Injectable({ providedIn: 'root' })
648
- export class SessionFacade {
733
+ export class SessionStore {
649
734
  private readonly customerStore = inject(CustomerStore);
650
735
  private readonly orderStore = inject(OrderStore);
651
- private readonly sessionStore = inject(SessionStore);
736
+ private readonly store = inject(Store.for<SessionStoreConfig>().build());
652
737
  private readonly destroyRef = inject(DestroyRef);
653
738
 
654
- readonly customers = this.sessionStore.get('CUSTOMERS');
655
- readonly orders = this.sessionStore.get('ORDERS');
739
+ readonly customers = this.store.get('CUSTOMERS');
740
+ readonly orders = this.store.get('ORDERS');
656
741
 
657
742
  constructor() {
658
- mirrorKey(this.customerStore, 'CUSTOMERS', this.sessionStore, { destroyRef: this.destroyRef });
659
- mirrorKey(this.orderStore, 'ORDERS', this.sessionStore, { destroyRef: this.destroyRef });
743
+ mirrorKey(this.customerStore, 'CUSTOMERS', this.store, { destroyRef: this.destroyRef });
744
+ mirrorKey(this.orderStore, 'ORDERS', this.store, { destroyRef: this.destroyRef });
660
745
  }
661
746
  }
662
747
  ```
@@ -667,6 +752,33 @@ Everything — loading flags, data, status, errors — is mirrored automatically
667
752
 
668
753
  Accumulates single-entity fetches into a `KeyedResourceData` cache on a target store. Each time the source emits a successful entity, it is merged into the target's keyed map by a user-provided `extractId` function.
669
754
 
755
+ ```
756
+ +--------------------+-----------------+--------------------------+
757
+ | CustomerStore | collectKeyed | SessionStore |
758
+ | | | |
759
+ | CUSTOMER_DETAILS | extractId(data) | CUSTOMER_CACHE |
760
+ | (one at a time) | finds the key | (KeyedResourceData) |
761
+ +--------+-----------+-----------------+ |
762
+ | | entities: |
763
+ | fetch("c1") -> Success | c1: { id, name } |
764
+ | fetch("c2") -> Success | c2: { id, name } |
765
+ | fetch("c3") -> Error | |
766
+ | | isLoading: |
767
+ | clear() -> removes last | c1: false |
768
+ | entity | c2: false |
769
+ | | |
770
+ '---- accumulates ----------->| status: |
771
+ | c1: 'Success' |
772
+ | c2: 'Success' |
773
+ | c3: 'Error' |
774
+ | |
775
+ | errors: |
776
+ | c3: [{ code, msg }] |
777
+ +--------------------------+
778
+ ```
779
+
780
+ Each entity is tracked independently — its own loading flag, status, and errors. The source store fetches one entity at a time; `collectKeyed` builds up the full cache on the target.
781
+
670
782
  ```typescript
671
783
  // Same key on both stores
672
784
  collectKeyed(customerStore, 'CUSTOMER_DETAILS', sessionStore, {
@@ -703,18 +815,17 @@ export const CustomerStore = Store.for<CustomerStoreConfig>().build();
703
815
  interface SessionStoreConfig {
704
816
  CUSTOMER_CACHE: KeyedResourceData<string, Customer>;
705
817
  }
706
- export const SessionStore = Store.for<SessionStoreConfig>().build();
707
818
 
708
819
  @Injectable({ providedIn: 'root' })
709
- export class SessionFacade {
820
+ export class SessionStore {
710
821
  private readonly customerStore = inject(CustomerStore);
711
- private readonly sessionStore = inject(SessionStore);
822
+ private readonly store = inject(Store.for<SessionStoreConfig>().build());
712
823
  private readonly destroyRef = inject(DestroyRef);
713
824
 
714
- readonly customerCache = this.sessionStore.get('CUSTOMER_CACHE');
825
+ readonly customerCache = this.store.get('CUSTOMER_CACHE');
715
826
 
716
827
  constructor() {
717
- collectKeyed(this.customerStore, 'CUSTOMER_DETAILS', this.sessionStore, 'CUSTOMER_CACHE', {
828
+ collectKeyed(this.customerStore, 'CUSTOMER_DETAILS', this.store, 'CUSTOMER_CACHE', {
718
829
  extractId: (data) => data?.id,
719
830
  destroyRef: this.destroyRef,
720
831
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flurryx",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
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.1",
42
- "@flurryx/store": "0.7.1",
43
- "@flurryx/rx": "0.7.1"
41
+ "@flurryx/core": "0.7.3",
42
+ "@flurryx/store": "0.7.3",
43
+ "@flurryx/rx": "0.7.3"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@angular/core": ">=17.0.0",