free-framework 5.0.0 → 5.0.1

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * compiler/generator.js
3
- * Code Generator for Free Ultra (v4.3).
3
+ * Code Generator for Free Ultra (v5.0.0 Convergence).
4
4
  * Optimized for Islands Architecture and Component Composition.
5
5
  */
6
6
 
@@ -22,8 +22,7 @@ function generate(ast) {
22
22
  "const modelsRegistry = {};\n";
23
23
 
24
24
  const CleanCSS = require('clean-css');
25
- const glob = require('fs'); // Just using fs for readdirSync
26
-
25
+
27
26
  const components = {};
28
27
  const actions = {};
29
28
  let globalCSS = "";
@@ -98,205 +97,101 @@ function generate(ast) {
98
97
  ast.forEach(node => {
99
98
  if (node.type === 'action') {
100
99
  output += `\nserver.app.post("/_free/action/${node.name}", async (req, res) => {
101
- try {
102
- const user = (res.context && res.context.user) ? res.context.user : null;
103
- let body = {};
104
- if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
105
- body = await req.json().catch(() => ({}));
106
- } else {
107
- body = await req.urlencoded().catch(() => ({}));
108
- }
109
- const result = await (async () => {\n`;
110
- if (node.params && node.params.length > 0) {
111
- output += ` const { ${node.params.join(', ')} } = body;\n`;
112
- }
113
- output += " " + node.code.replace(/res\.locals/g, 'res.context') + "\n" +
114
- " })();\n" +
115
- " if (!res.headersSent) res.json({ success: true, result });\n" +
116
- " } catch(e) {\n" +
117
- " if (!res.headersSent) res.status(500).json({ success: false, error: e.message });\n" +
118
- " }\n" +
119
- "});\n";
100
+ const body = req.body || {};
101
+ const logic = async (body, res) => { ${node.code} };
102
+ try {
103
+ const result = await logic(body, res);
104
+ if (!res.completed) res.json({ success: true, data: result });
105
+ } catch(e) {
106
+ if (!res.completed) res.status(500).json({ success: false, error: e.message });
107
+ }
108
+ });\n`;
120
109
  }
121
110
  });
122
111
 
123
- globalCSS = new CleanCSS().minify(globalCSS).styles;
124
- output += `\nconst scopedCSS = ${JSON.stringify(globalCSS.trim())};\n`;
125
-
126
- output += `\nserver.renderRegistry = {\n`;
127
- Object.keys(components).forEach((name, i) => {
128
- output += ` ${name}: render${name}${i === Object.keys(components).length - 1 ? '' : ','}\n`;
129
- });
130
- output += `};\n`;
131
-
132
- output += `\nserver.middleware((req, res, next) => {
133
- if (!res.context) res.context = {};
134
- res.context.csrfToken = crypto.randomBytes(32).toString('hex');
135
- next();
136
- });\n`;
137
-
138
- // Actually emit the component rendering functions (hoisted)
139
- Object.values(components).forEach(node => {
140
- output += generateUltraComponent(node);
112
+ output += "\n// Components (Islands Animation)\n";
113
+ Object.keys(components).forEach(name => {
114
+ output += generateComponentCode(components[name]);
141
115
  });
142
116
 
143
- // ── Auto-register Custom Middlewares from app/middleware ─────────────────
144
- output += `
145
- const mwDir = path.join(process.cwd(), 'app/middleware');
146
- if (fs.existsSync(mwDir)) {
147
- fs.readdirSync(mwDir).forEach(file => {
148
- if (file.endsWith('.js')) {
149
- const name = file.replace('.js', '');
150
- server.registerMiddleware(name, require(path.join(mwDir, file)));
151
- }
117
+ output += `\n// Routes & Global Rendering\n`;
118
+ output += `const helpers = {
119
+ renderComponent: (name, props, h) => {
120
+ const comp = islandsRegistry[name];
121
+ return comp ? comp(props, h) : \`<!-- error: \${name} not found -->\`;
122
+ },
123
+ e: (val) => String(val).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
124
+ };\n`;
125
+
126
+ output += `const islandsRegistry = {\n`;
127
+ Object.keys(components).forEach(name => {
128
+ output += ` '${name}': render_${name},\n`;
152
129
  });
153
- }
154
- if (server.namedMiddlewares['auth']) server.namedMiddlewares['AuthGuard'] = server.namedMiddlewares['auth'];
155
- \n`;
130
+ output += `};\n\n`;
156
131
 
157
132
  ast.forEach(node => {
158
133
  if (node.type === 'route') {
159
- const v = node.view || 'home';
160
- const viewName = v.charAt(0).toUpperCase() + v.slice(1);
161
- const middlewares = node.middleware ? [node.middleware] : (node.middlewares || []);
162
- let handlerCode = "";
163
- if (node.handler && typeof node.handler === 'object' && node.handler.type === 'action_ref') {
164
- handlerCode = actions[node.handler.name] || `console.error("Action not found: ${node.handler.name}");`;
165
- } else {
166
- handlerCode = node.handler || "";
167
- }
168
-
169
- const stylesRegistry = {};
170
- ast.filter(n => n.type === 'component').forEach(comp => {
171
- const styleNode = comp.body.find(b => b.type === 'style');
172
- stylesRegistry[comp.name] = styleNode ? styleNode.content.trim() : "";
173
- });
174
-
175
- output += `\nserver.route("${node.method.toLowerCase()}", "${node.path}", ${JSON.stringify(middlewares)}, async (req, res) => {
176
- const user = (res.context && res.context.user) ? res.context.user : null;
177
- let body = {};
178
- try {
179
- if (req.headers['content-length'] > 0) {
180
- if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
181
- body = await req.json().catch(() => ({}));
182
- } else {
183
- body = await req.urlencoded().catch(() => ({}));
184
- }
134
+ const method = node.method.toLowerCase() === 'get' || node.method === 'Get' ? 'get' : node.method.toLowerCase();
135
+ if (node.handler && node.handler.type === 'action_ref') {
136
+ output += `server.app.${method}('${node.path}', async (req, res) => {
137
+ const actionResults = await fetch(\`http://localhost:\${process.env.PORT || 3000}/_free/action/${node.handler.name}\`, { method: 'POST', body: JSON.stringify(req.body) }).then(r => r.json());
138
+ res.header('Content-Type', 'text/html');
139
+ res.send(render_${node.view}({ user: req.user, ...actionResults.data }, helpers));
140
+ });\n`;
141
+ } else if (node.handler) {
142
+ output += `server.app.${method}('${node.path}', async (req, res) => {
143
+ const logic = async (req, res) => { ${node.handler} };
144
+ const viewToRender = await logic(req, res);
145
+ if (viewToRender && islandsRegistry[viewToRender.name]) {
146
+ res.header('Content-Type', 'text/html');
147
+ res.send(islandsRegistry[viewToRender.name]({ user: req.user, ...req.body }, helpers));
148
+ }
149
+ });\n`;
150
+ } else if (node.view) {
151
+ output += `server.app.${method}('${node.path}', async (req, res) => {
152
+ res.header('Content-Type', 'text/html');
153
+ res.send(render_${node.view}({ user: req.user }, helpers));
154
+ });\n`;
185
155
  }
186
- } catch(e) {}
187
-
188
- const result = await (async () => {\n` +
189
- " " + handlerCode.replace(/res\.locals/g, 'res.context') + "\n" +
190
- " })();\n\n" +
191
- " if (res.headersSent) return;\n" +
192
- "\n" +
193
- " // If it's a PAGE route (has a view), render HTML\n" +
194
- " if (" + (node.view ? 'true' : 'false') + ") {\n" +
195
- " const stylesRegistry = " + JSON.stringify(stylesRegistry) + ";\n" +
196
- " const helpers = {\n" +
197
- " renderComponent: (name, props = {}) => {\n" +
198
- " server.renderRegistry = server.renderRegistry || {\n" +
199
- " " + Object.keys(components).map(name => `${name}: render${name}`).join(',\n ') + "\n" +
200
- " };\n" +
201
- " return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';\n" +
202
- " },\n" +
203
- " getScopedCSS: (used = []) => {\n" +
204
- " return used.map(name => stylesRegistry[name] || '').join('\\n');\n" +
205
- " }\n" +
206
- " };\n" +
207
- " const props = result || (res.context && res.context.props ? res.context.props : {});\n" +
208
- " const pageHtml = helpers.renderComponent('" + viewName + "', props);\n" +
209
- " const scopedCSS = helpers.getScopedCSS(['" + viewName + "', 'Header']);\n" +
210
- " if (req.headers['x-free-partial']) {\n" +
211
- " return res.json({ title: 'Free Ultra | " + viewName + "', content: pageHtml, url: req.url, css: scopedCSS });\n" +
212
- " }\n" +
213
- " const headerHtml = helpers.renderComponent('Header', props);\n" +
214
- " const fullHTML = `<!DOCTYPE html>\n" +
215
- "<html lang=\"en\">\n" +
216
- "<head>\n" +
217
- " <meta charset=\"UTF-8\">\n" +
218
- " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
219
- " <title>Free Ultra | " + viewName + "</title>\n" +
220
- " <meta name=\"csrf-token\" content=\"${res.context.csrfToken || ''}\">\n" +
221
- " <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n" +
222
- " <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n" +
223
- " <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap\" rel=\"stylesheet\">\n" +
224
- " <link rel=\"stylesheet\" href=\"/css/app.css\">\n" +
225
- " <style>\n" +
226
- " :root { --primary:#fff; --bg:#000; --border:#27272a; }\n" +
227
- " body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }\n" +
228
- " ${scopedCSS}\n" +
229
- " </style>\n" +
230
- " <script src=\"/free-runtime.js\" defer></script>\n" +
231
- "</head>\n" +
232
- "<body>\n" +
233
- " ${headerHtml}\n" +
234
- " <main id=\"free-app-root\">\n" +
235
- " ${pageHtml}\n" +
236
- " </main>\n" +
237
- " ${process.env.NODE_ENV !== 'production' ? `\n" +
238
- " <script>\n" +
239
- " (function connectHMR() {\n" +
240
- " const ws = new WebSocket('ws://' + location.host + '/_free_hmr');\n" +
241
- " ws.onclose = () => { \n" +
242
- " setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500); \n" +
243
- " };\n" +
244
- " console.log('[Free Engine] ⚡ HMR Active');\n" +
245
- " })();\n" +
246
- " </script>` : ''}\n" +
247
- "</body>\n" +
248
- "</html>`;\n" +
249
- " res.header('Content-Type', 'text/html').send(fullHTML);\n" +
250
- " } else {\n" +
251
- " res.json({ success: true, result });\n" +
252
- " }\n" +
253
- " });\n";
254
156
  }
255
157
  });
256
158
 
257
- output += `\nif (require.main === module) {
258
- ORM.migrate(modelsRegistry).then(() => {
259
- server.start(process.env.PORT || 4000);
260
- }).catch(err => console.error(err));
261
- }
159
+ output += `\n// Global Finalizing\n`;
160
+ output += `const cssMinifier = new CleanCSS({});\n`;
161
+ output += `const minifiedCSS = cssMinifier.minify(\`${globalCSS}\`).styles;\n`;
162
+ output += `server.app.get('/_free/styles.css', (req, res) => {
163
+ res.header('Content-Type', 'text/css');
164
+ res.send(minifiedCSS);
165
+ });\n`;
262
166
 
263
- module.exports = {...modelsRegistry, server, modelsRegistry, ORM};
264
- `;
167
+ output += `\nserver.start(process.env.PORT || 3000);\n`;
265
168
 
266
169
  return output;
267
170
  }
268
171
 
269
- function generateUltraComponent(node) {
270
- const onMount = node.body.find(n => n.type === 'onMount')?.code || '';
271
- const onDestroy = node.body.find(n => n.type === 'onDestroy')?.code || '';
272
- const isInteractive = node.states.length > 0 || onMount !== '' || onDestroy !== '' || hasInteractivity(node.body);
273
- const stateNames = node.states.map(s => s.name);
274
-
275
- let code = `\nfunction render${node.name}(props = { }, helpers = { }) {\n`;
276
- code += ` const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[m]));\n`;
277
- code += ` const state = {\n`;
278
- node.states.forEach(s => {
279
- let val = s.default;
280
- const isExpression = typeof val === 'string' && (val.includes('props.') || val.includes('||') || val.includes('&&') || val === 'true' || val === 'false' || val === 'null' || val.trim().startsWith('{') || val.trim().startsWith('['));
281
- const finalVal = (isExpression || typeof val === 'number') ? val : `"${val}"`;
282
- code += ` "${s.name}": ${finalVal},\n`;
283
- });
284
- code += ` };\n`;
285
- if (stateNames.length > 0) {
286
- code += ` const { ${stateNames.join(', ')} } = state;\n`;
287
- }
288
-
172
+ function generateComponentCode(node) {
173
+ let stateNames = node.states.map(s => s.name);
174
+ let isInteractive = hasInteractivity(node.body);
175
+ let code = `\nfunction render_${node.name}(props = {}, helpers) {\n`;
176
+ code += ` const { e } = helpers;\n`;
177
+ code += ` let html = "";\n`;
289
178
  code += ` const _events = [];\n`;
290
- code += ` const islandAttr = ${isInteractive} ? ' data-component="${node.name}"' : '';\n`;
291
- code += ` let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "&#39;") + "'";\n`;
292
-
293
- if (onMount) {
294
- code += ` _events.push({ id: 'onMount', fn: function(state) { ${onMount} } });\n`;
295
- code += ` html += " data-free-on-mount='onMount'";\n`;
179
+
180
+ // Inject state initializers for the island
181
+ if (node.states.length > 0) {
182
+ code += ` const state = { \n`;
183
+ node.states.forEach(s => {
184
+ code += ` ${s.name}: ${s.default},\n`;
185
+ });
186
+ code += ` };\n`;
187
+ } else {
188
+ code += ` const state = {};\n`;
296
189
  }
297
- if (onDestroy) {
298
- code += ` _events.push({ id: 'onDestroy', fn: function(state) { ${onDestroy} } });\n`;
299
- code += ` html += " data-free-on-destroy='onDestroy'";\n`;
190
+
191
+ code += ` html += "<div class='free-component' data-component='${node.name}'";\n`;
192
+ if (isInteractive) {
193
+ code += ` html += " data-island='true'";\n`;
194
+ code += ` html += " data-state='" + JSON.stringify(state).replace(/'/g, "&#39;") + "'";\n`;
300
195
  }
301
196
 
302
197
  code += ` html += ">";\n`;
@@ -355,7 +250,7 @@ function generateTagCode(tag, stateNames) {
355
250
  let propsStr = "{";
356
251
  if (tag.attributes) {
357
252
  Object.keys(tag.attributes).forEach((a, i) => {
358
- let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${" + p + "}");
253
+ let val = tag.attributes[a].replace(/([^\\]|^)\{([a-zA-Z0-9_$.]+)\}/g, '$1${$2}');
359
254
  propsStr += (i === 0 ? "" : ", ") + '"' + a + '": `' + val + '`';
360
255
  });
361
256
  }
@@ -378,7 +273,7 @@ function generateTagCode(tag, stateNames) {
378
273
  }
379
274
  code += ` html += " data-on-${eventName}='${uniqueId}'";\n`;
380
275
  } else {
381
- let val = tag.attributes[a].replace(/\{([^}]+)\}/g, (_, p) => "${e(" + p + ")}");
276
+ let val = tag.attributes[a].replace(/([^\\]|^)\{([a-zA-Z0-9_$.]+)\}/g, '$1${e($2)}');
382
277
  code += " html += \" \" + " + JSON.stringify(a) + " + \"='\" + `" + val + "` + \"'\";\n";
383
278
  }
384
279
  });
@@ -398,23 +293,17 @@ function generateTagCode(tag, stateNames) {
398
293
  code += ` html += ">";\n`;
399
294
 
400
295
  if (tag.content) {
401
- let cont = tag.content.replace(/`/g, '\\`').replace(/\$/g, '\\$');
402
- // Robust interpolation: only match expressions that don't contain nested braces or backslashes
403
- cont = cont.replace(/(?<!\\)\{([^\\{}]+)\}/g, (_, p) => {
404
- const trimmed = p.trim();
405
- const parts = trimmed.split('.');
406
- if (stateNames.includes(parts[0])) {
407
- return `\${e(state["${parts[0]}"]${parts.slice(1).map(part => `?.["${part}"]`).join('')})}`;
408
- }
409
- // If it's not a state variable, just treat it as a JS expression
410
- return `\${e(${p})}`;
411
- });
412
- // Remove escape characters from \{ and \}
413
- cont = cont.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
414
- code += ` html += \`${cont}\`;\n`;
296
+ // Ultra-safe: only interpolate if it's a simple variable-like name with no whitespace
297
+ let val = tag.content.replace(/([^\\]|^)\{([a-zA-Z0-9_$.]+)\}/g, '$1${e($2)}');
298
+ // Handle escaped braces
299
+ val = val.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
300
+ code += ` html += \`${val}\`;\n`;
301
+ }
302
+
303
+ if (tag.children) {
304
+ code += generateComponentBody(tag.children, stateNames);
415
305
  }
416
306
 
417
- if (tag.children) code += generateComponentBody(tag.children, stateNames);
418
307
  code += ` html += "</${tag.name}>";\n`;
419
308
  return code;
420
309
  }
package/compiler/lexer.js CHANGED
@@ -24,7 +24,8 @@ const KEYWORDS = new Set([
24
24
  'middleware', 'auth', 'login', 'register', 'upload', 'path', 'maxSize', 'error',
25
25
  'text', 'action', 'validate', 'store', 'onMount', 'onDestroy', 'queue', 'mail',
26
26
  'loop', 'condition', 'script', 'if', 'for',
27
- 'get', 'post', 'put', 'delete', 'patch', 'head', 'options'
27
+ 'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
28
+ 'Get', 'Post', 'Put', 'Delete', 'Patch', 'Head', 'Options'
28
29
  ]);
29
30
 
30
31
  function tokenize(code, filename = 'unknown.free') {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * compiler/parser.js
3
- * Parser for the Free Framework (v4.3).
3
+ * Parser for the Free Framework (v5.0.0 Convergence).
4
4
  * Standardized braced-body parsing and raw code slicing.
5
5
  */
6
6
 
@@ -21,7 +21,7 @@ function parse(tokens, filename = 'unknown.free', source = '') {
21
21
  if (token.type === 'KEYWORD') {
22
22
  if (token.value === 'route') {
23
23
  ast.push(parseRoute(tokens, filename, source));
24
- } else if (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'].includes(token.value)) {
24
+ } else if (['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'Get', 'Post', 'Put', 'Delete', 'Patch', 'Head', 'Options'].includes(token.value)) {
25
25
  tokens.unshift(token); // put back the method
26
26
  ast.push(parseRoute(tokens, filename, source));
27
27
  } else if (token.value === 'model') {
@@ -175,7 +175,7 @@ function parseComponent(tokens, filename, source) {
175
175
  body.push({ type: next.value, code: parseActionBody(tokens, source) });
176
176
  } else if (next.value === 'style') {
177
177
  body.push({ type: 'style', content: parseActionBody(tokens, source) });
178
- } else if (next.type === 'IDENTIFIER') {
178
+ } else if (next.type === 'IDENTIFIER' || next.type === 'KEYWORD') {
179
179
  body.push(parseTag(next, tokens, filename, source));
180
180
  }
181
181
  }
@@ -194,7 +194,6 @@ function parseComponentBody(tokens, filename, source) {
194
194
  let item, list = "";
195
195
 
196
196
  if (isLoop) {
197
- // loop list item {
198
197
  const loopTokens = [];
199
198
  while (tokens.length > 0 && tokens[0].value !== '{') {
200
199
  loopTokens.push(tokens.shift());
@@ -203,7 +202,6 @@ function parseComponentBody(tokens, filename, source) {
203
202
  item = loopTokens.pop().value;
204
203
  list = loopTokens.map(t => (t.type === 'STRING' ? `"${t.value.replace(/"/g, '')}"` : t.value)).join('');
205
204
  } else {
206
- // for item in list {
207
205
  item = tokens.shift().value;
208
206
  if (tokens[0] && tokens[0].value === 'in') tokens.shift();
209
207
  while (tokens.length > 0 && tokens[0].value !== '{') {
@@ -235,7 +233,12 @@ function parseComponentBody(tokens, filename, source) {
235
233
  }
236
234
  children.push({ type: 'tag', name: 'a', attributes: attrs, content: txt });
237
235
  } else if (next.type === 'IDENTIFIER' || next.type === 'KEYWORD') {
238
- children.push(parseTag(next, tokens, filename, source));
236
+ if (['class', 'id', 'style'].includes(next.value)) {
237
+ // Skip attribute-like identifiers inside bodies to prevent malformed HTML tags
238
+ if (tokens[0] && (tokens[0].type === 'STRING' || tokens[0].type === 'IDENTIFIER')) tokens.shift();
239
+ } else {
240
+ children.push(parseTag(next, tokens, filename, source));
241
+ }
239
242
  }
240
243
  }
241
244
  if (tokens[0] && tokens[0].value === '}') tokens.shift();
@@ -251,13 +254,11 @@ function parseTag(token, tokens, filename, source) {
251
254
 
252
255
  while (tokens.length > 0) {
253
256
  const next = tokens[0];
254
- // Break on braces or other symbols (except - for identifiers)
255
257
  if (next.value === '{' || next.value === '}' || (next.type === 'SYMBOL' && next.value !== '-' && next.value !== '=')) break;
256
258
 
257
259
  const attrNameToken = tokens.shift();
258
260
  const attrName = attrNameToken.value;
259
261
 
260
- // Skip = if present
261
262
  if (tokens[0] && tokens[0].value === '=') tokens.shift();
262
263
 
263
264
  if (tokens[0] && tokens[0].value === '{') {
@@ -318,7 +319,11 @@ function parseTag(token, tokens, filename, source) {
318
319
  }
319
320
  children.push({ type: 'tag', name: 'a', attributes: attrs, content: txt });
320
321
  } else if (next.type === 'IDENTIFIER' || next.type === 'KEYWORD') {
321
- children.push(parseTag(next, tokens, filename, source));
322
+ if (['class', 'id', 'style'].includes(next.value)) {
323
+ if (tokens[0] && (tokens[0].type === 'STRING' || tokens[0].type === 'IDENTIFIER')) tokens.shift();
324
+ } else {
325
+ children.push(parseTag(next, tokens, filename, source));
326
+ }
322
327
  }
323
328
  }
324
329
  if (tokens[0] && tokens[0].value === '}') tokens.shift();
@@ -347,18 +352,16 @@ function parseAction(tokens, source) {
347
352
  let name = nameToken.value;
348
353
  let params = [];
349
354
 
350
- // Support for parameterized actions: action myAction(id, name) { ... }
351
355
  if (tokens[0] && tokens[0].value === '(') {
352
- tokens.shift(); // consume '('
356
+ tokens.shift();
353
357
  while (tokens.length > 0 && tokens[0].value !== ')') {
354
358
  const pt = tokens.shift();
355
359
  if (pt.value !== ',') {
356
360
  params.push(pt.value);
357
361
  }
358
362
  }
359
- if (tokens[0] && tokens[0].value === ')') tokens.shift(); // consume ')'
363
+ if (tokens[0] && tokens[0].value === ')') tokens.shift();
360
364
  } else if (name.includes('(')) {
361
- // Handle case where lexer smushed it together: myAction(id)
362
365
  const parts = name.split('(');
363
366
  name = parts[0];
364
367
  const argPart = parts[1].replace(')', '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-framework",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "Professional Node.js engine for the .free language. Blazing-fast SSR, Islands Architecture, and built-in ORM.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "build": "free build"
9
9
  },
10
10
  "dependencies": {
11
- "free-framework": "latest",
11
+ "free-framework": "^5.0.1",
12
12
  "hyper-express": "^6.14.0",
13
13
  "knex": "^3.1.0",
14
14
  "mysql2": "^3.9.2",
@@ -3,15 +3,12 @@ component Counter {
3
3
  count: 0
4
4
  }
5
5
 
6
- div {
7
- class "p-4 bg-gray-800 rounded-lg text-center"
6
+ div class="p-4 bg-gray-800 rounded-lg text-center" {
8
7
  h2 { text "Reactive Counter" }
9
- p {
10
- class "text-4xl font-black text-primary my-4"
8
+ p class="text-4xl font-black text-primary my-4" {
11
9
  text "{count}"
12
10
  }
13
- button {
14
- class "px-6 py-2 bg-primary text-dark font-bold rounded-full hover:scale-105 transition-transform"
11
+ button class="px-6 py-2 bg-primary text-dark font-bold rounded-full hover:scale-105 transition-transform" {
15
12
  text "Increment"
16
13
  on-click { state.count++ }
17
14
  }
@@ -2,7 +2,7 @@ component Docs {
2
2
  div class="min-h-screen bg-black text-zinc-300 font-sans selection:bg-white selection:text-black" {
3
3
  // Navigation / Header Space
4
4
  div class="container-pro pt-32 pb-20" {
5
- span class="badge-pro mb-4 animate-pulse" { text "v4.8.10 | Cloud Docs" }
5
+ span class="badge-pro mb-4 animate-pulse" { text "v5.0.1 | High-Octane Docs" }
6
6
  h1 class="text-6xl md:text-8xl font-black text-white tracking-tighter mb-8 leading-none" {
7
7
  text "Free Ultra"
8
8
  br;
@@ -22,7 +22,6 @@ component Docs {
22
22
  // Content Sections
23
23
  div class="container-pro space-y-32 pb-40" {
24
24
 
25
- // 1. Core Philosophy
26
25
  section id="philosophy" {
27
26
  div class="grid md:grid-cols-2 gap-16 items-start" {
28
27
  div {
@@ -35,26 +34,30 @@ component Docs {
35
34
  text " to ensure your pages load instantly while remaining fully interactive."
36
35
  }
37
36
  ul class="space-y-4 text-sm" {
38
- li class="flex gap-3" { span class="text-white" { text "•" } text "Blazing-fast SSR powered by HyperExpress." }
39
- li class="flex gap-3" { span class="text-white" { text "•" } text "Selective Hydration: Only JavaScript that's needed enters the browser." }
40
- li class="flex gap-3" { span class="text-white" { text "•" } text "Unified Syntax: The .free language unifies HTML, CSS, and Logic." }
37
+ li class="flex gap-3" {
38
+ span class="text-white" { text "•" }
39
+ text "Blazing-fast SSR powered by HyperExpress."
40
+ }
41
+ li class="flex gap-3" {
42
+ span class="text-white" { text "•" }
43
+ text "Selective Hydration: Only JavaScript that's needed enters the browser."
44
+ }
45
+ li class="flex gap-3" {
46
+ span class="text-white" { text "•" }
47
+ text "Unified Syntax: The .free language unifies HTML, CSS, and Logic."
48
+ }
41
49
  }
42
50
  }
43
- div class="card-pro bg-zinc-900/20" {
44
- pre {
45
- code {
46
- text "// Pure Performance\n"
47
- text "component Fast \{ \n"
48
- text " h1 \{ text 'Zero Delay' \}\n"
49
- text " // Static by default\n"
50
- text "\}\n"
51
- }
51
+ div class="card-pro bg-zinc-900/20" {
52
+ pre {
53
+ code {
54
+ text "// Pure Performance\ncomponent Fast { \n h1 { text 'Zero Delay' }\n // Static by default\n}\n"
52
55
  }
53
56
  }
57
+ }
54
58
  }
55
59
  }
56
60
 
57
- // 2. The Free Language
58
61
  section id="language" {
59
62
  h2 class="text-4xl font-bold text-white mb-12 tracking-tight" { text "2. The .free Language" }
60
63
  div class="grid md:grid-cols-3 gap-8" {
@@ -85,7 +88,6 @@ component Docs {
85
88
  }
86
89
  }
87
90
 
88
- // 3. Routing & ORM
89
91
  section id="orm" {
90
92
  div class="grid md:grid-cols-2 gap-16" {
91
93
  div {
@@ -109,18 +111,13 @@ component Docs {
109
111
  div class="card-pro bg-zinc-950" {
110
112
  pre {
111
113
  code {
112
- text "get \"/dashboard\" -> Dashboard\n\n"
113
- text "post \"/api/login\" \{ \n"
114
- text " const user = await ORM.find('User', \{ email: body.email \});\n"
115
- text " return \{ success: !!user \};\n"
116
- text "\}"
114
+ text "get /dashboard -> Dashboard\n\npost /api/login { \n const user = await ORM.find('User', { email: body.email });\n return { success: !!user };\n}"
117
115
  }
118
116
  }
119
117
  }
120
118
  }
121
119
  }
122
120
 
123
- // 4. CLI Power-user
124
121
  section id="cli" {
125
122
  h2 class="text-4xl font-bold text-white mb-12 tracking-tight" { text "4. CLI Power-user Guide" }
126
123
  div class="grid grid-cols-1 md:grid-cols-2 gap-4" {
@@ -147,15 +144,15 @@ component Docs {
147
144
  }
148
145
  }
149
146
 
150
- // 5. IDE Extensions
151
147
  section id="vscode" {
152
148
  div class="bg-zinc-900/10 border border-zinc-800 rounded-3xl p-12" {
153
149
  h2 class="text-4xl font-bold text-white mb-8 tracking-tight" { text "5. IDE Ecosystem" }
154
-
155
150
  div class="grid md:grid-cols-2 gap-12" {
156
151
  div {
157
152
  h3 class="text-2xl font-bold text-white mb-4" { text "Visual Studio Code" }
158
- p class="mb-6 text-sm" { text "Get syntax highlighting, auto-completion, and deep linting for .free files." }
153
+ p class="mb-6 text-sm" {
154
+ text "Get syntax highlighting, auto-completion, and deep linting for .free files."
155
+ }
159
156
  div class="space-y-4" {
160
157
  a href="https://marketplace.visualstudio.com/items?itemName=dev-omartolba.free-vscode" class="block text-zinc-400 hover:text-white transition-colors flex items-center gap-2" {
161
158
  text "→ Install from VS Marketplace"
@@ -181,20 +178,13 @@ component Docs {
181
178
  }
182
179
  }
183
180
 
184
- // Footer
185
181
  footer class="border-t border-zinc-900 py-20" {
186
182
  div class="container-pro text-center" {
187
183
  p class="text-sm text-zinc-600" {
188
184
  text "Free Ultra Framework © 2026. Built for professionals. "
189
185
  a href="https://omar-fathy.xyz/free-docs/guide.php" class="text-zinc-400 hover:text-white" { text "Official Encyclopedia" }
190
186
  }
191
- </div>
192
- }
193
- }
194
-
195
- style {
196
- html { scroll-behavior: smooth; }
197
- .hero-title { background: linear-gradient(to right, #fff, #555); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
198
- .badge-pro { display: inline-block; }
187
+ }
188
+ </footer>
199
189
  }
200
190
  }