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.
package/README.md CHANGED
@@ -19,6 +19,9 @@ npm install -g naidejs
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
 
@@ -48,6 +51,14 @@ schema User:
48
51
  Types: `str`, `int`, `num`, `bool`, `auto` (UUID), `timestamp`, `enum(...)`
49
52
  Modifiers: `required`, `optional`, `min(n)`, `max(n)`, `email`, `url`, `unique`, `auto`, `default(val)`
50
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
+
51
62
  ### crud — Auto-generate REST endpoints
52
63
 
53
64
  ```python
@@ -57,6 +68,16 @@ server app port 3000:
57
68
 
58
69
  Generates GET (list + by ID), POST, PUT, DELETE routes with validation.
59
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
+
60
81
  ### auth — JWT authentication
61
82
 
62
83
  ```python
@@ -68,6 +89,24 @@ server app port 3000:
68
89
 
69
90
  Built-in JWT sign/verify with no external dependencies.
70
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
+
71
110
  ### cors — CORS middleware
72
111
 
73
112
  ```python
@@ -83,6 +122,83 @@ server app port 3000:
83
122
  limit "/api/*" 100 "1m"
84
123
  ```
85
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
+
86
202
  ### env — Environment variables with validation
87
203
 
88
204
  ```python
@@ -112,9 +228,34 @@ watch User.create (event):
112
228
 
113
229
  Automatically connected to `crud` events.
114
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
+
115
254
  ### Full example
116
255
 
117
256
  ```python
257
+ db "data/"
258
+
118
259
  env:
119
260
  PORT int default(3000)
120
261
  JWT_SECRET str required
@@ -123,22 +264,53 @@ schema User:
123
264
  id auto
124
265
  name str required min(2) max(50)
125
266
  email str required email
267
+ password str required
126
268
 
127
269
  server app port PORT:
128
270
  cors "*"
271
+ cookie
129
272
  auth JWT_SECRET:
130
273
  protect "/api/*"
131
274
  public "/api/auth/*"
132
275
  limit "/api/*" 100 "1m"
276
+ static "/public"
133
277
  crud "/api/users" User
134
- get "/health":
135
- ret {status: "ok"}
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"}
136
308
 
137
309
  watch User.create (event):
138
310
  log "new user: {event.data.name}"
139
311
  ```
140
312
 
141
- This generates a complete Express API with validation, auth, CORS, rate limiting, and CRUD — from 20 lines.
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.
142
314
 
143
315
  ## NAIDE syntax (.naide)
144
316
 
@@ -234,11 +406,16 @@ $app:3000
234
406
  cors "*"
235
407
  auth SECRET:
236
408
  protect "/api/*"
409
+ static "/public"
237
410
  crud "/api/users" User
238
411
  G"/users"
239
412
  >users
240
413
  P"/users"(req,res)
241
414
  >req.body
415
+ G"/old"
416
+ >.r "/new"
417
+ G"/"
418
+ >.h "<h1>Hello</h1>"
242
419
 
243
420
  -- Error handling
244
421
  !
@@ -268,10 +445,12 @@ $app:3000
268
445
  | `+` | export | `~` | await |
269
446
  | `G` | GET | `P` | POST |
270
447
  | `U` | PUT | `D` | DELETE |
448
+ | `>.s` | ret.status | `>.r` | ret.redirect |
449
+ | `>.h` | ret.html | `>.t` | ret.text |
271
450
 
272
451
  Types: `s`=str `i`=int `n`=num `b`=bool `l`=list `m`=map `a`=any
273
452
 
274
- High-level: `schema`, `crud`, `auth`, `cors`, `limit`, `env`, `every`, `watch` — same syntax in both modes.
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.
275
454
 
276
455
  ## Why?
277
456
 
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
@@ -84,6 +89,55 @@ for (const file of files) {
84
89
  }
85
90
 
86
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
+
87
141
  const result = compile(source, { mode, runtimePath });
88
142
 
89
143
  if (flags.tokens) {
@@ -1,4 +1,6 @@
1
- # Full NAIDE app with high-level features
1
+ # Full NAIDE 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
  server app port 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
+ get "/status":
35
+ ret {version: "1.0", status: "ok"}
36
+
37
+ post "/api/auth/register" (req, res):
38
+ str hashed = hash(req.body.password)
39
+ user = UserStore.create({name: req.body.name, email: req.body.email, password: hashed})
40
+ if user.error:
41
+ ret.status 400 {errors: user.error}
42
+ token = auth.sign({id: user.id, role: user.role})
43
+ ret {token, user: {id: user.id, name: user.name, email: user.email}}
44
+
45
+ post "/api/auth/login" (req, res):
46
+ user = UserStore.where({email: req.body.email})[0]
47
+ if not user:
48
+ ret.status 401 {error: "Invalid credentials"}
49
+ if not verify(req.body.password, user.password):
50
+ ret.status 401 {error: "Invalid credentials"}
51
+ token = auth.sign({id: user.id, role: user.role})
52
+ ret {token}
53
+
27
54
  get "/health":
28
55
  ret {status: "ok"}
29
- post "/api/auth/login" (req, res):
30
- str email = req.body.email
31
- ret {token: "jwt-token-here"}
56
+
57
+ get "/":
58
+ ret.html "<h1>Welcome to NAIDE</h1><p>API running.</p>"
59
+
60
+ ws "/chat":
61
+ on "connect":
62
+ send({type: "welcome", msg: "Connected to chat"})
63
+ on "message" (data):
64
+ broadcast(data)
65
+
66
+ error (err, req, res):
67
+ log.error err.message
68
+ ret.status 500 {error: "Internal server error"}
32
69
 
33
70
  every "30m":
34
71
  log "cleanup running"