optimistate
v1.0.1
Published
Framework-agnostic optimistic update + rollback utilities with first-class TypeScript support.
Maintainers
Readme
optimistate
Framework-agnostic optimistic update + rollback utilities with first-class TypeScript support.
Works with: Angular, React, Vue, Svelte, and any JavaScript framework with reactive state.
Packages
- Library:
optimistate(src/) – framework-agnostic core - Demo apps: Multiple framework examples in demos/
- Angular (demos/angular-demo)
- React (demos/react-demo)
- Vue (demos/vue-demo)
- Svelte (demos/svelte-demo)
Prerequisites
- Node.js 20+
- npm
Install
npm i optimistateBuild the library
npm run buildArtifacts land in dist/ (ESM + types built with tsc).
Run the demos
All demos are independent applications in the demos/ folder. Each has its own dependencies and build configuration.
# Angular demo
cd demos/angular-demo
npm install
npm start
# Open http://localhost:4200
# React demo
cd demos/react-demo
npm install
npm run dev
# Open http://localhost:5173
# Vue demo
cd demos/vue-demo
npm install
npm run dev
# Open http://localhost:5174
# Svelte demo
cd demos/svelte-demo
npm install
npm run dev
# Open http://localhost:5175See demos/README.md for detailed instructions.
Unit tests
# Test library
npm test
# Test individual demos (from their directories)
cd demos/angular-demo && npm test
cd demos/react-demo && npm test # (if tests exist)
cd demos/vue-demo && npm test # (if tests exist)
cd demos/svelte-demo && npm test # (if tests exist)Quick Start (Library Usage)
Basic Example (Framework-Agnostic)
import { createOptimisticAction, type WritableRef } from 'optimistate';
const state = { count: 0 };
const countRef: WritableRef<number> = {
get: () => state.count,
set: (next) => (state.count = next),
};
const increment = createOptimisticAction({
sources: { count: countRef },
mutate: (snapshot, payload: { delta: number }) => ({
count: snapshot.count + payload.delta,
}),
operation: async () => {
await fetch('/api/increment', { method: 'POST' });
},
});
// UI updates immediately, rolls back on error
await increment.run({ delta: 1 });With Angular Signals
Angular signals work directly (no wrapper needed):
import { signal } from '@angular/core';
import { createOptimisticAction } from 'optimistate';
const count = signal(0);
const status = signal<'idle' | 'pending' | 'success' | 'error'>('idle');
const error = signal<unknown | null>(null);
const increment = createOptimisticAction({
sources: { count }, // ← Pass signal directly
mutate: (snapshot, payload: { delta: number }) => ({
count: snapshot.count + payload.delta,
}),
operation: async () => {
await fetch('/api/increment', { method: 'POST' });
},
status, // ← Optional: wire your own status signal
error, // ← Optional: wire your own error signal
});
await increment.run({ delta: 1 });
// Even without providing status/error, they're always accessible:
console.log(increment.status); // Auto-created if not provided
console.log(increment.error); // Auto-created if not providedWith Custom Rollback
const save = createOptimisticAction({
sources: { profile: profileSignal },
mutate: (snapshot, payload: Partial<Profile>) => ({
profile: { ...snapshot.profile, ...payload },
}),
operation: async ({ payload }) => {
await fetch('/api/profile', {
method: 'PUT',
body: JSON.stringify(payload),
});
},
rollback: ({ snapshot, error }) => {
// Custom rollback logic instead of automatic revert
profileSignal.set(snapshot.profile);
console.error('Save failed:', error);
},
});
await save.run({ name: 'New Name' });With Angular HttpClient
import { firstValueFrom } from 'rxjs';
import { HttpClient } from '@angular/common/http';
const save = createOptimisticAction({
sources: { profile: profileSignal },
mutate: (snapshot, payload: Partial<Profile>) => ({
profile: { ...snapshot.profile, ...payload },
}),
operation: async ({ payload }) =>
firstValueFrom(http.put<Profile>('/api/profile', payload)),
});API Overview
Core Functions
createOptimisticAction(config)– Creates reusable action with auto status/error trackingrunOptimistic(operation, payload)– Low-level function for one-off updates
See the library API docs in this README for:
- Complete API documentation
- Framework integration examples (React, Vue, Svelte, Solid.js)
- Advanced usage patterns
Development Scripts
Library (from root):
npm run build # Build library
npm run watch # Watch mode
npm test # Run tests
npm run lint # Lint library code
npm run typecheck # TypeScript type checkingDemos (from their directories):
cd demos/angular-demo
npm install
npm start # Dev server
npm run build # Production build
npm test # Run tests
npm run lint # Lint codePublishing the Library
npm run build
cd dist
npm publishProject Structure
src/– Library source codesrc/lib/– Core implementationsrc/public-api.ts– Public API surface
demos/– Framework demo applicationsangular-demo/– Angular standalone app with signalsreact-demo/– React app with hooksvue-demo/– Vue 3 app with Composition APIsvelte-demo/– Svelte app with stores
dist/– Built library (afternpm run build)
License
MIT
