@miurajs/miura-data-flow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/store.ts ADDED
@@ -0,0 +1,237 @@
1
+ import { debugLog } from '@miura/miura-debugger';
2
+
3
+ /**
4
+ * Store state interface
5
+ */
6
+ export interface StoreState {
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ /**
11
+ * Store actions interface
12
+ */
13
+ export type StoreAction<T extends StoreState> = (
14
+ state: T,
15
+ ...args: any[]
16
+ ) => Partial<T> | Promise<Partial<T>>;
17
+
18
+ export interface StoreActions<T extends StoreState> {
19
+ [key: string]: StoreAction<T>;
20
+ }
21
+
22
+ /**
23
+ * Store middleware interface
24
+ */
25
+ export interface StoreMiddleware {
26
+ name: string;
27
+ before?: (action: string, args: unknown[], state: StoreState) => void | Promise<void>;
28
+ after?: (action: string, args: unknown[], state: StoreState, result: unknown) => void | Promise<void>;
29
+ error?: (action: string, args: unknown[], error: Error) => void | Promise<void>;
30
+ }
31
+
32
+ /**
33
+ * Store subscriber
34
+ */
35
+ export interface StoreSubscriber {
36
+ id: string;
37
+ selector?: (state: StoreState) => unknown;
38
+ callback: (state: StoreState, prevState: StoreState) => void;
39
+ }
40
+
41
+ /**
42
+ * Modern Store Implementation
43
+ * Combines the best of Redux, Zustand, and modern patterns
44
+ */
45
+ export class Store<T extends StoreState = StoreState> {
46
+ private state: T;
47
+ private actions: StoreActions<T> = {};
48
+ private subscribers = new Map<string, StoreSubscriber>();
49
+ private middlewares: StoreMiddleware[] = [];
50
+ private isUpdating = false;
51
+ private updateQueue: Array<() => void> = [];
52
+ private subscriberIdCounter = 0;
53
+
54
+ constructor(initialState: T) {
55
+ this.state = { ...initialState };
56
+ }
57
+
58
+ /**
59
+ * Get current state
60
+ */
61
+ getState(): T {
62
+ return { ...this.state };
63
+ }
64
+
65
+ /**
66
+ * Get a specific property from state
67
+ */
68
+ get<K extends keyof T>(key: K): T[K] {
69
+ return this.state[key];
70
+ }
71
+
72
+ /**
73
+ * Set state (immutable update)
74
+ */
75
+ setState(updater: Partial<T> | ((state: T) => Partial<T>)): void {
76
+ const newState = typeof updater === 'function' ? updater(this.state) : updater;
77
+
78
+ this.updateState(newState as Partial<T>);
79
+ }
80
+
81
+ /**
82
+ * Define actions (like Redux actions but simpler)
83
+ */
84
+ defineActions(actions: StoreActions<T>): void {
85
+ this.actions = { ...this.actions, ...actions };
86
+ }
87
+
88
+ /**
89
+ * Execute an action
90
+ */
91
+ async dispatch(action: string, ...args: unknown[]): Promise<void> {
92
+ if (!this.actions[action]) {
93
+ throw new Error(`Action '${action}' not found`);
94
+ }
95
+
96
+ try {
97
+ // Run before middlewares
98
+ for (const middleware of this.middlewares) {
99
+ if (middleware.before) {
100
+ await middleware.before(action, args, this.state);
101
+ }
102
+ }
103
+
104
+ // Execute action and update state
105
+ const result = await this.actions[action](this.state, ...args);
106
+ this.updateState(result as Partial<T>);
107
+
108
+ // Run after middlewares
109
+ for (const middleware of this.middlewares) {
110
+ if (middleware.after) {
111
+ await middleware.after(action, args, this.state, result);
112
+ }
113
+ }
114
+ } catch (error) {
115
+ // Run error middlewares
116
+ for (const middleware of this.middlewares) {
117
+ if (middleware.error) {
118
+ await middleware.error(action, args, error as Error);
119
+ }
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Subscribe to state changes
127
+ */
128
+ subscribe(
129
+ callback: (state: T, prevState: T) => void,
130
+ selector?: (state: T) => unknown
131
+ ): () => void {
132
+ const id = `subscriber_${++this.subscriberIdCounter}`;
133
+ const subscriber: StoreSubscriber = {
134
+ id,
135
+ selector: selector as (state: StoreState) => unknown,
136
+ callback: callback as (state: StoreState, prevState: StoreState) => void
137
+ };
138
+
139
+ this.subscribers.set(id, subscriber);
140
+ debugLog('element', 'Subscribed to store', { id, hasSelector: !!selector });
141
+
142
+ // Return unsubscribe function
143
+ return () => {
144
+ this.subscribers.delete(id);
145
+ debugLog('element', 'Unsubscribed from store', { id });
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Subscribe to specific property changes
151
+ */
152
+ subscribeTo<K extends keyof T>(
153
+ key: K,
154
+ callback: (value: T[K], prevValue: T[K]) => void
155
+ ): () => void {
156
+ return this.subscribe(
157
+ (state, prevState) => {
158
+ if (state[key] !== prevState[key]) {
159
+ callback(state[key], prevState[key]);
160
+ }
161
+ },
162
+ (state) => state[key]
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Add middleware
168
+ */
169
+ use(middleware: StoreMiddleware): void {
170
+ this.middlewares.push(middleware);
171
+ debugLog('element', 'Added middleware', { name: middleware.name });
172
+ }
173
+
174
+ /**
175
+ * Update state and notify subscribers
176
+ */
177
+ private updateState(updater: Partial<T>): void {
178
+ if (this.isUpdating) {
179
+ // Queue update if already updating
180
+ this.updateQueue.push(() => this.updateState(updater));
181
+ return;
182
+ }
183
+
184
+ this.isUpdating = true;
185
+ const prevState = { ...this.state };
186
+
187
+ // Apply updates
188
+ this.state = { ...this.state, ...updater };
189
+
190
+ // Notify subscribers
191
+ this.notifySubscribers(prevState);
192
+
193
+ this.isUpdating = false;
194
+
195
+ // Process queued updates
196
+ while (this.updateQueue.length > 0) {
197
+ const queuedUpdate = this.updateQueue.shift();
198
+ if (queuedUpdate) {
199
+ queuedUpdate();
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Notify all subscribers
206
+ */
207
+ private notifySubscribers(prevState: T): void {
208
+ for (const subscriber of this.subscribers.values()) {
209
+ try {
210
+ if (subscriber.selector) {
211
+ const currentValue = subscriber.selector(this.state);
212
+ const prevValue = subscriber.selector(prevState);
213
+
214
+ if (currentValue !== prevValue) {
215
+ subscriber.callback(this.state, prevState);
216
+ }
217
+ } else {
218
+ subscriber.callback(this.state, prevState);
219
+ }
220
+ } catch (error) {
221
+ console.error(`Error in store subscriber ${subscriber.id}:`, error);
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Get debug information
228
+ */
229
+ getDebugInfo() {
230
+ return {
231
+ state: this.getState(),
232
+ actions: Object.keys(this.actions),
233
+ subscribers: this.subscribers.size,
234
+ middlewares: this.middlewares.map(m => m.name)
235
+ };
236
+ }
237
+ }