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/README.md CHANGED
@@ -9,7 +9,7 @@ Two modes:
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- npm install -g naide
12
+ npm install -g naidejs
13
13
  ```
14
14
 
15
15
  ## Usage
@@ -19,6 +19,9 @@ npm install -g naide
19
19
  naide app.naide
20
20
  naide app.nx
21
21
 
22
+ # Watch mode (auto-restart on changes)
23
+ naide -w app.naide
24
+
22
25
  # Output generated JavaScript
23
26
  naide --emit app.nx
24
27
 
@@ -29,6 +32,286 @@ naide -o app.js app.nx
29
32
  naide --mid app.nx
30
33
  ```
31
34
 
35
+ ## High-Level Features
36
+
37
+ NAIDE includes built-in declarations for common backend patterns. Zero dependencies — the runtime is bundled with the package.
38
+
39
+ ### schema — Data models with validation
40
+
41
+ ```python
42
+ schema User:
43
+ id auto
44
+ name str required min(2) max(50)
45
+ email str required email unique
46
+ age int optional min(0) max(150)
47
+ role enum("admin", "user") default("user")
48
+ joined timestamp auto
49
+ ```
50
+
51
+ Types: `str`, `int`, `num`, `bool`, `auto` (UUID), `timestamp`, `enum(...)`
52
+ Modifiers: `required`, `optional`, `min(n)`, `max(n)`, `email`, `url`, `unique`, `auto`, `default(val)`
53
+
54
+ ### db — Persistent file storage
55
+
56
+ ```python
57
+ db "data/"
58
+ ```
59
+
60
+ When declared, all schema stores persist to JSON files automatically (`data/User.json`, `data/Todo.json`, etc.). Without `db`, data is in-memory only.
61
+
62
+ ### crud — Auto-generate REST endpoints
63
+
64
+ ```python
65
+ server app port 3000:
66
+ crud "/api/users" User
67
+ ```
68
+
69
+ Generates GET (list + by ID), POST, PUT, DELETE routes with validation.
70
+
71
+ **Built-in pagination, search, and sort:**
72
+ ```
73
+ GET /api/users # paginated (default 20 per page)
74
+ GET /api/users?page=2&limit=10 # page 2, 10 items
75
+ GET /api/users?q=john # search all string fields
76
+ GET /api/users?sort=name&order=asc # sort by field
77
+ ```
78
+
79
+ Response format: `{ data: [...], total, page, limit, pages }`
80
+
81
+ ### auth — JWT authentication
82
+
83
+ ```python
84
+ server app port 3000:
85
+ auth JWT_SECRET:
86
+ protect "/api/*"
87
+ public "/api/auth/*"
88
+ ```
89
+
90
+ Built-in JWT sign/verify with no external dependencies.
91
+
92
+ **Signing tokens in routes:**
93
+ ```python
94
+ post "/api/auth/login" (req, res):
95
+ token = auth.sign({id: user.id}) # uses auth secret automatically
96
+ ret {token}
97
+ ```
98
+
99
+ Also available as `sign(payload)` (same auto-secret) or `sign(payload, secret)` (explicit).
100
+
101
+ ### Password hashing
102
+
103
+ ```python
104
+ str hashed = hash("mypassword") # scrypt-based, returns salt:hash
105
+ bool ok = verify("mypassword", hashed) # timing-safe comparison
106
+ ```
107
+
108
+ Zero-dependency — uses Node.js built-in `crypto.scryptSync`.
109
+
110
+ ### cors — CORS middleware
111
+
112
+ ```python
113
+ server app port 3000:
114
+ cors "*"
115
+ # or: cors ["localhost:3000", "myapp.com"]
116
+ ```
117
+
118
+ ### limit — Rate limiting
119
+
120
+ ```python
121
+ server app port 3000:
122
+ limit "/api/*" 100 "1m"
123
+ ```
124
+
125
+ ### static — Serve static files
126
+
127
+ ```python
128
+ server app port 3000:
129
+ static "/public"
130
+ ```
131
+
132
+ Serves files from the `public/` directory.
133
+
134
+ ### ws — WebSocket
135
+
136
+ ```python
137
+ server app port 3000:
138
+ ws "/chat":
139
+ on "connect":
140
+ send({type: "welcome"})
141
+ on "message" (data):
142
+ broadcast(data)
143
+ on "close":
144
+ log "client left"
145
+ ```
146
+
147
+ Built-in `send(data)` and `broadcast(data)` helpers. Requires `npm install ws`.
148
+
149
+ ### group — Route groups
150
+
151
+ ```python
152
+ server app port 3000:
153
+ group "/api/v1":
154
+ get "/users":
155
+ ret users
156
+ post "/users" (req, res):
157
+ ret req.body
158
+ ```
159
+
160
+ Generates an Express Router mounted at the prefix. Groups can be nested.
161
+
162
+ ### cookie — Cookie parsing
163
+
164
+ ```python
165
+ server app port 3000:
166
+ cookie
167
+ get "/" (req, res):
168
+ str theme = req.cookies.theme
169
+ ret {theme}
170
+ ```
171
+
172
+ Parses `Cookie` headers into `req.cookies`. Zero-dependency.
173
+
174
+ ### error — Error handler
175
+
176
+ ```python
177
+ server app port 3000:
178
+ get "/":
179
+ ret {ok: true}
180
+ error (err, req, res):
181
+ log.error err.message
182
+ ret.status 500 {error: "Internal error"}
183
+ ```
184
+
185
+ Express error middleware. Catches unhandled errors in routes.
186
+
187
+ ### api — HTTP client
188
+
189
+ ```python
190
+ fn.async getUsers() -> any:
191
+ any users = await api.get("https://api.example.com/users")
192
+ ret users
193
+
194
+ fn.async createUser(map data) -> any:
195
+ any result = await api.post("https://api.example.com/users", data)
196
+ ret result
197
+ ```
198
+
199
+ Methods: `api.get(url)`, `api.post(url, body)`, `api.put(url, body)`, `api.del(url)`, `api.raw(url, opts)`.
200
+ Zero-dependency — uses Node.js 18+ built-in `fetch`. Auto-imported when used.
201
+
202
+ ### env — Environment variables with validation
203
+
204
+ ```python
205
+ env:
206
+ PORT int default(3000)
207
+ JWT_SECRET str required
208
+ DB_URL str default("sqlite:data.db")
209
+ ```
210
+
211
+ Variables become constants available throughout the file.
212
+
213
+ ### every — Scheduled tasks
214
+
215
+ ```python
216
+ every "5m":
217
+ log "cleanup running"
218
+ ```
219
+
220
+ Intervals: `"30s"`, `"5m"`, `"1h"`, `"1d"`
221
+
222
+ ### watch — React to events
223
+
224
+ ```python
225
+ watch User.create (event):
226
+ log "new user: {event.data.name}"
227
+ ```
228
+
229
+ Automatically connected to `crud` events.
230
+
231
+ ### Response helpers
232
+
233
+ ```python
234
+ ret {data: items} # JSON response (default)
235
+ ret.status 404 {error: "nope"} # custom status code
236
+ ret.redirect "/login" # HTTP redirect
237
+ ret.html "<h1>Hello</h1>" # HTML response
238
+ ret.text "pong" # plain text response
239
+ ret.file "/path/to/file" # send file
240
+ ```
241
+
242
+ ### Built-in functions
243
+
244
+ ```python
245
+ str id = uuid() # generate UUID
246
+ str hashed = hash("password") # hash password
247
+ bool ok = verify("password", hashed) # verify password
248
+ str token = sign({id: 1}) # sign JWT (uses auth secret)
249
+ str token = sign({id: 1}, "my-secret") # sign JWT (explicit secret)
250
+ ```
251
+
252
+ These are auto-imported from the runtime when used.
253
+
254
+ ### Full example
255
+
256
+ ```python
257
+ db "data/"
258
+
259
+ env:
260
+ PORT int default(3000)
261
+ JWT_SECRET str required
262
+
263
+ schema User:
264
+ id auto
265
+ name str required min(2) max(50)
266
+ email str required email
267
+ password str required
268
+
269
+ server app port PORT:
270
+ cors "*"
271
+ cookie
272
+ auth JWT_SECRET:
273
+ protect "/api/*"
274
+ public "/api/auth/*"
275
+ limit "/api/*" 100 "1m"
276
+ static "/public"
277
+ crud "/api/users" User
278
+
279
+ group "/api/v1":
280
+ get "/status":
281
+ ret {version: "1.0"}
282
+
283
+ post "/api/auth/register" (req, res):
284
+ str hashed = hash(req.body.password)
285
+ user = UserStore.create({...req.body, password: hashed})
286
+ token = auth.sign({id: user.id})
287
+ ret {token, user}
288
+
289
+ post "/api/auth/login" (req, res):
290
+ user = UserStore.where({email: req.body.email})[0]
291
+ if not user:
292
+ ret.status 401 {error: "Invalid credentials"}
293
+ if not verify(req.body.password, user.password):
294
+ ret.status 401 {error: "Invalid credentials"}
295
+ token = auth.sign({id: user.id})
296
+ ret {token}
297
+
298
+ ws "/chat":
299
+ on "message" (data):
300
+ broadcast(data)
301
+
302
+ get "/":
303
+ ret.html "<h1>Welcome</h1>"
304
+
305
+ error (err, req, res):
306
+ log.error err.message
307
+ ret.status 500 {error: "Something went wrong"}
308
+
309
+ watch User.create (event):
310
+ log "new user: {event.data.name}"
311
+ ```
312
+
313
+ This generates a complete production API with persistent storage, auth, password hashing, CORS, cookies, rate limiting, CRUD with pagination/search, WebSocket, route groups, error handling, and static files — from ~45 lines.
314
+
32
315
  ## NAIDE syntax (.naide)
