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 +6 -1083
- package/dist/index.cjs +13 -32
- package/dist/index.d.cts +130 -3
- package/dist/index.d.ts +130 -3
- package/dist/index.js +13 -7
- package/package.json +27 -9
package/README.md
CHANGED
|
@@ -1,49 +1,14 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
3
|
+
### 🚧 Coming Soon
|
|
39
4
|
|
|
40
|
-
|
|
5
|
+
> OrcheStore is currently under active development and is not yet ready for production use.
|
|
41
6
|
|
|
42
7
|
---
|
|
43
8
|
|
|
44
|
-
|
|
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
|
-
---
|