Skip to content

Commit 3219e5d

Browse files
committed
chore: refine readmes
1 parent f057991 commit 3219e5d

2 files changed

Lines changed: 44 additions & 25 deletions

File tree

ARCHITECTURE.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ function* createTodoSaga(action: ReturnType<typeof createTodo.stage>) {
237237
```
238238
src/
239239
├── index.ts # Public API surface (barrel export)
240-
├── optimistron.ts # Factory: wraps reducers, returns { reducer, selectOptimistic }
240+
├── optimistron.ts # Factory: wraps reducers, returns { reducer, selectors }
241241
├── transitions.ts # Transition operations, processTransition, sanitizeTransitions
242242
├── reducer.ts # resolveReducer, bindReducer
243243
├── constants.ts # META_KEY
@@ -249,8 +249,7 @@ src/
249249
│ └── types.ts # PreparePayload, PrepareError, ActionMeta, ItemPath, UpdateDTO, DeleteDTO
250250
251251
├── selectors/
252-
│ ├── internal.ts # createSelectOptimistic (returned from optimistron, not exported)
253-
│ └── selectors.ts # selectIsFailed, selectIsOptimistic, selectIsConflicting, etc.
252+
│ └── internal.ts # All selectors (returned from optimistron via selectors object, not exported)
254253
255254
├── state/
256255
│ ├── types.ts # TransitionState, StateHandler, WiredStateHandler, BoundStateHandler
@@ -269,7 +268,7 @@ Key implementation details:
269268

270269
- **`TransitionState<T>`** wraps user state with a non-enumerable `transitions` list (via `Object.defineProperties` — hidden from serializers and spreads)
271270
- **`transitionStateFactory`** returns the previous state object when both `state` and `transitions` are referentially equal (preserves memoization)
272-
- **`selectOptimistic`** is closed over the bound reducer — no global state needed
271+
- **`selectors`** are returned as a grouped object from `optimistron()` — no standalone exports, each slice is self-contained
273272
- **Action types** use `namespace::operation` format, matching uses `startsWith`
274273

275274
---
@@ -290,7 +289,7 @@ These are non-negotiable — the library design depends on them:
290289

291290
### `optimistron(namespace, initialState, handler, config, options?)`
292291

293-
Creates an optimistic reducer wrapper. Returns `{ reducer, selectOptimistic }`.
292+
Creates an optimistic reducer wrapper. Returns `{ reducer, selectors }`.
294293

295294
| Param | Type | Description |
296295
|-------|------|-------------|
@@ -300,14 +299,20 @@ Creates an optimistic reducer wrapper. Returns `{ reducer, selectOptimistic }`.
300299
| `config` | `ReducerConfig` | CRUD action map or reducer function |
301300
| `options.sanitizeAction` | `(action) => action` | Optional action transform before sanitization |
302301

303-
### `selectOptimistic(selector)`
302+
### `selectors`
304303

305-
Returned from `optimistron()`. Replays pending transitions before applying the selector. Always wrap with `createSelector`:
304+
Returned from `optimistron()` as a grouped object. No selectors are exported from the library — they are all bound to the slice's `TransitionState<S>`.
305+
306+
#### `selectors.selectOptimistic(selector)`
307+
308+
Replays pending transitions before applying the selector. Always wrap with `createSelector`:
306309

307310
```typescript
311+
const { selectors } = optimistron('todos', initial, handler, config);
312+
308313
const selectTodos = createSelector(
309314
(state: RootState) => state.todos,
310-
selectOptimistic((todos) => Object.values(todos.state)),
315+
selectors.selectOptimistic((todos) => Object.values(todos.state)),
311316
);
312317
```
313318

@@ -340,19 +345,18 @@ const crud = crudPrepare<ProjectTodo>()(['projectId', 'id']);
340345
// transitionId: "projectId-value/id-value"
341346
```
342347

343-
### Selectors
348+
#### Per-entity selectors
344349

345-
All transition selectors are curried: `selector(id)(transitionState)`.
350+
All returned on `selectors`, curried: `selectors.selector(id)(transitionState)`.
346351

347352
| Selector | Returns |
348353
|----------|---------|
349354
| `selectIsOptimistic(id)` | `boolean` — transition is pending |
350355
| `selectIsFailed(id)` | `boolean` — transition has failed |
351356
| `selectIsConflicting(id)` | `boolean` — transition conflicts with committed state |
352-
| `selectFailedTransition(id)` | `StagedAction \| undefined` |
353-
| `selectConflictingTransition(id)` | `StagedAction \| undefined` |
354-
| `selectFailedTransitions` | `(state) => StagedAction[]` — all failed in one slice |
355-
| `selectAllFailedTransitions` | `(...states) => StagedAction[]` — across slices |
357+
| `selectFailure(id)` | `StagedAction \| undefined` — failed transition for entity |
358+
| `selectConflict(id)` | `StagedAction \| undefined` — conflicting transition for entity |
359+
| `selectFailures` | `(state) => StagedAction[]` — all failed transitions in this slice |
356360

357361
### Enums
358362

README.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const editTodo = createTransitions('todos::edit')(crud.update); // DEFAULT mode
7676
const deleteTodo = createTransitions('todos::delete', TransitionMode.REVERTIBLE)(crud.remove);
7777

7878
// 4. Create the optimistic reducer
79-
const { reducer: todos, selectOptimistic } = optimistron(
79+
const { reducer: todos, selectors } = optimistron(
8080
'todos',
8181
{} as Record<string, Todo>,
8282
recordState<Todo>({
@@ -93,7 +93,7 @@ const store = configureStore({ reducer: { todos } });
9393
// 6. Select optimistic state (memoize with createSelector)
9494
const selectTodos = createSelector(
9595
(state: RootState) => state.todos,
96-
selectOptimistic((todos) => Object.values(todos.state)),
96+
selectors.selectOptimistic((todos) => Object.values(todos.state)),
9797
);
9898

9999
// 7. Dispatch transitions
@@ -222,30 +222,45 @@ Declared per action type — controls what happens on re-stage and failure:
222222

223223
## Selectors
224224

225+
All selectors are returned from `optimistron()` on the `selectors` object — there are no standalone selector exports.
226+
225227
### Optimistic state
226228

227229
```typescript
230+
const { selectors } = optimistron('todos', initial, handler, config);
231+
228232
const selectTodos = createSelector(
229233
(state: RootState) => state.todos,
230-
selectOptimistic((todos) => Object.values(todos.state)),
234+
selectors.selectOptimistic((todos) => Object.values(todos.state)),
231235
);
232236
```
233237

234238
### Per-entity status
235239

236240
```typescript
237-
import { selectIsOptimistic, selectIsFailed, selectIsConflicting } from '@lostsolution/optimistron';
238-
239-
selectIsOptimistic(id)(state.todos); // pending?
240-
selectIsFailed(id)(state.todos); // failed?
241-
selectIsConflicting(id)(state.todos); // stale conflict?
241+
const { selectors } = optimistron('todos', initial, handler, config);
242+
243+
selectors.selectIsOptimistic(id)(state.todos); // pending?
244+
selectors.selectIsFailed(id)(state.todos); // failed?
245+
selectors.selectIsConflicting(id)(state.todos); // stale conflict?
246+
selectors.selectFailures(state.todos); // all failed transitions in this slice
247+
selectors.selectFailure(id)(state.todos); // failed transition for a specific entity
248+
selectors.selectConflict(id)(state.todos); // conflicting transition for a specific entity
242249
```
243250

244-
### Aggregate failures
251+
### Aggregate failures across slices
252+
253+
Cross-slice aggregation is a consumer concern. Compose per-slice selectors:
245254

246255
```typescript
247-
import { selectAllFailedTransitions } from '@lostsolution/optimistron';
248-
selectAllFailedTransitions(state.todos, state.projects, state.activity);
256+
const selectAllFailed = createSelector(
257+
(state: RootState) => state.todos,
258+
(state: RootState) => state.projects,
259+
(todos, projects) => [
260+
...todosSelectors.selectFailures(todos),
261+
...projectsSelectors.selectFailures(projects),
262+
],
263+
);
249264
```
250265

251266
---

0 commit comments

Comments
 (0)