naidejs 1.1.0 → 1.3.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.
@@ -1,4 +1,6 @@
1
- -- Full NAIDE-X app with high-level features
1
+ -- Full NAIDE-X app with all features
2
+
3
+ db "data/"
2
4
 
3
5
  env:
4
6
  PORT int default(3000)
@@ -8,6 +10,7 @@ schema User:
8
10
  id auto
9
11
  name str required min(2) max(50)
10
12
  email str required email
13
+ password str required
11
14
  role str default("user")
12
15
 
13
16
  schema Todo:
@@ -18,17 +21,51 @@ schema Todo:
18
21
 
19
22
  $app:PORT
20
23
  cors "*"
24
+ cookie
21
25
  auth JWT_SECRET:
22
26
  protect "/api/*"
23
27
  public "/api/auth/*"
24
28
  limit "/api/*" 100 "1m"
29
+ static "/public"
25
30
  crud "/api/users" User
26
31
  crud "/api/todos" Todo
32
+
33
+ group "/api/v1":
34
+ G"/status"
35
+ >{version:"1.0",status:"ok"}
36
+
37
+ P"/api/auth/register"(req,res)
38
+ s:hashed=hash(req.body.password)
39
+ user=UserStore.create({name:req.body.name,email:req.body.email,password:hashed})
40
+ ?user.error
41
+ >.s 400 {errors:user.error}
42
+ token=auth.sign({id:user.id,role:user.role})
43
+ >{token,user:{id:user.id,name:user.name,email:user.email}}
44
+
45
+ P"/api/auth/login"(req,res)
46
+ user=UserStore.where({email:req.body.email})[0]
47
+ ?not user
48
+ >.s 401 {error:"Invalid credentials"}
49
+ ?not verify(req.body.password,user.password)
50
+ >.s 401 {error:"Invalid credentials"}
51
+ token=auth.sign({id:user.id,role:user.role})
52
+ >{token}
53
+
27
54
  G"/health"
28
55
  >{status:"ok"}
29
- P"/api/auth/login"(req,res)
30
- s:email=req.body.email
31
- >{token:"jwt-token-here"}
56
+
57
+ G"/"
58
+ >.h "<h1>Welcome to NAIDE</h1>"
59
+
60
+ ws "/chat":
61
+ on "connect":
62
+ send({type:"welcome"})
63
+ on "message" (data):
64
+ broadcast(data)
65
+
66
+ error (err, req, res):
67
+ log.error err.message
68
+ >.s 500 {error:"Internal server error"}
32
69
 
33
70
  every "30m":
34
71
  log"cleanup running"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "naidejs",
3
- "version": "1.1.0",
4
- "description": "NAIDE - Node AI Development Environment. A language optimized for AI code generation that transpiles to Node.js. Standard mode (~40% fewer tokens) and X mode (~80% fewer tokens).",
3
+ "version": "1.3.0",
4
+ "description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, WebSocket, and more — transpiles to Node.js. Standard mode (~40% fewer tokens) and X mode (~80% fewer tokens).",
5
5
  "main": "src/index.js",
6
6
  "exports": {
7
7
  ".": "./src/index.js",
@@ -35,15 +35,19 @@
35
35
  "claude",
36
36
  "chatgpt",
37
37
  "naide",
38
- "dsl"
38
+ "dsl",
39
+ "express",
40
+ "api",
41
+ "backend",
42
+ "server"
39
43
  ],
40
44
  "author": "pirikari.sena@gmail.com",
41
45
  "license": "MIT",
42
46
  "repository": {
43
47
  "type": "git",
44
- "url": "https://github.com/pirikari/naide"
48
+ "url": "https://github.com/irxk/naide"
45
49
  },
46
- "homepage": "https://github.com/pirikari/naide#readme",
50
+ "homepage": "https://github.com/irxk/naide#readme",
47
51
  "engines": {
48
52
  "node": ">=18.0.0"
49
53
  }
package/src/generator.js CHANGED
@@ -8,6 +8,10 @@ export class Generator {
8
8
  this.schemas = new Map();
9
9
  this.needsEventBus = false;
10
10
  this.runtimePath = options.runtimePath || 'naidejs/runtime';
11
+ this.dbDir = null;
12
+ this.authSecret = null;
13
+ this.hasWs = false;
14
+ this.wsNodes = [];
11
15
  }
