@@ -50,14 +50,14 @@ No `isLoading`, `error`, `isOptimistic` flags. A pending transition means loadin
5050
5151``` typescript
5252import { configureStore , createSelector } from ' @reduxjs/toolkit' ;
53- import { optimistron , createTransitions , crudPrepare , recordState } from ' @lostsolution/optimistron' ;
53+ import { optimistron , createTransitions , crudPrepare , recordState , TransitionMode } from ' @lostsolution/optimistron' ;
5454
5555type Todo = { id: string ; value: string ; done: boolean ; revision: number };
5656
5757const crud = crudPrepare <Todo >(' id' );
58- const createTodo = createTransitions (' todos::add' )(crud .create );
58+ const createTodo = createTransitions (' todos::add' , TransitionMode . DISPOSABLE )(crud .create );
5959const editTodo = createTransitions (' todos::edit' )(crud .update );
60- const deleteTodo = createTransitions (' todos::delete' )(crud .remove );
60+ const deleteTodo = createTransitions (' todos::delete' , TransitionMode . REVERTIBLE )(crud .remove );
6161
6262const { reducer : todos, selectOptimistic } = optimistron (
6363 ' todos' ,
@@ -225,13 +225,103 @@ All three modes are fully backwards compatible. The CRUD map only requires `{ ma
225225
226226---
227227
228+ ## Selectors
229+
230+ ### ` selectOptimistic `
231+
232+ Returned from ` optimistron() ` . Replays pending transitions onto committed state at read-time. Wrap with ` createSelector ` for memoization:
233+
234+ ``` typescript
235+ import { createSelector } from ' @reduxjs/toolkit' ;
236+
237+ const selectTodos = createSelector (
238+ (state : RootState ) => state .todos ,
239+ selectOptimistic ((todos ) => Object .values (todos .state )),
240+ );
241+ ```
242+
243+ ### Per-entity status
244+
245+ All transition selectors take a ` transitionId ` and a ` TransitionState ` — no global store shape required:
246+
247+ ``` typescript
248+ import {
249+ selectIsOptimistic ,
250+ selectIsFailed ,
251+ selectIsConflicting ,
252+ selectFailedTransition ,
253+ selectConflictingTransition ,
254+ selectRetryCount ,
255+ } from ' @lostsolution/optimistron' ;
256+
257+ selectIsOptimistic (id )(state .todos ) // true if transition is pending
258+ selectIsFailed (id )(state .todos ) // true if transition has failed
259+ selectIsConflicting (id )(state .todos ) // true if transition conflicts with committed state
260+
261+ selectFailedTransition (id )(state .todos ) // StagedAction | undefined
262+ selectConflictingTransition (id )(state .todos ) // StagedAction | undefined
263+ selectRetryCount (id )(state .todos ) // number of times this transition was re-staged after failure
264+ ```
265+
266+ ### Aggregate selectors
267+
268+ ``` typescript
269+ import { selectFailedTransitions , selectAllFailedTransitions } from ' @lostsolution/optimistron' ;
270+
271+ selectFailedTransitions (state .todos ) // all failed in one slice
272+ selectAllFailedTransitions (state .todos , state .projects , state .activity ) // all failed across slices
273+ ```
274+
275+ ---
276+
277+ ## Transition Modes
278+
279+ ` TransitionMode ` controls re-staging and failure behavior per action type — declared at the ` createTransitions ` site:
280+
281+ ``` typescript
282+ import { createTransitions , TransitionMode } from ' @lostsolution/optimistron' ;
283+
284+ const createTodo = createTransitions (' todos::add' , TransitionMode .DISPOSABLE )(crud .create );
285+ const editTodo = createTransitions (' todos::edit' )(crud .update ); // DEFAULT
286+ const deleteTodo = createTransitions (' todos::delete' , TransitionMode .REVERTIBLE )(crud .remove );
287+ ```
288+
289+ | Mode | On re-stage | On fail | Use case |
290+ | ------| -------------| ---------| ----------|
291+ | ** ` DEFAULT ` ** | Overwrite | Flag as failed | Edits — consumer retries manually |
292+ | ** ` DISPOSABLE ` ** | Overwrite | Drop transition | Creates — entity never existed server-side |
293+ | ** ` REVERTIBLE ` ** | Store trailing | Stash (revert to trailing) | Deletes — undo on failure |
294+
295+ ### Retry
296+
297+ Retry is a consumer concern — timing, backoff, and reconnect logic belong in your app layer. The library provides the building blocks:
298+
299+ ``` typescript
300+ import { retryTransition , selectFailedTransition , selectRetryCount } from ' @lostsolution/optimistron' ;
301+
302+ const failed = selectFailedTransition (id )(state .todos );
303+ if (failed ) {
304+ const retries = selectRetryCount (id )(state .todos );
305+ if (retries < 3 ) dispatch (retryTransition (failed )); // strips failure flags, re-stages
306+ }
307+ ```
308+
309+ ### Multi-slice failure aggregation
310+
311+ ``` typescript
312+ import { selectAllFailedTransitions } from ' @lostsolution/optimistron' ;
313+
314+ const allFailed = selectAllFailedTransitions (state .todos , state .projects , state .activity );
315+ ```
316+
317+ ---
318+
228319## Roadmap
229320
230321- ** Batch transitions** — stage multiple entities under a single correlation ID. Commit/fail/stash the batch atomically.
231- - ** Retry strategies** — configurable retry policies for failed transitions (exponential backoff, max attempts) built into the transition lifecycle.
232322- ** Devtools integration** — Redux DevTools timeline visualization for transitions, sanitization events, and conflict detection.
233323- ** Persistence adapters** — serialize/rehydrate pending transitions across page reloads (localStorage, IndexedDB).
234- - ** Middleware hooks ** — ` onConflict ` , ` onStale ` , ` onSanitize ` callbacks for custom side-effects without reducer coupling .
324+ - ** Transition expiry / TTL ** — automatic cleanup of stale failed transitions after a configurable time window .
235325
236326---
237327
0 commit comments