@miurajs/miura-data-flow
v0.1.4
Published
Modern, reactive state management for miura applications. Combines the best of Redux, Zustand, and modern patterns with zero boilerplate and maximum performance. **Now with optional dependencies for optimal bundle size!**
Downloads
710
Readme
miura Data Flow
Modern, reactive state management for miura applications. Combines the best of Redux, Zustand, and modern patterns with zero boilerplate and maximum performance. Now with optional dependencies for optimal bundle size!
Features
- 🚀 Reactive State Management - Automatic updates when state changes
- 🔧 Middleware System - Extensible with logging, persistence, API integration
- 🌍 Global State - Application-wide state management
- 💾 Built-in Caching - Intelligent caching with TTL
- 🔌 API Integration - Automatic API calls with state updates
- 🛠️ DevTools Support - Redux DevTools integration
- 📝 TypeScript First - Full type safety and IntelliSense
- ⚡ Performance Optimized - Efficient subscriptions and updates
- 📦 Optional Dependencies - Only install what you need for optimal bundle size
Installation
# Core package (includes built-in providers)
npm install @miurajs/miura-data-flow
# Optional providers (install as needed)
npm install graphql-request # For GraphQL provider
npm install @aws-sdk/client-s3 # For AWS S3 provider
npm install firebase # For Firebase provider
npm install @supabase/supabase-js # For Supabase provider
npm install grpc-web # For gRPC-Web providerQuick Start
import { Store, globalState, createLoggerMiddleware } from '@miurajs/miura-data-flow';
// Create a store
const store = new Store({ count: 0, user: null });
// Add middleware
store.use(createLoggerMiddleware());
// Define actions
store.defineActions({
increment: (state) => ({ count: state.count + 1 }),
setUser: (state, user) => ({ user })
});
// Subscribe to changes
const unsubscribe = store.subscribe((state, prevState) => {
console.log('State changed:', state);
});
// Dispatch actions
await store.dispatch('increment');
await store.dispatch('setUser', { id: '1', name: 'John' });Core Concepts
Store
The main state container that manages your application state.
import { Store } from '@miurajs/miura-data-flow';
interface AppState {
count: number;
user: User | null;
todos: Todo[];
}
const store = new Store<AppState>({
count: 0,
user: null,
todos: []
});Actions
Define how your state can be updated.
store.defineActions({
// Simple state update
increment: (state) => ({ count: state.count + 1 }),
// With parameters
setUser: (state, user: User) => ({ user }),
// Async actions
async fetchUser: async (state, userId: string) => {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
return { user };
},
// Complex updates
addTodo: (state, todo: Todo) => ({
todos: [...state.todos, todo]
})
});Subscriptions
React to state changes automatically.
// Subscribe to all changes
const unsubscribe = store.subscribe((state, prevState) => {
console.log('State changed:', state);
});
// Subscribe to specific property
const unsubscribeCount = store.subscribeTo('count', (count, prevCount) => {
console.log(`Count changed from ${prevCount} to ${count}`);
});
// Subscribe with selector
const unsubscribeUser = store.subscribe(
(state, prevState) => {
console.log('User changed:', state.user);
},
(state) => state.user // Only trigger when user changes
);Middleware System
Built-in Middleware
Logger Middleware
import { createLoggerMiddleware } from '@miurajs/miura-data-flow';
store.use(createLoggerMiddleware());
// Logs all actions and state changes to consolePersistence Middleware
import { createPersistenceMiddleware } from '@miurajs/miura-data-flow';
store.use(createPersistenceMiddleware(['user', 'settings']));
// Automatically saves/loads specified properties to localStorageAPI Middleware
import { createApiMiddleware } from '@miurajs/miura-data-flow';
store.use(createApiMiddleware({
baseURL: 'https://api.example.com',
headers: {
'Authorization': 'Bearer token'
},
timeout: 5000
}));
// Now you can dispatch API actions
store.defineActions({
api_fetchUsers: () => {}, // Will automatically fetch /fetchUsers
api_createUser: () => {} // Will automatically POST to /createUser
});
await store.dispatch('api_fetchUsers');
await store.dispatch('api_createUser', { method: 'POST', data: userData });Cache Middleware
import { createCacheMiddleware } from '@miurajs/miura-data-flow';
store.use(createCacheMiddleware(5 * 60 * 1000)); // 5 minutes TTL
// Caches API responses for 5 minutesDevTools Middleware
import { createDevToolsMiddleware } from '@miurajs/miura-data-flow';
store.use(createDevToolsMiddleware('MyApp'));
// Enables Redux DevTools integrationRedux DevTools Integration
Redux DevTools is a powerful browser extension that provides real-time debugging for state management. Even though miura Data Flow isn't Redux, we integrate with it because it's the industry standard for state debugging.
What Redux DevTools Provides:
- 🔍 State Inspector - Real-time visualization of your entire state tree
- 📜 Action Monitor - Complete log of all dispatched actions with timing
- ⏰ Time-travel Debugging - Jump back to any previous state
- 📊 Diff View - Before/after comparison with highlighted changes
- ⚡ Performance Metrics - Action timing and state size monitoring
Setup Instructions:
Install the Browser Extension:
- Chrome: Redux DevTools Extension
- Firefox: Redux DevTools Extension
Enable in Your Code:
import { createDevToolsMiddleware } from '@miurajs/miura-data-flow'; store.use(createDevToolsMiddleware('MyApp'));Open DevTools:
- Press
F12→ Click the Redux tab - Or right-click → Inspect → Redux tab
- Press
What You'll See:
When you dispatch actions, they appear in DevTools like this:
Action: increment
Payload: []
State Before: { count: 0, user: null }
State After: { count: 1, user: null }
Action: setUser
Payload: [{ id: '1', name: 'John' }]
State Before: { count: 1, user: null }
State After: { count: 1, user: { id: '1', name: 'John' } }Complete Example:
import { Store, createDevToolsMiddleware } from '@miurajs/miura-data-flow';
// Create store
const store = new Store({
user: null,
todos: [],
theme: 'light'
});
// Enable DevTools integration
store.use(createDevToolsMiddleware('TodoApp'));
// Define actions
store.defineActions({
login: (state, user) => ({ user }),
addTodo: (state, todo) => ({ todos: [...state.todos, todo] }),
toggleTheme: (state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })
});
// These actions will now be visible in Redux DevTools:
await store.dispatch('login', { id: '1', name: 'John' });
await store.dispatch('addTodo', { id: '1', text: 'Buy milk' });
await store.dispatch('toggleTheme');DevTools Features:
State Inspector:
- View your entire state tree in real-time
- Expand/collapse nested objects
- Search through state properties
Action Monitor:
- See all dispatched actions with timestamps
- Inspect action payloads and arguments
- Monitor action execution time
Time-travel Debugging:
- Jump to any previous state
- Replay actions step by step
- Compare states at different points in time
Performance Monitoring:
- Track action execution time
- Monitor state size changes
- Identify performance bottlenecks
Why Use Redux DevTools?
Even though we're not using Redux, Redux DevTools has become the de facto standard for state debugging because:
- Universal Adoption - Most developers are familiar with it
- Rich Features - Time-travel, diff view, performance metrics
- Browser Integration - No additional setup required
- Community Support - Well-maintained and documented
It's like having a super-powered console.log that shows you exactly what's happening with your state in real-time! 🚀
Debug Information
// Get debug info about your store
console.log(store.getDebugInfo());
console.log(globalState.getDebugInfo());API Reference
Store
new Store<T>(initialState: T)- Create a new storestore.getState(): T- Get current statestore.get<K>(key: K): T[K]- Get specific propertystore.setState(updater)- Update statestore.defineActions(actions)- Define actionsstore.dispatch(action, ...args)- Execute actionstore.subscribe(callback, selector?)- Subscribe to changesstore.subscribeTo(key, callback)- Subscribe to specific propertystore.use(middleware)- Add middleware
Global State
globalState.get(key)- Get global propertyglobalState.set(key, value)- Set global propertyglobalState.subscribe(componentId, properties, callback)- SubscribeglobalState.subscribeTo(componentId, key, callback)- Subscribe to propertyglobalState.dispatch(action, ...args)- Dispatch global action
Middleware
createLoggerMiddleware()- Console loggingcreatePersistenceMiddleware(keys, storageKey)- localStorage persistencecreateApiMiddleware(config)- API integrationcreateCacheMiddleware(ttl)- Response cachingcreateDevToolsMiddleware(name)- Redux DevTools
Best Practices
- Use TypeScript - Define interfaces for your state
- Subscribe Selectively - Only subscribe to what you need
- Unsubscribe Always - Prevent memory leaks
- Use Actions - Don't mutate state directly
- Middleware for Side Effects - Keep actions pure
- Global State Sparingly - Use for truly global data
- Batch Updates - Group related state changes
Examples
See the examples directory for complete working examples.
miura Data Flow - Modern state management that just works! 🚀
Custom Middleware
import { StoreMiddleware } from '@miurajs/miura-data-flow';
const analyticsMiddleware: StoreMiddleware = {
name: 'analytics',
before: (action, args, state) => {
// Track action before execution
analytics.track('action_started', { action, args });
},
after: (action, args, state, result) => {
// Track action completion
analytics.track('action_completed', { action, result });
},
error: (action, args, error) => {
// Track errors
analytics.track('action_error', { action, error });
}
};
store.use(analyticsMiddleware);Global State Management
Using Global State
import { globalState } from '@miurajs/miura-data-flow';
// Set global properties
globalState.set('theme', 'dark');
globalState.set('user', { id: '1', name: 'John' });
// Get global properties
const theme = globalState.get('theme');
const user = globalState.get('user');
// Subscribe to global state changes
const unsubscribe = globalState.subscribeTo('my-component', 'theme', (theme) => {
console.log('Theme changed:', theme);
});Global State in Components
import { MiuraElement, html } from '@miurajs/miura-element';
import { globalState } from '@miurajs/miura-data-flow';
class ThemeToggle extends MiuraElement {
static properties = {
theme: { type: String, default: 'light' }
};
connectedCallback() {
super.connectedCallback();
// Subscribe to global theme changes
this.unsubscribeTheme = globalState.subscribeTo(
this.tagName,
'theme',
(theme) => {
this.theme = theme;
this.requestUpdate();
}
);
}
disconnectedCallback() {
super.disconnectedCallback();
this.unsubscribeTheme?.();
}
toggleTheme() {
const newTheme = this.theme === 'light' ? 'dark' : 'light';
globalState.set('theme', newTheme);
}
render() {
return html`
<button @click=${this.toggleTheme}>
Current theme: ${this.theme}
</button>
`;
}
}Data Providers
miura Data Flow includes a flexible provider system for connecting to various data sources.
Built-in Providers (Always Available)
These providers are included by default and have no external dependencies:
import {
RestProviderFactory,
LocalStorageProviderFactory,
IndexedDBProviderFactory,
WebSocketProviderFactory
} from '@miurajs/miura-data-flow';
// Register and use built-in providers
import { registerProvider, createProvider } from '@miurajs/miura-data-flow';
// Register REST API provider
registerProvider('api', new RestProviderFactory());
// Create provider instance
const apiProvider = createProvider('api', {
baseUrl: 'https://api.example.com'
});
// Use the provider
const users = await apiProvider.get('users');Optional Providers (Require Additional Dependencies)
These providers are available as separate imports to keep the core package lightweight:
GraphQL Provider
# Install the optional dependency
npm install graphql-request// Import the GraphQL provider separately
import { GraphQLProviderFactory } from '@miurajs/miura-data-flow/providers/graphql';
// Register and use
registerProvider('graphql', new GraphQLProviderFactory());
const graphqlProvider = createProvider('graphql', {
endpoint: 'https://api.example.com/graphql'
});
const result = await graphqlProvider.query({
query: 'query { users { id name } }'
});AWS S3 Provider
# Install the optional dependency
npm install @aws-sdk/client-s3// Import the S3 provider separately
import { S3ProviderFactory } from '@miurajs/miura-data-flow/providers/s3';
// Register and use
registerProvider('s3', new S3ProviderFactory());
const s3Provider = createProvider('s3', {
region: 'us-east-1',
bucket: 'my-bucket'
});
const data = await s3Provider.get('file.json');
await s3Provider.put('file.json', jsonData);Firebase Provider
# Install the optional dependency
npm install firebase// Import the Firebase provider separately
import { FirebaseProviderFactory } from '@miurajs/miura-data-flow/providers/firebase';
// Register and use
registerProvider('firebase', new FirebaseProviderFactory());
const firebaseProvider = createProvider('firebase', {
apiKey: 'your-api-key',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project-id'
});Supabase Provider
# Install the optional dependency
npm install @supabase/supabase-js// Import the Supabase provider separately
import { SupabaseProviderFactory } from '@miurajs/miura-data-flow/providers/supabase';
// Register and use
registerProvider('supabase', new SupabaseProviderFactory());
const supabaseProvider = createProvider('supabase', {
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key'
});gRPC-Web Provider
# Install the optional dependency
npm install grpc-web// Import the gRPC-Web provider separately
import { GrpcWebProviderFactory } from '@miurajs/miura-data-flow/providers/grpc-web';
// Register and use
registerProvider('grpc', new GrpcWebProviderFactory());
const grpcProvider = createProvider('grpc', {
host: 'localhost:8080',
protoPath: './service.proto'
});Why This Structure?
- Bundle Size Optimization - Only include providers you actually use
- Dependency Management - Avoid forcing installation of large libraries
- Tree Shaking - Better optimization for production builds
- Flexibility - Use only what you need, when you need it
Provider Usage in Middleware
import { createApiMiddleware } from '@miurajs/miura-data-flow';
// Use any registered provider in middleware
store.use(createApiMiddleware({
provider: 's3', // Use the S3 provider we registered
bucket: 'my-app-data'
}));Integration with miura Framework
Framework Setup
import { Store, createApiMiddleware, createCacheMiddleware } from '@miurajs/miura-data-flow';
class miuraFramework {
private store: Store;
constructor() {
// Initialize store with app state
this.store = new Store({
user: null,
settings: {},
notifications: []
});
// Add middleware for API and caching
this.store.use(createApiMiddleware({
baseURL: process.env.API_URL,
headers: {
'Content-Type': 'application/json'
}
}));
this.store.use(createCacheMiddleware());
// Define global actions
this.store.defineActions({
setUser: (state, user) => ({ user }),
addNotification: (state, notification) => ({
notifications: [...state.notifications, notification]
})
});
}
// Expose store to components
getStore() {
return this.store;
}
}Component Integration
class MyComponent extends MiuraElement {
// Define global properties this component uses
global() {
return {
user: null,
theme: 'light'
};
}
connected() {
// Subscribe to global state
this.unsubscribeUser = globalState.subscribeTo(
this.tagName,
'user',
(user) => this.handleUserChange(user)
);
}
disconnected() {
this.unsubscribeUser?.();
}
handleUserChange(user) {
// React to global user changes
this.requestUpdate();
}
}Performance Optimization
Selective Subscriptions
// Only subscribe to what you need
const unsubscribe = store.subscribe(
(state, prevState) => {
// Only trigger when user.todos changes
},
(state) => state.user?.todos
);Batch Updates
// Multiple updates in one action
store.defineActions({
updateUserProfile: (state, updates) => ({
user: { ...state.user, ...updates },
lastUpdated: Date.now()
})
});Memory Management
// Always unsubscribe to prevent memory leaks
class MyComponent {
connectedCallback() {
this.unsubscribe = store.subscribe(this.handleStateChange);
}
disconnectedCallback() {
this.unsubscribe(); // Important!
}
}Debugging
Enable Debug Logging
import { enableDebug } from '@miurajs/miura-render';
enableDebug({
element: true, // Shows data flow logs
});DevTools Integration
// Install Redux DevTools browser extension
store.use(createDevToolsMiddleware('MyApp'));