naidejs 1.0.0 → 1.2.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/src/generator.js CHANGED
@@ -1,13 +1,43 @@
1
1
  export class Generator {
2
- constructor() {
2
+ constructor(options = {}) {
3
3
  this.indent = 0;
4
4
  this.output = [];
5
5
  this.usesExpress = false;
6
6
  this.models = new Map();
7
+ this.runtimeImports = new Set();
8
+ this.schemas = new Map();
9
+ this.needsEventBus = false;
10
+ this.runtimePath = options.runtimePath || 'naidejs/runtime';
11
+ this.dbDir = null;
12
+ this.authSecret = null;
13
+ this.hasWs = false;
14
+ this.wsNodes = [];
7
15
  }
8
16
 
9
17
  generate(ast) {
10
18
  this.visitProgram(ast);
19
+
20
+ const preamble = [];
21
+
22
+ if (this.needsEventBus) {
23
+ this.runtimeImports.add('createEventBus');
24
+ }
25
+
26
+ if (this.runtimeImports.size > 0) {
27
+ const imports = [...this.runtimeImports].join(', ');
28
+ preamble.push(`import { ${imports} } from '${this.runtimePath}';`);
29
+ preamble.push('');
30
+ }
31
+
32
+ if (this.needsEventBus) {
33
+ preamble.push('const __eventBus = createEventBus();');
34
+ preamble.push('');
35
+ }
36
+
37
+ if (preamble.length > 0) {
38
+ this.output.unshift(...preamble);
39
+ }
40
+
11
41
  return this.output.join('\n');
12
42
  }
13
43
 
@@ -32,6 +62,7 @@ export class Generator {
32
62
  case 'Function': return this.visitFunction(node);
33
63
  case 'Return': return this.visitReturn(node);
34
64
  case 'ReturnStatus': return this.visitReturnStatus(node);
65
+ case 'ReturnMethod': return this.visitReturnMethod(node);
35
66
  case 'TypedVar': return this.visitTypedVar(node);
36
67
  case 'If': return this.visitIf(node);
37
68
  case 'Each': return this.visitEach(node);
@@ -50,7 +81,21 @@ export class Generator {
50
81
  case 'CompoundAssign': return this.visitCompoundAssign(node);
51
82
  case 'ExprStatement': this.emit(this.expr(node.expression) + ';'); return;
52
83
  case 'DbConnect': return this.visitDbConnect(node);
84
+ case 'DbDir': return this.visitDbDir(node);
53
85
  case 'AwaitAll': return this.visitAwaitAllStatement(node);
86
+ case 'SchemaDecl': return this.visitSchema(node);
87
+ case 'CrudDecl': return this.visitCrudTopLevel(node);
88
+ case 'AuthDecl': return this.visitAuthTopLevel(node);
89
+ case 'CorsDecl': return this.visitCorsTopLevel(node);
90
+ case 'LimitDecl': return this.visitLimitTopLevel(node);
91
+ case 'EnvDecl': return this.visitEnv(node);
92
+ case 'EveryDecl': return this.visitEvery(node);
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);
54
99
  default:
55
100
  this.emit(`/* unknown: ${node.type} */`);
56
101
  }
@@ -101,6 +146,26 @@ export class Generator {
101
146
  this.emit(`return res.status(${this.expr(node.statusCode)}).json(${this.expr(node.body)});`);
102
147
  }
103
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
+
104
169
  visitTypedVar(node) {
105
170
  const keyword = node.isMut ? 'let' : 'const';
106
171
  const exp = node.isPublic ? 'export ' : '';
@@ -111,7 +176,6 @@ export class Generator {
111
176
  const target = this.expr(node.target);
112
177
  const value = this.expr(node.value);
113
178
 
114
- // Destructuring assignment
115
179
  if (node.target.type === 'Array') {
116
180
  const names = node.target.elements.map(e => this.expr(e)).join(', ');
117
181
  this.emit(`const [${names}] = ${value};`);
@@ -219,35 +283,72 @@ export class Generator {
219
283
  }
220
284
 
221
285
  visitServer(node) {
286
+ const hasWs = node.routes.some(r => r.type === 'WsDecl');
287
+
222
288
  this.emit(`import express from 'express';`);
289
+ if (hasWs) {
290
+ this.emit(`import { WebSocketServer } from 'ws';`);
291
+ }
223
292
  this.emitRaw('');
224
293
  this.emit(`const ${node.name} = express();`);
225
294
  this.emit(`${node.name}.use(express.json());`);
295
+ this.emit(`${node.name}.use(express.urlencoded({ extended: true }));`);
226
296
  this.emitRaw('');
227
297
 
228
- // Middleware
229
298
  for (const mid of node.middleware) {
230
299
  this.emit(`${node.name}.use(${this.generateMiddleware(mid)});`);
231
300
  this.emitRaw('');
232
301
  }
233
302
 
234
- // Routes
235
- for (const route of node.routes) {
236
- if (route.type === 'Route') {
237
- this.visitRoute(node.name, route);
303
+ const wsNodes = [];
304
+ const errorHandlers = [];
305
+
306
+ for (const child of node.routes) {
307
+ if (child.type === 'Route') {
308
+ this.visitRoute(node.name, child);
309
+ } else if (child.type === 'CrudDecl') {
310
+ this.visitCrud(node.name, child);
311
+ } else if (child.type === 'AuthDecl') {
312
+ this.visitAuth(node.name, child);
313
+ } else if (child.type === 'CorsDecl') {
314
+ this.visitCors(node.name, child);
315
+ } else if (child.type === 'LimitDecl') {
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);
238
327
  } else {
239
- this.visitStatement(route);
328
+ this.visitStatement(child);
240
329
  }
241
330
  }
242
331
 
243
- // Listen
332
+ for (const eh of errorHandlers) {
333
+ this.visitErrorHandler(node.name, eh);
334
+ }
335
+
244
336
  const port = node.port ? this.expr(node.port) : '3000';
245
337
  this.emitRaw('');
246
- 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
+ }
247
343
  this.indent++;
248
344
  this.emit(`console.log(\`Server running on port \${${port}}\`);`);
249
345
  this.indent--;
250
346
  this.emit('});');
347
+
348
+ for (const wsNode of wsNodes) {
349
+ this.emitRaw('');
350
+ this.visitWs(wsNode);
351
+ }
251
352
  }
252
353
 
253
354
  visitRoute(appName, route) {
@@ -255,17 +356,14 @@ export class Generator {
255
356
  const path = this.stringValue(route.path);
256
357
  const params = route.params.length > 0 ? route.params.join(', ') : 'req, res';
257
358
 
258
- // Check if body uses await
259
359
  const needsAsync = this.bodyUsesAwait(route.body);
260
360
  const asyncPrefix = needsAsync ? 'async ' : '';
261
361
 
262
362
  this.emit(`${appName}.${method}(${path}, ${asyncPrefix}(${params}) => {`);
263
363
  this.indent++;
264
364
 
265
- // If params don't include res, inject it
266
365
  const hasRes = params.includes('res');
267
366
 
268
- // Transform body: last expression with ret becomes res.json
269
367
  for (let i = 0; i < route.body.length; i++) {
270
368
  const stmt = route.body[i];
271
369
  if (stmt.type === 'Return' && stmt.value !== null) {
@@ -326,7 +424,6 @@ export class Generator {
326
424
  this.emitRaw('');
327
425
  }
328
426
 
329
- // Methods
330
427
  for (const method of node.methods) {
331
428
  const async = method.isAsync ? 'async ' : '';
332
429
  const params = method.params.map(p => {
@@ -353,8 +450,6 @@ export class Generator {
353
450
 
354
451
  visitOn(node) {
355
452
  const event = this.expr(node.event);
356
- // Split event into object and event name
357
- // e.g., process.exit -> process.on('exit', ...)
358
453
  if (node.event.type === 'MemberAccess') {
359
454
  const obj = this.expr(node.event.object);
360
455
  const evt = node.event.property;
@@ -376,7 +471,6 @@ export class Generator {
376
471
 
377
472
  visitThrow(node) {
378
473
  const value = this.expr(node.value);
379
- // If it's a string, wrap in Error
380
474
  if (node.value.type === 'String') {
381
475
  this.emit(`throw new Error(${value});`);
382
476
  } else {
@@ -388,12 +482,348 @@ export class Generator {
388
482
  this.emit(`const db = new Database(${this.expr(node.connectionString)});`);
389
483
  }
390
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
+
391
490
  visitAwaitAllStatement(node) {
392
491
  const exprs = node.expressions.map(e => this.expr(e)).join(', ');
393
492
  this.emit(`await Promise.all([${exprs}]);`);
394
493
  }
395
494
 
396
- // Expression generation
495
+ // ===== High-level features =====
496
+
497
+ visitSchema(node) {
498
+ this.runtimeImports.add('createSchema');
499
+ if (this.dbDir) {
500
+ this.runtimeImports.add('createFileStore');
501
+ } else {
502
+ this.runtimeImports.add('createStore');
503
+ }
504
+
505
+ this.emit(`const ${node.name}Schema = createSchema('${node.name}', {`);
506
+ this.indent++;
507
+
508
+ for (const field of node.fields) {
509
+ const props = [];
510
+
511
+ switch (field.type) {
512
+ case 'auto':
513
+ props.push("type: 'id'", 'auto: true');
514
+ break;
515
+ case 'timestamp':
516
+ props.push("type: 'timestamp'");
517
+ break;
518
+ case 'str':
519
+ props.push("type: 'string'");
520
+ break;
521
+ case 'int':
522
+ props.push("type: 'integer'");
523
+ break;
524
+ case 'num':
525
+ props.push("type: 'number'");
526
+ break;
527
+ case 'bool':
528
+ props.push("type: 'boolean'");
529
+ break;
530
+ case 'enum':
531
+ if (field.enumValues) {
532
+ const vals = field.enumValues.map(v => this.expr(v)).join(', ');
533
+ props.push("type: 'enum'", `values: [${vals}]`);
534
+ } else {
535
+ props.push("type: 'enum'");
536
+ }
537
+ break;
538
+ default:
539
+ props.push(`type: '${field.type}'`);
540
+ break;
541
+ }
542
+
543
+ for (const mod of field.modifiers) {
544
+ switch (mod.name) {
545
+ case 'required': props.push('required: true'); break;
546
+ case 'optional': props.push('required: false'); break;
547
+ case 'unique': props.push('unique: true'); break;
548
+ case 'email': props.push('email: true'); break;
549
+ case 'url': props.push('url: true'); break;
550
+ case 'auto': props.push('auto: true'); break;
551
+ case 'min': props.push(`min: ${this.expr(mod.args[0])}`); break;
552
+ case 'max': props.push(`max: ${this.expr(mod.args[0])}`); break;
553
+ case 'default': props.push(`default: ${this.expr(mod.args[0])}`); break;
554
+ }
555
+ }
556
+
557
+ this.emit(`${field.name}: { ${props.join(', ')} },`);
558
+ }
559
+
560
+ this.indent--;
561
+ this.emit('});');
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
+ }
567
+ this.emitRaw('');
568
+
569
+ this.schemas.set(node.name, node);
570
+ }
571
+
572
+ visitCrud(appName, node) {
573
+ this.runtimeImports.add('registerCrud');
574
+ this.needsEventBus = true;
575
+
576
+ const path = this.stringValue(node.path);
577
+ this.emit(`registerCrud(${appName}, ${path}, ${node.schemaName}Schema, ${node.schemaName}Store, __eventBus);`);
578
+ this.emitRaw('');
579
+ }
580
+
581
+ visitCrudTopLevel(node) {
582
+ this.visitCrud('app', node);
583
+ }
584
+
585
+ visitAuth(appName, node) {
586
+ this.runtimeImports.add('jwtAuth');
587
+
588
+ const secret = this.expr(node.secret);
589
+ this.authSecret = secret;
590
+
591
+ this.emit(`const __authSecret = ${secret};`);
592
+
593
+ const options = [];
594
+ if (node.publicPaths.length > 0) {
595
+ const paths = node.publicPaths.map(p => this.stringValue(p)).join(', ');
596
+ options.push(`public: [${paths}]`);
597
+ }
598
+ const optStr = options.length > 0 ? `, { ${options.join(', ')} }` : '';
599
+
600
+ if (node.protectedPaths.length > 0) {
601
+ const path = this.stringValue(node.protectedPaths[0]);
602
+ this.emit(`${appName}.use(${path}, jwtAuth(${secret}${optStr}));`);
603
+ } else {
604
+ this.emit(`${appName}.use(jwtAuth(${secret}${optStr}));`);
605
+ }
606
+ this.emitRaw('');
607
+ }
608
+
609
+ visitAuthTopLevel(node) {
610
+ this.visitAuth('app', node);
611
+ }
612
+
613
+ visitCors(appName, node) {
614
+ this.runtimeImports.add('corsMiddleware');
615
+
616
+ const origins = this.expr(node.origins);
617
+ if (node.origins.type === 'String') {
618
+ this.emit(`${appName}.use(corsMiddleware([${origins}]));`);
619
+ } else {
620
+ this.emit(`${appName}.use(corsMiddleware(${origins}));`);
621
+ }
622
+ this.emitRaw('');
623
+ }
624
+
625
+ visitCorsTopLevel(node) {
626
+ this.visitCors('app', node);
627
+ }
628
+
629
+ visitLimit(appName, node) {
630
+ this.runtimeImports.add('rateLimit');
631
+
632
+ const path = this.stringValue(node.path);
633
+ const max = this.expr(node.max);
634
+ const window = this.expr(node.window);
635
+ this.emit(`${appName}.use(${path}, rateLimit(${max}, ${window}));`);
636
+ this.emitRaw('');
637
+ }
638
+
639
+ visitLimitTopLevel(node) {
640
+ this.visitLimit('app', node);
641
+ }
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
+
762
+ visitEnv(node) {
763
+ this.runtimeImports.add('loadEnv');
764
+
765
+ const names = node.vars.map(v => v.name);
766
+ this.emit(`const { ${names.join(', ')} } = loadEnv({`);
767
+ this.indent++;
768
+
769
+ for (const v of node.vars) {
770
+ const props = [];
771
+
772
+ switch (v.type) {
773
+ case 'str': props.push("type: 'string'"); break;
774
+ case 'int': props.push("type: 'integer'"); break;
775
+ case 'num': props.push("type: 'number'"); break;
776
+ case 'bool': props.push("type: 'boolean'"); break;
777
+ default: props.push(`type: '${v.type}'`); break;
778
+ }
779
+
780
+ for (const mod of v.modifiers) {
781
+ switch (mod.name) {
782
+ case 'required': props.push('required: true'); break;
783
+ case 'default': props.push(`default: ${this.expr(mod.args[0])}`); break;
784
+ }
785
+ }
786
+
787
+ this.emit(`${v.name}: { ${props.join(', ')} },`);
788
+ }
789
+
790
+ this.indent--;
791
+ this.emit('});');
792
+ this.emitRaw('');
793
+ }
794
+
795
+ visitEvery(node) {
796
+ this.runtimeImports.add('scheduleEvery');
797
+
798
+ const interval = this.expr(node.interval);
799
+ const needsAsync = this.bodyUsesAwait(node.body);
800
+ const asyncPrefix = needsAsync ? 'async ' : '';
801
+
802
+ this.emit(`scheduleEvery(${interval}, ${asyncPrefix}() => {`);
803
+ this.indent++;
804
+ for (const stmt of node.body) this.visitStatement(stmt);
805
+ this.indent--;
806
+ this.emit('});');
807
+ this.emitRaw('');
808
+ }
809
+
810
+ visitWatch(node) {
811
+ this.needsEventBus = true;
812
+
813
+ const params = node.params.length > 0 ? node.params.join(', ') : 'event';
814
+ const needsAsync = this.bodyUsesAwait(node.body);
815
+ const asyncPrefix = needsAsync ? 'async ' : '';
816
+
817
+ this.emit(`__eventBus.on('${node.eventName}', ${asyncPrefix}(${params}) => {`);
818
+ this.indent++;
819
+ for (const stmt of node.body) this.visitStatement(stmt);
820
+ this.indent--;
821
+ this.emit('});');
822
+ this.emitRaw('');
823
+ }
824
+
825
+ // ===== Expression generation =====
826
+
397
827
  expr(node) {
398
828
  if (!node) return 'undefined';
399
829
 
@@ -430,7 +860,7 @@ export class Generator {
430
860
  return `${this.expr(node.object)}?.${node.property}`;
431
861
 
432
862
  case 'Call':
433
- return `${this.expr(node.callee)}(${node.args.map(a => this.expr(a)).join(', ')})`;
863
+ return this.generateCall(node);
434
864
 
435
865
  case 'IndexAccess':
436
866
  return `${this.expr(node.object)}[${this.expr(node.index)}]`;
@@ -483,6 +913,50 @@ export class Generator {
483
913
  }
484
914
  }
485
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
+
486
960
  generateString(strData) {
487
961
  if (!strData || !strData.parts) return '""';
488
962
 
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import { Parser } from './parser.js';
3
3
  import { Generator } from './generator.js';
4
4
  import { preprocess } from './preprocess.js';
5
5
 
6
- export function compile(source, { mode = 'naide' } = {}) {
6
+ export function compile(source, { mode = 'naide', runtimePath } = {}) {
7
7
  if (mode === 'x') {
8
8
  source = preprocess(source);
9
9
  }
@@ -11,7 +11,7 @@ export function compile(source, { mode = 'naide' } = {}) {
11
11
  const tokens = lexer.tokenize();
12
12
  const parser = new Parser(tokens);
13
13
  const ast = parser.parse();
14
- const generator = new Generator();
14
+ const generator = new Generator({ runtimePath });
15
15
  const js = generator.generate(ast);
16
16
  return { js, ast, tokens, naide: mode === 'x' ? source : null };
17
17
  }