Skip to content

Commit e64bf30

Browse files
committed
chore: update markdowns
1 parent 499bf0c commit e64bf30

2 files changed

Lines changed: 116 additions & 16 deletions

File tree

ARCHITECTURE.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Internals, API, and advanced patterns. For quick start, see [README.md](./README
1010
- [Sanitization](#sanitization)
1111
- [Data Flow](#data-flow)
1212
- [Custom Handlers](#custom-handlers)
13-
- [TRAILING Mode](#trailing-mode)
13+
- [Transition Modes](#transition-modes)
1414
- [Async Patterns](#async-patterns)
1515
- [API Reference](#api-reference)
1616

@@ -192,19 +192,23 @@ For shapes not covered by the built-ins, implement `StateHandler` directly.
192192

193193
---
194194

195-
## TRAILING Mode
195+
## Transition Modes
196196

197-
`DedupeMode.TRAILING` — undo-on-failure for destructive ops:
197+
`TransitionMode` controls re-staging and failure behavior per action type:
198198

199-
```typescript
200-
const crud = crudPrepare<Todo>('id');
201-
const deleteTodo = createTransitions('todos::delete', DedupeMode.TRAILING)(crud.remove);
199+
| Mode | On re-stage | On fail | Use case |
200+
|------|-------------|---------|----------|
201+
| `DEFAULT` | Overwrite | Flag as failed | Edits |
202+
| `DISPOSABLE` | Overwrite | Drop transition | Creates |
203+
| `REVERTIBLE` | Store trailing | Stash (revert) | Deletes |
202204

203-
dispatch(deleteTodo.stage(id)); // gone from UI (transitionId auto-detected)
204-
dispatch(deleteTodo.stash(id)); // back — restored from trailing
205+
```typescript
206+
const createTodo = createTransitions('todos::add', TransitionMode.DISPOSABLE)(crud.create);
207+
const editTodo = createTransitions('todos::edit')(crud.update);
208+
const deleteTodo = createTransitions('todos::delete', TransitionMode.REVERTIBLE)(crud.remove);
205209
```
206210

207-
Replaced transitions are stored as fallback. `stash` restores instead of dropping.
211+
`REVERTIBLE` stores the replaced transition as a trailing fallback. On fail or explicit stash, the previous transition is restored.
208212

209213
---
210214

@@ -312,7 +316,11 @@ const crud = crudPrepare<ProjectTodo>()(['projectId', 'id']);
312316
// crud.remove(projectId, id) → { payload: { path }, transitionId: "projectId/id" }
313317
```
314318

315-
### `createTransitions(type, dedupe?)(prepare)`
319+
### `retryTransition(action)`
320+
321+
Strips `failed` and `conflict` flags from a `StagedAction`, returning a clean action ready for re-dispatch.
322+
323+
### `createTransitions(type, mode?)(prepare)`
316324

317325
Creates `.stage`, `.amend`, `.commit`, `.fail`, `.stash`, `.match`.
318326

@@ -335,6 +343,8 @@ createTransitions('todos::add')({
335343
| `selectFailedTransition(id)` | `StagedAction \| undefined` |
336344
| `selectConflictingTransition(id)` | `StagedAction \| undefined` |
337345
| `selectFailedTransitions` | `StagedAction[]` |
346+
| `selectAllFailedTransitions(...states)` | `StagedAction[]` — aggregated across slices |
347+
| `selectRetryCount(id)` | `number` — retry count (0 if none) |
338348

339349
### State Handler Factories
340350

@@ -349,6 +359,6 @@ createTransitions('todos::add')({
349359

350360
```typescript
351361
Operation.STAGE | .AMEND | .COMMIT | .STASH | .FAIL
352-
DedupeMode.OVERWRITE | .TRAILING
362+
TransitionMode.DEFAULT | .DISPOSABLE | .REVERTIBLE
353363
OptimisticMergeResult.SKIP | .CONFLICT
354364
```

README.md

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ No `isLoading`, `error`, `isOptimistic` flags. A pending transition means loadin
5050

5151
```typescript
5252
import { 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

5555
type Todo = { id: string; value: string; done: boolean; revision: number };
5656

5757
const crud = crudPrepare<Todo>('id');
58-
const createTodo = createTransitions('todos::add')(crud.create);
58+
const createTodo = createTransitions('todos::add', TransitionMode.DISPOSABLE)(crud.create);
5959
const 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

6262
const { 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

Comments
 (0)