voltjs-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.
- package/LICENSE +21 -0
- package/README.md +1265 -0
- package/bin/volt.js +139 -0
- package/package.json +56 -0
- package/src/api/graphql.js +399 -0
- package/src/api/rest.js +204 -0
- package/src/api/websocket.js +285 -0
- package/src/cli/build.js +111 -0
- package/src/cli/create.js +371 -0
- package/src/cli/db.js +106 -0
- package/src/cli/dev.js +114 -0
- package/src/cli/generate.js +278 -0
- package/src/cli/lint.js +172 -0
- package/src/cli/routes.js +118 -0
- package/src/cli/start.js +42 -0
- package/src/cli/test.js +138 -0
- package/src/core/app.js +701 -0
- package/src/core/config.js +232 -0
- package/src/core/middleware.js +133 -0
- package/src/core/plugins.js +88 -0
- package/src/core/react-renderer.js +244 -0
- package/src/core/renderer.js +337 -0
- package/src/core/router.js +183 -0
- package/src/database/index.js +461 -0
- package/src/database/migration.js +192 -0
- package/src/database/model.js +285 -0
- package/src/database/query.js +394 -0
- package/src/database/seeder.js +89 -0
- package/src/index.js +156 -0
- package/src/security/auth.js +425 -0
- package/src/security/cors.js +80 -0
- package/src/security/csrf.js +125 -0
- package/src/security/encryption.js +110 -0
- package/src/security/helmet.js +103 -0
- package/src/security/index.js +75 -0
- package/src/security/rateLimit.js +119 -0
- package/src/security/sanitizer.js +113 -0
- package/src/security/xss.js +110 -0
- package/src/ui/component.js +224 -0
- package/src/ui/reactive.js +503 -0
- package/src/ui/template.js +448 -0
- package/src/utils/cache.js +216 -0
- package/src/utils/collection.js +772 -0
- package/src/utils/cron.js +213 -0
- package/src/utils/date.js +223 -0
- package/src/utils/events.js +181 -0
- package/src/utils/excel.js +482 -0
- package/src/utils/form.js +547 -0
- package/src/utils/hash.js +121 -0
- package/src/utils/http.js +461 -0
- package/src/utils/logger.js +186 -0
- package/src/utils/mail.js +347 -0
- package/src/utils/paginator.js +179 -0
- package/src/utils/pdf.js +417 -0
- package/src/utils/queue.js +199 -0
- package/src/utils/schema.js +985 -0
- package/src/utils/sms.js +243 -0
- package/src/utils/storage.js +348 -0
- package/src/utils/string.js +236 -0
- package/src/utils/validation.js +318 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS Reactive System
|
|
3
|
+
*
|
|
4
|
+
* Fine-grained reactivity engine (inspired by Solid/Svelte signals).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const { Reactive } = require('voltjs');
|
|
8
|
+
*
|
|
9
|
+
* const count = Reactive.signal(0);
|
|
10
|
+
* const doubled = Reactive.computed(() => count.value * 2);
|
|
11
|
+
*
|
|
12
|
+
* Reactive.effect(() => {
|
|
13
|
+
* console.log(`Count: ${count.value}, Doubled: ${doubled.value}`);
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* count.value = 5; // Logs: "Count: 5, Doubled: 10"
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
let currentEffect = null;
|
|
22
|
+
const effectQueue = [];
|
|
23
|
+
let batchDepth = 0;
|
|
24
|
+
let pendingEffects = new Set();
|
|
25
|
+
|
|
26
|
+
class Signal {
|
|
27
|
+
constructor(value) {
|
|
28
|
+
this._value = value;
|
|
29
|
+
this._subscribers = new Set();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get value() {
|
|
33
|
+
// Track dependency
|
|
34
|
+
if (currentEffect) {
|
|
35
|
+
this._subscribers.add(currentEffect);
|
|
36
|
+
currentEffect._deps.add(this);
|
|
37
|
+
}
|
|
38
|
+
return this._value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set value(newValue) {
|
|
42
|
+
if (Object.is(this._value, newValue)) return;
|
|
43
|
+
this._value = newValue;
|
|
44
|
+
this._notify();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Update value with function */
|
|
48
|
+
update(fn) {
|
|
49
|
+
this.value = fn(this._value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Peek at value without tracking */
|
|
53
|
+
peek() {
|
|
54
|
+
return this._value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_notify() {
|
|
58
|
+
for (const effect of this._subscribers) {
|
|
59
|
+
if (batchDepth > 0) {
|
|
60
|
+
pendingEffects.add(effect);
|
|
61
|
+
} else {
|
|
62
|
+
effect._run();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toString() { return String(this._value); }
|
|
68
|
+
toJSON() { return this._value; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class Computed {
|
|
72
|
+
constructor(fn) {
|
|
73
|
+
this._fn = fn;
|
|
74
|
+
this._value = undefined;
|
|
75
|
+
this._dirty = true;
|
|
76
|
+
this._subscribers = new Set();
|
|
77
|
+
this._deps = new Set();
|
|
78
|
+
|
|
79
|
+
// Set up effect to track dependencies
|
|
80
|
+
this._effect = new Effect(() => {
|
|
81
|
+
this._value = this._fn();
|
|
82
|
+
this._dirty = false;
|
|
83
|
+
// Notify our subscribers
|
|
84
|
+
for (const sub of this._subscribers) {
|
|
85
|
+
if (batchDepth > 0) {
|
|
86
|
+
pendingEffects.add(sub);
|
|
87
|
+
} else {
|
|
88
|
+
sub._run();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
this._effect._run();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get value() {
|
|
96
|
+
if (currentEffect) {
|
|
97
|
+
this._subscribers.add(currentEffect);
|
|
98
|
+
currentEffect._deps.add(this);
|
|
99
|
+
}
|
|
100
|
+
return this._value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
peek() { return this._value; }
|
|
104
|
+
toString() { return String(this._value); }
|
|
105
|
+
toJSON() { return this._value; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class Effect {
|
|
109
|
+
constructor(fn) {
|
|
110
|
+
this._fn = fn;
|
|
111
|
+
this._deps = new Set();
|
|
112
|
+
this._active = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_run() {
|
|
116
|
+
if (!this._active) return;
|
|
117
|
+
|
|
118
|
+
// Cleanup old deps
|
|
119
|
+
for (const dep of this._deps) {
|
|
120
|
+
dep._subscribers.delete(this);
|
|
121
|
+
}
|
|
122
|
+
this._deps.clear();
|
|
123
|
+
|
|
124
|
+
// Run with tracking
|
|
125
|
+
const prevEffect = currentEffect;
|
|
126
|
+
currentEffect = this;
|
|
127
|
+
try {
|
|
128
|
+
this._fn();
|
|
129
|
+
} finally {
|
|
130
|
+
currentEffect = prevEffect;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dispose() {
|
|
135
|
+
this._active = false;
|
|
136
|
+
for (const dep of this._deps) {
|
|
137
|
+
dep._subscribers.delete(this);
|
|
138
|
+
}
|
|
139
|
+
this._deps.clear();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class Reactive {
|
|
144
|
+
/** Create a reactive signal */
|
|
145
|
+
static signal(initialValue) {
|
|
146
|
+
return new Signal(initialValue);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Create a computed value */
|
|
150
|
+
static computed(fn) {
|
|
151
|
+
return new Computed(fn);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Create a side effect that re-runs when dependencies change */
|
|
155
|
+
static effect(fn) {
|
|
156
|
+
const effect = new Effect(fn);
|
|
157
|
+
effect._run();
|
|
158
|
+
return () => effect.dispose(); // Return cleanup function
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Batch multiple updates (single re-render) */
|
|
162
|
+
static batch(fn) {
|
|
163
|
+
batchDepth++;
|
|
164
|
+
try {
|
|
165
|
+
fn();
|
|
166
|
+
} finally {
|
|
167
|
+
batchDepth--;
|
|
168
|
+
if (batchDepth === 0) {
|
|
169
|
+
const effects = [...pendingEffects];
|
|
170
|
+
pendingEffects.clear();
|
|
171
|
+
for (const effect of effects) {
|
|
172
|
+
effect._run();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Create a reactive store (object with reactive properties) */
|
|
179
|
+
static store(initialState) {
|
|
180
|
+
const signals = {};
|
|
181
|
+
const store = {};
|
|
182
|
+
|
|
183
|
+
for (const [key, value] of Object.entries(initialState)) {
|
|
184
|
+
signals[key] = new Signal(value);
|
|
185
|
+
Object.defineProperty(store, key, {
|
|
186
|
+
get: () => signals[key].value,
|
|
187
|
+
set: (v) => { signals[key].value = v; },
|
|
188
|
+
enumerable: true,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
store.$set = (key, value) => {
|
|
193
|
+
if (!signals[key]) {
|
|
194
|
+
signals[key] = new Signal(value);
|
|
195
|
+
Object.defineProperty(store, key, {
|
|
196
|
+
get: () => signals[key].value,
|
|
197
|
+
set: (v) => { signals[key].value = v; },
|
|
198
|
+
enumerable: true,
|
|
199
|
+
configurable: true,
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
signals[key].value = value;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
store.$snapshot = () => {
|
|
207
|
+
const obj = {};
|
|
208
|
+
for (const [key, sig] of Object.entries(signals)) {
|
|
209
|
+
obj[key] = sig.peek();
|
|
210
|
+
}
|
|
211
|
+
return obj;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
store.$watch = (key, fn) => {
|
|
215
|
+
return Reactive.effect(() => {
|
|
216
|
+
const val = signals[key]?.value;
|
|
217
|
+
fn(val);
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
store.$reset = (state) => {
|
|
222
|
+
for (const [key, value] of Object.entries(state)) {
|
|
223
|
+
if (signals[key]) signals[key].value = value;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return store;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Create a reactive list */
|
|
231
|
+
static list(initialItems = []) {
|
|
232
|
+
const items = Reactive.signal([...initialItems]);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
get value() { return items.value; },
|
|
236
|
+
get length() { return items.value.length; },
|
|
237
|
+
add(item) { items.value = [...items.value, item]; },
|
|
238
|
+
remove(index) { items.value = items.value.filter((_, i) => i !== index); },
|
|
239
|
+
removeWhere(fn) { items.value = items.value.filter((item) => !fn(item)); },
|
|
240
|
+
update(index, item) {
|
|
241
|
+
const arr = [...items.value];
|
|
242
|
+
arr[index] = typeof item === 'function' ? item(arr[index]) : item;
|
|
243
|
+
items.value = arr;
|
|
244
|
+
},
|
|
245
|
+
clear() { items.value = []; },
|
|
246
|
+
find(fn) { return items.value.find(fn); },
|
|
247
|
+
filter(fn) { return items.value.filter(fn); },
|
|
248
|
+
map(fn) { return items.value.map(fn); },
|
|
249
|
+
sort(fn) { items.value = [...items.value].sort(fn); },
|
|
250
|
+
toArray() { return [...items.value]; },
|
|
251
|
+
[Symbol.iterator]() { return items.value[Symbol.iterator](); },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Untrack — read without creating dependency */
|
|
256
|
+
static untrack(fn) {
|
|
257
|
+
const prevEffect = currentEffect;
|
|
258
|
+
currentEffect = null;
|
|
259
|
+
try {
|
|
260
|
+
return fn();
|
|
261
|
+
} finally {
|
|
262
|
+
currentEffect = prevEffect;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Create a derived signal from multiple sources */
|
|
267
|
+
static derive(sources, fn) {
|
|
268
|
+
return Reactive.computed(() => {
|
|
269
|
+
const values = sources.map(s => s.value);
|
|
270
|
+
return fn(...values);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===== ADVANCED STATE MANAGEMENT =====
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create a store with actions & middleware (Redux-like)
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* const counter = Reactive.createStore({
|
|
281
|
+
* state: { count: 0 },
|
|
282
|
+
* actions: {
|
|
283
|
+
* increment(state) { state.count++; },
|
|
284
|
+
* add(state, amount) { state.count += amount; },
|
|
285
|
+
* },
|
|
286
|
+
* middleware: [loggerMiddleware],
|
|
287
|
+
* });
|
|
288
|
+
*
|
|
289
|
+
* counter.dispatch('increment');
|
|
290
|
+
* counter.dispatch('add', 5);
|
|
291
|
+
* counter.select(s => s.count); // Computed selector
|
|
292
|
+
*/
|
|
293
|
+
static createStore(config = {}) {
|
|
294
|
+
const { state: initialState = {}, actions = {}, middleware = [], persist = null } = config;
|
|
295
|
+
|
|
296
|
+
// Load persisted state if available
|
|
297
|
+
let loadedState = { ...initialState };
|
|
298
|
+
if (persist) {
|
|
299
|
+
try {
|
|
300
|
+
const fs = require('fs');
|
|
301
|
+
const key = persist.key || 'volt_store';
|
|
302
|
+
if (persist.storage === 'file' && persist.path) {
|
|
303
|
+
if (fs.existsSync(persist.path)) {
|
|
304
|
+
const saved = JSON.parse(fs.readFileSync(persist.path, 'utf-8'));
|
|
305
|
+
loadedState = { ...loadedState, ...saved };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch { /* ignore */ }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Create reactive signals for each state key
|
|
312
|
+
const signals = {};
|
|
313
|
+
const storeObj = {};
|
|
314
|
+
|
|
315
|
+
for (const [key, value] of Object.entries(loadedState)) {
|
|
316
|
+
signals[key] = new Signal(value);
|
|
317
|
+
Object.defineProperty(storeObj, key, {
|
|
318
|
+
get: () => signals[key].value,
|
|
319
|
+
set: (v) => { signals[key].value = v; },
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// History for undo/redo
|
|
326
|
+
const history = [];
|
|
327
|
+
const future = [];
|
|
328
|
+
let historyEnabled = config.history !== false;
|
|
329
|
+
const maxHistory = config.maxHistory || 50;
|
|
330
|
+
|
|
331
|
+
const _snapshot = () => {
|
|
332
|
+
const obj = {};
|
|
333
|
+
for (const [k, sig] of Object.entries(signals)) obj[k] = sig.peek();
|
|
334
|
+
return JSON.parse(JSON.stringify(obj));
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const _restore = (snapshot) => {
|
|
338
|
+
Reactive.batch(() => {
|
|
339
|
+
for (const [k, v] of Object.entries(snapshot)) {
|
|
340
|
+
if (signals[k]) signals[k].value = v;
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const _persist = () => {
|
|
346
|
+
if (!persist) return;
|
|
347
|
+
try {
|
|
348
|
+
const fs = require('fs');
|
|
349
|
+
const data = JSON.stringify(_snapshot());
|
|
350
|
+
if (persist.storage === 'file' && persist.path) {
|
|
351
|
+
const path = require('path');
|
|
352
|
+
const dir = path.dirname(persist.path);
|
|
353
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
354
|
+
fs.writeFileSync(persist.path, data);
|
|
355
|
+
}
|
|
356
|
+
} catch { /* ignore */ }
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/** Dispatch an action by name */
|
|
360
|
+
storeObj.dispatch = (actionName, ...args) => {
|
|
361
|
+
const action = actions[actionName];
|
|
362
|
+
if (!action) throw new Error(`Unknown action: ${actionName}`);
|
|
363
|
+
|
|
364
|
+
// Run middleware (before)
|
|
365
|
+
const ctx = { store: storeObj, action: actionName, args, state: _snapshot() };
|
|
366
|
+
for (const mw of middleware) {
|
|
367
|
+
if (mw.before) mw.before(ctx);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Save to history
|
|
371
|
+
if (historyEnabled) {
|
|
372
|
+
history.push(_snapshot());
|
|
373
|
+
if (history.length > maxHistory) history.shift();
|
|
374
|
+
future.length = 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Execute action — pass a mutable proxy
|
|
378
|
+
const proxy = {};
|
|
379
|
+
for (const [k, sig] of Object.entries(signals)) {
|
|
380
|
+
Object.defineProperty(proxy, k, {
|
|
381
|
+
get: () => sig.peek(),
|
|
382
|
+
set: (v) => { sig.value = v; },
|
|
383
|
+
enumerable: true,
|
|
384
|
+
configurable: true,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
Reactive.batch(() => {
|
|
389
|
+
const result = action(proxy, ...args);
|
|
390
|
+
// Sync proxy writes back (already done via signal set)
|
|
391
|
+
return result;
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Run middleware (after)
|
|
395
|
+
const afterCtx = { ...ctx, newState: _snapshot() };
|
|
396
|
+
for (const mw of middleware) {
|
|
397
|
+
if (mw.after) mw.after(afterCtx);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
_persist();
|
|
401
|
+
return storeObj;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/** Create a memoized selector */
|
|
405
|
+
storeObj.select = (selectorFn) => {
|
|
406
|
+
return Reactive.computed(() => selectorFn(storeObj));
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/** Watch a specific key */
|
|
410
|
+
storeObj.$watch = (key, fn) => {
|
|
411
|
+
return Reactive.effect(() => {
|
|
412
|
+
const val = signals[key]?.value;
|
|
413
|
+
fn(val);
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/** Watch multiple keys */
|
|
418
|
+
storeObj.$watchMany = (keys, fn) => {
|
|
419
|
+
return Reactive.effect(() => {
|
|
420
|
+
const vals = {};
|
|
421
|
+
for (const k of keys) vals[k] = signals[k]?.value;
|
|
422
|
+
fn(vals);
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/** Get a snapshot of current state */
|
|
427
|
+
storeObj.$snapshot = _snapshot;
|
|
428
|
+
|
|
429
|
+
/** Reset state to initial values */
|
|
430
|
+
storeObj.$reset = () => {
|
|
431
|
+
_restore(initialState);
|
|
432
|
+
history.length = 0;
|
|
433
|
+
future.length = 0;
|
|
434
|
+
_persist();
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/** Set a new key dynamically */
|
|
438
|
+
storeObj.$set = (key, value) => {
|
|
439
|
+
if (!signals[key]) {
|
|
440
|
+
signals[key] = new Signal(value);
|
|
441
|
+
Object.defineProperty(storeObj, key, {
|
|
442
|
+
get: () => signals[key].value,
|
|
443
|
+
set: (v) => { signals[key].value = v; },
|
|
444
|
+
enumerable: true,
|
|
445
|
+
configurable: true,
|
|
446
|
+
});
|
|
447
|
+
} else {
|
|
448
|
+
signals[key].value = value;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/** Undo last action */
|
|
453
|
+
storeObj.$undo = () => {
|
|
454
|
+
if (history.length === 0) return false;
|
|
455
|
+
future.push(_snapshot());
|
|
456
|
+
_restore(history.pop());
|
|
457
|
+
return true;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/** Redo last undone action */
|
|
461
|
+
storeObj.$redo = () => {
|
|
462
|
+
if (future.length === 0) return false;
|
|
463
|
+
history.push(_snapshot());
|
|
464
|
+
_restore(future.pop());
|
|
465
|
+
return true;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/** Check if undo/redo is available */
|
|
469
|
+
storeObj.$canUndo = () => history.length > 0;
|
|
470
|
+
storeObj.$canRedo = () => future.length > 0;
|
|
471
|
+
|
|
472
|
+
/** Subscribe to all state changes */
|
|
473
|
+
storeObj.$subscribe = (fn) => {
|
|
474
|
+
const disposers = [];
|
|
475
|
+
for (const key of Object.keys(signals)) {
|
|
476
|
+
disposers.push(Reactive.effect(() => {
|
|
477
|
+
const val = signals[key].value;
|
|
478
|
+
fn(key, val, _snapshot());
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
return () => disposers.forEach(d => d());
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
return storeObj;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Built-in logger middleware for createStore
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* Reactive.createStore({ middleware: [Reactive.loggerMiddleware] })
|
|
492
|
+
*/
|
|
493
|
+
static loggerMiddleware = {
|
|
494
|
+
before(ctx) {
|
|
495
|
+
console.log(`[Store] Action: ${ctx.action}`, ctx.args, 'State:', ctx.state);
|
|
496
|
+
},
|
|
497
|
+
after(ctx) {
|
|
498
|
+
console.log(`[Store] After ${ctx.action}`, 'New State:', ctx.newState);
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
module.exports = { Reactive, Signal, Computed, Effect };
|