storion 0.7.7 → 0.7.9

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 +125 -1
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1453,6 +1453,126 @@ const myStore = store({
1453
1453
  | Arguments | None | Supports extra args |
1454
1454
  | Use case | Shared services | Configured instances |
1455
1455
 
1456
+ ### Mixins (Reusable Logic)
1457
+
1458
+ Mixins let you compose reusable logic across stores and selectors.
1459
+
1460
+ **Store Mixin — reusable actions:**
1461
+
1462
+ ```ts
1463
+ import { store, type StoreContext } from "storion";
1464
+
1465
+ // Define a reusable mixin
1466
+ const counterMixin = (ctx: StoreContext<{ count: number }>) => ({
1467
+ increment: () => ctx.state.count++,
1468
+ decrement: () => ctx.state.count--,
1469
+ reset: () => ctx.reset(),
1470
+ });
1471
+
1472
+ // Use in multiple stores
1473
+ const store1 = store({
1474
+ name: "counter1",
1475
+ state: { count: 0, label: "Counter 1" },
1476
+ setup: (ctx) => ({
1477
+ ...ctx.mixin(counterMixin),
1478
+ setLabel: (label: string) => (ctx.state.label = label),
1479
+ }),
1480
+ });
1481
+
1482
+ const store2 = store({
1483
+ name: "counter2",
1484
+ state: { count: 100 },
1485
+ setup: (ctx) => ctx.mixin(counterMixin),
1486
+ });
1487
+ ```
1488
+
1489
+ **Selector Mixin — reusable selector logic:**
1490
+
1491
+ ```tsx
1492
+ import { useStore, type SelectorContext } from "storion/react";
1493
+
1494
+ // Define a reusable selector mixin
1495
+ const sumMixin = (
1496
+ ctx: SelectorContext,
1497
+ stores: StoreSpec<{ value: number }>[]
1498
+ ) => {
1499
+ return stores.reduce((sum, spec) => {
1500
+ const [state] = ctx.get(spec);
1501
+ return sum + state.value;
1502
+ }, 0);
1503
+ };
1504
+
1505
+ function Dashboard() {
1506
+ const { total } = useStore((ctx) => ({
1507
+ total: ctx.mixin(sumMixin, [store1, store2, store3]),
1508
+ }));
1509
+
1510
+ return <div>Total: {total}</div>;
1511
+ }
1512
+ ```
1513
+
1514
+ **Important: Mixins are NOT singletons**
1515
+
1516
+ Each call to `mixin()` creates a fresh instance. If you need singleton behavior **within the same store/selector scope**, wrap with memoize:
1517
+
1518
+ ```ts
1519
+ import memoize from "lodash/memoize";
1520
+ import { store, type StoreContext } from "storion";
1521
+
1522
+ // Shared mixin - memoized to be singleton within same store setup
1523
+ const sharedLogicMixin = memoize((ctx: StoreContext<any>) => {
1524
+ console.log("sharedLogicMixin created"); // Only logs once per store!
1525
+ return {
1526
+ doSomething: () => console.log("shared logic"),
1527
+ };
1528
+ });
1529
+
1530
+ // Feature A mixin - uses sharedLogicMixin
1531
+ const featureAMixin = (ctx: StoreContext<any>) => {
1532
+ const shared = ctx.mixin(sharedLogicMixin); // Gets cached instance
1533
+ return {
1534
+ featureA: () => shared.doSomething(),
1535
+ };
1536
+ };
1537
+
1538
+ // Feature B mixin - also uses sharedLogicMixin
1539
+ const featureBMixin = (ctx: StoreContext<any>) => {
1540
+ const shared = ctx.mixin(sharedLogicMixin); // Gets SAME cached instance
1541
+ return {
1542
+ featureB: () => shared.doSomething(),
1543
+ };
1544
+ };
1545
+
1546
+ // Main store - composes both features
1547
+ const myStore = store({
1548
+ name: "myStore",
1549
+ state: { value: 0 },
1550
+ setup: (ctx) => {
1551
+ const featureA = ctx.mixin(featureAMixin);
1552
+ const featureB = ctx.mixin(featureBMixin);
1553
+
1554
+ // Both features share the same sharedLogicMixin instance!
1555
+ // featureA.featureA and featureB.featureB call the same shared.doSomething
1556
+
1557
+ return { ...featureA, ...featureB };
1558
+ },
1559
+ });
1560
+ ```
1561
+
1562
+ **What happens:**
1563
+
1564
+ 1. `featureAMixin` calls `mixin(sharedLogicMixin)` → creates instance, memoize caches it
1565
+ 2. `featureBMixin` calls `mixin(sharedLogicMixin)` → returns cached instance
1566
+ 3. Both features share the same `sharedLogicMixin` instance within this store
1567
+
1568
+ **When to use mixin vs service:**
1569
+
1570
+ | Pattern | Caching | Access to context | Use case |
1571
+ | -------------------- | ------------------------- | ---------------------- | ----------------------------------- |
1572
+ | `get(service)` | ✅ Global singleton | ❌ No StoreContext | Shared utilities, API clients |
1573
+ | `mixin(fn)` | ❌ Fresh each call | ✅ Full context access | Reusable actions, computed values |
1574
+ | `mixin(memoize(fn))` | ✅ Singleton (same scope) | ✅ Full context access | Shared logic across multiple mixins |
1575
+
1456
1576
  ### Equality Strategies
1457
1577
 
1458
1578
  Storion supports equality checks at **two levels**, giving you fine-grained control over when updates happen.
@@ -1634,7 +1754,7 @@ const myStore = store({
1634
1754
 
1635
1755
  ### DevTools Integration
1636
1756
 
1637
- ![img](./img/image.png)
1757
+ ![Storion DevTools](https://raw.githubusercontent.com/linq2js/storion/main/packages/storion/img/image.png)
1638
1758
 
1639
1759
  ```ts
1640
1760
  import { devtools } from "storion/devtools";
@@ -1965,6 +2085,10 @@ fix(react): resolve hook issue
1965
2085
  docs: update README
1966
2086
  ```
1967
2087
 
2088
+ ### AI Assistance
2089
+
2090
+ For AI coding assistants, see [AI_GUIDE.md](./AI_GUIDE.md) for rules and patterns when generating Storion code.
2091
+
1968
2092
  ---
1969
2093
 
1970
2094
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storion",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Reactive stores for modern apps. Type-safe. Auto-tracked. Effortlessly composable",
5
5
  "type": "module",
6
6
  "main": "./dist/storion.js",