@zubridge/electron
Version:
A streamlined state management library for Electron applications using Zustand.
96 lines (95 loc) • 3.76 kB
JavaScript
import { useStore } from 'zustand';
import { createStore as createZustandStore } from 'zustand/vanilla';
// Store registry to implement singleton pattern
// Maps handler objects to their corresponding stores
const storeRegistry = new WeakMap();
// Internal implementation of store creation
const createStore = (bridge) => {
// Check if a store already exists for these handlers
if (storeRegistry.has(bridge)) {
return storeRegistry.get(bridge);
}
// Create a new store if one doesn't exist
const newStore = createZustandStore((setState) => {
// subscribe to changes
bridge.subscribe((state) => setState(state));
// get initial state
bridge.getState().then((state) => setState(state));
// no state keys - they will all come from main
return {};
});
// Register the store
storeRegistry.set(bridge, newStore);
return newStore;
};
// Create Electron-specific handlers
export const createHandlers = () => {
if (typeof window === 'undefined' || !window.zubridge) {
throw new Error('Zubridge handlers not found in window. Make sure the preload script is properly set up.');
}
return window.zubridge;
};
/**
* Creates a hook for accessing the store state in React components
*/
export const createUseStore = (customHandlers) => {
const handlers = customHandlers || createHandlers();
const vanillaStore = createStore(handlers);
const useBoundStore = (selector) => useStore(vanillaStore, selector);
Object.assign(useBoundStore, vanillaStore);
// return store hook
return useBoundStore;
};
/**
* Creates a dispatch function for sending actions to the main process
*
* @template S The state type
* @template TActions A record of action types to payload types mapping (optional)
* @param customHandlers Optional custom handlers to use instead of window.zubridge
* @returns A typed dispatch function
*
* @example
* // Basic usage
* const dispatch = useDispatch();
*
* @example
* // With typed actions
* type CounterActions = {
* 'COUNTER:INCREMENT': void;
* 'COUNTER:DECREMENT': void;
* 'COUNTER:SET': number;
* };
* const dispatch = useDispatch<State, CounterActions>();
* dispatch({ type: 'COUNTER:SET', payload: 5 }); // Type-safe payload
* dispatch({ type: 'UNKNOWN' }); // Type error
*/
export const useDispatch = (customHandlers) => {
const handlers = customHandlers || createHandlers();
// Ensure we have a store for these handlers
const store = storeRegistry.has(handlers) ? storeRegistry.get(handlers) : createStore(handlers);
// Create a dispatch function that will handle both generic and typed actions
const dispatch = ((action, payload) => {
if (typeof action === 'function') {
// Handle thunks - execute them with the store's getState and our dispatch function
return action(store.getState, dispatch);
}
// Handle string action type with payload
if (typeof action === 'string') {
// Only pass the payload parameter if it's not undefined, and handle promise return value
return payload !== undefined ? handlers.dispatch(action, payload) : handlers.dispatch(action);
}
// For action objects, normalize to standard Action format
if (typeof action.type !== 'string') {
throw new Error(`Invalid action type: ${action.type}. Expected a string.`);
}
const normalizedAction = {
type: action.type,
payload: action.payload,
};
// Return the promise from dispatch
return handlers.dispatch(normalizedAction);
});
return dispatch;
};
// Export environment utilities
export * from './utils/environment';