free-framework 4.4.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/bin/free.js +118 -0
  4. package/cli/commands/build.js +124 -0
  5. package/cli/commands/deploy.js +143 -0
  6. package/cli/commands/devtools.js +210 -0
  7. package/cli/commands/doctor.js +72 -0
  8. package/cli/commands/install.js +28 -0
  9. package/cli/commands/make.js +74 -0
  10. package/cli/commands/migrate.js +67 -0
  11. package/cli/commands/new.js +54 -0
  12. package/cli/commands/serve.js +73 -0
  13. package/cli/commands/test.js +57 -0
  14. package/compiler/analyzer.js +102 -0
  15. package/compiler/generator.js +386 -0
  16. package/compiler/lexer.js +166 -0
  17. package/compiler/parser.js +410 -0
  18. package/database/model.js +6 -0
  19. package/database/orm.js +379 -0
  20. package/database/query-builder.js +179 -0
  21. package/index.js +51 -0
  22. package/package.json +80 -0
  23. package/plugins/auth.js +212 -0
  24. package/plugins/cache.js +85 -0
  25. package/plugins/chat.js +32 -0
  26. package/plugins/mail.js +53 -0
  27. package/plugins/metrics.js +126 -0
  28. package/plugins/payments.js +59 -0
  29. package/plugins/queue.js +139 -0
  30. package/plugins/search.js +51 -0
  31. package/plugins/storage.js +123 -0
  32. package/plugins/upload.js +62 -0
  33. package/router/router.js +57 -0
  34. package/runtime/app.js +14 -0
  35. package/runtime/client.js +254 -0
  36. package/runtime/cluster.js +35 -0
  37. package/runtime/edge.js +62 -0
  38. package/runtime/middleware/maintenance.js +54 -0
  39. package/runtime/middleware/security.js +30 -0
  40. package/runtime/server.js +130 -0
  41. package/runtime/validator.js +102 -0
  42. package/runtime/views/error.free +104 -0
  43. package/runtime/views/maintenance.free +0 -0
  44. package/template-engine/renderer.js +24 -0
  45. package/templates/app-template/.env +23 -0
  46. package/templates/app-template/app/Exceptions/Handler.js +65 -0
  47. package/templates/app-template/app/Http/Controllers/AuthController.free +91 -0
  48. package/templates/app-template/app/Http/Middleware/AuthGuard.js +46 -0
  49. package/templates/app-template/app/Services/Storage.js +75 -0
  50. package/templates/app-template/app/Services/Validator.js +91 -0
  51. package/templates/app-template/app/controllers/AuthController.free +42 -0
  52. package/templates/app-template/app/middleware/auth.js +25 -0
  53. package/templates/app-template/app/models/User.free +32 -0
  54. package/templates/app-template/app/routes.free +12 -0
  55. package/templates/app-template/app/styles.css +23 -0
  56. package/templates/app-template/app/views/counter.free +23 -0
  57. package/templates/app-template/app/views/header.free +13 -0
  58. package/templates/app-template/config/app.js +32 -0
  59. package/templates/app-template/config/auth.js +39 -0
  60. package/templates/app-template/config/database.js +54 -0
  61. package/templates/app-template/package.json +28 -0
  62. package/templates/app-template/resources/css/app.css +11 -0
  63. package/templates/app-template/resources/views/dashboard.free +25 -0
  64. package/templates/app-template/resources/views/home.free +26 -0
  65. package/templates/app-template/routes/api.free +22 -0
  66. package/templates/app-template/routes/web.free +25 -0
  67. package/templates/app-template/tailwind.config.js +21 -0
  68. package/templates/app-template/views/about.ejs +47 -0
  69. package/templates/app-template/views/home.ejs +52 -0
  70. package/templates/auth/login.html +144 -0
  71. package/templates/auth/register.html +146 -0
  72. package/utils/logger.js +20 -0
