luxaura 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/README.md +342 -0
- package/bin/luxaura.js +632 -0
- package/examples/TodoApp.lux +97 -0
- package/package.json +35 -0
- package/src/compiler/index.js +389 -0
- package/src/index.js +44 -0
- package/src/parser/index.js +319 -0
- package/src/runtime/luxaura.runtime.js +350 -0
- package/src/vault/server.js +207 -0
- package/templates/luxaura.config +43 -0
- package/ui-kit/luxaura.min.css +779 -0
- package/ui-kit/luxaura.min.js +271 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Luxaura Vault Server
|
|
5
|
+
* - Serves the development app
|
|
6
|
+
* - Handles /__lux_rpc__ calls (server block functions)
|
|
7
|
+
* - Hot Module Replacement via WebSocket
|
|
8
|
+
* - Proxy support for headless mode
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const express = require('express');
|
|
12
|
+
const http = require('http');
|
|
13
|
+
const WebSocket = require('ws');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
17
|
+
|
|
18
|
+
// ─── CSRF Validation ─────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const CSRF_TOKENS = new Set();
|
|
21
|
+
|
|
22
|
+
function validateCsrf(token) {
|
|
23
|
+
// In production you'd use signed tokens + expiry.
|
|
24
|
+
// For dev, accept any non-empty token.
|
|
25
|
+
return typeof token === 'string' && token.length > 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── RPC Registry ─────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
class VaultServer {
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
this.port = options.port || 3000;
|
|
33
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
34
|
+
this.distDir = options.distDir || path.join(this.rootDir, 'dist', 'client');
|
|
35
|
+
this.config = this._loadConfig();
|
|
36
|
+
this._rpcModules = {};
|
|
37
|
+
this._app = express();
|
|
38
|
+
this._server = http.createServer(this._app);
|
|
39
|
+
this._wss = new WebSocket.Server({ server: this._server });
|
|
40
|
+
this._clients = new Set();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_loadConfig() {
|
|
44
|
+
const cfgPath = path.join(this.rootDir, 'luxaura.config');
|
|
45
|
+
if (!fs.existsSync(cfgPath)) return {};
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(cfgPath, 'utf8');
|
|
48
|
+
return this._parseConfig(raw);
|
|
49
|
+
} catch { return {}; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_parseConfig(raw) {
|
|
53
|
+
const cfg = {};
|
|
54
|
+
for (const line of raw.split('\n')) {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
57
|
+
const m = trimmed.match(/^([\w.]+)\s*:\s*(.+)$/);
|
|
58
|
+
if (m) {
|
|
59
|
+
const keys = m[1].split('.');
|
|
60
|
+
let obj = cfg;
|
|
61
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
62
|
+
obj[keys[i]] = obj[keys[i]] || {};
|
|
63
|
+
obj = obj[keys[i]];
|
|
64
|
+
}
|
|
65
|
+
obj[keys[keys.length - 1]] = m[2].trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return cfg;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Register a compiled server module for RPC dispatch */
|
|
72
|
+
registerModule(name, mod) {
|
|
73
|
+
this._rpcModules[name] = mod;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Load all server modules from dist/server */
|
|
77
|
+
loadServerModules() {
|
|
78
|
+
const serverDir = path.join(this.rootDir, 'dist', 'server');
|
|
79
|
+
if (!fs.existsSync(serverDir)) return;
|
|
80
|
+
for (const file of fs.readdirSync(serverDir)) {
|
|
81
|
+
if (!file.endsWith('.js')) continue;
|
|
82
|
+
const name = file.replace('.js', '');
|
|
83
|
+
try {
|
|
84
|
+
this._rpcModules[name] = require(path.join(serverDir, file));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(`[Vault] Failed to load module ${name}:`, e.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_setupMiddleware() {
|
|
92
|
+
this._app.use(express.json({ limit: '10mb' }));
|
|
93
|
+
|
|
94
|
+
// Security headers
|
|
95
|
+
this._app.use((req, res, next) => {
|
|
96
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
97
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
98
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
99
|
+
next();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Static files
|
|
103
|
+
this._app.use(express.static(this.distDir));
|
|
104
|
+
|
|
105
|
+
// HMR script injection
|
|
106
|
+
this._app.use((req, res, next) => {
|
|
107
|
+
if (req.path.endsWith('.html') || req.path === '/') {
|
|
108
|
+
// Let static serve it, but inject HMR script via the HTML middleware
|
|
109
|
+
}
|
|
110
|
+
next();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// RPC endpoint
|
|
114
|
+
this._app.post('/__lux_rpc__', async (req, res) => {
|
|
115
|
+
const csrfToken = req.headers['x-lux-csrf'];
|
|
116
|
+
if (!validateCsrf(csrfToken)) {
|
|
117
|
+
return res.status(403).json({ error: 'Invalid CSRF token' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { fn, args = [], module: modName } = req.body;
|
|
121
|
+
if (!fn) return res.status(400).json({ error: 'Missing fn' });
|
|
122
|
+
|
|
123
|
+
// Sanitize inputs: auto-parameterize for db calls is handled in db module
|
|
124
|
+
const sanitizedArgs = args.map(a =>
|
|
125
|
+
typeof a === 'string' ? a.replace(/[<>]/g, '') : a
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Resolve function from module registry
|
|
129
|
+
let handler;
|
|
130
|
+
if (modName && this._rpcModules[modName]) {
|
|
131
|
+
handler = this._rpcModules[modName][fn];
|
|
132
|
+
} else {
|
|
133
|
+
// Search all modules
|
|
134
|
+
for (const mod of Object.values(this._rpcModules)) {
|
|
135
|
+
if (typeof mod[fn] === 'function') { handler = mod[fn]; break; }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!handler) {
|
|
140
|
+
return res.status(404).json({ error: `RPC function "${fn}" not found` });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const result = await handler(...sanitizedArgs);
|
|
145
|
+
res.json({ ok: true, result });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`[Vault] RPC error in ${fn}:`, err.message);
|
|
148
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Headless proxy
|
|
153
|
+
if (this.config.mode === 'headless' && this.config.proxy) {
|
|
154
|
+
this._app.use('/api', createProxyMiddleware({
|
|
155
|
+
target: this.config.proxy,
|
|
156
|
+
changeOrigin: true,
|
|
157
|
+
pathRewrite: { '^/api': '' },
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// SPA fallback
|
|
162
|
+
this._app.get('*', (req, res) => {
|
|
163
|
+
const indexPath = path.join(this.distDir, 'index.html');
|
|
164
|
+
if (fs.existsSync(indexPath)) {
|
|
165
|
+
res.sendFile(indexPath);
|
|
166
|
+
} else {
|
|
167
|
+
res.status(404).send('Luxaura dev server: index.html not found. Run `luxaura build` first.');
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_setupWebSocket() {
|
|
173
|
+
this._wss.on('connection', (ws) => {
|
|
174
|
+
this._clients.add(ws);
|
|
175
|
+
ws.on('close', () => this._clients.delete(ws));
|
|
176
|
+
ws.send(JSON.stringify({ type: 'connected', version: '1.0' }));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Broadcast HMR update to all connected browsers */
|
|
181
|
+
triggerHMR(changedFile) {
|
|
182
|
+
const msg = JSON.stringify({ type: 'hmr', file: changedFile, ts: Date.now() });
|
|
183
|
+
for (const ws of this._clients) {
|
|
184
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
start() {
|
|
189
|
+
this._setupMiddleware();
|
|
190
|
+
this._setupWebSocket();
|
|
191
|
+
this.loadServerModules();
|
|
192
|
+
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
this._server.listen(this.port, () => {
|
|
195
|
+
resolve(this.port);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
stop() {
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
this._server.close(resolve);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = { VaultServer };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Luxaura Framework Configuration
|
|
2
|
+
# All settings are optional — sensible defaults apply.
|
|
3
|
+
|
|
4
|
+
# ─── App ─────────────────────────────────────────────────────
|
|
5
|
+
app.name: MyApp
|
|
6
|
+
app.version: 1.0.0
|
|
7
|
+
|
|
8
|
+
# ─── Theme ───────────────────────────────────────────────────
|
|
9
|
+
# Options: light | dark
|
|
10
|
+
theme: light
|
|
11
|
+
|
|
12
|
+
# ─── Mode ────────────────────────────────────────────────────
|
|
13
|
+
# Options: full | headless
|
|
14
|
+
# full: Luxaura manages both frontend and backend (Vault)
|
|
15
|
+
# headless: Frontend only; API requests proxied to external backend
|
|
16
|
+
mode: full
|
|
17
|
+
|
|
18
|
+
# ─── Database ────────────────────────────────────────────────
|
|
19
|
+
# Uncomment and configure your database:
|
|
20
|
+
# db.type: postgres
|
|
21
|
+
# db.url: postgresql://user:pass@localhost:5432/myapp
|
|
22
|
+
|
|
23
|
+
# db.type: mysql
|
|
24
|
+
# db.url: mysql://user:pass@localhost:3306/myapp
|
|
25
|
+
|
|
26
|
+
# db.type: mongodb
|
|
27
|
+
# db.url: mongodb://localhost:27017/myapp
|
|
28
|
+
|
|
29
|
+
# ─── Headless Proxy ──────────────────────────────────────────
|
|
30
|
+
# Only applies when mode: headless
|
|
31
|
+
# Proxies /api/* requests to this address during development
|
|
32
|
+
# proxy: http://localhost:8080
|
|
33
|
+
|
|
34
|
+
# ─── Build ───────────────────────────────────────────────────
|
|
35
|
+
# build.minify: true
|
|
36
|
+
# build.sourcemaps: false
|
|
37
|
+
# build.target: es2020
|
|
38
|
+
|
|
39
|
+
# ─── Plugins ─────────────────────────────────────────────────
|
|
40
|
+
# CSS frameworks installed via `luxaura install <lib>`
|
|
41
|
+
# plugins:
|
|
42
|
+
# - tailwindcss
|
|
43
|
+
# - bootstrap
|