orchestore 0.1.5 → 0.1.7

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
@@ -4,9 +4,22 @@
4
4
 
5
5
  OrcheStore is currently under active development and is not yet ready for production use.
6
6
 
7
- > 📅 **Planned First Stable Release:** **2026-06-30**
7
+ ### 📅 Future Release Plans
8
8
 
9
- > ⚠️ APIs, behavior, and internal implementation details may change without notice until the first stable release.
9
+ **Planned First Stable Release (v1.0.0):** **2026-06-30**
10
+
11
+ Development is ongoing, and progress is published regularly. New commits are pushed periodically to the public GitHub repository, and pre-release versions (v0.x.x) may be published to npm before the first stable release.
12
+
13
+ ### ⚠️ Pre-release Notice
14
+
15
+ OrcheStore is currently in a pre-release phase.
16
+
17
+ * Not recommended for production use
18
+ * APIs may change before the first stable release
19
+ * Internal architecture and runtime behavior are still evolving
20
+ * Documentation and feature coverage are actively being expanded
21
+
22
+ Stay tuned for updates.
10
23
 
11
24
  ---
12
25
 
@@ -22,6 +35,56 @@ The goal is simple:
22
35
 
23
36
  > ⚡ Spend less time wiring state management infrastructure and more time building application features.
24
37
 