@@ -0,0 +1,410 @@
1
+ /**
2
+ * compiler/parser.js
3
+ * Parser for the Free Framework (v4.3).
4
+ * Standardized braced-body parsing and raw code slicing.
5
+ */
6
+
7
+ const { FreeSyntaxError } = require('./lexer');
8
+
9
+ function expect(tokens, expectedValue, filename) {
10
+ const token = tokens.shift();
11
+ if (!token || token.value !== expectedValue) {
12
+ throw new FreeSyntaxError(`Expected "${expectedValue}" but found "${token ? token.value : 'EOF'}"`, filename, token ? token.line : 0, token ? token.col : 0);
13
+ }
14
+ return token;
15
+ }
16
+
17
+ function parse(tokens, filename = 'unknown.free', source = '') {
18
+ const ast = [];
19
+ while (tokens.length > 0) {
20
+ const token = tokens.shift();
21
+ if (token.type === 'KEYWORD') {
22
+ if (token.value === 'route') {
23
+ ast.push(parseRoute(tokens, filename, source));
24
+ } else if (token.value === 'model') {
25
+ ast.push(parseModel(tokens));
26
+ } else if (token.value === 'component') {
27
+ ast.push(parseComponent(tokens, filename, source));
28
+ } else if (token.value === 'auth') {
29
+ ast.push(parseAuth(tokens));
30
+ } else if (token.value === 'upload') {
31
+ ast.push(parseUpload(tokens));
32
+ } else if (token.value === 'error') {
33
+ ast.push(parseError(tokens));
34
+ } else if (token.value === 'action') {
35
+ ast.push(parseAction(tokens, source));
36
+ } else if (token.value === 'validate') {
37
+ ast.push(parseValidate(tokens, filename));
38
+ } else if (token.value === 'store') {
39
+ ast.push(parseStore(tokens, filename));
40
+ }
41
+ }
42
+ }
43
+ return ast;
44
+ }
45
+
46
+ function parseRoute(tokens, filename, source) {
47
+ const methodToken = tokens.shift();
48
+ const method = methodToken.value;
49
+ const path = tokens.shift().value.replace(/"/g, '');
50
+ let view = null;
51
+ let middleware = null;
52
+ let handler = null;
53
+
54
+ if (tokens[0] && tokens[0].value === '{') {
55
+ tokens.shift();
56
+ while (tokens.length > 0 && tokens[0].value !== '}') {
57
+ const next = tokens.shift();
58
+ if (next.value === 'view') view = tokens.shift().value.replace(/"/g, '');
59
+ else if (next.value === 'middleware') middleware = tokens.shift().value.replace(/"/g, '');
60
+ else if (next.value === 'handler') {
61
+ if (tokens[0] && tokens[0].value === '{') {
62
+ handler = parseActionBody(tokens, source);
63
+ } else {
64
+ handler = { type: 'action_ref', name: tokens.shift().value };
65
+ }
66
+ }
67
+ }
68
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
69
+ }
70
+ return { type: 'route', method, path, view, middleware, handler, line: methodToken.line };
71
+ }
72
+
73
+ function parseModel(tokens) {
74
+ const name = tokens.shift().value;
75
+ const fields = [];
76
+ if (tokens[0] && tokens[0].value === '{') tokens.shift();
77
+
78
+ while (tokens.length > 0 && tokens[0].value !== '}') {
79
+ if (tokens.length < 2) break; // Safety check
80
+
81
+ const fieldNameToken = tokens.shift();
82
+ const fieldName = fieldNameToken.value;
83
+
84
+ if (fieldName === 'timestamps') {
85
+ fields.push({ name: 'timestamps', type: 'directive' });
86
+ continue;
87
+ }
88
+
89
+ const fieldTypeToken = tokens.shift();
90
+ if (!fieldTypeToken) break;
91
+ const field = { name: fieldName, type: fieldTypeToken.value };
92
+
93
+ // Handle modifiers (unique, index, hidden, default) on the same line
94
+ while (tokens.length > 0 &&
95
+ tokens[0].value !== '}' &&
96
+ tokens[0].line === fieldTypeToken.line) {
97
+
98
+ const modToken = tokens.shift();
99
+ const mod = modToken.value;
100
+ // Skip brackets and commas
101
+ if (mod === '[' || mod === ']' || mod === ',') continue;
102
+
103
+ if (mod === 'unique') field.unique = true;
104
+ else if (mod === 'index') field.index = true;
105
+ else if (mod === 'primary') field.primary = true;
106
+ else if (mod === 'increment') field.increment = true;
107
+ else if (mod === 'hidden') field.hidden = true;
108
+ else if (mod === 'default') {
109
+ if (tokens[0] && tokens[0].value === ':') tokens.shift(); // skip :
110
+ if (tokens.length > 0) {
111
+ const valToken = tokens.shift();
112
+ let val = valToken.value.replace(/"/g, '');
113
+ if (val === 'false') val = false;
114
+ else if (val === 'true') val = true;
115
+ else if (val === 'null') val = null;
116
+ field.default = val;
117
+ }
118
+ }
119
+ }
120
+ fields.push(field);
121
+ }
122
+
123
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
124
+ return { type: 'model', name, fields };
125
+ }
126
+
127
+ function parseComponent(tokens, filename, source) {
128
+ const name = tokens.shift().value;
129
+ const states = [];
130
+ const body = [];
131
+ if (tokens[0] && tokens[0].value === '{') tokens.shift();
132
+ while (tokens.length > 0 && tokens[0].value !== '}' && tokens[0].value !== 'component') {
133
+ const next = tokens.shift();
134
+ if (next.value === 'state') {
135
+ const sName = tokens.shift().value;
136
+ if (tokens[0] && tokens[0].value === '=') tokens.shift();
137
+
138
+ let sVal = "";
139
+ let braceCount = 0;
140
+ let bracketCount = 0;
141
+
142
+ while (tokens.length > 0) {
143
+ const t = tokens.shift();
144
+ sVal += t.value;
145
+ if (t.value === '{') braceCount++;
146
+ if (t.value === '}') braceCount--;
147
+ if (t.value === '[') bracketCount++;
148
+ if (t.value === ']') bracketCount--;
149
+
150
+ if (braceCount === 0 && bracketCount === 0) {
151
+ // Check if next token is on same line (simple case) or if we are done with complex
152
+ if (tokens[0] && tokens[0].line !== t.line && !['{', '[', '(', ','].includes(t.value)) {
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ sVal = sVal.replace(/"/g, '');
158
+ if (!isNaN(sVal) && sVal.trim() !== "" && !sVal.includes('.')) {
159
+ states.push({ name: sName, default: Number(sVal) });
160
+ } else {
161
+ states.push({ name: sName, default: sVal.trim() || "null" });
162
+ }
163
+ } else if (next.value === 'onMount') {
164
+ body.push({ type: 'onMount', code: parseActionBody(tokens, source) });
165
+ } else if (next.value === 'onDestroy') {
166
+ body.push({ type: 'onDestroy', code: parseActionBody(tokens, source) });
167
+ } else if (next.value === 'handler' || next.value === 'script') {
168
+ body.push({ type: next.value, code: parseActionBody(tokens, source) });
169
+ } else if (next.value === 'style') {
170
+ body.push({ type: 'style', content: parseActionBody(tokens, source) });
171
+ } else if (next.type === 'IDENTIFIER') {
172
+ body.push(parseTag(next, tokens, filename, source));
173
+ }
174
+ }
175
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
176
+ return { type: 'component', name, states, body };
177
+ }
178
+
179
+ function parseComponentBody(tokens, filename, source) {
180
+ const children = [];
181
+ if (tokens[0] && tokens[0].value === '{') {
182
+ tokens.shift();
183
+ while (tokens.length > 0 && tokens[0].value !== '}') {
184
+ const next = tokens.shift();
185
+ if (next.value === 'for' || next.value === 'loop') {
186
+ const isLoop = next.value === 'loop';
187
+ let item, list = "";
188
+
189
+ if (isLoop) {
190
+ // loop list item {
191
+ const loopTokens = [];
192
+ while (tokens.length > 0 && tokens[0].value !== '{') {
193
+ loopTokens.push(tokens.shift());
194
+ }
195
+ if (loopTokens.length < 2) throw new FreeSyntaxError("Loop requires list and item", filename, next.line, next.col);
196
+ item = loopTokens.pop().value;
197
+ list = loopTokens.map(t => (t.type === 'STRING' ? `"${t.value.replace(/"/g, '')}"` : t.value)).join('');
198
+ } else {
199
+ // for item in list {
200
+ item = tokens.shift().value;
201
+ if (tokens[0] && tokens[0].value === 'in') tokens.shift();
202
+ while (tokens.length > 0 && tokens[0].value !== '{') {
203
+ list += (tokens[0].type === 'STRING' ? `"${tokens.shift().value.replace(/"/g, '')}"` : tokens.shift().value);
204
+ }
205
+ }
206
+ const loopBody = parseComponentBody(tokens, filename, source);
207
+ children.push({ type: 'loop', item, list, body: loopBody });
208
+ } else if (next.value === 'if' || next.value === 'condition') {
209
+ let condition = "";
210
+ while (tokens.length > 0 && tokens[0].value !== '{') {
211
+ condition += tokens.shift().value;
212
+ }
213
+ const ifBody = parseComponentBody(tokens, filename, source);
214
+ children.push({ type: 'condition', condition, body: ifBody });
215
+ } else if (next.value === 'script' || next.value === 'style' || next.value.startsWith('on-')) {
216
+ const type = next.value.startsWith('on-') ? 'event' : next.value;
217
+ const event = type === 'event' ? next.value.substring(3) : null;
218
+ const code = parseActionBody(tokens, source);
219
+ children.push({ type, event, code });
220
+ } else if (next.value === 'link') {
221
+ const txt = tokens.shift().value.replace(/"/g, '');
222
+ const lnk = tokens.shift().value.replace(/"/g, '');
223
+ const attrs = { href: lnk };
224
+ while (tokens.length > 0 && tokens[0].line === next.line && tokens[0].value !== '{' && tokens[0].value !== '}') {
225
+ const attrName = tokens.shift().value;
226
+ if (tokens[0] && tokens[0].value === '=') tokens.shift(); const attrVal = tokens.shift().value.replace(/"/g, '');
227
+ attrs[attrName] = attrVal;
228
+ }
229
+ children.push({ type: 'tag', name: 'a', attributes: attrs, content: txt });
230
+ } else if (next.type === 'IDENTIFIER' || next.type === 'KEYWORD') {
231
+ children.push(parseTag(next, tokens, filename, source));
232
+ }
233
+ }
234
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
235
+ }
236
+ return children;
237
+ }
238
+
239
+ function parseTag(token, tokens, filename, source) {
240
+ const name = token.value;
241
+ const attributes = {};
242
+ const children = [];
243
+ let content = "";
244
+
245
+ while (tokens.length > 0) {
246
+ const next = tokens[0];
247
+ // Break on braces or other symbols (except - for identifiers)
248
+ if (next.value === '{' || next.value === '}' || (next.type === 'SYMBOL' && next.value !== '-' && next.value !== '=')) break;
249
+
250
+ const attrNameToken = tokens.shift();
251
+ const attrName = attrNameToken.value;
252
+
253
+ // Skip = if present
254
+ if (tokens[0] && tokens[0].value === '=') tokens.shift();
255
+
256
+ if (tokens[0] && tokens[0].value === '{') {
257
+ attributes[attrName] = parseActionBody(tokens, source);
258
+ } else if (tokens[0] && tokens[0].type !== 'SYMBOL' && tokens[0].type !== 'BRACE') {
259
+ attributes[attrName] = tokens.shift().value.replace(/"/g, '');
260
+ } else {
261
+ attributes[attrName] = "true";
262
+ }
263
+ }
264
+
265
+ if (tokens[0] && tokens[0].value === '{') {
266
+ tokens.shift();
267
+ while (tokens.length > 0 && tokens[0].value !== '}') {
268
+ const next = tokens.shift();
269
+ if (next.value === 'text') {
270
+ content += tokens.shift().value.replace(/"/g, '');
271
+ } else if (next.value === 'for' || next.value === 'loop') {
272
+ const isLoop = next.value === 'loop';
273
+ let item, list = "";
274
+ if (isLoop) {
275
+ const loopTokens = [];
276
+ while (tokens.length > 0 && tokens[0].value !== '{') {
277
+ loopTokens.push(tokens.shift());
278
+ }
279
+ if (loopTokens.length < 2) throw new FreeSyntaxError("Loop requires list and item", filename, next.line, next.col);
280
+ item = loopTokens.pop().value;
281
+ list = loopTokens.map(t => (t.type === 'STRING' ? `"${t.value.replace(/"/g, '')}"` : t.value)).join('');
282
+ } else {
283
+ item = tokens.shift().value;
284
+ if (tokens[0] && tokens[0].value === 'in') tokens.shift();
285
+ while (tokens.length > 0 && tokens[0].value !== '{') {
286
+ list += (tokens[0].type === 'STRING' ? `"${tokens.shift().value.replace(/"/g, '')}"` : tokens.shift().value);
287
+ }
288
+ }
289
+ const loopBody = parseComponentBody(tokens, filename, source);
290
+ children.push({ type: 'loop', item, list, body: loopBody });
291
+ } else if (next.value === 'if' || next.value === 'condition') {
292
+ let condition = "";
293
+ while (tokens.length > 0 && tokens[0].value !== '{') {
294
+ condition += tokens.shift().value;
295
+ }
296
+ const ifBody = parseComponentBody(tokens, filename, source);
297
+ children.push({ type: 'condition', condition, body: ifBody });
298
+ } else if (next.value === 'script' || next.value === 'style' || next.value.startsWith('on-')) {
299
+ const type = next.value.startsWith('on-') ? 'event' : next.value;
300
+ const event = type === 'event' ? next.value.substring(3) : null;
301
+ const code = parseActionBody(tokens, source);
302
+ children.push({ type, event, code });
303
+ } else if (next.value === 'link') {
304
+ const txt = tokens.shift().value.replace(/"/g, '');
305
+ const lnk = tokens.shift().value.replace(/"/g, '');
306
+ const attrs = { href: lnk };
307
+ while (tokens.length > 0 && tokens[0].line === next.line && tokens[0].value !== '{' && tokens[0].value !== '}') {
308
+ const attrName = tokens.shift().value;
309
+ if (tokens[0] && tokens[0].value === '=') tokens.shift(); const attrVal = tokens.shift().value.replace(/"/g, '');
310
+ attrs[attrName] = attrVal;
311
+ }
312
+ children.push({ type: 'tag', name: 'a', attributes: attrs, content: txt });
313
+ } else if (next.type === 'IDENTIFIER') {
314
+ children.push(parseTag(next, tokens, filename, source));
315
+ }
316
+ }
317
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
318
+ }
319
+ return { type: 'tag', name, attributes, children, content };
320
+ }
321
+
322
+ function parseActionBody(tokens, source = '') {
323
+ if (tokens[0] && tokens[0].value === '{') {
324
+ const startToken = tokens.shift();
325
+ let braceCount = 1;
326
+ while (tokens.length > 0 && braceCount > 0) {
327
+ const next = tokens.shift();
328
+ if (next.value === '{') braceCount++;
329
+ if (next.value === '}') braceCount--;
330
+ if (braceCount === 0) {
331
+ return source.slice(startToken.end, next.start).trim();
332
+ }
333
+ }
334
+ }
335
+ return '';
336
+ }
337
+
338
+ function parseAction(tokens, source) {
339
+ const nameToken = tokens.shift();
340
+ let name = nameToken.value;
341
+ let params = [];
342
+
343
+ // Support for parameterized actions: action myAction(id, name) { ... }
344
+ if (tokens[0] && tokens[0].value === '(') {
345
+ tokens.shift(); // consume '('
346
+ while (tokens.length > 0 && tokens[0].value !== ')') {
347
+ const pt = tokens.shift();
348
+ if (pt.value !== ',') {
349
+ params.push(pt.value);
350
+ }
351
+ }
352
+ if (tokens[0] && tokens[0].value === ')') tokens.shift(); // consume ')'
353
+ } else if (name.includes('(')) {
354
+ // Handle case where lexer smushed it together: myAction(id)
355
+ const parts = name.split('(');
356
+ name = parts[0];
357
+ const argPart = parts[1].replace(')', '');
358
+ if (argPart) {
359
+ params = argPart.split(',').map(p => p.trim()).filter(Boolean);
360
+ }
361
+ }
362
+
363
+ const code = parseActionBody(tokens, source);
364
+ return { type: 'action', name, params, code };
365
+ }
366
+
367
+ function parseAuth(tokens) {
368
+ const config = {};
369
+ if (tokens[0] && tokens[0].value === '{') tokens.shift();
370
+ while (tokens.length > 0 && tokens[0].value !== '}') {
371
+ const key = tokens.shift().value;
372
+ const val = tokens.shift().value.replace(/"/g, '');
373
+ config[key] = val;
374
+ }
375
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
376
+ return { type: 'auth', config };
377
+ }
378
+
379
+ function parseUpload(tokens) {
380
+ const name = tokens.shift().value;
381
+ const config = {};
382
+ if (tokens[0] && tokens[0].value === '{') tokens.shift();
383
+ while (tokens.length > 0 && tokens[0].value !== '}') {
384
+ const key = tokens.shift().value;
385
+ const val = tokens.shift().value.replace(/"/g, '');
386
+ config[key] = val;
387
+ }
388
+ if (tokens[0] && tokens[0].value === '}') tokens.shift();
389
+ return { type: 'upload', name, config };
390
+ }
391
+
392
+ function parseError(tokens) {
393
+ const code = tokens.shift().value;
394
+ const view = tokens.shift().value.replace(/"/g, '');
395
+ return { type: 'error', code, view };
396
+ }
397
+
398
+ function parseValidate(tokens, filename) {
399
+ const model = tokens.shift().value;
400
+ const rules = parseActionBody(tokens);
401
+ return { type: 'validate', model, rules };
402
+ }
403
+
404
+ function parseStore(tokens, filename) {
405
+ const name = tokens.shift().value;
406
+ const body = parseActionBody(tokens);
407
+ return { type: 'store', name, body };
408
+ }
409
+
410
+ module.exports = { parse, FreeSyntaxError };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * database/model.js
3
+ * Proxy to centralized Model in orm.js
4
+ */
5
+ const { Model } = require('./orm');
6
+ module.exports = { Model };