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,236 @@
1
+ /**
2
+ * VoltJS StringHelper
3
+ *
4
+ * String manipulation utilities.
5
+ *
6
+ * @example
7
+ * const { StringHelper } = require('voltjs');
8
+ *
9
+ * StringHelper.slug('Hello World!'); // 'hello-world'
10
+ * StringHelper.camelCase('user_first_name'); // 'userFirstName'
11
+ * StringHelper.truncate('Long text...', 10); // 'Long te...'
12
+ * StringHelper.random(16); // 'a8f3k2m9p1q4r7...'
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const crypto = require('crypto');
18
+
19
+ class StringHelper {
20
+ /** Convert to URL-friendly slug */
21
+ static slug(str, separator = '-') {
22
+ return str
23
+ .toString()
24
+ .toLowerCase()
25
+ .normalize('NFD')
26
+ .replace(/[\u0300-\u036f]/g, '') // Remove accents
27
+ .replace(/[^a-z0-9\s-]/g, '') // Remove non-alphanumeric
28
+ .replace(/[\s_]+/g, separator) // Replace spaces/underscores
29
+ .replace(new RegExp(`${separator}+`, 'g'), separator) // Collapse separators
30
+ .replace(new RegExp(`^${separator}|${separator}$`, 'g'), ''); // Trim edges
31
+ }
32
+
33
+ /** camelCase */
34
+ static camelCase(str) {
35
+ return str
36
+ .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
37
+ .replace(/^./, c => c.toLowerCase());
38
+ }
39
+
40
+ /** PascalCase */
41
+ static pascalCase(str) {
42
+ const camel = StringHelper.camelCase(str);
43
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
44
+ }
45
+
46
+ /** snake_case */
47
+ static snakeCase(str) {
48
+ return str
49
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
50
+ .replace(/[-\s]+/g, '_')
51
+ .toLowerCase();
52
+ }
53
+
54
+ /** kebab-case */
55
+ static kebabCase(str) {
56
+ return str
57
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
58
+ .replace(/[_\s]+/g, '-')
59
+ .toLowerCase();
60
+ }
61
+
62
+ /** Title Case */
63
+ static titleCase(str) {
64
+ return str.replace(/\b\w/g, c => c.toUpperCase());
65
+ }
66
+
67
+ /** sentence case */
68
+ static sentenceCase(str) {
69
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
70
+ }
71
+
72
+ /** Truncate with ellipsis */
73
+ static truncate(str, length = 100, suffix = '...') {
74
+ if (str.length <= length) return str;
75
+ return str.substring(0, length - suffix.length) + suffix;
76
+ }
77
+
78
+ /** Truncate words */
79
+ static truncateWords(str, count = 10, suffix = '...') {
80
+ const words = str.split(/\s+/);
81
+ if (words.length <= count) return str;
82
+ return words.slice(0, count).join(' ') + suffix;
83
+ }
84
+
85
+ /** Pad left */
86
+ static padLeft(str, length, char = ' ') {
87
+ return String(str).padStart(length, char);
88
+ }
89
+
90
+ /** Pad right */
91
+ static padRight(str, length, char = ' ') {
92
+ return String(str).padEnd(length, char);
93
+ }
94
+
95
+ /** Check if string contains substring (case-insensitive) */
96
+ static contains(str, substr) {
97
+ return str.toLowerCase().includes(substr.toLowerCase());
98
+ }
99
+
100
+ /** Check if string starts with (case-insensitive) */
101
+ static startsWith(str, prefix) {
102
+ return str.toLowerCase().startsWith(prefix.toLowerCase());
103
+ }
104
+
105
+ /** Check if string ends with (case-insensitive) */
106
+ static endsWith(str, suffix) {
107
+ return str.toLowerCase().endsWith(suffix.toLowerCase());
108
+ }
109
+
110
+ /** Count words */
111
+ static wordCount(str) {
112
+ return str.trim().split(/\s+/).filter(Boolean).length;
113
+ }
114
+
115
+ /** Count characters (excluding spaces) */
116
+ static charCount(str, excludeSpaces = false) {
117
+ return excludeSpaces ? str.replace(/\s/g, '').length : str.length;
118
+ }
119
+
120
+ /** Strip HTML tags */
121
+ static stripTags(html) {
122
+ return html.replace(/<[^>]*>/g, '');
123
+ }
124
+
125
+ /** Escape HTML entities */
126
+ static escapeHtml(str) {
127
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
128
+ return str.replace(/[&<>"']/g, c => map[c]);
129
+ }
130
+
131
+ /** Unescape HTML entities */
132
+ static unescapeHtml(str) {
133
+ const map = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#039;': "'" };
134
+ return str.replace(/&(amp|lt|gt|quot|#039);/g, m => map[m]);
135
+ }
136
+
137
+ /** Generate random string */
138
+ static random(length = 16, charset = 'alphanumeric') {
139
+ const charsets = {
140
+ alpha: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
141
+ numeric: '0123456789',
142
+ alphanumeric: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
143
+ hex: '0123456789abcdef',
144
+ all: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*',
145
+ };
146
+ const chars = charsets[charset] || charset;
147
+ const bytes = crypto.randomBytes(length);
148
+ let result = '';
149
+ for (let i = 0; i < length; i++) {
150
+ result += chars[bytes[i] % chars.length];
151
+ }
152
+ return result;
153
+ }
154
+
155
+ /** Reverse a string */
156
+ static reverse(str) {
157
+ return [...str].reverse().join('');
158
+ }
159
+
160
+ /** Repeat a string */
161
+ static repeat(str, count) {
162
+ return str.repeat(count);
163
+ }
164
+
165
+ /** Replace all occurrences */
166
+ static replaceAll(str, search, replacement) {
167
+ return str.split(search).join(replacement);
168
+ }
169
+
170
+ /** Extract between delimiters */
171
+ static between(str, start, end) {
172
+ const startIdx = str.indexOf(start);
173
+ if (startIdx === -1) return '';
174
+ const endIdx = str.indexOf(end, startIdx + start.length);
175
+ if (endIdx === -1) return '';
176
+ return str.substring(startIdx + start.length, endIdx);
177
+ }
178
+
179
+ /** Mask a string (for sensitive data) */
180
+ static mask(str, visibleStart = 0, visibleEnd = 4, maskChar = '*') {
181
+ if (str.length <= visibleStart + visibleEnd) return str;
182
+ const start = str.substring(0, visibleStart);
183
+ const end = str.substring(str.length - visibleEnd);
184
+ const middle = maskChar.repeat(str.length - visibleStart - visibleEnd);
185
+ return start + middle + end;
186
+ }
187
+
188
+ /** Convert bytes to human-readable size */
189
+ static fileSize(bytes) {
190
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
191
+ let i = 0;
192
+ let size = bytes;
193
+ while (size >= 1024 && i < units.length - 1) {
194
+ size /= 1024;
195
+ i++;
196
+ }
197
+ return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
198
+ }
199
+
200
+ /** Convert number to ordinal: 1 -> "1st" */
201
+ static ordinal(n) {
202
+ const s = ['th', 'st', 'nd', 'rd'];
203
+ const v = n % 100;
204
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
205
+ }
206
+
207
+ /** Pluralize a word (simple English rules) */
208
+ static pluralize(word, count) {
209
+ if (count === 1) return word;
210
+ if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) return word.slice(0, -1) + 'ies';
211
+ if (/(?:s|x|z|ch|sh)$/i.test(word)) return word + 'es';
212
+ return word + 's';
213
+ }
214
+
215
+ /** Convert to boolean */
216
+ static toBoolean(str) {
217
+ return ['true', '1', 'yes', 'on', 'y'].includes(String(str).toLowerCase().trim());
218
+ }
219
+
220
+ /** Extract initials */
221
+ static initials(str, count = 2) {
222
+ return str
223
+ .split(/\s+/)
224
+ .slice(0, count)
225
+ .map(w => w[0])
226
+ .join('')
227
+ .toUpperCase();
228
+ }
229
+
230
+ /** Template interpolation: "Hello {{name}}" */
231
+ static template(str, data) {
232
+ return str.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] ?? '');
233
+ }
234
+ }
235
+
236
+ module.exports = { StringHelper };
@@ -0,0 +1,318 @@
1
+ /**
2
+ * VoltJS Validator
3
+ *
4
+ * Comprehensive, chainable validation for any data.
5
+ *
6
+ * @example
7
+ * const { Validator } = require('voltjs');
8
+ *
9
+ * const result = Validator.validate(req.body, {
10
+ * name: 'required|string|min:2|max:50',
11
+ * email: 'required|email',
12
+ * age: 'required|integer|min:18|max:120',
13
+ * password: 'required|min:8|strongPassword',
14
+ * website: 'url',
15
+ * role: 'in:admin,user,editor',
16
+ * });
17
+ *
18
+ * if (!result.valid) {
19
+ * console.log(result.errors); // { email: ['Invalid email format'] }
20
+ * }
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ class Validator {
26
+ /** Validate data against rules */
27
+ static validate(data, rules) {
28
+ const errors = {};
29
+ let valid = true;
30
+
31
+ for (const [field, ruleString] of Object.entries(rules)) {
32
+ const fieldRules = typeof ruleString === 'string' ? ruleString.split('|') : ruleString;
33
+ const value = data[field];
34
+ const fieldErrors = [];
35
+
36
+ for (const rule of fieldRules) {
37
+ const [ruleName, ...ruleParams] = rule.split(':');
38
+ const params = ruleParams.join(':').split(',');
39
+
40
+ const error = Validator._checkRule(ruleName, value, params, field, data);
41
+ if (error) fieldErrors.push(error);
42
+ }
43
+
44
+ if (fieldErrors.length > 0) {
45
+ errors[field] = fieldErrors;
46
+ valid = false;
47
+ }
48
+ }
49
+
50
+ return { valid, errors, data: valid ? data : null };
51
+ }
52
+
53
+ /** Middleware: Validate request body */
54
+ static body(rules) {
55
+ return (req, res) => {
56
+ const result = Validator.validate(req.body || {}, rules);
57
+ if (!result.valid) {
58
+ res.json({ error: 'Validation failed', errors: result.errors }, 422);
59
+ return false;
60
+ }
61
+ req.validated = result.data;
62
+ };
63
+ }
64
+
65
+ /** Middleware: Validate query parameters */
66
+ static query(rules) {
67
+ return (req, res) => {
68
+ const result = Validator.validate(req.query || {}, rules);
69
+ if (!result.valid) {
70
+ res.json({ error: 'Validation failed', errors: result.errors }, 422);
71
+ return false;
72
+ }
73
+ req.validatedQuery = result.data;
74
+ };
75
+ }
76
+
77
+ /** Middleware: Validate URL parameters */
78
+ static params(rules) {
79
+ return (req, res) => {
80
+ const result = Validator.validate(req.params || {}, rules);
81
+ if (!result.valid) {
82
+ res.json({ error: 'Validation failed', errors: result.errors }, 422);
83
+ return false;
84
+ }
85
+ };
86
+ }
87
+
88
+ /** Individual validators */
89
+ static isEmail(value) {
90
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value));
91
+ }
92
+
93
+ static isURL(value) {
94
+ try { new URL(value); return true; } catch { return false; }
95
+ }
96
+
97
+ static isIP(value) {
98
+ return /^(\d{1,3}\.){3}\d{1,3}$/.test(value) || /^([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$/i.test(value);
99
+ }
100
+
101
+ static isUUID(value) {
102
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
103
+ }
104
+
105
+ static isHex(value) {
106
+ return /^#?[0-9a-f]+$/i.test(value);
107
+ }
108
+
109
+ static isJSON(value) {
110
+ try { JSON.parse(value); return true; } catch { return false; }
111
+ }
112
+
113
+ static isAlpha(value) {
114
+ return /^[a-zA-Z]+$/.test(value);
115
+ }
116
+
117
+ static isAlphanumeric(value) {
118
+ return /^[a-zA-Z0-9]+$/.test(value);
119
+ }
120
+
121
+ static isNumeric(value) {
122
+ return !isNaN(value) && !isNaN(parseFloat(value));
123
+ }
124
+
125
+ static isSlug(value) {
126
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
127
+ }
128
+
129
+ static isCreditCard(value) {
130
+ const cleaned = String(value).replace(/[\s-]/g, '');
131
+ if (!/^\d{13,19}$/.test(cleaned)) return false;
132
+ // Luhn algorithm
133
+ let sum = 0;
134
+ let alternate = false;
135
+ for (let i = cleaned.length - 1; i >= 0; i--) {
136
+ let n = parseInt(cleaned[i], 10);
137
+ if (alternate) {
138
+ n *= 2;
139
+ if (n > 9) n -= 9;
140
+ }
141
+ sum += n;
142
+ alternate = !alternate;
143
+ }
144
+ return sum % 10 === 0;
145
+ }
146
+
147
+ static isPhone(value) {
148
+ return /^\+?[\d\s()-]{7,15}$/.test(value);
149
+ }
150
+
151
+ static isStrongPassword(value) {
152
+ // At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char
153
+ return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{8,}$/.test(value);
154
+ }
155
+
156
+ static isDate(value) {
157
+ return !isNaN(Date.parse(value));
158
+ }
159
+
160
+ // ===== INTERNAL =====
161
+
162
+ static _checkRule(ruleName, value, params, field, data) {
163
+ // Required check
164
+ if (ruleName === 'required') {
165
+ if (value === undefined || value === null || value === '') {
166
+ return `${field} is required`;
167
+ }
168
+ return null;
169
+ }
170
+
171
+ // Skip other validations if value is empty and not required
172
+ if (value === undefined || value === null || value === '') return null;
173
+
174
+ switch (ruleName) {
175
+ case 'string':
176
+ if (typeof value !== 'string') return `${field} must be a string`;
177
+ break;
178
+ case 'number':
179
+ case 'numeric':
180
+ if (isNaN(value)) return `${field} must be a number`;
181
+ break;
182
+ case 'integer':
183
+ case 'int':
184
+ if (!Number.isInteger(Number(value))) return `${field} must be an integer`;
185
+ break;
186
+ case 'float':
187
+ if (isNaN(parseFloat(value))) return `${field} must be a decimal number`;
188
+ break;
189
+ case 'boolean':
190
+ case 'bool':
191
+ if (typeof value !== 'boolean' && !['true', 'false', '0', '1'].includes(String(value))) {
192
+ return `${field} must be a boolean`;
193
+ }
194
+ break;
195
+ case 'array':
196
+ if (!Array.isArray(value)) return `${field} must be an array`;
197
+ break;
198
+ case 'object':
199
+ if (typeof value !== 'object' || Array.isArray(value)) return `${field} must be an object`;
200
+ break;
201
+ case 'email':
202
+ if (!Validator.isEmail(value)) return `${field} must be a valid email`;
203
+ break;
204
+ case 'url':
205
+ if (!Validator.isURL(value)) return `${field} must be a valid URL`;
206
+ break;
207
+ case 'ip':
208
+ if (!Validator.isIP(value)) return `${field} must be a valid IP address`;
209
+ break;
210
+ case 'uuid':
211
+ if (!Validator.isUUID(value)) return `${field} must be a valid UUID`;
212
+ break;
213
+ case 'date':
214
+ if (!Validator.isDate(value)) return `${field} must be a valid date`;
215
+ break;
216
+ case 'alpha':
217
+ if (!Validator.isAlpha(value)) return `${field} must contain only letters`;
218
+ break;
219
+ case 'alphanumeric':
220
+ case 'alphanum':
221
+ if (!Validator.isAlphanumeric(value)) return `${field} must be alphanumeric`;
222
+ break;
223
+ case 'slug':
224
+ if (!Validator.isSlug(value)) return `${field} must be a valid slug`;
225
+ break;
226
+ case 'phone':
227
+ if (!Validator.isPhone(value)) return `${field} must be a valid phone number`;
228
+ break;
229
+ case 'creditCard':
230
+ case 'credit_card':
231
+ if (!Validator.isCreditCard(value)) return `${field} must be a valid credit card number`;
232
+ break;
233
+ case 'strongPassword':
234
+ case 'strong_password':
235
+ if (!Validator.isStrongPassword(value)) {
236
+ return `${field} must have 8+ chars with uppercase, lowercase, number, and special character`;
237
+ }
238
+ break;
239
+ case 'json':
240
+ if (!Validator.isJSON(value)) return `${field} must be valid JSON`;
241
+ break;
242
+ case 'hex':
243
+ if (!Validator.isHex(value)) return `${field} must be a hex value`;
244
+ break;
245
+ case 'min':
246
+ if (typeof value === 'string' && value.length < parseInt(params[0])) {
247
+ return `${field} must be at least ${params[0]} characters`;
248
+ }
249
+ if (typeof value === 'number' && value < parseFloat(params[0])) {
250
+ return `${field} must be at least ${params[0]}`;
251
+ }
252
+ break;
253
+ case 'max':
254
+ if (typeof value === 'string' && value.length > parseInt(params[0])) {
255
+ return `${field} must be at most ${params[0]} characters`;
256
+ }
257
+ if (typeof value === 'number' && value > parseFloat(params[0])) {
258
+ return `${field} must be at most ${params[0]}`;
259
+ }
260
+ break;
261
+ case 'length':
262
+ case 'size':
263
+ if (String(value).length !== parseInt(params[0])) {
264
+ return `${field} must be exactly ${params[0]} characters`;
265
+ }
266
+ break;
267
+ case 'in':
268
+ if (!params.includes(String(value))) {
269
+ return `${field} must be one of: ${params.join(', ')}`;
270
+ }
271
+ break;
272
+ case 'notIn':
273
+ case 'not_in':
274
+ if (params.includes(String(value))) {
275
+ return `${field} must not be one of: ${params.join(', ')}`;
276
+ }
277
+ break;
278
+ case 'regex':
279
+ case 'pattern':
280
+ if (!new RegExp(params[0]).test(value)) {
281
+ return `${field} format is invalid`;
282
+ }
283
+ break;
284
+ case 'confirmed':
285
+ if (value !== data[`${field}_confirmation`]) {
286
+ return `${field} confirmation does not match`;
287
+ }
288
+ break;
289
+ case 'same':
290
+ if (value !== data[params[0]]) {
291
+ return `${field} must match ${params[0]}`;
292
+ }
293
+ break;
294
+ case 'different':
295
+ if (value === data[params[0]]) {
296
+ return `${field} must be different from ${params[0]}`;
297
+ }
298
+ break;
299
+ case 'before':
300
+ if (new Date(value) >= new Date(params[0])) {
301
+ return `${field} must be before ${params[0]}`;
302
+ }
303
+ break;
304
+ case 'after':
305
+ if (new Date(value) <= new Date(params[0])) {
306
+ return `${field} must be after ${params[0]}`;
307
+ }
308
+ break;
309
+ default:
310
+ // Unknown rule — skip
311
+ break;
312
+ }
313
+
314
+ return null;
315
+ }
316
+ }
317
+
318
+ module.exports = { Validator };