33
316
 
34
317
  ```python
@@ -37,7 +320,7 @@ str name = "World"
37
320
  int count = 3
38
321
  mut int counter = 0
39
322
 
40
- # Functions — one way only
323
+ # Functions
41
324
  fn greet(str who) -> str:
42
325
  ret "Hello, {who}!"
43
326
 
@@ -118,12 +401,21 @@ f greet(s:who)s
118
401
  @i<0..10
119
402
  log i
120
403
 
121
- -- Server
404
+ -- Server with high-level features
122
405
  $app:3000
406
+ cors "*"
407
+ auth SECRET:
408
+ protect "/api/*"
409
+ static "/public"
410
+ crud "/api/users" User
123
411
  G"/users"
124
412
  >users
125
413
  P"/users"(req,res)
126
414
  >req.body
415
+ G"/old"
416
+ >.r "/new"
417
+ G"/"
418
+ >.h "<h1>Hello</h1>"
127
419
 
128
420
  -- Error handling
129
421
  !
@@ -153,9 +445,13 @@ $app:3000
153
445
  | `+` | export | `~` | await |
154
446
  | `G` | GET | `P` | POST |
155
447
  | `U` | PUT | `D` | DELETE |
448
+ | `>.s` | ret.status | `>.r` | ret.redirect |
449
+ | `>.h` | ret.html | `>.t` | ret.text |
156
450
 
157
451
  Types: `s`=str `i`=int `n`=num `b`=bool `l`=list `m`=map `a`=any
158
452
 
453
+ High-level: `schema`, `crud`, `auth`, `cors`, `limit`, `env`, `every`, `watch`, `static`, `ws`, `db`, `group`, `cookie`, `error` — same syntax in both modes. `api` is auto-imported.
454
+
159
455
  ## Why?
160
456
 
161
457
  AI code generation speed depends on:
package/SPEC-X.nx CHANGED
@@ -112,13 +112,86 @@ l:result=[1,2,3,4,5]
112
112
  f perms()l
113
113
  >["read","write","delete"]
114
114
 
115
- -- Server: $name:port
116
- -- Routes: G P U D + "path"
117
- -- $app:3000
118
- -- G"/users"
119
- -- >users
120
- -- P"/users"(req,res)
121
- -- >req.body
115
+ -- DB persistence: db "path/"
116
+ db "data/"
117
+
118
+ -- Environment variables
119
+ env:
120
+ PORT int default(3000)
121
+ JWT_SECRET str required
122
+
123
+ -- Schema: types + modifiers
124
+ schema User:
125
+ id auto
126
+ name str required min(2) max(50)
127
+ email str required email unique
128
+ role enum("admin","user") default("user")
129
+ joined timestamp auto
130
+
131
+ -- Server with high-level features
132
+ $app:PORT
133
+ cors "*"
134
+ auth JWT_SECRET:
135
+ protect "/api/*"
136
+ public "/api/auth/*"
137
+ limit "/api/*" 100 "1m"
138
+ static "/public"
139
+ crud "/api/users" User
140
+ cookie
141
+ group "/api/v1":
142
+ G"/status"
143
+ >{version:"1.0"}
144
+ G"/"
145
+ >.h "<h1>Welcome</h1>"
146
+ P"/api/auth/register"(req,res)
147
+ s:hashed=hash(req.body.password)
148
+ user=UserStore.create({...req.body,password:hashed})
149
+ token=auth.sign({id:user.id})
150
+ >{token,user}
151
+ P"/api/auth/login"(req,res)
152
+ user=UserStore.where({email:req.body.email})[0]
153
+ ?not user
154
+ >.s 401 {error:"Invalid credentials"}
155
+ ?not verify(req.body.password,user.password)
156
+ >.s 401 {error:"Invalid credentials"}
157
+ token=sign({id:user.id})
158
+ >{token}
159
+ ws "/chat":
160
+ on "connect":
161
+ send({type:"welcome"})
162
+ on "message" (data):
163
+ broadcast(data)
164
+
165
+ -- Error handler
166
+ error (err, req, res):
167
+ log.e err.message
168
+ >.s 500 {error:"Internal error"}
169
+
170
+ -- HTTP client (auto-imported)
171
+ -- api.get(url) api.post(url,body) api.put(url,body) api.del(url) api.raw(url,opts)
172
+
173
+ -- Return helpers
174
+ -- >expr → JSON
175
+ -- >.s CODE expr → status code
176
+ -- >.r "/path" → redirect
177
+ -- >.h "<html>" → HTML
178
+ -- >.t "text" → plain text
179
+ -- >.f "/file" → send file
180
+
181
+ -- Built-in functions (auto-imported)
182
+ -- uuid() → UUID
183
+ -- hash("password") → scrypt hash
184
+ -- verify("pw",hashed) → verify
185
+ -- sign({id:1}) → JWT (uses auth secret)
186
+ -- sign({id:1},"secret") → JWT (explicit)
187
+
188
+ -- Scheduled tasks
189
+ every "5m":
190
+ log"cleanup"
191
+
192
+ -- Event watch
193
+ watch User.create (event):
194
+ log"new: {event.data.name}"
122
195
 
123
196
  -- Import: <module or <{names}"module"
124
197
  -- <express
package/SPEC.naide CHANGED
@@ -137,11 +137,124 @@ model Admin extends User:
137
137
  ret ["read", "write", "delete"]
138
138
 
139
139
 
140
+ # ---- データベース永続化 ----
141
+ # db ディレクトリパス → スキーマストアがJSONファイルに自動保存
142
+ db "data/"
143
+
144
+
145
+ # ---- 環境変数 ----
146
+ env:
147
+ PORT int default(3000)
148
+ JWT_SECRET str required
149
+ DB_URL str default("sqlite:data.db")
150
+
151
+
152
+ # ---- スキーマ定義 ----
153
+ # 型: str, int, num, bool, auto(UUID), timestamp, enum(...)
154
+ # 修飾: required, optional, min(n), max(n), email, url, unique, auto, default(val)
155
+ schema User:
156
+ id auto
157
+ name str required min(2) max(50)
158
+ email str required email unique
159
+ age int optional min(0) max(150)
160
+ role enum("admin", "user") default("user")
161
+ joined timestamp auto
162
+
163
+
140
164
  # ---- サーバー定義 ----
141
- # Express.jsのボイラープレートが不要
142
- # server 名前 port ポート:
143
- # get "/path" (req, res):
144
- # ret {data: "response"}
165
+ server app port PORT:
166
+ # CORS
167
+ cors "*"
168
+
169
+ # JWT認証
170
+ auth JWT_SECRET:
171
+ protect "/api/*"
172
+ public "/api/auth/*"
173
+
174
+ # レート制限
175
+ limit "/api/*" 100 "1m"
176
+
177
+ # 静的ファイル配信
178
+ static "/public"
179
+
180
+ # CRUD自動生成 (ページネーション・検索・ソート付き)
181
+ crud "/api/users" User
182
+
183
+ # クッキーパーサー
184
+ cookie
185
+
186
+ # ルートグループ
187
+ group "/api/v1":
188
+ get "/status":
189
+ ret {version: "1.0"}
190
+
191
+ # ルート
192
+ get "/":
193
+ ret.html "<h1>Welcome</h1>"
194
+
195
+ post "/api/auth/register" (req, res):
196
+ str hashed = hash(req.body.password)
197
+ user = UserStore.create({...req.body, password: hashed})
198
+ token = auth.sign({id: user.id})
199
+ ret {token, user}
200
+
201
+ post "/api/auth/login" (req, res):
202
+ user = UserStore.where({email: req.body.email})[0]
203
+ if not user:
204
+ ret.status 401 {error: "Invalid credentials"}
205
+ if not verify(req.body.password, user.password):
206
+ ret.status 401 {error: "Invalid credentials"}
207
+ token = sign({id: user.id})
208
+ ret {token}
209
+
210
+ # WebSocket
211
+ ws "/chat":
212
+ on "connect":
213
+ send({type: "welcome"})
214
+ on "message" (data):
215
+ broadcast(data)
216
+ on "close":
217
+ log "client left"
218
+
219
+ # エラーハンドラー
220
+ error (err, req, res):
221
+ log.error err.message
222
+ ret.status 500 {error: "Internal error"}
223
+
224
+
225
+ # ---- HTTPクライアント (自動インポート) ----
226
+ # any data = await api.get("https://api.example.com/users")
227
+ # any result = await api.post("https://api.example.com/users", {name: "test"})
228
+ # any result = await api.put(url, body)
229
+ # any result = await api.del(url)
230
+ # any raw = await api.raw(url, opts)
231
+
232
+
233
+ # ---- レスポンスヘルパー ----
234
+ # ret {data} → JSON応答
235
+ # ret.status 404 {error} → ステータスコード付き
236
+ # ret.redirect "/login" → リダイレクト
237
+ # ret.html "<h1>Hi</h1>" → HTML応答
238
+ # ret.text "pong" → テキスト応答
239
+ # ret.file "/path/to/file" → ファイル送信
240
+
241
+
242
+ # ---- 組み込み関数 (自動インポート) ----
243
+ # str id = uuid() → UUID生成
244
+ # str h = hash("password") → パスワードハッシュ (scrypt)
245
+ # bool ok = verify("password", h) → パスワード検証
246
+ # str token = sign({id: 1}) → JWT署名 (auth秘密鍵を自動使用)
247
+ # str token = sign({id: 1}, "secret") → JWT署名 (明示的秘密鍵)
248
+
249
+
250
+ # ---- スケジュールタスク ----
251
+ every "5m":
252
+ log "cleanup running"
253
+
254
+
255
+ # ---- イベント監視 ----
256
+ watch User.create (event):
257
+ log "new user: {event.data.name}"
145
258
 
146
259
 
147
260
  # ---- インポート ----
@@ -151,11 +264,6 @@ model Admin extends User:
151
264
  # use モジュール as 別名
152
265
 
153
266
 
154
- # ---- イベント ----
155
- # on オブジェクト.イベント:
156
- # ハンドラ
157
-
158
-
159
267
  # ---- ログ ----
160
268
  log "info message"
161
269
  log.error "error message"
package/bin/naide.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync, writeFileSync } from 'fs';
3
+ import { readFileSync, writeFileSync, unlinkSync, watch as fsWatch } from 'fs';
4
4
  import { resolve, basename, extname } from 'path';
5
5
  import { compile } from '../src/index.js';
6
+ import { spawn } from 'child_process';
6
7
 
7
8
  const args = process.argv.slice(2);
8
9
 
@@ -15,6 +16,7 @@ const flags = {
15
16
  help: false,
16
17
  mode: null,
17
18
  mid: false,
19
+ watch: false,
18
20
  };
19
21
 
20
22
  const files = [];
@@ -29,6 +31,7 @@ for (let i = 0; i < args.length; i++) {
29
31
  case '--help': case '-h': flags.help = true; break;
30
32
  case '--x': case '-x': flags.mode = 'x'; break;
31
33
  case '--mid': flags.mid = true; flags.run = false; break;
34
+ case '--watch': case '-w': flags.watch = true; break;
32
35
  default: files.push(arg);
33
36
  }
34
37
  }
@@ -44,6 +47,7 @@ if (flags.help || files.length === 0) {
44
47
  naide --emit <file.nx> Output generated JavaScript
45
48
  naide --mid <file.nx> Output intermediate NAIDE v1 (debug)
46
49
  naide -x <file.naide> Force NAIDE-X mode
50
+ naide -w <file.naide> Watch mode (auto-restart on changes)
47
51
 
48
52
  Modes:
49
53
  .naide Standard NAIDE (~40% fewer tokens than JS)
@@ -53,6 +57,7 @@ if (flags.help || files.length === 0) {
53
57
  -e, --emit Print generated JavaScript
54
58
  -o, --output Write generated JavaScript to file
55
59
  -x Force NAIDE-X mode
60
+ -w, --watch Watch mode: restart on file changes
56
61
  --mid Show intermediate NAIDE v1 (X mode only)
57
62
  --ast Print AST
58
63
  --tokens Print tokens
@@ -61,6 +66,8 @@ if (flags.help || files.length === 0) {
61
66
  process.exit(0);
62
67
  }
63
68
 
69
+ const runtimeUrl = new URL('../src/runtime.js', import.meta.url).href;
70
+
64
71
  for (const file of files) {
65
72
  const filePath = resolve(file);
66
73
  let source;
@@ -81,7 +88,57 @@ for (const file of files) {
81
88
  continue;
82
89
  }
83
90
 
84
- const result = compile(source, { mode });
91
+ const runtimePath = flags.emit || flags.output ? 'naidejs/runtime' : runtimeUrl;
92
+
93
+ if (flags.watch) {
94
+ const tempFile = resolve(`.naide_tmp_${basename(file, ext)}.mjs`);
95
+ let child = null;
96
+
97
+ function start() {
98
+ try {
99
+ source = readFileSync(filePath, 'utf-8');
100
+ const result = compile(source, { mode, runtimePath });
101
+ writeFileSync(tempFile, result.js, 'utf-8');
102
+ child = spawn(process.execPath, [tempFile], { stdio: 'inherit' });
103
+ child.on('error', (err) => console.error(`[NAIDE] Process error: ${err.message}`));
104
+ child.on('exit', (code) => {
105
+ if (code !== null && code !== 0) console.log(`[NAIDE] Process exited with code ${code}`);
106
+ });
107
+ } catch (err) {
108
+ console.error(`\n${err.message}`);
109
+ }
110
+ }
111
+
112
+ function restart() {
113
+ console.log('\n[NAIDE] Change detected. Restarting...');
114
+ if (child) { child.kill(); child = null; }
115
+ start();
116
+ }
117
+
118
+ console.log(`[NAIDE] Watch mode — ${file}`);
119
+ start();
120
+
121
+ let debounce = null;
122
+ fsWatch(filePath, () => {
123
+ if (debounce) clearTimeout(debounce);
124
+ debounce = setTimeout(restart, 200);
125
+ });
126
+
127
+ process.on('SIGINT', () => {
128
+ if (child) child.kill();
129
+ try { unlinkSync(tempFile); } catch {}
130
+ process.exit(0);
131
+ });
132
+
133
+ process.on('SIGTERM', () => {
134
+ if (child) child.kill();
135
+ process.exit(0);
136
+ });
137
+
138
+ continue;
139
+ }
140
+
141
+ const result = compile(source, { mode, runtimePath });
85
142
 
86
143
  if (flags.tokens) {
87
144
  console.log(JSON.stringify(result.tokens, null, 2));