12
16
 
13
17
  generate(ast) {
@@ -58,6 +62,7 @@ export class Generator {
58
62
  case 'Function': return this.visitFunction(node);
59
63
  case 'Return': return this.visitReturn(node);
60
64
  case 'ReturnStatus': return this.visitReturnStatus(node);
65
+ case 'ReturnMethod': return this.visitReturnMethod(node);
61
66
  case 'TypedVar': return this.visitTypedVar(node);
62
67
  case 'If': return this.visitIf(node);
63
68
  case 'Each': return this.visitEach(node);
@@ -76,6 +81,7 @@ export class Generator {
76
81
  case 'CompoundAssign': return this.visitCompoundAssign(node);
77
82
  case 'ExprStatement': this.emit(this.expr(node.expression) + ';'); return;
78
83
  case 'DbConnect': return this.visitDbConnect(node);
84
+ case 'DbDir': return this.visitDbDir(node);
79
85
  case 'AwaitAll': return this.visitAwaitAllStatement(node);
80
86
  case 'SchemaDecl': return this.visitSchema(node);
81
87
  case 'CrudDecl': return this.visitCrudTopLevel(node);
@@ -85,6 +91,11 @@ export class Generator {
85
91
  case 'EnvDecl': return this.visitEnv(node);
86
92
  case 'EveryDecl': return this.visitEvery(node);
87
93
  case 'WatchDecl': return this.visitWatch(node);
94
+ case 'StaticDecl': return this.visitStaticTopLevel(node);
95
+ case 'WsDecl': return this.visitWsTopLevel(node);
96
+ case 'GroupDecl': return this.visitGroupTopLevel(node);
97
+ case 'ErrorHandler': return this.visitErrorHandlerTopLevel(node);
98
+ case 'CookieDecl': return this.visitCookieTopLevel(node);
88
99
  default:
89
100
  this.emit(`/* unknown: ${node.type} */`);
90
101
  }
@@ -135,6 +146,26 @@ export class Generator {
135
146
  this.emit(`return res.status(${this.expr(node.statusCode)}).json(${this.expr(node.body)});`);
136
147
  }
137
148
 
149
+ visitReturnMethod(node) {
150
+ const val = this.expr(node.value);
151
+ switch (node.method) {
152
+ case 'redirect':
153
+ this.emit(`return res.redirect(${val});`);
154
+ break;
155
+ case 'html':
156
+ this.emit(`return res.type('html').send(${val});`);
157
+ break;
158
+ case 'text':
159
+ this.emit(`return res.type('text').send(${val});`);
160
+ break;
161
+ case 'file':
162
+ this.emit(`return res.sendFile(${val});`);
163
+ break;
164
+ default:
165
+ this.emit(`return res.${node.method}(${val});`);
166
+ }
167
+ }
168
+
138
169
  visitTypedVar(node) {
139
170
  const keyword = node.isMut ? 'let' : 'const';
140
171
  const exp = node.isPublic ? 'export ' : '';
@@ -252,10 +283,16 @@ export class Generator {
252
283
  }
253
284
 
254
285
  visitServer(node) {
286
+ const hasWs = node.routes.some(r => r.type === 'WsDecl');
287
+
255
288
  this.emit(`import express from 'express';`);
289
+ if (hasWs) {
290
+ this.emit(`import { WebSocketServer } from 'ws';`);
291
+ }
256
292
  this.emitRaw('');
257
293
  this.emit(`const ${node.name} = express();`);
258
294
  this.emit(`${node.name}.use(express.json());`);
295
+ this.emit(`${node.name}.use(express.urlencoded({ extended: true }));`);
259
296
  this.emitRaw('');
260
297
 
261
298
  for (const mid of node.middleware) {
@@ -263,6 +300,9 @@ export class Generator {
263
300
  this.emitRaw('');
264
301
  }
265
302
 
303
+ const wsNodes = [];
304
+ const errorHandlers = [];
305
+
266
306
  for (const child of node.routes) {
267
307
  if (child.type === 'Route') {
268
308
  this.visitRoute(node.name, child);
@@ -274,18 +314,41 @@ export class Generator {
274
314
  this.visitCors(node.name, child);
275
315
  } else if (child.type === 'LimitDecl') {
276
316
  this.visitLimit(node.name, child);
317
+ } else if (child.type === 'StaticDecl') {
318
+ this.visitStatic(node.name, child);
319
+ } else if (child.type === 'WsDecl') {
320
+ wsNodes.push(child);
321
+ } else if (child.type === 'GroupDecl') {
322
+ this.visitGroup(node.name, child);
323
+ } else if (child.type === 'ErrorHandler') {
324
+ errorHandlers.push(child);
325
+ } else if (child.type === 'CookieDecl') {
326
+ this.visitCookie(node.name, child);
277
327
  } else {
278
328
  this.visitStatement(child);
279
329
  }
280
330
  }
281
331
 
332
+ for (const eh of errorHandlers) {
333
+ this.visitErrorHandler(node.name, eh);
334
+ }
335
+
282
336
  const port = node.port ? this.expr(node.port) : '3000';
283
337
  this.emitRaw('');
284
- this.emit(`${node.name}.listen(${port}, () => {`);
338
+ if (hasWs) {
339
+ this.emit(`const __server = ${node.name}.listen(${port}, () => {`);
340
+ } else {
341
+ this.emit(`${node.name}.listen(${port}, () => {`);
342
+ }
285
343
  this.indent++;
286
344
  this.emit(`console.log(\`Server running on port \${${port}}\`);`);
287
345
  this.indent--;
288
346
  this.emit('});');
347
+
348
+ for (const wsNode of wsNodes) {
349
+ this.emitRaw('');
350
+ this.visitWs(wsNode);
351
+ }
289
352
  }
290
353
 
291
354
  visitRoute(appName, route) {
@@ -419,16 +482,25 @@ export class Generator {
419
482
  this.emit(`const db = new Database(${this.expr(node.connectionString)});`);
420
483
  }
421
484
 
485
+ visitDbDir(node) {
486
+ const raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'data/';
487
+ this.dbDir = raw.endsWith('/') ? raw : raw + '/';
488
+ }
489
+
422
490
  visitAwaitAllStatement(node) {
423
491
  const exprs = node.expressions.map(e => this.expr(e)).join(', ');
424
492
  this.emit(`await Promise.all([${exprs}]);`);
425
493
  }
426
494
 
427
- // ===== New high-level features =====
495
+ // ===== High-level features =====
428
496
 
429
497
  visitSchema(node) {
430
498
  this.runtimeImports.add('createSchema');
431
- this.runtimeImports.add('createStore');
499
+ if (this.dbDir) {
500
+ this.runtimeImports.add('createFileStore');
501
+ } else {
502
+ this.runtimeImports.add('createStore');
503
+ }
432
504
 
433
505
  this.emit(`const ${node.name}Schema = createSchema('${node.name}', {`);
434
506
  this.indent++;
@@ -487,7 +559,11 @@ export class Generator {
487
559
 
488
560
  this.indent--;
489
561
  this.emit('});');
490
- this.emit(`const ${node.name}Store = createStore(${node.name}Schema);`);
562
+ if (this.dbDir) {
563
+ this.emit(`const ${node.name}Store = createFileStore(${node.name}Schema, '${this.dbDir}${node.name}.json');`);
564
+ } else {
565
+ this.emit(`const ${node.name}Store = createStore(${node.name}Schema);`);
566
+ }
491
567
  this.emitRaw('');
492
568
 
493
569
  this.schemas.set(node.name, node);
@@ -510,6 +586,10 @@ export class Generator {
510
586
  this.runtimeImports.add('jwtAuth');
511
587
 
512
588
  const secret = this.expr(node.secret);
589
+ this.authSecret = secret;
590
+
591
+ this.emit(`const __authSecret = ${secret};`);
592
+
513
593
  const options = [];
514
594
  if (node.publicPaths.length > 0) {
515
595
  const paths = node.publicPaths.map(p => this.stringValue(p)).join(', ');
@@ -560,6 +640,125 @@ export class Generator {
560
640
  this.visitLimit('app', node);
561
641
  }
562
642
 
643
+ visitStatic(appName, node) {
644
+ let raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'public';
645
+ if (raw.startsWith('/')) raw = raw.slice(1);
646
+ this.emit(`${appName}.use(express.static(${JSON.stringify(raw)}));`);
647
+ this.emitRaw('');
648
+ }
649
+
650
+ visitStaticTopLevel(node) {
651
+ this.visitStatic('app', node);
652
+ }
653
+
654
+ visitWs(node) {
655
+ const path = this.stringValue(node.path);
656
+ this.emit(`const __wss = new WebSocketServer({ server: __server, path: ${path} });`);
657
+ this.emit(`__wss.on('connection', (__ws) => {`);
658
+ this.indent++;
659
+ this.emit(`const send = (d) => __ws.send(typeof d === 'string' ? d : JSON.stringify(d));`);
660
+ this.emit(`const broadcast = (d) => { const m = typeof d === 'string' ? d : JSON.stringify(d); for (const c of __wss.clients) if (c.readyState === 1) c.send(m); };`);
661
+
662
+ const connectEvents = node.events.filter(e => {
663
+ const name = e.name.raw || e.name.parts?.map(p => p.value).join('');
664
+ return name === 'connect' || name === 'open';
665
+ });
666
+ const otherEvents = node.events.filter(e => {
667
+ const name = e.name.raw || e.name.parts?.map(p => p.value).join('');
668
+ return name !== 'connect' && name !== 'open';
669
+ });
670
+
671
+ for (const evt of connectEvents) {
672
+ this.emitRaw('');
673
+ for (const stmt of evt.body) this.visitStatement(stmt);
674
+ }
675
+
676
+ for (const evt of otherEvents) {
677
+ const evtName = evt.name.raw || evt.name.parts?.map(p => p.value).join('');
678
+ this.emitRaw('');
679
+
680
+ if (evtName === 'message') {
681
+ const needsAsync = this.bodyUsesAwait(evt.body);
682
+ const asyncPrefix = needsAsync ? 'async ' : '';
683
+ this.emit(`__ws.on('message', ${asyncPrefix}(__raw) => {`);
684
+ this.indent++;
685
+ const dataParam = evt.params[0] || 'data';
686
+ this.emit(`const ${dataParam} = JSON.parse(__raw);`);
687
+ for (const stmt of evt.body) this.visitStatement(stmt);
688
+ this.indent--;
689
+ this.emit('});');
690
+ } else {
691
+ const params = evt.params.length > 0 ? evt.params.join(', ') : '';
692
+ const needsAsync = this.bodyUsesAwait(evt.body);
693
+ const asyncPrefix = needsAsync ? 'async ' : '';
694
+ this.emit(`__ws.on('${evtName}', ${asyncPrefix}(${params}) => {`);
695
+ this.indent++;
696
+ for (const stmt of evt.body) this.visitStatement(stmt);
697
+ this.indent--;
698
+ this.emit('});');
699
+ }
700
+ }
701
+
702
+ this.indent--;
703
+ this.emit('});');
704
+ }
705
+
706
+ visitWsTopLevel(node) {
707
+ this.visitWs(node);
708
+ }
709
+
710
+ visitGroup(appName, node) {
711
+ const prefix = this.stringValue(node.prefix);
712
+ this.groupCounter = (this.groupCounter || 0) + 1;
713
+ const routerName = `__router${this.groupCounter}`;
714
+ this.emit(`const ${routerName} = express.Router();`);
715
+
716
+ for (const child of node.routes) {
717
+ if (child.type === 'Route') {
718
+ this.visitRoute(routerName, child);
719
+ } else if (child.type === 'CrudDecl') {
720
+ this.visitCrud(routerName, child);
721
+ } else if (child.type === 'AuthDecl') {
722
+ this.visitAuth(routerName, child);
723
+ } else if (child.type === 'GroupDecl') {
724
+ this.visitGroup(routerName, child);
725
+ } else {
726
+ this.visitStatement(child);
727
+ }
728
+ }
729
+
730
+ this.emit(`${appName}.use(${prefix}, ${routerName});`);
731
+ this.emitRaw('');
732
+ }
733
+
734
+ visitGroupTopLevel(node) {
735
+ this.visitGroup('app', node);
736
+ }
737
+
738
+ visitErrorHandler(appName, node) {
739
+ const params = node.params.join(', ');
740
+ this.emit(`${appName}.use((${params}, next) => {`);
741
+ this.indent++;
742
+ for (const stmt of node.body) this.visitStatement(stmt);
743
+ this.indent--;
744
+ this.emit('});');
745
+ this.emitRaw('');
746
+ }
747
+
748
+ visitErrorHandlerTopLevel(node) {
749
+ this.visitErrorHandler('app', node);
750
+ }
751
+
752
+ visitCookie(appName, node) {
753
+ this.runtimeImports.add('cookieParser');
754
+ this.emit(`${appName}.use(cookieParser());`);
755
+ this.emitRaw('');
756
+ }
757
+
758
+ visitCookieTopLevel(node) {
759
+ this.visitCookie('app', node);
760
+ }
761
+
563
762
  visitEnv(node) {
564
763
  this.runtimeImports.add('loadEnv');
565
764
 
@@ -661,7 +860,7 @@ export class Generator {
661
860
  return `${this.expr(node.object)}?.${node.property}`;
662
861
 
663
862
  case 'Call':
664
- return `${this.expr(node.callee)}(${node.args.map(a => this.expr(a)).join(', ')})`;
863
+ return this.generateCall(node);
665
864
 
666
865
  case 'IndexAccess':
667
866
  return `${this.expr(node.object)}[${this.expr(node.index)}]`;
@@ -714,6 +913,50 @@ export class Generator {
714
913
  }
715
914
  }
716
915
 
916
+ generateCall(node) {
917
+ const AUTO_IMPORT = { 'hash': 'hash', 'verify': 'verify', 'uuid': 'uuid' };
918
+
919
+ if (node.callee.type === 'Identifier' && AUTO_IMPORT[node.callee.name]) {
920
+ const runtimeFn = AUTO_IMPORT[node.callee.name];
921
+ this.runtimeImports.add(runtimeFn);
922
+ return `${runtimeFn}(${node.args.map(a => this.expr(a)).join(', ')})`;
923
+ }
924
+
925
+ if (node.callee.type === 'Identifier' && node.callee.name === 'sign') {
926
+ this.runtimeImports.add('jwtSign');
927
+ const args = node.args.map(a => this.expr(a));
928
+ if (args.length === 1 && this.authSecret) {
929
+ return `jwtSign(${args[0]}, __authSecret)`;
930
+ }
931
+ return `jwtSign(${args.join(', ')})`;
932
+ }
933
+
934
+ if (node.callee.type === 'MemberAccess' &&
935
+ node.callee.object.type === 'Identifier' &&
936
+ node.callee.object.name === 'api') {
937
+ this.runtimeImports.add('api');
938
+ }
939
+
940
+ if (node.callee.type === 'MemberAccess' &&
941
+ node.callee.object.type === 'Identifier' &&
942
+ node.callee.object.name === 'auth') {
943
+ if (node.callee.property === 'sign') {
944
+ this.runtimeImports.add('jwtSign');
945
+ const args = node.args.map(a => this.expr(a));
946
+ if (args.length === 1 && this.authSecret) {
947
+ return `jwtSign(${args[0]}, __authSecret)`;
948
+ }
949
+ return `jwtSign(${args.join(', ')})`;
950
+ }
951
+ if (node.callee.property === 'verify') {
952
+ this.runtimeImports.add('jwtVerify');
953
+ return `jwtVerify(${node.args.map(a => this.expr(a)).join(', ')})`;
954
+ }
955
+ }
956
+
957
+ return `${this.expr(node.callee)}(${node.args.map(a => this.expr(a)).join(', ')})`;
958
+ }
959
+
717
960
  generateString(strData) {
718
961
  if (!strData || !strData.parts) return '""';
719
962
 
package/src/index.js CHANGED
@@ -4,16 +4,34 @@ import { Generator } from './generator.js';
4
4
  import { preprocess } from './preprocess.js';
5
5
 
6
6
  export function compile(source, { mode = 'naide', runtimePath } = {}) {
7
+ let processedSource = source;
7
8
  if (mode === 'x') {
8
- source = preprocess(source);
9
+ processedSource = preprocess(source);
10
+ }
11
+ try {
12
+ const lexer = new Lexer(processedSource);
13
+ const tokens = lexer.tokenize();
14
+ const parser = new Parser(tokens);
15
+ const ast = parser.parse();
16
+ const generator = new Generator({ runtimePath });
17
+ const js = generator.generate(ast);
18
+ return { js, ast, tokens, naide: mode === 'x' ? processedSource : null };
19
+ } catch (e) {
20
+ const lineMatch = e.message.match(/line (\d+)/);
21
+ if (lineMatch) {
22
+ const lineNum = parseInt(lineMatch[1]);
23
+ const lines = processedSource.split('\n');
24
+ const start = Math.max(0, lineNum - 3);
25
+ const end = Math.min(lines.length, lineNum + 2);
26
+ const context = lines.slice(start, end).map((l, i) => {
27
+ const num = start + i + 1;
28
+ const marker = num === lineNum ? ' >> ' : ' ';
29
+ return `${marker}${num} | ${l}`;
30
+ }).join('\n');
31
+ e.message += `\n\n${context}\n`;
32
+ }
33
+ throw e;
9
34
  }
10
- const lexer = new Lexer(source);
11
- const tokens = lexer.tokenize();
12
- const parser = new Parser(tokens);
13
- const ast = parser.parse();
14
- const generator = new Generator({ runtimePath });
15
- const js = generator.generate(ast);
16
- return { js, ast, tokens, naide: mode === 'x' ? source : null };
17
35
  }
18
36
 
19
37
  export function transpile(source, opts) {
package/src/lexer.js CHANGED
@@ -11,7 +11,7 @@ class Token {
11
11
 
12
12
  export class Lexer {
13
13
  constructor(source) {
14
- this.source = source;
14
+ this.source = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
15
15
  this.pos = 0;
16
16
  this.line = 1;
17
17
  this.col = 1;
@@ -78,6 +78,11 @@ export class Lexer {
78
78
  continue;
79
79
  }
80
80
 
81
+ if (ch === ';') {
82
+ this.advance();
83
+ continue;
84
+ }
85
+
81
86
  if (ch === '"' || ch === "'") {
82
87
  this.readString(ch);
83
88
  continue;
@@ -195,7 +200,11 @@ export class Lexer {
195
200
  }
196
201
  }
197
202
 
198
- if (this.peek() === quote) this.advance(); // skip closing quote
203
+ if (this.peek() === quote) {
204
+ this.advance();
205
+ } else {
206
+ throw new Error(`[NAIDE Lexer Error] Unterminated string starting at line ${startLine}:${startCol}`);
207
+ }
199
208
 
200
209
  if (current) parts.push({ type: 'text', value: current });
201
210
 
@@ -221,7 +230,11 @@ export class Lexer {
221
230
  value += this.advance();
222
231
  }
223
232
  }
224
- if (this.peek() === '`') this.advance();
233
+ if (this.peek() === '`') {
234
+ this.advance();
235
+ } else {
236
+ throw new Error(`[NAIDE Lexer Error] Unterminated template string starting at line ${startLine}:${startCol}`);
237
+ }
225
238
  this.tokens.push(new Token(T.STRING, { parts: [{ type: 'text', value }], raw: value }, startLine, startCol));
226
239
  }
227
240
 
@@ -371,7 +384,7 @@ export class Lexer {
371
384
  case ':': this.tokens.push(new Token(T.COLON, ':', startLine, startCol)); break;
372
385
  case ',': this.tokens.push(new Token(T.COMMA, ',', startLine, startCol)); break;
373
386
  default:
374
- throw new Error(`Unexpected character '${ch}' at line ${startLine}:${startCol}`);
387
+ throw new Error(`[NAIDE Lexer Error] Unexpected character '${ch}' at line ${startLine}:${startCol}`);
375
388
  }
376
389
  }
377
390