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,113 @@
1
+ /**
2
+ * VoltJS Input Sanitizer
3
+ *
4
+ * Comprehensive input sanitization to prevent injection attacks.
5
+ * Handles SQL injection, NoSQL injection, path traversal, and more.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ class InputSanitizer {
11
+ /** Sanitize request (body, query, params) */
12
+ static sanitizeRequest(req) {
13
+ if (req.body) req.body = InputSanitizer.sanitize(req.body);
14
+ if (req.query) req.query = InputSanitizer.sanitize(req.query);
15
+ if (req.params) req.params = InputSanitizer.sanitize(req.params);
16
+ }
17
+
18
+ /** Sanitize any value recursively */
19
+ static sanitize(value) {
20
+ if (typeof value === 'string') {
21
+ return InputSanitizer.sanitizeString(value);
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map(v => InputSanitizer.sanitize(v));
25
+ }
26
+ if (value && typeof value === 'object') {
27
+ const result = {};
28
+ for (const [key, val] of Object.entries(value)) {
29
+ // Prevent NoSQL injection via operator keys
30
+ const cleanKey = key.startsWith('$') ? key.slice(1) : key;
31
+ result[cleanKey] = InputSanitizer.sanitize(val);
32
+ }
33
+ return result;
34
+ }
35
+ return value;
36
+ }
37
+
38
+ /** Sanitize a string */
39
+ static sanitizeString(str) {
40
+ return str
41
+ // Remove null bytes
42
+ .replace(/\0/g, '')
43
+ // Prevent path traversal
44
+ .replace(/\.\.\//g, '')
45
+ .replace(/\.\.\\/g, '')
46
+ // Remove common SQL injection patterns
47
+ .replace(/('|--|;|\/\*|\*\/|xp_|UNION\s+SELECT|INSERT\s+INTO|DROP\s+TABLE|DELETE\s+FROM|UPDATE\s+.+\s+SET)/gi, '')
48
+ // Remove NoSQL injection operators
49
+ .replace(/\$(?:gt|gte|lt|lte|ne|in|nin|and|or|not|exists|regex|where|elemMatch)\b/gi, '');
50
+ }
51
+
52
+ /** Sanitize email */
53
+ static sanitizeEmail(email) {
54
+ return String(email)
55
+ .toLowerCase()
56
+ .trim()
57
+ .replace(/[^a-z0-9@._+-]/g, '');
58
+ }
59
+
60
+ /** Sanitize phone number */
61
+ static sanitizePhone(phone) {
62
+ return String(phone).replace(/[^0-9+\-() ]/g, '');
63
+ }
64
+
65
+ /** Sanitize filename */
66
+ static sanitizeFilename(filename) {
67
+ return String(filename)
68
+ .replace(/[^a-zA-Z0-9._-]/g, '_')
69
+ .replace(/\.{2,}/g, '.')
70
+ .replace(/^\./, '');
71
+ }
72
+
73
+ /** Sanitize URL */
74
+ static sanitizeUrl(urlStr) {
75
+ try {
76
+ const parsed = new URL(urlStr);
77
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
78
+ return '';
79
+ }
80
+ return parsed.toString();
81
+ } catch {
82
+ return '';
83
+ }
84
+ }
85
+
86
+ /** Sanitize integer */
87
+ static sanitizeInt(value, min = -Infinity, max = Infinity) {
88
+ const num = parseInt(value, 10);
89
+ if (isNaN(num)) return 0;
90
+ return Math.min(Math.max(num, min), max);
91
+ }
92
+
93
+ /** Sanitize float */
94
+ static sanitizeFloat(value, decimals = 2) {
95
+ const num = parseFloat(value);
96
+ if (isNaN(num)) return 0;
97
+ return parseFloat(num.toFixed(decimals));
98
+ }
99
+
100
+ /** Strip all HTML tags */
101
+ static stripTags(str) {
102
+ return String(str).replace(/<[^>]*>/g, '');
103
+ }
104
+
105
+ /** Middleware: Auto-sanitize all input */
106
+ static middleware() {
107
+ return (req) => {
108
+ InputSanitizer.sanitizeRequest(req);
109
+ };
110
+ }
111
+ }
112
+
113
+ module.exports = { InputSanitizer };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * VoltJS XSS Protection
3
+ *
4
+ * Comprehensive cross-site scripting prevention.
5
+ * Sanitizes all input data by default.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ class XSSProtection {
11
+ /** Sanitize a string to prevent XSS */
12
+ static sanitize(input) {
13
+ if (typeof input !== 'string') return input;
14
+
15
+ return input
16
+ // Remove script tags and content
17
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
18
+ // Remove event handlers
19
+ .replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
20
+ // Remove javascript: protocol
21
+ .replace(/javascript\s*:/gi, '')
22
+ // Remove data: protocol (can be used for XSS)
23
+ .replace(/data\s*:\s*text\/html/gi, '')
24
+ // Remove vbscript: protocol
25
+ .replace(/vbscript\s*:/gi, '')
26
+ // Encode dangerous characters
27
+ .replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&amp;')
28
+ .replace(/</g, '&lt;')
29
+ .replace(/>/g, '&gt;')
30
+ .replace(/"/g, '&quot;')
31
+ .replace(/'/g, '&#x27;');
32
+ }
33
+
34
+ /** Sanitize HTML but allow safe tags */
35
+ static sanitizeHtml(input, allowedTags = ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'span', 'div']) {
36
+ if (typeof input !== 'string') return input;
37
+
38
+ // First, remove script tags and event handlers
39
+ let result = input
40
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
41
+ .replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
42
+ .replace(/javascript\s*:/gi, '')
43
+ .replace(/vbscript\s*:/gi, '');
44
+
45
+ // Remove tags not in allowed list
46
+ const tagPattern = /<\/?(\w+)([^>]*)>/g;
47
+ result = result.replace(tagPattern, (match, tag, attrs) => {
48
+ if (allowedTags.includes(tag.toLowerCase())) {
49
+ // For allowed tags, sanitize attributes
50
+ const safeAttrs = attrs
51
+ .replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
52
+ .replace(/javascript\s*:/gi, '')
53
+ .replace(/style\s*=\s*(?:"[^"]*"|'[^']*')/gi, '');
54
+ return `<${match.startsWith('</') ? '/' : ''}${tag}${safeAttrs}>`;
55
+ }
56
+ return ''; // Remove disallowed tags
57
+ });
58
+
59
+ return result;
60
+ }
61
+
62
+ /** Sanitize an entire object recursively */
63
+ static sanitizeObject(obj) {
64
+ if (typeof obj === 'string') return XSSProtection.sanitize(obj);
65
+ if (Array.isArray(obj)) return obj.map(item => XSSProtection.sanitizeObject(item));
66
+ if (obj && typeof obj === 'object') {
67
+ const result = {};
68
+ for (const [key, value] of Object.entries(obj)) {
69
+ result[XSSProtection.sanitize(key)] = XSSProtection.sanitizeObject(value);
70
+ }
71
+ return result;
72
+ }
73
+ return obj;
74
+ }
75
+
76
+ /** Middleware: Auto-sanitize all input */
77
+ static middleware() {
78
+ return (req, res) => {
79
+ if (req.body && typeof req.body === 'object') {
80
+ req.body = XSSProtection.sanitizeObject(req.body);
81
+ }
82
+ if (req.query && typeof req.query === 'object') {
83
+ req.query = XSSProtection.sanitizeObject(req.query);
84
+ }
85
+ if (req.params && typeof req.params === 'object') {
86
+ req.params = XSSProtection.sanitizeObject(req.params);
87
+ }
88
+ };
89
+ }
90
+
91
+ /** Escape HTML entities */
92
+ static escape(str) {
93
+ const escapeMap = {
94
+ '&': '&amp;', '<': '&lt;', '>': '&gt;',
95
+ '"': '&quot;', "'": '&#39;', '`': '&#96;',
96
+ };
97
+ return String(str).replace(/[&<>"'`]/g, c => escapeMap[c]);
98
+ }
99
+
100
+ /** Unescape HTML entities */
101
+ static unescape(str) {
102
+ const unescapeMap = {
103
+ '&amp;': '&', '&lt;': '<', '&gt;': '>',
104
+ '&quot;': '"', '&#39;': "'", '&#96;': '`', '&#x27;': "'",
105
+ };
106
+ return String(str).replace(/&(?:amp|lt|gt|quot|#39|#96|#x27);/g, c => unescapeMap[c] || c);
107
+ }
108
+ }
109
+
110
+ module.exports = { XSSProtection };
@@ -0,0 +1,224 @@
1
+ /**
2
+ * VoltJS Component System
3
+ *
4
+ * Server-side component system with reactive state, lifecycle hooks,
5
+ * and template rendering.
6
+ *
7
+ * @example
8
+ * const { Component } = require('voltjs');
9
+ *
10
+ * class Counter extends Component {
11
+ * setup() {
12
+ * this.state = { count: 0 };
13
+ * }
14
+ *
15
+ * increment() { this.setState({ count: this.state.count + 1 }); }
16
+ *
17
+ * render() {
18
+ * return `
19
+ * <div class="counter">
20
+ * <span>Count: ${this.state.count}</span>
21
+ * <button volt-click="increment">+1</button>
22
+ * </div>
23
+ * `;
24
+ * }
25
+ * }
26
+ */
27
+
28
+ 'use strict';
29
+
30
+ const crypto = require('crypto');
31
+
32
+ class Component {
33
+ constructor(props = {}) {
34
+ this.props = Object.freeze({ ...props });
35
+ this.state = {};
36
+ this.children = '';
37
+ this._id = crypto.randomBytes(4).toString('hex');
38
+ this._mounted = false;
39
+ this._hooks = {
40
+ beforeMount: [],
41
+ mounted: [],
42
+ beforeUpdate: [],
43
+ updated: [],
44
+ beforeDestroy: [],
45
+ destroyed: [],
46
+ };
47
+
48
+ this.setup();
49
+ }
50
+
51
+ /** Override to initialize state and register hooks */
52
+ setup() {}
53
+
54
+ /** Override to return HTML string */
55
+ render() { return ''; }
56
+
57
+ /** Update state and trigger re-render */
58
+ setState(partial) {
59
+ this._runHooks('beforeUpdate');
60
+ this.state = { ...this.state, ...partial };
61
+ this._runHooks('updated');
62
+ }
63
+
64
+ /** Register lifecycle hooks */
65
+ onBeforeMount(fn) { this._hooks.beforeMount.push(fn); }
66
+ onMounted(fn) { this._hooks.mounted.push(fn); }
67
+ onBeforeUpdate(fn) { this._hooks.beforeUpdate.push(fn); }
68
+ onUpdated(fn) { this._hooks.updated.push(fn); }
69
+ onBeforeDestroy(fn) { this._hooks.beforeDestroy.push(fn); }
70
+ onDestroyed(fn) { this._hooks.destroyed.push(fn); }
71
+
72
+ /** Mount the component — returns full HTML */
73
+ mount() {
74
+ this._runHooks('beforeMount');
75
+ this._mounted = true;
76
+ const html = this.render();
77
+ this._runHooks('mounted');
78
+ return this._wrapComponent(html);
79
+ }
80
+
81
+ /** Destroy the component */
82
+ destroy() {
83
+ this._runHooks('beforeDestroy');
84
+ this._mounted = false;
85
+ this._runHooks('destroyed');
86
+ }
87
+
88
+ /** Convert to HTML string */
89
+ toString() {
90
+ return this.mount();
91
+ }
92
+
93
+ /** Get reactive client-side script */
94
+ clientScript() { return ''; }
95
+
96
+ // ===== HELPERS =====
97
+
98
+ /** Conditional rendering */
99
+ $if(condition, trueHtml, falseHtml = '') {
100
+ return condition ? trueHtml : falseHtml;
101
+ }
102
+
103
+ /** Loop rendering */
104
+ $each(items, callback) {
105
+ return items.map((item, index) => callback(item, index)).join('');
106
+ }
107
+
108
+ /** Slot rendering */
109
+ $slot(name, defaultContent = '') {
110
+ return this.props[`slot:${name}`] || defaultContent;
111
+ }
112
+
113
+ /** Escape HTML */
114
+ $escape(str) {
115
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
116
+ return String(str).replace(/[&<>"']/g, c => map[c]);
117
+ }
118
+
119
+ /** Render a child component */
120
+ $component(ComponentClass, props = {}) {
121
+ const child = new ComponentClass(props);
122
+ return child.mount();
123
+ }
124
+
125
+ /** Generate CSS class string from object */
126
+ $class(classes) {
127
+ if (typeof classes === 'string') return classes;
128
+ if (Array.isArray(classes)) return classes.filter(Boolean).join(' ');
129
+ return Object.entries(classes).filter(([, v]) => v).map(([k]) => k).join(' ');
130
+ }
131
+
132
+ /** Generate style string from object */
133
+ $style(styles) {
134
+ if (typeof styles === 'string') return styles;
135
+ return Object.entries(styles)
136
+ .filter(([, v]) => v !== null && v !== undefined)
137
+ .map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`)
138
+ .join('; ');
139
+ }
140
+
141
+ // ===== INTERNAL =====
142
+
143
+ _wrapComponent(html) {
144
+ return `<div data-volt-component="${this.constructor.name}" data-volt-id="${this._id}">${html}</div>`;
145
+ }
146
+
147
+ _runHooks(name) {
148
+ for (const hook of this._hooks[name]) {
149
+ hook.call(this);
150
+ }
151
+ }
152
+
153
+ // ===== STATIC HELPERS =====
154
+
155
+ /** Create a functional component */
156
+ static create(name, renderFn) {
157
+ const CompClass = class extends Component {
158
+ render() { return renderFn(this.props, this.state, this); }
159
+ };
160
+ Object.defineProperty(CompClass, 'name', { value: name });
161
+ return CompClass;
162
+ }
163
+
164
+ /** Render a component to HTML string */
165
+ static render(ComponentClass, props = {}) {
166
+ const instance = new ComponentClass(props);
167
+ return instance.mount();
168
+ }
169
+
170
+ /** Create a layout component */
171
+ static layout(templateFn) {
172
+ return class extends Component {
173
+ render() {
174
+ return templateFn({ ...this.props, children: this.children }, this);
175
+ }
176
+ };
177
+ }
178
+ }
179
+
180
+ /** Page component with head metadata */
181
+ class Page extends Component {
182
+ head() {
183
+ return { title: '', meta: [], links: [], scripts: [] };
184
+ }
185
+
186
+ renderHead() {
187
+ const h = this.head();
188
+ const parts = [];
189
+ if (h.title) parts.push(`<title>${this.$escape(h.title)}</title>`);
190
+ for (const meta of (h.meta || [])) {
191
+ const attrs = Object.entries(meta).map(([k, v]) => `${k}="${this.$escape(v)}"`).join(' ');
192
+ parts.push(`<meta ${attrs}>`);
193
+ }
194
+ for (const link of (h.links || [])) {
195
+ const attrs = Object.entries(link).map(([k, v]) => `${k}="${this.$escape(v)}"`).join(' ');
196
+ parts.push(`<link ${attrs}>`);
197
+ }
198
+ for (const script of (h.scripts || [])) {
199
+ if (typeof script === 'string') {
200
+ parts.push(`<script src="${this.$escape(script)}"></script>`);
201
+ } else {
202
+ const attrs = Object.entries(script).map(([k, v]) => `${k}="${this.$escape(v)}"`).join(' ');
203
+ parts.push(`<script ${attrs}></script>`);
204
+ }
205
+ }
206
+ return parts.join('\n ');
207
+ }
208
+
209
+ toFullPage() {
210
+ return `<!DOCTYPE html>
211
+ <html lang="en">
212
+ <head>
213
+ <meta charset="UTF-8">
214
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
215
+ ${this.renderHead()}
216
+ </head>
217
+ <body>
218
+ ${this.mount()}
219
+ </body>
220
+ </html>`;
221
+ }
222
+ }
223
+
224
+ module.exports = { Component, Page };