naider 1.11.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -15
- package/SPEC.naide +118 -0
- package/lsp/server.js +21 -1
- package/package.json +16 -3
- package/src/generator-bun.js +89 -0
- package/src/generator-python.js +632 -2
- package/src/generator.js +639 -4
- package/src/parser.js +333 -2
- package/src/tokens.js +22 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Node AI Development Environment** — A programming language designed for AI-speed code generation that transpiles to Node.js.
|
|
4
4
|
|
|
5
|
-
NAIDE is built on three principles: one way to write everything (zero ambiguity), keyword-driven intent (the first token decides meaning), and minimal token count (fewer tokens = faster AI generation). It includes built-in declarations for servers, databases, authentication, file uploads, WebSockets, Discord
|
|
5
|
+
NAIDE is built on three principles: one way to write everything (zero ambiguity), keyword-driven intent (the first token decides meaning), and minimal token count (fewer tokens = faster AI generation). It includes built-in declarations for servers, databases, authentication, file uploads, WebSockets, bots (Discord/Slack/Telegram/LINE), CLI apps, HTML pages, email, GraphQL, desktop apps, mobile screens, cron jobs, job queues, testing, and more — all with zero external dependencies.
|
|
6
6
|
|
|
7
7
|
Two syntax modes:
|
|
8
8
|
|
|
@@ -545,36 +545,120 @@ server app port 3000:
|
|
|
545
545
|
ret {queued: true}
|
|
546
546
|
```
|
|
547
547
|
|
|
548
|
-
## Discord
|
|
548
|
+
## Bots (Discord / Slack / Telegram / LINE)
|
|
549
549
|
|
|
550
|
-
Built-in `bot` syntax
|
|
550
|
+
Built-in `bot` syntax with multi-platform support — events, message handling, and slash commands with zero boilerplate:
|
|
551
551
|
|
|
552
552
|
```python
|
|
553
|
+
# Discord (default)
|
|
553
554
|
bot myBot token DISCORD_TOKEN:
|
|
554
555
|
on "ready":
|
|
555
556
|
log "Bot is online!"
|
|
556
|
-
|
|
557
557
|
on "message" (msg):
|
|
558
558
|
if msg.content == "!ping":
|
|
559
559
|
msg.reply("Pong!")
|
|
560
|
-
|
|
561
560
|
slash "hello" "Says hello":
|
|
562
561
|
interaction.reply("Hello!")
|
|
563
562
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
563
|
+
# Slack
|
|
564
|
+
bot slackBot type "slack" token SLACK_TOKEN:
|
|
565
|
+
on "message" (msg):
|
|
566
|
+
msg.reply("Hi from Slack!")
|
|
567
|
+
|
|
568
|
+
# Telegram
|
|
569
|
+
bot tgBot type "telegram" token TG_TOKEN:
|
|
570
|
+
on "message" (msg):
|
|
571
|
+
msg.reply("Hi from Telegram!")
|
|
572
|
+
|
|
573
|
+
# LINE
|
|
574
|
+
bot lineBot type "line" token LINE_TOKEN:
|
|
575
|
+
on "message" (event):
|
|
576
|
+
log event
|
|
567
577
|
```
|
|
568
578
|
|
|
569
|
-
Compiles to
|
|
579
|
+
Compiles to the right SDK per platform and per target:
|
|
570
580
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
581
|
+
| Platform | Node.js / Bun | Python |
|
|
582
|
+
|----------|---------------|--------|
|
|
583
|
+
| Discord | discord.js | discord.py |
|
|
584
|
+
| Slack | @slack/bolt | slack_bolt |
|
|
585
|
+
| Telegram | node-telegram-bot-api | python-telegram-bot |
|
|
586
|
+
| LINE | @line/bot-sdk | linebot |
|
|
587
|
+
|
|
588
|
+
## Page Generation (HTML)
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
page "index.html":
|
|
592
|
+
title "My App"
|
|
593
|
+
style "styles.css"
|
|
594
|
+
div "container":
|
|
595
|
+
h1 "Hello World"
|
|
596
|
+
p "Welcome"
|
|
597
|
+
a "https://example.com" "Click here"
|
|
598
|
+
script "app.js"
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Generates a complete HTML file with proper head/body structure.
|
|
602
|
+
|
|
603
|
+
## CLI Apps
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
cli myTool "A useful tool":
|
|
607
|
+
arg "name" str "Your name"
|
|
608
|
+
arg "count" int "How many times"
|
|
609
|
+
flag "v" "verbose" "Verbose output"
|
|
610
|
+
run (args):
|
|
611
|
+
log "Hello {args.name}"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Compiles to `process.argv` parser (Node.js/Bun) or `argparse` (Python).
|
|
615
|
+
|
|
616
|
+
## Email
|
|
617
|
+
|
|
618
|
+
```python
|
|
619
|
+
mail "smtp.gmail.com" 587:
|
|
620
|
+
user env.MAIL_USER
|
|
621
|
+
pass env.MAIL_PASS
|
|
622
|
+
# Usage: mail.send("to@email.com", "Subject", "Body")
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Compiles to `nodemailer` (Node.js/Bun) or `smtplib` (Python).
|
|
626
|
+
|
|
627
|
+
## GraphQL
|
|
628
|
+
|
|
629
|
+
Add GraphQL inside any server block — auto-generates schema from `schema` declarations:
|
|
630
|
+
|
|
631
|
+
```python
|
|
632
|
+
server app port 3000:
|
|
633
|
+
schema User:
|
|
634
|
+
id auto
|
|
635
|
+
name str
|
|
636
|
+
email str
|
|
637
|
+
graphql "/graphql"
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
## Desktop Apps
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
desktop myApp:
|
|
644
|
+
title "My Desktop App"
|
|
645
|
+
size 1024 768
|
|
646
|
+
load "index.html"
|
|
575
647
|
```
|
|
576
648
|
|
|
577
|
-
|
|
649
|
+
Compiles to Electron (Node.js/Bun) or pywebview (Python).
|
|
650
|
+
|
|
651
|
+
## Mobile Screens
|
|
652
|
+
|
|
653
|
+
```python
|
|
654
|
+
screen Home:
|
|
655
|
+
text "Hello World"
|
|
656
|
+
button "Click Me"
|
|
657
|
+
input "Enter your name"
|
|
658
|
+
image "logo.png"
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
Compiles to React Native (Node.js/Bun) or Kivy (Python).
|
|
578
662
|
|
|
579
663
|
## Scheduled Tasks & Events
|
|
580
664
|
|
|
@@ -582,13 +666,67 @@ The `on "message"` event maps to `messageCreate` (discord.js) / `on_message` (di
|
|
|
582
666
|
every "5m":
|
|
583
667
|
log "cleanup running"
|
|
584
668
|
|
|
669
|
+
every "*/5 * * * *":
|
|
670
|
+
log "cron every 5 minutes"
|
|
671
|
+
|
|
585
672
|
watch User.create (event):
|
|
586
673
|
log "new user: {event.data.name}"
|
|
587
674
|
```
|
|
588
675
|
|
|
589
|
-
Intervals: `"30s"`, `"5m"`, `"1h"`, `"1d"`.
|
|
676
|
+
Intervals: `"30s"`, `"5m"`, `"1h"`, `"1d"`. Cron expressions auto-detected.
|
|
590
677
|
`watch` connects to `crud` events automatically.
|
|
591
678
|
|
|
679
|
+
## OAuth (Social Login)
|
|
680
|
+
|
|
681
|
+
```python
|
|
682
|
+
oauth "google" env.CLIENT_ID env.CLIENT_SECRET:
|
|
683
|
+
callback "/auth/callback"
|
|
684
|
+
scope "email profile"
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Supports Google, GitHub. Compiles to passport.js (Node.js) or authlib (Python).
|
|
688
|
+
|
|
689
|
+
## Payment (Stripe)
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
pay "stripe" env.STRIPE_KEY:
|
|
693
|
+
webhook "/webhook"
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Usage: `pay.checkout([{name: "Item", price: 1000, qty: 1}])`. Compiles to Stripe SDK.
|
|
697
|
+
|
|
698
|
+
## Cloud Storage (S3/GCS)
|
|
699
|
+
|
|
700
|
+
```python
|
|
701
|
+
storage "s3" env.BUCKET env.AWS_KEY env.AWS_SECRET:
|
|
702
|
+
region "ap-northeast-1"
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Usage: `storage.upload("key", data)`, `storage.download("key")`, `storage.remove("key")`. Compiles to AWS SDK (Node.js) or boto3 (Python).
|
|
706
|
+
|
|
707
|
+
## PDF Generation
|
|
708
|
+
|
|
709
|
+
```python
|
|
710
|
+
pdf "report.pdf":
|
|
711
|
+
title "Monthly Report"
|
|
712
|
+
h1 "Summary"
|
|
713
|
+
text "This is the content."
|
|
714
|
+
image "chart.png"
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Compiles to pdfkit (Node.js) or FPDF (Python).
|
|
718
|
+
|
|
719
|
+
## i18n (Internationalization)
|
|
720
|
+
|
|
721
|
+
```python
|
|
722
|
+
i18n "locales/":
|
|
723
|
+
default "en"
|
|
724
|
+
lang "en" "en.json"
|
|
725
|
+
lang "ja" "ja.json"
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Usage: `i18n.t("greeting.hello")`, `i18n.setLang("ja")`.
|
|
729
|
+
|
|
592
730
|
## Environment Variables
|
|
593
731
|
|
|
594
732
|
```python
|
package/SPEC.naide
CHANGED
|
@@ -446,3 +446,121 @@ any val = data?.nested?.value ?? "default"
|
|
|
446
446
|
# ---- スプレッド ----
|
|
447
447
|
list combined = [...items, 4, 5, 6]
|
|
448
448
|
map merged = {...config, debug: true}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ---- ページ生成 (HTML) ----
|
|
452
|
+
page "index.html":
|
|
453
|
+
title "My App"
|
|
454
|
+
style "styles.css"
|
|
455
|
+
div "container":
|
|
456
|
+
h1 "Hello World"
|
|
457
|
+
p "Welcome to NAIDE"
|
|
458
|
+
a "https://example.com" "Click here"
|
|
459
|
+
script "app.js"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
# ---- CLI アプリ ----
|
|
463
|
+
cli myTool "A useful tool":
|
|
464
|
+
arg "name" str "Your name"
|
|
465
|
+
arg "count" int "Number of times"
|
|
466
|
+
flag "v" "verbose" "Verbose output"
|
|
467
|
+
run (args):
|
|
468
|
+
log "Hello {args.name}"
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ---- メール送信 ----
|
|
472
|
+
mail "smtp.gmail.com" 587:
|
|
473
|
+
user env.MAIL_USER
|
|
474
|
+
pass env.MAIL_PASS
|
|
475
|
+
# usage: mail.send("to@email.com", "Subject", "Body")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
# ---- Cron / スケジュール ----
|
|
479
|
+
every "5s":
|
|
480
|
+
log "every 5 seconds"
|
|
481
|
+
|
|
482
|
+
every "*/5 * * * *":
|
|
483
|
+
log "cron: every 5 minutes"
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
# ---- GraphQL (サーバー内) ----
|
|
487
|
+
server app port 3000:
|
|
488
|
+
graphql "/graphql"
|
|
489
|
+
# schema定義からGraphQLスキーマを自動生成
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# ---- マルチプラットフォーム Bot ----
|
|
493
|
+
bot myBot token "TOKEN":
|
|
494
|
+
on "message" (msg):
|
|
495
|
+
msg.reply("Hello!")
|
|
496
|
+
|
|
497
|
+
bot slackBot type "slack" token "xoxb-xxx":
|
|
498
|
+
on "message" (msg):
|
|
499
|
+
msg.reply("Hi from Slack!")
|
|
500
|
+
|
|
501
|
+
bot tgBot type "telegram" token "123:ABC":
|
|
502
|
+
on "message" (msg):
|
|
503
|
+
msg.reply("Hi from Telegram!")
|
|
504
|
+
|
|
505
|
+
bot lineBot type "line" token "LINE_TOKEN":
|
|
506
|
+
on "message" (event):
|
|
507
|
+
log event
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# ---- デスクトップアプリ ----
|
|
511
|
+
desktop myApp:
|
|
512
|
+
title "My Desktop App"
|
|
513
|
+
size 1024 768
|
|
514
|
+
load "index.html"
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# ---- モバイル / スクリーン ----
|
|
518
|
+
screen Home:
|
|
519
|
+
text "Hello World"
|
|
520
|
+
button "Click Me"
|
|
521
|
+
input "Enter your name"
|
|
522
|
+
image "logo.png"
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# ---- OAuth (ソーシャルログイン) ----
|
|
526
|
+
oauth "google" env.CLIENT_ID env.CLIENT_SECRET:
|
|
527
|
+
callback "/auth/callback"
|
|
528
|
+
scope "email profile"
|
|
529
|
+
|
|
530
|
+
oauth "github" env.GH_ID env.GH_SECRET:
|
|
531
|
+
callback "/auth/github/callback"
|
|
532
|
+
scope "user:email"
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# ---- 決済 (Stripe) ----
|
|
536
|
+
pay "stripe" env.STRIPE_KEY:
|
|
537
|
+
webhook "/webhook"
|
|
538
|
+
success "/thanks"
|
|
539
|
+
cancel "/cancel"
|
|
540
|
+
# usage: pay.checkout([{name: "Item", price: 1000, qty: 1}])
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
# ---- ファイルストレージ (S3/GCS) ----
|
|
544
|
+
storage "s3" env.BUCKET env.AWS_KEY env.AWS_SECRET:
|
|
545
|
+
region "ap-northeast-1"
|
|
546
|
+
# usage: storage.upload("key", data)
|
|
547
|
+
# usage: storage.download("key")
|
|
548
|
+
# usage: storage.remove("key")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# ---- PDF生成 ----
|
|
552
|
+
pdf "report.pdf":
|
|
553
|
+
title "Monthly Report"
|
|
554
|
+
h1 "Summary"
|
|
555
|
+
text "This is the report content."
|
|
556
|
+
line
|
|
557
|
+
image "chart.png"
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# ---- i18n (多言語) ----
|
|
561
|
+
i18n "locales/":
|
|
562
|
+
default "en"
|
|
563
|
+
lang "en" "en.json"
|
|
564
|
+
lang "ja" "ja.json"
|
|
565
|
+
# usage: i18n.t("greeting.hello")
|
|
566
|
+
# usage: i18n.setLang("ja")
|
package/lsp/server.js
CHANGED
|
@@ -282,12 +282,14 @@ function getCompletions() {
|
|
|
282
282
|
'try', 'fail', 'ensure', 'server', 'model', 'schema', 'use', 'pub', 'mut',
|
|
283
283
|
'log', 'typeof', 'instanceof', 'not', 'and', 'or', 'break', 'continue',
|
|
284
284
|
'throw', 'new', 'await', 'test', 'assert', 'queue', 'job', 'db', 'env',
|
|
285
|
-
'get', 'post', 'put', 'del', 'patch',
|
|
285
|
+
'get', 'post', 'put', 'del', 'patch', 'every', 'watch',
|
|
286
286
|
];
|
|
287
287
|
const types = ['str', 'int', 'num', 'bool', 'list', 'map', 'any', 'json', 'void'];
|
|
288
288
|
const features = [
|
|
289
289
|
'cors', 'auth', 'crud', 'limit', 'cookie', 'session', 'static', 'ws', 'sse',
|
|
290
290
|
'cache', 'view', 'upload', 'group', 'validate', 'openapi', 'error', 'mid', 'prompt',
|
|
291
|
+
'page', 'cli', 'mail', 'graphql', 'desktop', 'screen',
|
|
292
|
+
'oauth', 'pay', 'storage', 'pdf', 'i18n',
|
|
291
293
|
];
|
|
292
294
|
const builtins = [
|
|
293
295
|
{ label: 'uuid()', detail: 'Generate UUID v4', insertText: 'uuid()' },
|
|
@@ -306,6 +308,12 @@ function getCompletions() {
|
|
|
306
308
|
{ label: 'createSpy(obj, method)', detail: 'Spy on method', insertText: 'createSpy(' },
|
|
307
309
|
{ label: 'registerPlugin(name, setup)', detail: 'Register plugin', insertText: 'registerPlugin(' },
|
|
308
310
|
{ label: 'usePlugin(name)', detail: 'Use registered plugin', insertText: 'usePlugin(' },
|
|
311
|
+
{ label: 'page "file.html":', detail: 'Generate HTML page', insertText: 'page ' },
|
|
312
|
+
{ label: 'cli name "desc":', detail: 'CLI application', insertText: 'cli ' },
|
|
313
|
+
{ label: 'mail "host" port:', detail: 'Email config', insertText: 'mail ' },
|
|
314
|
+
{ label: 'graphql "/path"', detail: 'GraphQL endpoint', insertText: 'graphql ' },
|
|
315
|
+
{ label: 'desktop name:', detail: 'Desktop app', insertText: 'desktop ' },
|
|
316
|
+
{ label: 'screen Name:', detail: 'Mobile screen', insertText: 'screen ' },
|
|
309
317
|
];
|
|
310
318
|
|
|
311
319
|
return [
|
|
@@ -331,6 +339,18 @@ const HOVER_DOCS = {
|
|
|
331
339
|
'ensure': '**ensure** — Finally block (always runs)\n```naide\ntry:\n risky()\nensure:\n cleanup()\n```',
|
|
332
340
|
'test': '**test** — Test case\n```naide\ntest "math": assert 1 + 1 == 2\n```',
|
|
333
341
|
'queue': '**queue** — Async job queue\n```naide\nqueue tasks:\n job "send" (data): log data\n```',
|
|
342
|
+
'page': '**page** — Generate HTML page\n```naide\npage "index.html":\n title "My App"\n h1 "Hello"\n div "container":\n p "Welcome"\n```',
|
|
343
|
+
'cli': '**cli** — CLI application\n```naide\ncli myTool "description":\n arg "name" str "Your name"\n flag "v" "verbose" "Verbose output"\n run (args):\n log args.name\n```',
|
|
344
|
+
'mail': '**mail** — Email sending\n```naide\nmail "smtp.gmail.com" 587:\n user "me@gmail.com"\n pass env.MAIL_PASS\n```',
|
|
345
|
+
'graphql': '**graphql** — GraphQL endpoint (inside server)\n```naide\nserver app port 3000:\n graphql "/graphql"\n```',
|
|
346
|
+
'desktop': '**desktop** — Desktop app (Electron/pywebview)\n```naide\ndesktop myApp:\n title "My App"\n size 1024 768\n load "index.html"\n```',
|
|
347
|
+
'screen': '**screen** — Mobile screen (React Native/Kivy)\n```naide\nscreen Home:\n text "Hello World"\n button "Click Me"\n input "Enter name"\n```',
|
|
348
|
+
'every': '**every** — Scheduled task / cron\n```naide\nevery "5s":\n log "tick"\nevery "*/5 * * * *":\n log "cron"\n```',
|
|
349
|
+
'oauth': '**oauth** — Social login (Google/GitHub)\n```naide\noauth "google" env.CLIENT_ID env.CLIENT_SECRET:\n callback "/auth/callback"\n scope "email profile"\n```',
|
|
350
|
+
'pay': '**pay** — Payment (Stripe)\n```naide\npay "stripe" env.STRIPE_KEY:\n webhook "/webhook"\n```\nUsage: `pay.checkout(items, successUrl, cancelUrl)`',
|
|
351
|
+
'storage': '**storage** — Cloud storage (S3/GCS)\n```naide\nstorage "s3" env.BUCKET env.KEY env.SECRET:\n region "ap-northeast-1"\n```\nUsage: `storage.upload(key, body)`, `storage.download(key)`',
|
|
352
|
+
'pdf': '**pdf** — PDF generation\n```naide\npdf "report.pdf":\n title "Report"\n text "Hello"\n```',
|
|
353
|
+
'i18n': '**i18n** — Internationalization\n```naide\ni18n "locales/":\n default "en"\n lang "en" "en.json"\n lang "ja" "ja.json"\n```\nUsage: `i18n.t("key")`, `i18n.setLang("ja")`',
|
|
334
354
|
};
|
|
335
355
|
|
|
336
356
|
function getHover(params) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naider",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, SQL, AI/LLM, WebSocket, Discord
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, SQL, AI/LLM, WebSocket, bots (Discord/Slack/Telegram/LINE), CLI, HTML, email, GraphQL, desktop, mobile, and more — transpiles to Node.js/Python/Bun.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
@@ -43,7 +43,20 @@
|
|
|
43
43
|
"backend",
|
|
44
44
|
"server",
|
|
45
45
|
"discord",
|
|
46
|
-
"discord-bot"
|
|
46
|
+
"discord-bot",
|
|
47
|
+
"slack",
|
|
48
|
+
"telegram",
|
|
49
|
+
"line",
|
|
50
|
+
"graphql",
|
|
51
|
+
"cli",
|
|
52
|
+
"email",
|
|
53
|
+
"desktop",
|
|
54
|
+
"mobile",
|
|
55
|
+
"electron",
|
|
56
|
+
"react-native",
|
|
57
|
+
"cron",
|
|
58
|
+
"python",
|
|
59
|
+
"bun"
|
|
47
60
|
],
|
|
48
61
|
"author": "pirikari.sena@gmail.com",
|
|
49
62
|
"license": "MIT",
|
package/src/generator-bun.js
CHANGED
|
@@ -77,6 +77,8 @@ export class BunGenerator extends Generator {
|
|
|
77
77
|
this.visitBunGroup(child);
|
|
78
78
|
} else if (child.type === 'SchemaDecl') {
|
|
79
79
|
this.visitSchema(child);
|
|
80
|
+
} else if (child.type === 'GraphqlDecl') {
|
|
81
|
+
this.visitBunGraphql(child);
|
|
80
82
|
} else {
|
|
81
83
|
this.visitStatement(child);
|
|
82
84
|
}
|
|
@@ -234,6 +236,18 @@ export class BunGenerator extends Generator {
|
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
for (const route of this.routes) {
|
|
239
|
+
if (route.__graphql) {
|
|
240
|
+
const gqlPath = JSON.stringify(route.__graphqlPath);
|
|
241
|
+
this.emit(`if (method === 'POST' && path === ${gqlPath}) {`);
|
|
242
|
+
this.indent++;
|
|
243
|
+
this.emit(`const { query, variables } = await req.json();`);
|
|
244
|
+
this.emit(`const result = await graphql({ schema: __graphqlSchema, source: query, rootValue: __graphqlRoot, variableValues: variables });`);
|
|
245
|
+
this.emit(`return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json' } });`);
|
|
246
|
+
this.indent--;
|
|
247
|
+
this.emit(`}`);
|
|
248
|
+
this.emitRaw('');
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
237
251
|
this.emitBunRoute(route);
|
|
238
252
|
}
|
|
239
253
|
|
|
@@ -498,6 +512,81 @@ export class BunGenerator extends Generator {
|
|
|
498
512
|
}
|
|
499
513
|
}
|
|
500
514
|
|
|
515
|
+
visitBunGraphql(node) {
|
|
516
|
+
const path = this.stringValue(node.path);
|
|
517
|
+
this.emit(`import { buildSchema, graphql } from 'graphql';`);
|
|
518
|
+
this.emitRaw('');
|
|
519
|
+
|
|
520
|
+
const schemaNames = [...this.schemas.keys()];
|
|
521
|
+
if (schemaNames.length > 0) {
|
|
522
|
+
let schemaDef = '';
|
|
523
|
+
for (const name of schemaNames) {
|
|
524
|
+
const schema = this.schemas.get(name);
|
|
525
|
+
const fields = schema.fields.map(f => {
|
|
526
|
+
const gqlType = this.toGraphqlType(f.type);
|
|
527
|
+
return ` ${f.name}: ${gqlType}`;
|
|
528
|
+
}).join('\\n');
|
|
529
|
+
schemaDef += `type ${name} {\\n${fields}\\n}\\n`;
|
|
530
|
+
}
|
|
531
|
+
schemaDef += `type Query {\\n`;
|
|
532
|
+
for (const name of schemaNames) {
|
|
533
|
+
schemaDef += ` ${name.toLowerCase()}s: [${name}]\\n`;
|
|
534
|
+
schemaDef += ` ${name.toLowerCase()}(id: ID): ${name}\\n`;
|
|
535
|
+
}
|
|
536
|
+
schemaDef += `}`;
|
|
537
|
+
|
|
538
|
+
this.emit(`const __graphqlSchema = buildSchema(\`${schemaDef}\`);`);
|
|
539
|
+
this.emit(`const __graphqlRoot = {`);
|
|
540
|
+
this.indent++;
|
|
541
|
+
for (const name of schemaNames) {
|
|
542
|
+
this.emit(`${name.toLowerCase()}s: () => ${name}Store.getAll(),`);
|
|
543
|
+
this.emit(`${name.toLowerCase()}: ({ id }) => ${name}Store.getById(id),`);
|
|
544
|
+
}
|
|
545
|
+
this.indent--;
|
|
546
|
+
this.emit(`};`);
|
|
547
|
+
} else {
|
|
548
|
+
this.emit(`const __graphqlSchema = buildSchema(\`type Query { hello: String }\`);`);
|
|
549
|
+
this.emit(`const __graphqlRoot = { hello: () => 'Hello from NAIDE GraphQL' };`);
|
|
550
|
+
}
|
|
551
|
+
this.emitRaw('');
|
|
552
|
+
|
|
553
|
+
const rawPath = this.rawString(node.path);
|
|
554
|
+
this.routes.push({
|
|
555
|
+
type: 'Route', method: 'post', path: node.path, prefix: '',
|
|
556
|
+
body: [], __graphql: true, __graphqlPath: rawPath,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
visitPage(node) {
|
|
561
|
+
const filename = this.rawString(node.filename);
|
|
562
|
+
const headParts = [];
|
|
563
|
+
const bodyParts = [];
|
|
564
|
+
let pageTitle = 'NAIDE Page';
|
|
565
|
+
|
|
566
|
+
for (const el of node.elements) {
|
|
567
|
+
this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const htmlLines = [
|
|
571
|
+
'<!DOCTYPE html>',
|
|
572
|
+
'<html lang="en">',
|
|
573
|
+
'<head>',
|
|
574
|
+
'<meta charset="UTF-8">',
|
|
575
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|
576
|
+
`<title>${pageTitle}</title>`,
|
|
577
|
+
...headParts,
|
|
578
|
+
'</head>',
|
|
579
|
+
'<body>',
|
|
580
|
+
...bodyParts,
|
|
581
|
+
'</body>',
|
|
582
|
+
'</html>',
|
|
583
|
+
];
|
|
584
|
+
const html = htmlLines.join('\\n');
|
|
585
|
+
this.emit(`await Bun.write(${JSON.stringify(filename)}, \`${html}\`);`);
|
|
586
|
+
this.emit(`console.log('Generated: ${filename}');`);
|
|
587
|
+
this.emitRaw('');
|
|
588
|
+
}
|
|
589
|
+
|
|
501
590
|
visitTest(node) {
|
|
502
591
|
this.hasTests = true;
|
|
503
592
|
const name = this.stringValue(node.name);
|