38
+ ## Table of Contents
39
+
40
+ - [Introduction](#orchestore)
41
+ - [Core Principles](#core-principles)
42
+ - [Why OrcheStore?](#why-orchestore)
43
+ - [Redux Toolkit Comparison](#redux-toolkit-comparison)
44
+ - [Architecture Overview](#architecture-overview)
45
+
46
+ - [Quick Example](#quick-example)
47
+
48
+ - [Slice Layers](#slice-layers)
49
+ - [name](#name)
50
+ - [state](#state)
51
+ - [Mutations](#mutations)
52
+ - [Methods](#methods)
53
+ - [Computed State (Planned)](#computed-state-planned)
54
+ - [Nested Slices](#nested-slices)
55
+ - [Reusing Slices](#reusing-slices)
56
+ - [Runtime Paths](#runtime-paths)
57
+
58
+ - [State Access & Subscriptions](#state-access--subscriptions)
59
+ - [State Snapshots](#state-snapshots)
60
+ - [State Subscriptions](#state-subscriptions)
61
+ - [Draft State](#draft-state)
62
+
63
+ - [Store Integration](#store-integration)
64
+ - [Creating the Store](#creating-the-store)
65
+ - [Store Provider](#store-provider)
66
+ - [Accessing Slices through Store](#accessing-slices-through-store)
67
+ - [Accessing Store from Slices](#accessing-store-from-slices)
68
+ - [Root Store Type Extension (Planned)](#root-store-type-extension-planned)
69
+
70
+ - [Lineage & Clones](#lineage--clones)
71
+ - [Automatic Cloning](#automatic-cloning)
72
+ - [Manual Cloning](#manual-cloning)
73
+ - [Inspecting a Lineage](#inspecting-a-lineage)
74
+ - [Definition Type Checking](#definition-type-checking)
75
+
76
+ - [Global Utilities](#global-utilities)
77
+ - [Accessing Global Utilities](#accessing-global-utilities)
78
+ - [Utilities Type Extension](#utilities-type-extension)
79
+ - [Providing Runtime Utilities](#providing-runtime-utilities)
80
+ - [Using Global Utilities in Slices](#using-global-utilities-in-slices)
81
+
82
+ - [TypeScript Inference](#typescript-inference)
83
+
84
+ - [Status](#status)
85
+
86
+ ---
87
+
25
88
  ## Core Principles
26
89
 
27
90
  - Simplify state management architecture
@@ -33,26 +96,1254 @@ The goal is simple:
33
96
  - Maintain strong TypeScript inference
34
97
  - Scale naturally through composition
35
98
 
99
+ ## Why OrcheStore?
100
+
101
+ Redux Toolkit significantly improves the Redux developer experience, but many applications still require developers to coordinate logic across multiple concepts:
102
+
103
+ - reducers
104
+ - action creators
105
+ - thunks
106
+ - selectors
107
+ - middleware
108
+ - hooks
109
+ - utility functions
110
+
111
+ As applications grow, state management often becomes less about solving business problems and more about connecting infrastructure.
112
+
113
+ OrcheStore reduces that coordination overhead by exposing state management through unified slice modules.
114
+
115
+ A slice is more than a state container. It is a runtime module that can encapsulate:
116
+
117
+ - state
118
+ - computed state
119
+ - mutations
120
+ - methods
121
+ - selectors
122
+ - child slices
123
+ - shared utilities
124
+
125
+ This allows state and application logic to evolve together within the same domain boundary.
126
+
127
+ Many common Redux patterns are automated by default:
128
+
129
+ | Traditional Redux Pattern | OrcheStore |
130
+ | ----------------------------- | ----------------------------------------------- |
131
+ | Action creators | Direct callable mutations |
132
+ | Thunks | Built-in methods |
133
+ | Dispatch calls | Direct function calls |
134
+ | `PayloadAction` wrappers | Native function arguments |
135
+ | Cross-slice imports | Root store access |
136
+ | Shared service wiring | Global utilities |
137
+ | Manual state tree composition | Nested slices with automatic cloning & isolation |
138
+ | Complex type declarations | Automatic inference |
139
+ | Instance identity management | Lineage-based slice model (shared definition, isolated mounts) |
140
+
141
+ The result is a simpler architecture with fewer moving parts, less boilerplate, and a more direct development experience.
142
+
143
+ Developers can focus on application behavior rather than framework plumbing.
144
+
145
+ ## Redux Toolkit Comparison
146
+
147
+ OrcheStore builds on top of Redux Toolkit while providing a higher-level API for organizing state and behavior.
148
+
149
+ | Feature | OrcheStore | Redux Toolkit |
150
+ | ------------------------------ | ---------- | ------------- |
151
+ | Multiple mutation arguments | ✅ | ❌ |
152
+ | Direct callable mutations | ✅ | ❌ |
153
+ | `PayloadAction` wrappers | ❌ | ✅ |
154
+ | Dispatch required | ❌ | ✅ |
155
+ | Built-in orchestration methods | ✅ | ❌ |
156
+ | Nested slice composition | ✅ (isolated context) | ⚠️ Manual (shared state) |
157
+ | Automatic path generation | ✅ | ⚠️ Manual |
158
+ | Global utilities | ✅ | ❌ |
159
+ | Unified slice API | ✅ | ❌ |
160
+ | Per-slice React hooks | ✅ | ❌ |
161
+ | Deep TypeScript inference | ✅ | ⚠️ Partial |
162
+ | Lineage & cloning model | ✅ | ❌ |
163
+
164
+ OrcheStore does not replace Redux Toolkit. Instead, it builds on top of it by automating common patterns and providing a more cohesive developer experience.
165
+
166
+ ## Architecture Overview
167
+
168
+ | Layer | Responsibility |
169
+ | ----------- | ---------------------------------------- |
170
+ | `name` | Unique slice identifier |
171
+ | `path` | Hierarchical slice path |
172
+ | `state` | Slice data storage definition |
173
+ | `mutations` | Synchronous state transitions |
174
+ | `methods` | Orchestration and side effects |
175
+ | `computed` | Derived and computed state |
176
+ | `children` | Nested slice composition |
177
+ | `getState` | Imperative state access |
178
+ | `useSelect` | Reactive state subscriptions |
179
+ | `prototype` | Lineage, cloning, and instance utilities |
180
+
36
181
  ---
37
182
 
38
- ## Release Status
183
+ # Quick Example
39
184
 
40
- ### Pre-release Notice
185
+ **Comparing OrcheStore with Redux Toolkit**
41
186
 
42
- OrcheStore is currently in a pre-release phase.
187
+ ## Slice Creation
43
188
 
44
- - Not recommended for production use
45
- - APIs may change before the first stable release
46
- - Internal architecture and runtime behavior are still evolving
47
- - Documentation and feature coverage are actively being expanded
189
+ ✔️ OrcheStore centrelize unified options API.
48
190
 
49
- ### Roadmap
191
+ ```tsx
192
+ import { createSlice } from "orchestore";
50
193
 
51
- | Milestone | Status |
52
- | -------------------- | -------------- |
53
- | Core Architecture | 🚧 In Progress |
54
- | Documentation | 🚧 In Progress |
55
- | Public Preview | 🚧 Planned |
56
- | First Stable Release | 📅 2026-06-30 |
194
+ export const counter = createSlice({
195
+ name: "counter",
57
196
 
58
- Stay tuned for updates.
197
+ state: {
198
+ value: 0,
199
+ },
200
+
201
+ mutations: {
202
+ // Direct arguments without PayloadAction wrappers
203
+ increment(state, amount: number) {
204
+ state.value += amount;
205
+ },
206
+
207
+ // Multiple typed arguments without payload objects
208
+ incrementLimited(state, amount: number, max = Infinity) {
209
+ state.value = Math.min(state.value + amount, max);
210
+ },
211
+ },
212
+
213
+ methods: {
214
+ // Reusable async method inside slice
215
+ sleep(delay: number) {
216
+ return new Promise((resolve) => setTimeout(resolve, delay));
217
+ },
218
+
219
+ // Orchestration layer with full slice access via `this`
220
+ async incrementAfter(amount: number, delay: number) {
221
+ await this.sleep(delay);
222
+ this.increment(amount);
223
+ },
224
+ },
225
+ });
226
+ ```
227
+
228
+ ⛔ Redux Toolkit splits logic between reducers, extra reducers, actions, and async workflows.
229
+
230
+ ```tsx
231
+ import { createSlice, createAsyncThunk, type PayloadAction } from "@reduxjs/toolkit";
232
+
233
+ export const counter = createSlice({
234
+ name: "counter",
235
+
236
+ initialState: {
237
+ value: 0,
238
+ },
239
+
240
+ reducers: {
241
+ // PayloadAction wrapper required
242
+ increment(state, action: PayloadAction<number>) {
243
+ state.value += action.payload;
244
+ },
245
+
246
+ // Multiple arguments must be wrapped into a payload object
247
+ incrementLimited(state, action: PayloadAction<{ amount: number; max?: number }>) {
248
+ state.value = Math.min(
249
+ state.value + action.payload.amount,
250
+ action.payload.max ?? Infinity
251
+ );
252
+ },
253
+ },
254
+ });
255
+
256
+ // Separate APIs for async workflows
257
+ export const incrementAfter = createAsyncThunk(
258
+ "counter/incrementAfter",
259
+ async (
260
+ { amount, delay }: { amount: number; delay: number },
261
+ { dispatch }
262
+ ) => {
263
+ await new Promise((resolve) => setTimeout(resolve, delay));
264
+
265
+ dispatch(counter.actions.increment(amount));
266
+ }
267
+ );
268
+ ```
269
+
270
+ ## Slice Usage
271
+
272
+ ✔️ OrcheStore exposes direct callable mutations and methods.
273
+
274
+ ```ts
275
+ counter.increment(4);
276
+ counter.incrementLimited(1, 50);
277
+ counter.incrementAfter(5, 1000);
278
+ ```
279
+
280
+ ⛔ Redux Toolkit follows dispatch-based execution
281
+
282
+ ```ts
283
+ import { useDispatch } from "react-redux";
284
+
285
+ const dispatch = useDispatch();
286
+
287
+ dispatch(counter.actions.increment(4));
288
+ dispatch(counter.actions.incrementLimited({ amount: 1, max: 50 }));
289
+ dispatch(incrementAfter({ amount: 5, delay: 1000 }));
290
+ ```
291
+
292
+ ## Store Integration & Context Providing
293
+
294
+ ✔️ OrcheStore exposes fully typed slice APIs directly.
295
+
296
+ ```ts
297
+ import { createStore } from "orchestore";
298
+ import { counter } from "./counterSlice";
299
+
300
+ export const store = createStore({
301
+ slices: {
302
+ counter,
303
+ },
304
+ });
305
+ ```
306
+
307
+ ```tsx
308
+ import { StoreProvider } from "orchestore";
309
+ import { store } from "./store";
310
+
311
+ export default function App() {
312
+ return (
313
+ <StoreProvider store={store}>
314
+ <CounterComponent />
315
+ </StoreProvider>
316
+ );
317
+ }
318
+ ```
319
+
320
+ ⛔ Redux Toolkit requires manual store configuration and type wiring.
321
+
322
+ ```ts
323
+ import { configureStore } from "@reduxjs/toolkit";
324
+ import { counter } from "./counterSlice";
325
+ import { useDispatch, useSelector } from "react-redux";
326
+
327
+ export const store = configureStore({
328
+ reducer: {
329
+ counter: counter.reducer,
330
+ },
331
+ });
332
+
333
+ export type RootState = ReturnType<typeof store.getState>;
334
+ export type AppDispatch = typeof store.dispatch;
335
+
336
+ export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
337
+ export const useAppSelector = useSelector.withTypes<RootState>();
338
+ ```
339
+
340
+ ```tsx
341
+ import { Provider } from "react-redux";
342
+ import { store } from "./store/index";
343
+
344
+ export default function App() {
345
+ return (
346
+ <Provider store={store}>
347
+ <CounterComponent />
348
+ </Provider>
349
+ );
350
+ }
351
+ ```
352
+
353
+ ## React Usage
354
+
355
+ OrcheStore exposes direct usable child slices through a unified store instance.
356
+
357
+ ```tsx
358
+ import { store } from "./store/index";
359
+ import { counter } from "./store/counterSlice";
360
+
361
+ export function CounterComponent() {
362
+ const value = counter.useSelect((state) => state.value);
363
+ const alias = store.counter.useSelect((state) => state.value);
364
+
365
+ return (
366
+ <>
367
+ <div>Counter: {value}</div>
368
+
369
+ <button onClick={() => counter.increment(1)}>Increment</button>
370
+
371
+ <button onClick={() => store.counter.incrementAfter(1, 1000)}>Increment Later</button>
372
+ </>
373
+ );
374
+ }
375
+ ```
376
+
377
+ > 📌 If the slice is mounted only once, `store.counter` and `counter` refer to the same runtime instance and can be used interchangeably.
378
+ >
379
+ > 🔄 When a slice is mounted multiple times, each mount receives its own isolated instance. See [Reusing Slices](#reusing-slices) and [Lineage & Clones](#lineage--clones) for details.
380
+
381
+ ---
382
+
383
+ # Slice Layers
384
+
385
+ Slices are created using `createSlice`.
386
+
387
+ ```ts
388
+ import { createSlice } from "orchestore";
389
+
390
+ const counter = createSlice({
391
+ name: "counter",
392
+
393
+ state: {},
394
+
395
+ computed: {}, // Planned
396
+
397
+ mutations: {},
398
+
399
+ methods: {},
400
+
401
+ children: {},
402
+
403
+ subscribe: {}, // Planned
404
+ });
405
+ ```
406
+
407
+ ## name
408
+
409
+ Every slice requires a unique and stable name.
410
+
411
+ The name primarily exists to ensure slice uniqueness and path generation.
412
+
413
+ ```ts
414
+ const counter = createSlice({
415
+ name: "counter",
416
+ });
417
+ ```
418
+
419
+ The name is exposed at runtime:
420
+
421
+ ```ts
422
+ console.log(counter.name); // "counter"
423
+ ```
424
+
425
+ It is also accessible inside methods:
426
+
427
+ ```ts
428
+ methods: {
429
+ logName() {
430
+ console.log(this.name);
431
+ }
432
+ }
433
+ ```
434
+
435
+ **Rules:**
436
+
437
+ - Slice names should not contain `"."` or `"/"`, because dots are reserved for nested slice paths
438
+ - Two slices cannot share the same name
439
+ - Registering the same slice instance multiple times with the same name is not allowed
440
+
441
+ ---
442
+
443
+ ## state
444
+
445
+ The `state` property defines the initial slice state.
446
+
447
+ ```ts
448
+ const counter = createSlice({
449
+ name: "counter",
450
+
451
+ state: {
452
+ value: 0,
453
+ },
454
+ });
455
+ ```
456
+
457
+ State can also be initialized lazily by providing a function.
458
+
459
+ The initializer runs only once before the first state access.
460
+
461
+ ```ts
462
+ const counter = createSlice({
463
+ name: "counter",
464
+
465
+ state: () => ({
466
+ value: 0,
467
+ }),
468
+ });
469
+ ```
470
+
471
+ **Useful for:**
472
+
473
+ - expensive initialization
474
+ - persisted state restoration
475
+ - runtime-dependent values
476
+
477
+ ---
478
+
479
+ ## Mutations
480
+
481
+ Mutations are synchronous state transition functions.
482
+
483
+ **Characteristics:**
484
+
485
+ - receive mutable draft state as the first parameter
486
+ - support multiple user-defined arguments
487
+ - directly callable from the exposed slices or store
488
+
489
+ **Responsibilities:**
490
+
491
+ Mutations should contain:
492
+
493
+ - synchronous state updates
494
+ - deterministic state transitions
495
+ - normalization logic
496
+
497
+ For anything else, use methods instead.
498
+
499
+ **Example:**
500
+
501
+ ```ts
502
+ const counter = createSlice({
503
+ name: "counter",
504
+
505
+ state: {
506
+ value: 0,
507
+ },
508
+
509
+ mutations: {
510
+ increment(state, amount = 1, max = Infinity) {
511
+ state.value = Math.min(state.value + amount, max);
512
+ },
513
+ },
514
+ });
515
+
516
+ counter.increment(1, 50);
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Methods
522
+
523
+ Methods are the orchestration layer of a slice.
524
+
525
+ **Characteristics:**
526
+
527
+ - receive any number of arguments
528
+ - can return synchronous values or Promises
529
+ - can access:
530
+ - state, mutations, slibling methods, nested slices (through `this`)
531
+ - Application-wide utilities (`this.global`)
532
+
533
+ Methods are not serialized, replayable, or represented in Redux DevTools action history.
534
+
535
+ **Responsibilities:**
536
+
537
+ Methods are designed for centralizing any slice-related logic:
538
+
539
+ - asynchronous workflows
540
+ - business logic orchestration
541
+ - API communication and network calls
542
+ - timers, delayed or scheduled executions, such as `setTimeout` or event listeners
543
+ - side effects such as `localStorage`, `sessionStorage` and DOM manipulation
544
+ - Slice-related React hooks such as `slice.useSelect`, tanstack `useQuery` and `useMutation`
545
+
546
+ **Restrictions:**
547
+
548
+ Methods should NOT:
549
+
550
+ - include slice-unrelated logic.
551
+ - mutate state directly. use mutations instead.
552
+
553
+ Prefer:
554
+
555
+ ```ts
556
+ this.increment(1);
557
+ ```
558
+
559
+ Instead of:
560
+
561
+ ```ts
562
+ this.getState().value++;
563
+ ```
564
+
565
+ **Example:**
566
+
567
+ ```ts
568
+ const counter = createSlice({
569
+ name: "counter",
570
+
571
+ state: {
572
+ value: 0,
573
+ },
574
+
575
+ mutations: {
576
+ increment(state, amount: number) {
577
+ state.value += amount;
578
+ },
579
+ },
580
+
581
+ methods: {
582
+ async incrementAfter(amount: number, delay = 1000) {
583
+ await new Promise((resolve) => setTimeout(resolve, delay));
584
+ this.increment(amount);
585
+ this.global.logger.info("Counter incremented");
586
+ },
587
+ },
588
+ });
589
+ ```
590
+
591
+ ---
592
+
593
+ ## Computed State (Planned)
594
+
595
+ This currently not supported
596
+
597
+ ~~The `computed` property provides reusable derived state.~~
598
+
599
+ ```ts
600
+ // const counter = createSlice({
601
+ // name: "counter",
602
+
603
+ // state: {
604
+ // value: 10,
605
+ // },
606
+
607
+ // computed: {
608
+ // doubled(state) {
609
+ // return state.value * 2;
610
+ // },
611
+
612
+ // multiplied(state, amount: number) {
613
+ // return state.value * amount;
614
+ // },
615
+ // },
616
+ // });
617
+ ```
618
+
619
+ ---
620
+
621
+ ## Nested Slices
622
+
623
+ Slices can be composed by registering other slice instances through the `children` property.
624
+
625
+ This allows related state and behavior to be organized into a hierarchical structure while preserving full type inference and ownership isolation.
626
+
627
+ ```ts
628
+ import { products } from "./productsSlice";
629
+ import { categories } from "./categoriesSlice";
630
+
631
+ export const shop = createSlice({
632
+ name: "shop",
633
+
634
+ state: {},
635
+
636
+ children: {
637
+ products,
638
+ categories,
639
+ },
640
+ });
641
+ ```
642
+
643
+ **Accessing Child Slices:**
644
+
645
+ Child slices are exposed directly on their parent slice.
646
+
647
+ ```ts
648
+ shop.products.add(...)
649
+ shop.categories.create(...)
650
+
651
+ console.log(shop.products.getState());
652
+ ```
653
+
654
+ Deeply nested slice hierarchies are fully supported.
655
+
656
+ ```ts
657
+ admin.users.permissions.grant(...);
658
+
659
+ console.log(admin.users.permissions.getState());
660
+ ```
661
+
662
+ ### Reusing Slices
663
+
664
+ A slice can be mounted multiple times within the same tree.
665
+
666
+ When the same slice definition is reused, OrcheStore automatically creates a separate mounted instance for each location.
667
+
668
+ ```ts
669
+ const paginationSlice = createSlice({ ... });
670
+
671
+ const shopSlice = createSlice({
672
+ name: "shop",
673
+
674
+ state: {},
675
+
676
+ children: {
677
+ categories: paginationSlice,
678
+ products: paginationSlice,
679
+ },
680
+ });
681
+
682
+ const adminSlice = createSlice({
683
+ name: "admin",
684
+
685
+ state: {},
686
+
687
+ children: {
688
+ products: paginationSlice,
689
+ },
690
+ });
691
+ ```
692
+
693
+ Each mounted instance has:
694
+
695
+ - its own path
696
+ - its own ownership context
697
+ - its own runtime state
698
+
699
+ **Runtime identity:**
700
+
701
+ Although all mounted slices originate from `paginationSlice`, they are not necessarily the same runtime instance.
702
+
703
+ ```ts
704
+ paginationSlice === shopSlice.categories; // First mount uses the original instance
705
+
706
+ paginationSlice !== shopSlice.products; // Different mount location creates a clone
707
+ paginationSlice !== adminSlice.products; // Different mount location creates a clone
708
+
709
+ shopSlice.products !== adminSlice.products; // Independent mounted clones
710
+ ```
711
+
712
+ Every mount location receives its own isolated instance.
713
+
714
+ For a deeper explanation of how slice reuse works, see [Lineage & Clones](#lineage--clones).
715
+
716
+ ### Runtime Paths
717
+
718
+ Every slice exposes a runtime path through `slice.path`.
719
+
720
+ ```ts
721
+ store.counter.name; // "counter"
722
+ store.counter.path; // "counter"
723
+ ```
724
+
725
+ Nested slices automatically inherit their parent path.
726
+
727
+ ```ts
728
+ store.admin.users.name; // "users"
729
+ store.admin.users.path; // "admin.users"
730
+
731
+ store.admin.users.permissions.name; // "permissions"
732
+ store.admin.users.permissions.path; // "admin.users.permissions"
733
+ ```
734
+
735
+ **Notes:**
736
+
737
+ Paths are generated automatically from the slice hierarchy.
738
+
739
+ OrcheStore builds on the same concepts as Redux Toolkit's `reducerPath` and `combineReducers`, but automates path generation, reducer registration, and nested slice composition.
740
+
741
+ No manual path configuration or reducer injection is required.
742
+
743
+ ---
744
+
745
+ # State Access & Subscriptions
746
+
747
+ OrcheStore provides multiple ways to access state depending on the context.
748
+
749
+ | API | Purpose |
750
+ | ------------------- | -------------------------------------------------------- |
751
+ | `slice.getState()` | Read the current state snapshot |
752
+ | `slice.useSelect()` | Subscribe to state changes inside React |
753
+ | Draft state | Temporary state access available during state evaluation |
754
+
755
+ ## State Snapshots
756
+
757
+ `getState()` returns the latest immutable state snapshot.
758
+
759
+ Use it whenever you need to read state imperatively outside of React subscriptions.
760
+
761
+ ```ts
762
+ counter.getState().value;
763
+ ```
764
+
765
+ **Available:**
766
+
767
+ - outside React
768
+ - inside methods
769
+
770
+ **Notes:**
771
+
772
+ Each call returns the current state at the moment it is executed.
773
+
774
+ Previously captured snapshots are not updated after future mutations.
775
+
776
+ ```ts
777
+ methods: {
778
+ logValue() {
779
+ this.changeValue("John");
780
+
781
+ const snapshot = this.getState();
782
+
783
+ this.changeValue("Alice");
784
+
785
+ console.log(snapshot.value); // "John"
786
+ console.log(this.getState().value); // "Alice"
787
+ }
788
+ }
789
+ ```
790
+
791
+ ---
792
+
793
+ ## State Subscriptions
794
+
795
+ `useSelect()` provides reactive state subscriptions for React components.
796
+
797
+ Internally, it is powered by Redux Toolkit's `useSelector`, while exposing a fully typed, slice-scoped API.
798
+
799
+ **Available:**
800
+
801
+ - inside React components
802
+ - inside slice methods that serve as custom React hooks
803
+
804
+ **Notes:**
805
+
806
+ Components automatically re-render when the selected value changes.
807
+
808
+ ```tsx
809
+ const value = counter.useSelect((state) => state.value);
810
+ ```
811
+
812
+ Selectors can also be accessed through the store to access multiple slices.
813
+
814
+ ```tsx
815
+ const canEdit = store.useSelect((state) => {
816
+ return (
817
+ state.auth.isAuthenticated &&
818
+ state.users.permissions.list.includes("edit_user")
819
+ );
820
+ });
821
+ ```
822
+
823
+ ---
824
+
825
+ ## Draft State
826
+
827
+ Draft state provides temporary access to slice state during state evaluation.
828
+
829
+ It is context-dependent and only available within specific APIs:
830
+
831
+ - Inside Mutations
832
+
833
+ Mutations receive a mutable draft state that can be updated directly.
834
+
835
+ ```ts
836
+ mutations: {
837
+ setName(state, name: string) {
838
+ state.name = name;
839
+ }
840
+ }
841
+ ```
842
+
843
+ ~~- Inside Computed State~~
844
+
845
+ ~~Computed functions receive a read-only draft state extended with additional runtime helpers.~~
846
+
847
+ ```ts
848
+ // computed: {
849
+ // fullName(state) {
850
+ // return `${state.firstName} ${state.lastName}`;
851
+ // }
852
+ // }
853
+ ```
854
+
855
+ - Inside useSelect
856
+
857
+ Selectors receive a read-only draft state extended ~~with additional runtime helpers~~.
858
+
859
+ ```tsx
860
+ const displayName = users.useSelect((state) => {
861
+ return state.user.name;
862
+ });
863
+ ```
864
+
865
+ **~~Available additions include:~~**
866
+
867
+ ~~- `state.computed` — computed state access~~
868
+
869
+ ---
870
+
871
+ # Store Integration
872
+
873
+ ## Creating the Store
874
+
875
+ Create the root store using `createStore`.
876
+
877
+ ```ts
878
+ import { createStore } from "orchestore";
879
+ import { counter } from "./counterSlice";
880
+ import { users } from "./usersSlice";
881
+
882
+ export const store = createStore({
883
+ slices: {
884
+ counter,
885
+ users,
886
+ },
887
+ });
888
+ ```
889
+
890
+ ## Store Provider
891
+
892
+ Wrapping the application component inside this provider is required.
893
+
894
+ ```tsx
895
+ import { StoreProvider } from "orchestore";
896
+ import { store } from "./store";
897
+
898
+ export default function App() {
899
+ return (
900
+ <StoreProvider store={store}>
901
+ <Routes />
902
+ </StoreProvider>
903
+ );
904
+ }
905
+ ```
906
+
907
+ ## Accessing Slices through Store
908
+
909
+ The root store behaves similarly to a parent slice and exposes all registered slices.
910
+
911
+ ```ts
912
+ store.counter.increment(1);
913
+
914
+ console.log(store.counter.getState());
915
+ ```
916
+
917
+ ## Accessing Store from Slices
918
+
919
+ Every slice has access to the root store instance through `this.root`.
920
+
921
+ ```ts
922
+ this.root.auth.getState().isAuthenticated;
923
+ ```
924
+
925
+ **Useful for:**
926
+
927
+ - cross-slice coordination
928
+ - avoiding circular imports
929
+ - application-wide orchestration
930
+
931
+ ## Root Store Type Extension (Planned)
932
+
933
+ Overriding `OrcheStore.Slots.root` provides full root store typing throughout the application.
934
+
935
+ > 🐞 Under active development: this currently causes a circular type inference limitation.
936
+
937
+ ```ts
938
+ import { createStore } from "orchestore";
939
+
940
+ export const store = createStore({
941
+ slices: {
942
+ counter,
943
+ },
944
+ });
945
+
946
+ declare module "orchestore" {
947
+ namespace OrcheStore {
948
+ interface Slots {
949
+ root: typeof store; // Bugfix: Causes a circular type inference
950
+ }
951
+ }
952
+ }
953
+ ```
954
+
955
+ ```ts
956
+ this.root; // Before: any
957
+ this.root; // After: fully typed store
958
+ ```
959
+
960
+ **Rules:**
961
+
962
+ - `root` must be a store instance created using `createStore`
963
+ - `null` and `undefined` are excluded automatically
964
+ - `typeof store | null | undefined` is equivalent to `typeof store`
965
+ - Invalid types fall back to `any`
966
+
967
+ ---
968
+
969
+ # Lineage & Clones
970
+
971
+ OrcheStore uses a lineage-based model for slice identity.
972
+
973
+ **Why?**
974
+
975
+ Slices can be used in multiple places in the store tree.
976
+
977
+ When this happens, OrcheStore creates a separate runtime instance for each usage. These instances are called **clones**.
978
+
979
+ A clone is an independent instance copy of a slice at runtime. It has its own state and runs separately from other clones, while still remaining part of a shared lineage.
980
+
981
+ A lineage (or family) is the set of all instances that come from the same slice definition.
982
+
983
+ **This means:**
984
+
985
+ - slices are not singletons
986
+ - a slice can appear multiple times in a tree
987
+ - each clone is fully isolated
988
+ - all instances cloned from the same slice are linked through lineage
989
+
990
+ ## Automatic Cloning
991
+
992
+ When a slice is reused through `children` or `slices`, OrcheStore automatically creates a new mounted instance for each usage.
993
+
994
+ ```ts
995
+ const paginationSlice = createSlice({ ... });
996
+
997
+ const shopSlice = createSlice({
998
+ name: "shop",
999
+
1000
+ state: {},
1001
+
1002
+ children: {
1003
+ a: paginationSlice,
1004
+ b: paginationSlice,
1005
+ },
1006
+ });
1007
+
1008
+ const adminSlice = createSlice({
1009
+ name: "admin",
1010
+
1011
+ state: {},
1012
+
1013
+ children: {
1014
+ a: paginationSlice,
1015
+ },
1016
+ });
1017
+ ```
1018
+
1019
+ Each mount becomes a separate runtime node:
1020
+
1021
+ ```ts
1022
+ shopSlice.a !== shopSlice.b;
1023
+ shopSlice.a !== adminSlice.a;
1024
+ shopSlice.b !== adminSlice.a;
1025
+ ```
1026
+
1027
+ Each instance also receives its own path and its own ownership context:
1028
+
1029
+ ```ts
1030
+ shopSlice.a.path; // "shop.a"
1031
+ shopSlice.b.path; // "shop.b"
1032
+ adminSlice.a.path; // "admin.a"
1033
+ ```
1034
+
1035
+ Although these instances are independent at runtime, they still belong to the same lineage.
1036
+
1037
+ ## Manual Cloning
1038
+
1039
+ A new detached clone can be created manually from any slice instance:
1040
+
1041
+ ```ts
1042
+ const clone = slice.prototype.clone();
1043
+ ```
1044
+
1045
+ The new instance:
1046
+
1047
+ - belongs to the same lineage
1048
+ - starts detached from the tree
1049
+ - has no mounted path initially
1050
+ - has its own ownership context
1051
+
1052
+ ## Inspecting a Lineage
1053
+
1054
+ **Get All Related Instances:**
1055
+
1056
+ Returns every instance in the lineage, **including** the current one.
1057
+
1058
+ ```ts
1059
+ const lineage = slice.prototype.getLineage();
1060
+ ```
1061
+
1062
+ Useful for:
1063
+
1064
+ - debugging slice reuse
1065
+ - inspecting mounted instances
1066
+ - understanding tree distribution
1067
+
1068
+ **Get Clones:**
1069
+
1070
+ Returns all lineage members **except** the current instance.
1071
+
1072
+ ```ts
1073
+ const siblings = slice.prototype.getClones();
1074
+ ```
1075
+
1076
+ Useful for:
1077
+
1078
+ - communicating between clones
1079
+ - broadcast or synchronization scenarios
1080
+ - comparing mounted instances
1081
+
1082
+ ## Definition Type Checking
1083
+
1084
+ You can determine whether two slices belong to the same lineage:
1085
+
1086
+ You can check whether two slices belong to the same lineage:
1087
+
1088
+ ```ts
1089
+ const isSameLineage = slice.prototype.isTypeOf(otherSlice);
1090
+ ```
1091
+
1092
+ Returns `true` when both slices originate from the same slice definition, even if they are different runtime instances.
1093
+
1094
+ ```ts
1095
+ const slice1 = createSlice(...);
1096
+ const slice2 = createSlice(...);
1097
+
1098
+ const clone1 = slice1.prototype.clone();
1099
+ const clone2 = clone1.prototype.clone();
1100
+
1101
+ slice1.prototype.isTypeOf(clone1); // true
1102
+ clone1.prototype.isTypeOf(clone2); // true
1103
+ clone2.prototype.isTypeOf(slice1); // true
1104
+
1105
+ slice1.prototype.isTypeOf(slice2); // false
1106
+ slice2.prototype.isTypeOf(clone1); // false
1107
+ ```
1108
+
1109
+ ## Summary
1110
+
1111
+ - Reusing a slice automatically creates mounted clones.
1112
+ - `clone()` creates a new detached lineage member.
1113
+ - Every clone is isolated at runtime.
1114
+ - All clones from the same definition belong to a shared lineage.
1115
+ - `getLineage()` returns all instances in a lineage.
1116
+ - `getClones()` returns all related instances except the current one.
1117
+ - `isTypeOf()` checks whether two instances belong to the same lineage.
1118
+
1119
+ ---
1120
+
1121
+ # Global Utilities
1122
+
1123
+ Global utilities allow slices and the root store to access shared runtime services through `global`.
1124
+
1125
+ Common use cases include:
1126
+
1127
+ - notifications and toasts
1128
+ - navigation and routing
1129
+ - analytics and tracking
1130
+ - API clients and service wrappers
1131
+ - runtime values that are difficult to access directly from slices
1132
+ - integrations with React hooks and third-party libraries
1133
+
1134
+ Utilities are registered using `provideGlobalUtils` and are accessible from any slice or the root store.
1135
+
1136
+ ## Accessing Global Utilities
1137
+
1138
+ **Available:**
1139
+
1140
+ - Through the exposed store or slice instances
1141
+
1142
+ ```ts
1143
+ store.global;
1144
+ slice.global;
1145
+ ```
1146
+
1147
+ - Inside slice methods
1148
+
1149
+ ```ts
1150
+ this.global.notify("success", "Saved!");
1151
+ ```
1152
+
1153
+ ## Utilities Type Extension
1154
+
1155
+ Overriding `OrcheStore.Slots.global` provides full typing everywhere.
1156
+
1157
+ ```ts
1158
+ import type { NavigateFunction } from "react-router";
1159
+
1160
+ declare module "orchestore" {
1161
+ namespace OrcheStore {
1162
+ interface Slots {
1163
+ global: {
1164
+ navigate: NavigateFunction;
1165
+
1166
+ notify(type: "info" | "error" | "success", message: string): void;
1167
+ };
1168
+ }
1169
+ }
1170
+ }
1171
+ ```
1172
+
1173
+ ```ts
1174
+ this.global; // Before: any
1175
+ this.global; // After: fully typed
1176
+ ```
1177
+
1178
+ **Rules:**
1179
+
1180
+ - `global` must be an object
1181
+ - `null` and `undefined` are excluded automatically
1182
+ - `object | null | undefined` is equivalent to `object`
1183
+ - Invalid types fall back to `any`
1184
+
1185
+ ## Providing Runtime Utilities
1186
+
1187
+ Global utility values can be registered or updated at runtime.
1188
+
1189
+ ```ts
1190
+ import { useEffect } from "react";
1191
+ import { useNavigate } from "react-router";
1192
+ import { provideGlobalUtils } from "orchestore";
1193
+ import { feedbacks } from "./ui-feedbacks";
1194
+ import { store } from "./store";
1195
+
1196
+ provideGlobalUtils({
1197
+ notify(type, message) {
1198
+ feedbacks.notify(type, message);
1199
+ },
1200
+ });
1201
+
1202
+ export default function App() {
1203
+ const navigate = useNavigate();
1204
+
1205
+ useEffect(() => {
1206
+ provideGlobalUtils({ navigate });
1207
+ }, [navigate]);
1208
+
1209
+ return (
1210
+ <StoreProvider store={store}>
1211
+ <Routes />
1212
+ </StoreProvider>
1213
+ );
1214
+ }
1215
+ ```
1216
+
1217
+ ## Using Global Utilities in Slices
1218
+
1219
+ Global utilities can be used anywhere a slice instance is available.
1220
+
1221
+ ```ts
1222
+ methods: {
1223
+ async insertUser(data: UserInput) {
1224
+ try {
1225
+ this.setLoading(true);
1226
+
1227
+ const response = await api.users.add(data);
1228
+
1229
+ this.global.notify("success", "User added successfully!");
1230
+
1231
+ this.setLoading(false);
1232
+
1233
+ this.global.navigate("/users/" + response.id);
1234
+ } catch (error) {
1235
+ this.global.notify("error", "Failed to add user");
1236
+
1237
+ console.error(error);
1238
+ }
1239
+ }
1240
+ }
1241
+ ```
1242
+
1243
+ ---
1244
+
1245
+ # TypeScript Inference
1246
+
1247
+ OrcheStore is designed around deep TypeScript inference.
1248
+
1249
+ ```ts
1250
+ const counter = createSlice({
1251
+ name: "counter",
1252
+
1253
+ state: {
1254
+ value: 0,
1255
+ },
1256
+
1257
+ mutations: {
1258
+ increment(state, amount: number) {
1259
+ state.value += amount;
1260
+ },
1261
+ },
1262
+
1263
+ methods: {
1264
+ async incrementAfter(amount: number, delay = 1000) {
1265
+ await new Promise((resolve) => setTimeout(resolve, delay));
1266
+ this.increment(amount);
1267
+ },
1268
+ },
1269
+
1270
+ children: {
1271
+ subCounter: createSlice({
1272
+ name: "subCounter",
1273
+
1274
+ state: {
1275
+ value: 0,
1276
+ },
1277
+ }),
1278
+ },
1279
+ });
1280
+ ```
1281
+
1282
+ Automatically produces:
1283
+
1284
+ ```ts
1285
+ counter.getState();
1286
+ // { value: number, subCounter: { value: number } }
1287
+
1288
+ counter.subCounter.getState();
1289
+ // { value: number }
1290
+
1291
+ counter.increment(amount: number): void;
1292
+
1293
+ counter.incrementAfter(amount: number, delay?: number): Promise<number>;
1294
+ ```
1295
+
1296
+ No manual type declarations required.
1297
+
1298
+ ## Framework Type Extensions
1299
+
1300
+ OrcheStore also exposes user-definable type slots through `OrcheStore.Slots`.
1301
+
1302
+ These slots allow application-specific types to be injected into the framework and become available everywhere with full type safety.
1303
+
1304
+ | Slot | Purpose |
1305
+ | -------------------------- | ----------------------- |
1306
+ | `OrcheStore.Slots.root` | Root store typing |
1307
+ | `OrcheStore.Slots.global` | Global utilities typing |
1308
+
1309
+ ```ts
1310
+ declare module "orhestore" {
1311
+ namespace OrcheStore {
1312
+ interface Slots {
1313
+ root: typeof store; // Bugfix: Causes a circular type inference
1314
+
1315
+ global: {
1316
+ navigate: NavigateFunction;
1317
+ notify(type: "info" | "error" | "success", message: string): void;
1318
+ };
1319
+ }
1320
+ }
1321
+ }
1322
+ ```
1323
+
1324
+ This provides full typing for APIs such as:
1325
+
1326
+ ```ts
1327
+ this.root;
1328
+ this.global;
1329
+
1330
+ store.global;
1331
+ counter.global;
1332
+ ```
1333
+
1334
+ ---
1335
+
1336
+ # Status
1337
+
1338
+ OrcheStore is currently experimental and under active development.
1339
+
1340
+ Planned features:
1341
+
1342
+ - middleware and plugin system
1343
+ - persistence utilities
1344
+ - SSR support
1345
+ - deep readonly enforcement
1346
+ - lifecycle hooks
1347
+ - enhanced DevTools integration
1348
+
1349
+ ---