storion 0.2.1 โ†’ 0.2.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 +153 -13
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -382,6 +382,139 @@ instance.subscribe("@save", (event) => {
382
382
  });
383
383
  ```
384
384
 
385
+ ### ๐Ÿงฉ Mixins โ€” Split & Reuse Store Logic
386
+
387
+ Large stores can be split into mixins using `use()`. Each mixin only knows about its own state slice:
388
+
389
+ ```tsx
390
+ import { store, effect, type StoreContext } from "storion/react";
391
+
392
+ // Each mixin defines its OWN state shape
393
+ interface UserState {
394
+ users: User[];
395
+ }
396
+
397
+ interface PostState {
398
+ posts: Post[];
399
+ }
400
+
401
+ interface NotificationState {
402
+ notifications: Notification[];
403
+ }
404
+
405
+ // Mixin 1: Notifications (base mixin)
406
+ const notificationMixin = ({ state }: StoreContext<NotificationState>) => ({
407
+ notify: (msg: string) => {
408
+ state.notifications.push({ id: Date.now(), message: msg });
409
+ },
410
+ clearAll: () => {
411
+ state.notifications = [];
412
+ },
413
+ });
414
+
415
+ // Mixin 2: User management โ€” uses notificationMixin!
416
+ const userMixin = ({
417
+ state,
418
+ use,
419
+ }: StoreContext<UserState & NotificationState>) => {
420
+ const { notify } = use(notificationMixin); // Compose another mixin
421
+
422
+ return {
423
+ addUser: (user: User) => {
424
+ state.users.push(user);
425
+ notify(`User ${user.name} added`); // Use action from other mixin
426
+ },
427
+ removeUser: (id: string) => {
428
+ state.users = state.users.filter((u) => u.id !== id);
429
+ notify(`User removed`);
430
+ },
431
+ };
432
+ };
433
+
434
+ // Mixin 3: Post management โ€” also uses notificationMixin!
435
+ const postMixin = ({
436
+ state,
437
+ use,
438
+ }: StoreContext<PostState & NotificationState>) => {
439
+ const { notify } = use(notificationMixin);
440
+
441
+ effect(() => {
442
+ console.log(`Posts updated: ${state.posts.length} total`);
443
+ });
444
+
445
+ return {
446
+ addPost: (post: Post) => {
447
+ state.posts.push(post);
448
+ notify(`New post: ${post.title}`);
449
+ },
450
+ deletePost: (id: string) => {
451
+ state.posts = state.posts.filter((p) => p.id !== id);
452
+ notify(`Post deleted`);
453
+ },
454
+ };
455
+ };
456
+
457
+ // Compose: AppState = UserState & PostState & NotificationState
458
+ const appStore = store({
459
+ name: "app",
460
+ state: {
461
+ users: [] as User[],
462
+ posts: [] as Post[],
463
+ notifications: [] as Notification[],
464
+ },
465
+ setup({ use }) {
466
+ // Each mixin works with its slice of state
467
+ const userActions = use(userMixin);
468
+ const postActions = use(postMixin);
469
+ const notificationActions = use(notificationMixin);
470
+
471
+ return {
472
+ ...userActions,
473
+ ...postActions,
474
+ ...notificationActions,
475
+ };
476
+ },
477
+ });
478
+ ```
479
+
480
+ **Benefits:**
481
+
482
+ - ๐Ÿ“ **Organization** โ€” Split 500+ line stores into focused modules
483
+ - โ™ป๏ธ **Reuse** โ€” Mixins are decoupled, reusable across stores
484
+ - ๐Ÿงช **Testable** โ€” Test mixins in isolation with minimal state
485
+ - ๐ŸŽญ **Effects** โ€” Mixins can define their own reactive effects
486
+
487
+ **Note:** `use()` runs the mixin fresh each call (no singleton). This enables parameterized mixins:
488
+
489
+ ```ts
490
+ const apiMixin = ({ state }, endpoint: string) => ({
491
+ fetch: () => fetch(endpoint),
492
+ });
493
+
494
+ use(apiMixin, "/api/users"); // Different instances
495
+ use(apiMixin, "/api/posts");
496
+ ```
497
+
498
+ If you need singleton per store, use `memoize` (e.g., from lodash):
499
+
500
+ ```ts
501
+ import memoize from "lodash/memoize";
502
+
503
+ // Memoized by first arg (StoreContext) โ€” singleton per store
504
+ const notificationMixin = memoize(
505
+ ({ state }: StoreContext<NotificationState>) => ({
506
+ notify: (msg: string) =>
507
+ state.notifications.push({ id: Date.now(), message: msg }),
508
+ clearAll: () => {
509
+ state.notifications = [];
510
+ },
511
+ })
512
+ );
513
+
514
+ // Same store context โ†’ same mixin instance
515
+ // Different store context โ†’ new mixin instance
516
+ ```
517
+
385
518
  ---
386
519
 
387
520
  ## API Reference
@@ -566,31 +699,38 @@ const { count } = useCounter((state, actions) => ({ count: state.count }));
566
699
  ### Middleware
567
700
 
568
701
  ```ts
569
- import { applyFor, applyExcept, compose } from "storion";
702
+ import { applyFor, applyExcept } from "storion";
703
+
704
+ // Just pass an array of middleware
705
+ container({ middleware: [logger, devtools, persist] });
570
706
 
571
707
  // Conditional middleware
572
- applyFor("user*", logger); // Wildcard match
573
- applyFor(/Store$/, [logger, devtools]); // RegExp match
574
- applyFor((spec) => spec.meta?.persist, persistMiddleware); // Predicate
708
+ const conditionalLogger = applyFor("user*", logger); // Wildcard match
709
+ const multiMiddleware = applyFor(/Store$/, [logger, devtools]); // RegExp match
710
+ const persistOnly = applyFor((spec) => spec.meta?.persist, persistMiddleware); // Predicate
575
711
 
576
712
  // Exclude middleware
577
- applyExcept("_internal*", logger);
578
-
579
- // Compose multiple
580
- const combined = compose(logger, devtools, persist);
713
+ const noInternalLogging = applyExcept("_internal*", logger);
581
714
 
582
- // Use in container
583
- container({ middleware: [combined] });
715
+ // Combine them
716
+ container({ middleware: [conditionalLogger, persistOnly, noInternalLogging] });
584
717
  ```
585
718
 
586
719
  ### Middleware Signature
587
720
 
588
721
  ```ts
589
722
  type StoreMiddleware = (
590
- instance: StoreInstance,
591
723
  spec: StoreSpec,
592
- container: StoreContainer
593
- ) => void | (() => void); // Return cleanup function
724
+ next: (spec: StoreSpec) => StoreInstance
725
+ ) => StoreInstance;
726
+
727
+ // Example: logging middleware
728
+ const logger: StoreMiddleware = (spec, next) => {
729
+ console.log(`Creating store: ${spec.name}`);
730
+ const instance = next(spec); // Call next to create the instance
731
+ console.log(`Created: ${instance.id}`);
732
+ return instance;
733
+ };
594
734
  ```
595
735
 
596
736
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storion",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Reactive stores for modern apps. Type-safe. Auto-tracked. Effortlessly composable",
5
5
  "type": "module",
6
6
  "main": "./dist/storion.js",