orchestore 0.1.0 → 0.1.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
@@ -1,49 +1,14 @@
1
- # Table of Contents
2
-
3
- - [Introduction](#orchestore)
4
- - [Core Principles](#core-principles)
5
- - [Why OrcheStore?](#why-orchestore)
6
- - [Redux Toolkit Comparison](#redux-toolkit-comparison)
7
- - [Architecture Overview](#architecture-overview)
8
-
9
- - [Quick Example](#quick-example)
10
-
11
- - [Slice Layers](#slice-layers)
12
- - [name](#name)
13
- - [state](#state)
14
- - [Mutations](#mutations)
15
- - [Methods](#methods)
16
- - [Computed State](#computed-state)
17
- - [Nested Slices](#nested-slices)
18
- - [Runtime Paths](#runtime-paths)
19
-
20
- - [State Access & Subscriptions](#state-access--subscriptions)
21
- - [State Snapshots](#state-snapshots)
22
- - [State Subscriptions](#state-subscriptions)
23
- - [Draft State](#draft-state)
24
-
25
- - [Store Integration](#store-integration)
26
- - [Creating the Store](#creating-the-store)
27
- - [Store Provider](#store-provider)
28
- - [Accessing Slices through Store](#accessing-slices-through-store)
29
- - [Accessing Store from Slices](#accessing-store-from-slices)
30
- - [Root Store Type Extension](#root-store-type-extension)
31
-
32
- - [Global Utilities](#global-utilities)
33
- - [Accessing Global Utilities](#accessing-global-utilities)
34
- - [Utilities Type Extension](#utilities-type-extension)
35
- - [Providing Runtime Utilities](#providing-runtime-utilities)
36
- - [Using Global Utilities in Slices](#using-global-utilities-in-slices)
1
+ # OrcheStore
37
2
 
38
- - [TypeScript Inference](#typescript-inference)
3
+ ### 🚧 Coming Soon
39
4
 
40
- - [Status](#status)
5
+ > OrcheStore is currently under active development and is not yet ready for production use.
41
6
 
42
7
  ---
43
8
 
44
- # OrcheStore
9
+ ## About
45
10
 
46
- > A function-oriented state orchestration architecture built on top of Redux Toolkit.
11
+ > 🧩 A function-oriented state orchestration architecture built on top of Redux Toolkit.
47
12
 
48
13
  OrcheStore simplifies and automates common state management patterns in React, Redux Toolkit, and TypeScript applications by unifying state and behavior into directly callable runtime modules.
49
14
 
@@ -51,7 +16,7 @@ Instead of distributing logic across reducers, actions, thunks, selectors, hooks
51
16
 
52
17
  The goal is simple:
53
18
 
54
- > Spend less time wiring state management infrastructure and more time building application features.
19
+ > Spend less time wiring state management infrastructure and more time building application features.
55
20
 
56
21
  ## Core Principles
57
22
 
@@ -63,1045 +28,3 @@ The goal is simple:
63
28
  - Preserve predictable state transitions
64
29
  - Maintain strong TypeScript inference
65
30
  - Scale naturally through composition
66
-
67
- ## Why OrcheStore?
68
-
69
- Redux Toolkit significantly improves the Redux developer experience, but many applications still require developers to coordinate logic across multiple concepts:
70
-
71
- - reducers
72
- - action creators
73
- - thunks
74
- - selectors
75
- - middleware
76
- - hooks
77
- - utility functions
78
-
79
- As applications grow, state management often becomes less about solving business problems and more about connecting infrastructure.
80
-
81
- OrcheStore reduces that coordination overhead by exposing state management through unified slice modules.
82
-
83
- A slice is more than a state container. It is a runtime module that can encapsulate:
84
-
85
- - state
86
- - computed state
87
- - mutations
88
- - methods
89
- - selectors
90
- - child slices
91
- - shared utilities
92
-
93
- This allows state and application logic to evolve together within the same domain boundary.
94
-
95
- Many common Redux patterns are automated by default:
96
-
97
- | Traditional Redux Pattern | OrcheStore |
98
- | ----------------------------- | ------------------------- |
99
- | Action creators | Direct callable mutations |
100
- | Thunks | Built-in methods |
101
- | Dispatch calls | Direct function calls |
102
- | `PayloadAction` wrappers | Native function arguments |
103
- | Cross-slice imports | Root store access |
104
- | Shared service wiring | Global utilities |
105
- | Manual state tree composition | Nested slices |
106
- | Complex type declarations | Automatic inference |
107
-
108
- The result is a simpler architecture with fewer moving parts, less boilerplate, and a more direct development experience.
109
-
110
- Developers can focus on application behavior rather than framework plumbing.
111
-
112
- ## Redux Toolkit Comparison
113
-
114
- OrcheStore builds on top of Redux Toolkit while providing a higher-level API for organizing state and behavior.
115
-
116
- | Feature | OrcheStore | Redux Toolkit |
117
- | ------------------------------ | ---------- | ------------- |
118
- | Direct callable mutations | ✅ | ❌ |
119
- | Multiple mutation arguments | ✅ | ❌ |
120
- | Dispatch required | ❌ | ✅ |
121
- | `PayloadAction` wrappers | ❌ | ✅ |
122
- | Built-in orchestration methods | ✅ | ❌ |
123
- | Nested slice composition | ✅ | ⚠️ Manual |
124
- | Automatic path generation | ✅ | ⚠️ Manual |
125
- | Global utilities | ✅ | ❌ |
126
- | Unified slice API | ✅ | ❌ |
127
- | Per-slice React hooks | ✅ | ❌ |
128
- | Deep TypeScript inference | ✅ | ⚠️ Partial |
129
-
130
- OrcheStore does not replace Redux Toolkit. Instead, it builds on top of it by automating common patterns and providing a more cohesive developer experience.
131
-
132
- ## Architecture Overview
133
-
134
- | Layer | Responsibility |
135
- | ----------- | ------------------------------ |
136
- | `name` | Unique slice identifier |
137
- | `path` | Hierarchical slice path |
138
- | `state` | Slice data storage definition |
139
- | `mutations` | Synchronous state transitions |
140
- | `methods` | Orchestration and side effects |
141
- | `computed` | Derived and computed state |
142
- | `children` | Nested slice composition |
143
- | `getState` | Imperative state access |
144
- | `useSelect` | Reactive state subscriptions |
145
-
146
- ---
147
-
148
- # Quick Example
149
-
150
- **Comparing OrcheStore with Redux Toolkit**
151
-
152
- ## Slice Creation
153
-
154
- ✔️ OrcheStore centrelize unified options API.
155
-
156
- ```tsx
157
- import { createSlice } from "orchestore";
158
-
159
- export const counter = createSlice({
160
- name: "counter",
161
-
162
- state: {
163
- value: 0,
164
- },
165
-
166
- mutations: {
167
- // Direct arguments without PayloadAction wrappers
168
- increment(state, amount: number) {
169
- state.value += amount;
170
- },
171
-
172
- // Multiple typed arguments without payload objects
173
- incrementLimited(state, amount: number, max = Infinity) {
174
- state.value = Math.min(state.value + amount, max);
175
- },
176
- },
177
-
178
- methods: {
179
- // Reusable async method inside slice
180
- sleep(delay: number) {
181
- return new Promise((resolve) => setTimeout(resolve, delay));
182
- },
183
-
184
- // Orchestration layer with full slice access via `this`
185
- async incrementAfter(amount: number, delay: number) {
186
- await this.sleep(delay);
187
- this.increment(amount);
188
- },
189
- },
190
- });
191
- ```
192
-
193
- ⛔ Redux Toolkit splits logic between reducers, extra reducers, actions, and async workflows.
194
-
195
- ```tsx
196
- import { createSlice, createAsyncThunk, type PayloadAction } from "@reduxjs/toolkit";
197
-
198
- export const counter = createSlice({
199
- name: "counter",
200
-
201
- initialState: {
202
- value: 0,
203
- },
204
-
205
- reducers: {
206
- // PayloadAction wrapper required
207
- increment(state, action: PayloadAction<number>) {
208
- state.value += action.payload;
209
- },
210
-
211
- // Multiple arguments must be wrapped into a payload object
212
- incrementLimited(state, action: PayloadAction<{ amount: number; max?: number }>) {
213
- state.value = Math.min(
214
- state.value + action.payload.amount,
215
- action.payload.max ?? Infinity
216
- );
217
- },
218
- },
219
- });
220
-
221
- // Separate APIs for async workflows
222
- export const incrementAfter = createAsyncThunk(
223
- "counter/incrementAfter",
224
- async (
225
- { amount, delay }: { amount: number; delay: number },
226
- { dispatch }
227
- ) => {
228
- await new Promise((resolve) => setTimeout(resolve, delay));
229
-
230
- dispatch(counter.actions.increment(amount));
231
- }
232
- );
233
- ```
234
-
235
- ## Slice Usage
236
-
237
- ✔️ OrcheStore exposes direct callable mutations and methods.
238
-
239
- ```ts
240
- counter.increment(4);
241
- counter.incrementLimited(1, 50);
242
- counter.incrementAfter(5, 1000);
243
- ```
244
-
245
- ⛔ Redux Toolkit follows dispatch-based execution
246
-
247
- ```ts
248
- import { useDispatch } from "react-redux";
249
-
250
- const dispatch = useDispatch();
251
-
252
- dispatch(counter.actions.increment(4));
253
- dispatch(counter.actions.incrementLimited({ amount: 1, max: 50 }));
254
- dispatch(incrementAfter({ amount: 5, delay: 1000 }));
255
- ```
256
-
257
- ## Store Integration & Context Providing
258
-
259
- ✔️ OrcheStore exposes fully typed slice APIs directly.
260
-
261
- ```ts
262
- import { defineStore } from "orchestore";
263
- import { counter } from "./counterSlice";
264
-
265
- export const store = defineStore({
266
- slices: {
267
- counter,
268
- },
269
- });
270
- ```
271
-
272
- ```tsx
273
- import { StoreProvider } from "orchestore";
274
-
275
- export default function App() {
276
- return (
277
- <StoreProvider>
278
- <CounterComponent />
279
- </StoreProvider>
280
- );
281
- }
282
- ```
283
-
284
- ⛔ Redux Toolkit requires manual store configuration and type wiring.
285
-
286
- ```ts
287
- import { configureStore } from "@reduxjs/toolkit";
288
- import { counter } from "./counterSlice";
289
- import { useDispatch, useSelector } from "react-redux";
290
-
291
- export const store = configureStore({
292
- reducer: {
293
- counter: counter.reducer,
294
- },
295
- });
296
-
297
- export type RootState = ReturnType<typeof store.getState>;
298
- export type AppDispatch = typeof store.dispatch;
299
-
300
- export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
301
- export const useAppSelector = useSelector.withTypes<RootState>();
302
- ```
303
-
304
- ```tsx
305
- import { Provider } from "react-redux";
306
- import { store } from "./store/index";
307
-
308
- export default function App() {
309
- return (
310
- <Provider store={store}>
311
- <CounterComponent />
312
- </Provider>
313
- );
314
- }
315
- ```
316
-
317
- ## React Usage
318
-
319
- OrcheStore exposes direct usable child slices through a unified store instance.
320
-
321
- ```tsx
322
- // store.counter is equivalent to counter, import only one and use it
323
- import { store } from "./store/index";
324
- import { counter } from "./store/counterSlice";
325
-
326
- export function CounterComponent() {
327
- const value = counter.useSelect((state) => state.value);
328
- const alias = store.counter.useSelect((state) => state.value);
329
-
330
- return (
331
- <>
332
- <div>Counter: {value}</div>
333
-
334
- <button onClick={() => counter.increment(1)}>Increment</button>
335
-
336
- <button onClick={() => store.counter.incrementAfter(1, 1000)}>Increment Later</button>
337
- </>
338
- );
339
- }
340
- ```
341
-
342
- ---
343
-
344
- # Slice Layers
345
-
346
- Slices are created using `createSlice`.
347
-
348
- ```ts
349
- import { createSlice } from "orchestore";
350
-
351
- const counter = createSlice({
352
- name: "counter",
353
-
354
- state: {},
355
-
356
- computed: {},
357
-
358
- mutations: {},
359
-
360
- methods: {},
361
-
362
- children: {},
363
- });
364
- ```
365
-
366
- ## name
367
-
368
- Every slice requires a unique and stable name.
369
-
370
- The name primarily exists to ensure slice uniqueness and path generation.
371
-
372
- ```ts
373
- const counter = createSlice({
374
- name: "counter",
375
- });
376
- ```
377
-
378
- The name is exposed at runtime:
379
-
380
- ```ts
381
- console.log(counter.name); // "counter"
382
- ```
383
-
384
- It is also accessible inside methods:
385
-
386
- ```ts
387
- methods: {
388
- logName() {
389
- console.log(this.name);
390
- }
391
- }
392
- ```
393
-
394
- **Rules:**
395
-
396
- - Slice names should not contain `"."`, because dots are reserved for nested slice paths
397
- - Two slices cannot share the same name
398
- - Registering the same slice instance multiple times with the same name is not allowed
399
-
400
- ---
401
-
402
- ## state
403
-
404
- The `state` property defines the initial slice state.
405
-
406
- ```ts
407
- const counter = createSlice({
408
- name: "counter",
409
-
410
- state: {
411
- value: 0,
412
- },
413
- });
414
- ```
415
-
416
- State can also be initialized lazily by providing a function.
417
-
418
- The initializer runs only once before the first state access.
419
-
420
- ```ts
421
- const counter = createSlice({
422
- name: "counter",
423
-
424
- state: () => ({
425
- value: 0,
426
- }),
427
- });
428
- ```
429
-
430
- **Useful for:**
431
-
432
- - expensive initialization
433
- - persisted state restoration
434
- - runtime-dependent values
435
-
436
- ---
437
-
438
- ## Mutations
439
-
440
- Mutations are synchronous state transition functions.
441
-
442
- **Characteristics:**
443
-
444
- - receive mutable draft state as the first parameter
445
- - support multiple user-defined arguments
446
- - directly callable from the exposed slices or store
447
-
448
- **Responsibilities:**
449
-
450
- Mutations should contain:
451
-
452
- - synchronous state updates
453
- - deterministic state transitions
454
- - normalization logic
455
-
456
- For anything else, use methods instead.
457
-
458
- **Example:**
459
-
460
- ```ts
461
- const counter = createSlice({
462
- name: "counter",
463
-
464
- state: {
465
- value: 0,
466
- },
467
-
468
- mutations: {
469
- increment(state, amount = 1, max = Infinity) {
470
- state.value = Math.min(state.value + amount, max);
471
- },
472
- },
473
- });
474
-
475
- counter.increment(1, 50);
476
- ```
477
-
478
- ---
479
-
480
- ## Methods
481
-
482
- Methods are the orchestration layer of a slice.
483
-
484
- **Characteristics:**
485
-
486
- - receive any number of arguments
487
- - can return synchronous values or Promises
488
- - can access:
489
- - state, mutations, slibling methods, nested slices (through `this`)
490
- - Application-wide store (`this.root`) and utilities (`this.global`)
491
-
492
- Methods are not serialized, replayable, or represented in Redux DevTools action history.
493
-
494
- **Responsibilities:**
495
-
496
- Methods are designed for centralizing any slice-related logic:
497
-
498
- - asynchronous workflows
499
- - business logic orchestration
500
- - API communication and network calls
501
- - timers, delayed or scheduled executions, such as `setTimeout` or event listeners
502
- - side effects such as `localStorage`, `sessionStorage` and DOM manipulation
503
- - Slice-related React hooks such as `slice.useSelect`, tanstack `useQuery` and `useMutation`
504
-
505
- **Restrictions:**
506
-
507
- Methods should NOT:
508
-
509
- - include slice-unrelated logic.
510
- - mutate state directly. use mutations instead.
511
-
512
- Prefer:
513
-
514
- ```ts
515
- this.increment(1);
516
- ```
517
-
518
- Instead of:
519
-
520
- ```ts
521
- this.getState().value++;
522
- ```
523
-
524
- **Example:**
525
-
526
- ```ts
527
- const counter = createSlice({
528
- name: "counter",
529
-
530
- state: {
531
- value: 0,
532
- },
533
-
534
- mutations: {
535
- increment(state, amount: number) {
536
- state.value += amount;
537
- },
538
- },
539
-
540
- methods: {
541
- async incrementAfter(amount: number, delay = 1000) {
542
- await new Promise((resolve) => setTimeout(resolve, delay));
543
- this.increment(amount);
544
- this.global.logger.info("Counter incremented");
545
- },
546
- },
547
- });
548
- ```
549
-
550
- ---
551
-
552
- ## ~~Computed State~~
553
-
554
- This currently not supported
555
-
556
- ~~The `computed` property provides reusable derived state.~~
557
-
558
- ```ts
559
- // const counter = createSlice({
560
- // name: "counter",
561
-
562
- // state: {
563
- // value: 10,
564
- // },
565
-
566
- // computed: {
567
- // doubled(state) {
568
- // return state.value * 2;
569
- // },
570
-
571
- // multiplied(state, amount: number) {
572
- // return state.value * amount;
573
- // },
574
- // },
575
- // });
576
- ```
577
-
578
- ---
579
-
580
- ## Nested Slices
581
-
582
- Slices can be composed by registering other slices through the `children` property.
583
-
584
- This allows related state and behavior to be organized into a hierarchical structure while preserving full type inference.
585
-
586
- ```ts
587
- import { counter } from "./counterSlice";
588
- import { users } from "./usersSlice";
589
-
590
- export const app = createSlice({
591
- name: "app",
592
-
593
- state: {},
594
-
595
- children: {
596
- counter,
597
- users,
598
- },
599
- });
600
- ```
601
-
602
- **Rules:**
603
-
604
- - A slice instance can only be registered once within the same state tree
605
- - Existing slice instances cannot be wrapped again with `createSlice`
606
- - Child slices must be registered through the `children` property
607
-
608
- **Accessing Child Slices:**
609
-
610
- Child slices are exposed directly on their parent slice.
611
-
612
- ```ts
613
- app.counter.increment(1);
614
-
615
- console.log(app.counter.getState());
616
- ```
617
-
618
- Deeply nested slice hierarchies are fully supported.
619
-
620
- ```ts
621
- admin.users.permissions.grant(...);
622
-
623
- console.log(admin.users.permissions.getState());
624
- ```
625
-
626
- ### Runtime Paths
627
-
628
- Every slice exposes a unique runtime path through `path`.
629
-
630
- ```ts
631
- store.counter.name; // "counter"
632
- store.counter.path; // "counter"
633
- ```
634
-
635
- Nested slices automatically inherit their parent path.
636
-
637
- ```ts
638
- store.admin.users.name; // "users"
639
- store.admin.users.path; // "admin.users"
640
-
641
- store.admin.users.permissions.name; // "permissions"
642
- store.admin.users.permissions.path; // "admin.users.permissions"
643
- ```
644
-
645
- Paths are generated automatically from the slice hierarchy.
646
-
647
- OrcheStore builds on the same concepts as Redux Toolkit's `reducerPath` and `injectInto`, but automates path generation, reducer registration, and nested slice composition.
648
-
649
- No manual path configuration or reducer injection is required.
650
-
651
- ---
652
-
653
- # State Access & Subscriptions
654
-
655
- OrcheStore provides multiple ways to access state depending on the context.
656
-
657
- | API | Purpose |
658
- | ------------------- | -------------------------------------------------------- |
659
- | `slice.getState()` | Read the current state snapshot |
660
- | `slice.useSelect()` | Subscribe to state changes inside React |
661
- | Draft state | Temporary state access available during state evaluation |
662
-
663
- ## State Snapshots
664
-
665
- `getState()` returns the latest immutable state snapshot.
666
-
667
- Use it whenever you need to read state imperatively outside of React subscriptions.
668
-
669
- ```ts
670
- counter.getState().value;
671
- ```
672
-
673
- **Available:**
674
-
675
- - outside React
676
- - inside methods
677
-
678
- **Notes:**
679
-
680
- Each call returns the current state at the moment it is executed.
681
-
682
- Previously captured snapshots are not updated after future mutations.
683
-
684
- ```ts
685
- methods: {
686
- logValue() {
687
- this.changeValue("John");
688
-
689
- const snapshot = this.getState();
690
-
691
- this.changeValue("Alice");
692
-
693
- console.log(snapshot.value); // "John"
694
- console.log(this.getState().value); // "Alice"
695
- }
696
- }
697
- ```
698
-
699
- ---
700
-
701
- ## State Subscriptions
702
-
703
- `useSelect()` provides reactive state subscriptions for React components.
704
-
705
- Internally, it is powered by Redux Toolkit's `useSelector`, while exposing a fully typed, slice-scoped API.
706
-
707
- **Available:**
708
-
709
- - inside React components
710
- - inside slice methods that serve as custom React hooks
711
-
712
- **Notes:**
713
-
714
- Components automatically re-render when the selected value changes.
715
-
716
- ```tsx
717
- const value = counter.useSelect((state) => state.value);
718
- ```
719
-
720
- Selectors can also access root state and computed values.
721
-
722
- ```tsx
723
- const canEdit = users.useSelect((state) => {
724
- return (
725
- state.root.auth.isAuthenticated &&
726
- state.permissions.list.includes("edit_user")
727
- );
728
- });
729
- ```
730
-
731
- ---
732
-
733
- ## Draft State
734
-
735
- Draft state provides temporary access to slice state during state evaluation.
736
-
737
- It is context-dependent and only available within specific APIs:
738
-
739
- - Inside Mutations
740
-
741
- Mutations receive a mutable draft state that can be updated directly.
742
-
743
- ```ts
744
- mutations: {
745
- setName(state, name: string) {
746
- state.name = name;
747
- }
748
- }
749
- ```
750
-
751
- ~~- Inside Computed State~~
752
-
753
- ~~Computed functions receive a read-only draft state extended with additional runtime helpers.~~
754
-
755
- ```ts
756
- // computed: {
757
- // fullName(state) {
758
- // return `${state.firstName} ${state.lastName}`;
759
- // }
760
- // }
761
- ```
762
-
763
- - Inside useSelect
764
-
765
- Selectors receive a read-only draft state extended with additional runtime helpers.
766
-
767
- ```tsx
768
- const displayName = users.useSelect((state) => {
769
- return state.computed.fullName;
770
- });
771
- ```
772
-
773
- **Available additions include:**
774
-
775
- - `state.root` — root state access
776
-
777
- ~~- `state.computed` — computed state access~~
778
-
779
- ---
780
-
781
- # Store Integration
782
-
783
- ## Creating the Store
784
-
785
- Create the root store using `defineStore`.
786
-
787
- ```ts
788
- import { defineStore } from "orchestore";
789
- import { counter } from "./counterSlice";
790
- import { users } from "./usersSlice";
791
-
792
- export const store = defineStore({
793
- slices: {
794
- counter,
795
- users,
796
- },
797
- });
798
- ```
799
-
800
- ## Store Provider
801
-
802
- Wrapping the application component inside this provider is required.
803
-
804
- ```tsx
805
- import { StoreProvider } from "orchestore";
806
-
807
- export default function App() {
808
- return (
809
- <StoreProvider>
810
- <Routes />
811
- </StoreProvider>
812
- );
813
- }
814
- ```
815
-
816
- ## Accessing Slices through Store
817
-
818
- The root store behaves similarly to a parent slice and exposes all registered slices.
819
-
820
- ```ts
821
- store.counter.increment(1);
822
-
823
- console.log(store.counter.getState());
824
- ```
825
-
826
- ## Accessing Store from Slices
827
-
828
- Every slice has access to the root store instance through `this.root`.
829
-
830
- ```ts
831
- this.root.auth.getState().isAuthenticated;
832
- ```
833
-
834
- **Useful for:**
835
-
836
- - cross-slice coordination
837
- - avoiding circular imports
838
- - application-wide orchestration
839
-
840
- ## Root Store Type Extension
841
-
842
- Overriding `OrcheStore.Define.root` provides full root store typing throughout the application.
843
-
844
- > Under active development: this currently causes a circular type inference limitation.
845
-
846
- ```ts
847
- import { defineStore } from "orchestore";
848
-
849
- export const store = defineStore({
850
- slices: {
851
- counter,
852
- },
853
- });
854
-
855
- declare global {
856
- namespace OrcheStore {
857
- interface Define {
858
- root: typeof store;
859
- }
860
- }
861
- }
862
- ```
863
-
864
- ```ts
865
- this.root; // Before: any
866
- this.root; // After: fully typed store
867
- ```
868
-
869
- **Rules:**
870
-
871
- - `root` must be a store instance created using `defineStore`
872
- - `null` and `undefined` are excluded automatically
873
- - `typeof store | null | undefined` is equivalent to `typeof store`
874
- - Invalid types fall back to `any`
875
-
876
- ---
877
-
878
- # Global Utilities
879
-
880
- Global utilities allow slices and the root store to access shared runtime services through `global`.
881
-
882
- Common use cases include:
883
-
884
- - notifications and toasts
885
- - navigation and routing
886
- - analytics and tracking
887
- - API clients and service wrappers
888
- - runtime values that are difficult to access directly from slices
889
- - integrations with React hooks and third-party libraries
890
-
891
- Utilities are registered using `provideGlobalUtils` and are accessible from any slice or the root store.
892
-
893
- ## Accessing Global Utilities
894
-
895
- **Available:**
896
-
897
- - Through the exposed store or slice instances
898
-
899
- ```ts
900
- store.global;
901
- slice.global;
902
- ```
903
-
904
- - Inside slice methods
905
-
906
- ```ts
907
- this.global.notify("success", "Saved!");
908
- ```
909
-
910
- ## Utilities Type Extension
911
-
912
- Overriding `OrcheStore.Define.global` provides full typing everywhere.
913
-
914
- ```ts
915
- import type { NavigateFunction } from "react-router";
916
-
917
- declare global {
918
- namespace OrcheStore {
919
- interface Define {
920
- global: {
921
- navigate: NavigateFunction;
922
-
923
- notify(type: "info" | "error" | "success", message: string): void;
924
- };
925
- }
926
- }
927
- }
928
- ```
929
-
930
- ```ts
931
- this.global; // Before: any
932
- this.global; // After: fully typed
933
- ```
934
-
935
- **Rules:**
936
-
937
- - `global` must be an object
938
- - `null` and `undefined` are excluded automatically
939
- - `object | null | undefined` is equivalent to `object`
940
- - Invalid types fall back to `any`
941
-
942
- ## Providing Runtime Utilities
943
-
944
- Global utility values can be registered or updated at runtime.
945
-
946
- ```ts
947
- import { useEffect } from "react";
948
- import { useNavigate } from "react-router";
949
- import { provideGlobalUtils } from "orchestore";
950
- import { feedbacks } from "./ui-feedbacks";
951
-
952
- provideGlobalUtils({
953
- notify(type, message) {
954
- feedbacks.notify(type, message);
955
- },
956
- });
957
-
958
- export default function App() {
959
- const navigate = useNavigate();
960
-
961
- useEffect(() => {
962
- provideGlobalUtils({ navigate });
963
- }, [navigate]);
964
-
965
- return (
966
- <StoreProvider>
967
- <Routes />
968
- </StoreProvider>
969
- );
970
- }
971
- ```
972
-
973
- ## Using Global Utilities in Slices
974
-
975
- Global utilities can be used anywhere a slice instance is available.
976
-
977
- ```ts
978
- methods: {
979
- async insertUser(data: UserInput) {
980
- try {
981
- this.setLoading(true);
982
-
983
- const response = await api.users.add(data);
984
-
985
- this.global.notify("success", "User added successfully!");
986
-
987
- this.setLoading(false);
988
-
989
- this.global.navigate("/users/" + response.id);
990
- } catch (error) {
991
- this.global.notify("error", "Failed to add user");
992
-
993
- console.error(error);
994
- }
995
- }
996
- }
997
- ```
998
-
999
- ---
1000
-
1001
- # TypeScript Inference
1002
-
1003
- OrcheStore is designed around deep TypeScript inference.
1004
-
1005
- ```ts
1006
- const counter = createSlice({
1007
- name: "counter",
1008
-
1009
- state: {
1010
- value: 0,
1011
- },
1012
-
1013
- mutations: {
1014
- increment(state, amount: number) {
1015
- state.value += amount;
1016
- },
1017
- },
1018
-
1019
- methods: {
1020
- async incrementAfter(amount: number, delay = 1000) {
1021
- await new Promise((resolve) => setTimeout(resolve, delay));
1022
- this.increment(amount);
1023
- },
1024
- },
1025
-
1026
- children: {
1027
- subCounter: createSlice({
1028
- name: "subCounter",
1029
-
1030
- state: {
1031
- value: 0,
1032
- },
1033
- }),
1034
- },
1035
- });
1036
- ```
1037
-
1038
- Automatically produces:
1039
-
1040
- ```ts
1041
- counter.getState();
1042
- // { value: number, subCounter: { value: number } }
1043
-
1044
- counter.subCounter.getState();
1045
- // { value: number }
1046
-
1047
- counter.increment(amount: number): void;
1048
-
1049
- counter.incrementAfter(amount: number, delay?: number): Promise<number>;
1050
- ```
1051
-
1052
- No manual type declarations required.
1053
-
1054
- ## Framework Type Extensions
1055
-
1056
- OrcheStore also exposes user-definable type slots through `OrcheStore.Define`.
1057
-
1058
- These slots allow application-specific types to be injected into the framework and become available everywhere with full type safety.
1059
-
1060
- | Slot | Purpose |
1061
- | -------------------------- | ----------------------- |
1062
- | `OrcheStore.Define.root` | Root store typing |
1063
- | `OrcheStore.Define.global` | Global utilities typing |
1064
-
1065
- ```ts
1066
- declare global {
1067
- namespace OrcheStore {
1068
- interface Define {
1069
- root: typeof store;
1070
-
1071
- global: {
1072
- navigate: NavigateFunction;
1073
- notify(type: "info" | "error" | "success", message: string): void;
1074
- };
1075
- }
1076
- }
1077
- }
1078
- ```
1079
-
1080
- This provides full typing for APIs such as:
1081
-
1082
- ```ts
1083
- state.root;
1084
-
1085
- this.root;
1086
- this.global;
1087
-
1088
- store.global;
1089
- counter.global;
1090
- ```
1091
-
1092
- ---
1093
-
1094
- # Status
1095
-
1096
- OrcheStore is currently experimental and under active development.
1097
-
1098
- Planned features:
1099
-
1100
- - middleware and plugin system
1101
- - persistence utilities
1102
- - SSR support
1103
- - deep readonly enforcement
1104
- - lifecycle hooks
1105
- - enhanced DevTools integration
1106
-
1107
- ---