pulse-js-framework 1.0.0

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.
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Pulse Store - Global state management
3
+ *
4
+ * A simple but powerful store that integrates with Pulse reactivity
5
+ */
6
+
7
+ import { pulse, computed, effect, batch } from './pulse.js';
8
+
9
+ /**
10
+ * Create a global store
11
+ */
12
+ export function createStore(initialState = {}, options = {}) {
13
+ const { persist = false, storageKey = 'pulse-store' } = options;
14
+
15
+ // Load persisted state if enabled
16
+ let state = initialState;
17
+ if (persist && typeof localStorage !== 'undefined') {
18
+ try {
19
+ const saved = localStorage.getItem(storageKey);
20
+ if (saved) {
21
+ state = { ...initialState, ...JSON.parse(saved) };
22
+ }
23
+ } catch (e) {
24
+ console.warn('Failed to load persisted state:', e);
25
+ }
26
+ }
27
+
28
+ // Create pulses for each state property
29
+ const pulses = {};
30
+ const store = {};
31
+
32
+ function createPulse(key, value) {
33
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
34
+ // Nested object - create nested store
35
+ const nested = {};
36
+ for (const [k, v] of Object.entries(value)) {
37
+ nested[k] = createPulse(`${key}.${k}`, v);
38
+ }
39
+ return nested;
40
+ }
41
+
42
+ const p = pulse(value);
43
+ pulses[key] = p;
44
+ return p;
45
+ }
46
+
47
+ // Initialize state
48
+ for (const [key, value] of Object.entries(state)) {
49
+ store[key] = createPulse(key, value);
50
+ }
51
+
52
+ // Persist state changes
53
+ if (persist) {
54
+ effect(() => {
55
+ const snapshot = {};
56
+ for (const [key, p] of Object.entries(pulses)) {
57
+ snapshot[key] = p.get();
58
+ }
59
+ try {
60
+ localStorage.setItem(storageKey, JSON.stringify(snapshot));
61
+ } catch (e) {
62
+ console.warn('Failed to persist state:', e);
63
+ }
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Get a snapshot of the current state
69
+ */
70
+ function getState() {
71
+ const snapshot = {};
72
+ for (const [key, p] of Object.entries(pulses)) {
73
+ snapshot[key] = p.peek();
74
+ }
75
+ return snapshot;
76
+ }
77
+
78
+ /**
79
+ * Set multiple values at once
80
+ */
81
+ function setState(updates) {
82
+ batch(() => {
83
+ for (const [key, value] of Object.entries(updates)) {
84
+ if (pulses[key]) {
85
+ pulses[key].set(value);
86
+ }
87
+ }
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Reset state to initial values
93
+ */
94
+ function reset() {
95
+ batch(() => {
96
+ for (const [key, value] of Object.entries(initialState)) {
97
+ if (pulses[key]) {
98
+ pulses[key].set(value);
99
+ }
100
+ }
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Subscribe to all state changes
106
+ */
107
+ function subscribe(callback) {
108
+ return effect(() => {
109
+ const state = getState();
110
+ callback(state);
111
+ });
112
+ }
113
+
114
+ // Attach methods to store
115
+ store.$getState = getState;
116
+ store.$setState = setState;
117
+ store.$reset = reset;
118
+ store.$subscribe = subscribe;
119
+ store.$pulses = pulses;
120
+
121
+ return store;
122
+ }
123
+
124
+ /**
125
+ * Create actions that can modify the store
126
+ */
127
+ export function createActions(store, actions) {
128
+ const boundActions = {};
129
+
130
+ for (const [name, action] of Object.entries(actions)) {
131
+ boundActions[name] = (...args) => {
132
+ return action(store, ...args);
133
+ };
134
+ }
135
+
136
+ return boundActions;
137
+ }
138
+
139
+ /**
140
+ * Create getters (computed values) for the store
141
+ */
142
+ export function createGetters(store, getters) {
143
+ const boundGetters = {};
144
+
145
+ for (const [name, getter] of Object.entries(getters)) {
146
+ boundGetters[name] = computed(() => getter(store));
147
+ }
148
+
149
+ return boundGetters;
150
+ }
151
+
152
+ /**
153
+ * Combine multiple stores
154
+ */
155
+ export function combineStores(stores) {
156
+ const combined = {};
157
+
158
+ for (const [namespace, store] of Object.entries(stores)) {
159
+ combined[namespace] = store;
160
+ }
161
+
162
+ return combined;
163
+ }
164
+
165
+ /**
166
+ * Create a module-based store (like Vuex modules)
167
+ */
168
+ export function createModuleStore(modules) {
169
+ const stores = {};
170
+ const rootStore = {};
171
+
172
+ for (const [name, module] of Object.entries(modules)) {
173
+ const { state = {}, actions = {}, getters = {} } = module;
174
+
175
+ const store = createStore(state);
176
+ const boundActions = createActions(store, actions);
177
+ const boundGetters = createGetters(store, getters);
178
+
179
+ stores[name] = {
180
+ ...store,
181
+ ...boundActions,
182
+ ...boundGetters
183
+ };
184
+
185
+ // Also expose at root level
186
+ rootStore[name] = stores[name];
187
+ }
188
+
189
+ // Root methods
190
+ rootStore.$getState = () => {
191
+ const state = {};
192
+ for (const [name, store] of Object.entries(stores)) {
193
+ state[name] = store.$getState();
194
+ }
195
+ return state;
196
+ };
197
+
198
+ rootStore.$reset = () => {
199
+ for (const store of Object.values(stores)) {
200
+ store.$reset();
201
+ }
202
+ };
203
+
204
+ return rootStore;
205
+ }
206
+
207
+ /**
208
+ * Plugin system for store
209
+ */
210
+ export function usePlugin(store, plugin) {
211
+ return plugin(store);
212
+ }
213
+
214
+ /**
215
+ * Logger plugin - logs all state changes
216
+ */
217
+ export function loggerPlugin(store) {
218
+ const originalSetState = store.$setState;
219
+
220
+ store.$setState = (updates) => {
221
+ console.group('Store Update');
222
+ console.log('Previous:', store.$getState());
223
+ console.log('Updates:', updates);
224
+ originalSetState(updates);
225
+ console.log('Next:', store.$getState());
226
+ console.groupEnd();
227
+ };
228
+
229
+ return store;
230
+ }
231
+
232
+ /**
233
+ * History plugin - enables undo/redo
234
+ */
235
+ export function historyPlugin(store, maxHistory = 50) {
236
+ const history = [store.$getState()];
237
+ let currentIndex = 0;
238
+
239
+ const originalSetState = store.$setState;
240
+
241
+ store.$setState = (updates) => {
242
+ // Remove any future states if we're not at the end
243
+ if (currentIndex < history.length - 1) {
244
+ history.splice(currentIndex + 1);
245
+ }
246
+
247
+ originalSetState(updates);
248
+
249
+ // Add new state to history
250
+ history.push(store.$getState());
251
+ if (history.length > maxHistory) {
252
+ history.shift();
253
+ } else {
254
+ currentIndex++;
255
+ }
256
+ };
257
+
258
+ store.$undo = () => {
259
+ if (currentIndex > 0) {
260
+ currentIndex--;
261
+ const state = history[currentIndex];
262
+ batch(() => {
263
+ for (const [key, value] of Object.entries(state)) {
264
+ if (store.$pulses[key]) {
265
+ store.$pulses[key].set(value);
266
+ }
267
+ }
268
+ });
269
+ }
270
+ };
271
+
272
+ store.$redo = () => {
273
+ if (currentIndex < history.length - 1) {
274
+ currentIndex++;
275
+ const state = history[currentIndex];
276
+ batch(() => {
277
+ for (const [key, value] of Object.entries(state)) {
278
+ if (store.$pulses[key]) {
279
+ store.$pulses[key].set(value);
280
+ }
281
+ }
282
+ });
283
+ }
284
+ };
285
+
286
+ store.$canUndo = () => currentIndex > 0;
287
+ store.$canRedo = () => currentIndex < history.length - 1;
288
+
289
+ return store;
290
+ }
291
+
292
+ export default {
293
+ createStore,
294
+ createActions,
295
+ createGetters,
296
+ combineStores,
297
+ createModuleStore,
298
+ usePlugin,
299
+ loggerPlugin,
300
+ historyPlugin
301
+ };