refactor: Remove cross-store dependencies using Zustand event store

Replace direct store imports and calls with a Zustand-based event system
for decoupled cross-store communication.

Changes:
- Created storeEvents.ts using Zustand for event management
- Removed direct usePatternStore import from useMachineStore
- Removed dynamic imports for useMachineUploadStore and useMachineCacheStore
- Added event subscriptions in usePatternStore, useMachineUploadStore, and useMachineCacheStore
- useMachineStore now emits patternDeleted event instead of calling other stores directly

Benefits:
- Stores can be tested in isolation
- No tight coupling between stores
- Clear, explicit event-driven data flow
- Uses Zustand's built-in subscription system
- Easier to refactor stores independently

Fixes #37

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-12-27 17:39:26 +01:00
parent e49a63a4b1
commit 20e9fa13e7
5 changed files with 65 additions and 11 deletions

44
src/stores/storeEvents.ts Normal file
View file

@ -0,0 +1,44 @@
/**
* Store Events
*
* Zustand-based event store for cross-store communication without tight coupling.
* Uses Zustand's built-in subscription system to emit and react to events.
*/
import { create } from "zustand";
interface EventState {
// Event counters - incrementing these triggers subscriptions
patternDeletedCount: number;
// Actions to emit events
emitPatternDeleted: () => void;
}
/**
* Event store using Zustand for cross-store communication.
* Stores can emit events by calling actions, and subscribe to events using Zustand's subscribe.
*/
export const useEventStore = create<EventState>((set) => ({
patternDeletedCount: 0,
emitPatternDeleted: () => {
set((state) => ({ patternDeletedCount: state.patternDeletedCount + 1 }));
},
}));
/**
* Subscribe to pattern deleted event
* @param callback - Function to call when event is emitted
* @returns Unsubscribe function
*/
export const onPatternDeleted = (callback: () => void): (() => void) => {
let prevCount = useEventStore.getState().patternDeletedCount;
return useEventStore.subscribe((state) => {
if (state.patternDeletedCount !== prevCount) {
prevCount = state.patternDeletedCount;
callback();
}
});
};

View file

@ -1,6 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { onPatternDeleted } from "./storeEvents";
/** /**
* Machine Cache Store * Machine Cache Store
@ -192,3 +193,8 @@ export const useMachineCacheStore = create<MachineCacheState>((set, get) => ({
}); });
}, },
})); }));
// Subscribe to pattern deleted event
onPatternDeleted(() => {
useMachineCacheStore.getState().clearResumeState();
});

View file

@ -13,7 +13,7 @@ import { SewingMachineError } from "../utils/errorCodeHelpers";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { createStorageService } from "../platform"; import { createStorageService } from "../platform";
import type { IStorageService } from "../platform/interfaces/IStorageService"; import type { IStorageService } from "../platform/interfaces/IStorageService";
import { usePatternStore } from "./usePatternStore"; import { useEventStore } from "./storeEvents";
interface MachineState { interface MachineState {
// Service instances // Service instances
@ -291,16 +291,8 @@ export const useMachineStore = create<MachineState>((set, get) => ({
sewingProgress: null, sewingProgress: null,
}); });
// Clear uploaded pattern data in pattern store // Emit pattern deleted event for other stores to react
usePatternStore.getState().clearUploadedPattern(); useEventStore.getState().emitPatternDeleted();
// Clear upload state in upload store
const { useMachineUploadStore } = await import("./useMachineUploadStore");
useMachineUploadStore.getState().reset();
// Clear resume state in cache store
const { useMachineCacheStore } = await import("./useMachineCacheStore");
useMachineCacheStore.getState().clearResumeState();
await refreshStatus(); await refreshStatus();
} catch (err) { } catch (err) {

View file

@ -1,6 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { onPatternDeleted } from "./storeEvents";
/** /**
* Machine Upload Store * Machine Upload Store
@ -126,3 +127,8 @@ export const useMachineUploadStore = create<MachineUploadState>((set) => ({
set({ uploadProgress: 0, isUploading: false }); set({ uploadProgress: 0, isUploading: false });
}, },
})); }));
// Subscribe to pattern deleted event
onPatternDeleted(() => {
useMachineUploadStore.getState().reset();
});

View file

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { onPatternDeleted } from "./storeEvents";
interface PatternState { interface PatternState {
// Original pattern (pre-upload) // Original pattern (pre-upload)
@ -121,3 +122,8 @@ export const useUploadedPatternOffset = () =>
usePatternStore((state) => state.uploadedPatternOffset); usePatternStore((state) => state.uploadedPatternOffset);
export const usePatternRotation = () => export const usePatternRotation = () =>
usePatternStore((state) => state.patternRotation); usePatternStore((state) => state.patternRotation);
// Subscribe to pattern deleted event
onPatternDeleted(() => {
usePatternStore.getState().clearUploadedPattern();
});