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.
- package/README.md +153 -13
- 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
|
|
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
|
-
//
|
|
583
|
-
container({ middleware: [
|
|
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
|
-
|
|
593
|
-
) =>
|
|
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
|
---
|