naidejs 1.1.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 +183 -4
- package/SPEC-X.nx +80 -7
- package/SPEC.naide +117 -9
- package/bin/naide.js +55 -1
- package/examples/fullapp.naide +41 -4
- package/examples/fullapp.nx +41 -4
- package/package.json +9 -5
- package/src/generator.js +248 -5
- package/src/parser.js +143 -90
- package/src/preprocess.js +8 -0
- package/src/runtime.js +162 -4
- package/src/tokens.js +8 -0
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
|
-
|
|
135
|
-
|
|
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
|
|
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
|
-
--
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
--
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
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) {
|
package/examples/fullapp.naide
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# Full NAIDE app with
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
ret
|
|
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"
|