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 +299 -3
- package/SPEC-X.nx +80 -7
- package/SPEC.naide +117 -9
- package/bin/naide.js +59 -2
- package/examples/fullapp.naide +74 -0
- package/examples/fullapp.nx +74 -0
- package/package.json +13 -5
- package/src/generator.js +493 -19
- package/src/index.js +2 -2
- package/src/parser.js +376 -73
- package/src/preprocess.js +8 -0
- package/src/runtime.js +425 -0
- package/src/tokens.js +24 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Two modes:
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install -g
|
|
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
|
|
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
|
-
--
|
|
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
|
|
@@ -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
|
|
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));
|