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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1265 -0
  3. package/bin/volt.js +139 -0
  4. package/package.json +56 -0
  5. package/src/api/graphql.js +399 -0
  6. package/src/api/rest.js +204 -0
  7. package/src/api/websocket.js +285 -0
  8. package/src/cli/build.js +111 -0
  9. package/src/cli/create.js +371 -0
  10. package/src/cli/db.js +106 -0
  11. package/src/cli/dev.js +114 -0
  12. package/src/cli/generate.js +278 -0
  13. package/src/cli/lint.js +172 -0
  14. package/src/cli/routes.js +118 -0
  15. package/src/cli/start.js +42 -0
  16. package/src/cli/test.js +138 -0
  17. package/src/core/app.js +701 -0
  18. package/src/core/config.js +232 -0
  19. package/src/core/middleware.js +133 -0
  20. package/src/core/plugins.js +88 -0
  21. package/src/core/react-renderer.js +244 -0
  22. package/src/core/renderer.js +337 -0
  23. package/src/core/router.js +183 -0
  24. package/src/database/index.js +461 -0
  25. package/src/database/migration.js +192 -0
  26. package/src/database/model.js +285 -0
  27. package/src/database/query.js +394 -0
  28. package/src/database/seeder.js +89 -0
  29. package/src/index.js +156 -0
  30. package/src/security/auth.js +425 -0
  31. package/src/security/cors.js +80 -0
  32. package/src/security/csrf.js +125 -0
  33. package/src/security/encryption.js +110 -0
  34. package/src/security/helmet.js +103 -0
  35. package/src/security/index.js +75 -0
  36. package/src/security/rateLimit.js +119 -0
  37. package/src/security/sanitizer.js +113 -0
  38. package/src/security/xss.js +110 -0
  39. package/src/ui/component.js +224 -0
  40. package/src/ui/reactive.js +503 -0
  41. package/src/ui/template.js +448 -0
  42. package/src/utils/cache.js +216 -0
  43. package/src/utils/collection.js +772 -0
  44. package/src/utils/cron.js +213 -0
  45. package/src/utils/date.js +223 -0
  46. package/src/utils/events.js +181 -0
  47. package/src/utils/excel.js +482 -0
  48. package/src/utils/form.js +547 -0
  49. package/src/utils/hash.js +121 -0
  50. package/src/utils/http.js +461 -0
  51. package/src/utils/logger.js +186 -0
  52. package/src/utils/mail.js +347 -0
  53. package/src/utils/paginator.js +179 -0
  54. package/src/utils/pdf.js +417 -0
  55. package/src/utils/queue.js +199 -0
  56. package/src/utils/schema.js +985 -0
  57. package/src/utils/sms.js +243 -0
  58. package/src/utils/storage.js +348 -0
  59. package/src/utils/string.js +236 -0
  60. 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 };