speexjs 0.2.2 → 0.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 +44 -497
- package/dist/cli/index.js +314 -105
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1048 -285
- package/dist/index.js.map +1 -1
- package/dist/rpc/index.d.ts +1 -1
- package/dist/schema/index.d.ts +36 -30
- package/dist/schema/index.js +12 -229
- package/dist/schema/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1 -0
- package/dist/server/auth/index.js +18 -12
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/database/index.d.ts +22 -2
- package/dist/server/database/index.js +993 -30
- package/dist/server/database/index.js.map +1 -1
- package/dist/server/http/index.js +19 -2
- package/dist/server/http/index.js.map +1 -1
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.js +1028 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/index.js +53 -33
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/router/index.js +8 -6
- package/dist/server/router/index.js.map +1 -1
- package/dist/{types-CXH8hPei.d.ts → types-aW38f63o.d.ts} +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,555 +1,102 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SpeexJS
|
|
2
2
|
|
|
3
|
-
**Fullstack
|
|
4
|
-
**🇮🇩 Indonesia First, Built for the World**
|
|
3
|
+
**Fullstack TypeScript Framework — zero dependencies.**
|
|
5
4
|
|
|
6
5
|
```bash
|
|
7
6
|
npm install speexjs
|
|
8
7
|
```
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 📦 Kenapa speexjs?
|
|
15
|
-
|
|
16
|
-
| Masalah | Solusi speexjs |
|
|
17
|
-
|---------|---------------|
|
|
18
|
-
| Next.js terlalu berat & vendor lock-in | speexjs ringan, zero-dep, engine-swappable |
|
|
19
|
-
| Express terlalu minimalis | speexjs Laravel-like: routing, middleware, controller, DI, ORM |
|
|
20
|
-
| Laravel pakai PHP | speexjs TypeScript native, end-to-end type safe |
|
|
21
|
-
| Fetch API ribet | SuperRequest/SuperResponse wrapper sendiri — lebih mudah |
|
|
22
|
-
| Framework asing untuk Indonesia | 🇮🇩 NIK, NPWP, Phone, Rupiah, terbilang built-in |
|
|
23
|
-
| React/Vue dependency berat | Signal-based VDOM sendiri — ringan, cepat |
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## ✨ Fitur Lengkap
|
|
28
|
-
|
|
29
|
-
### 🔴 Server (Laravel-like, TypeScript Native)
|
|
30
|
-
|
|
31
|
-
| Modul | Fitur |
|
|
32
|
-
|-------|-------|
|
|
33
|
-
| **Routing** | `get()`, `post()`, `put()`, `patch()`, `delete()`, `resource()`, `group()`, named routes |
|
|
34
|
-
| **Middleware** | CORS, CSRF, Auth, Throttle, Session, Helmet, Logger, Body Parser, Compress, Static Files |
|
|
35
|
-
| **Controller** | Base class + `@controller`/`@get`/`@post` decorators |
|
|
36
|
-
| **Container (DI)** | `bind()`, `singleton()`, `instance()`, `resolve()` |
|
|
37
|
-
| **HTTP** | SuperRequest + SuperResponse **(BUKAN Fetch API)** — `.json()`, `.html()`, `.redirect()`, `.stream()`, `.file()` |
|
|
38
|
-
| **Engine** | Node.js default, bisa ganti ke Bun/Deno via `app.setEngine()` |
|
|
39
|
-
| **Validation** | 25+ schema types + 🇮🇩 NIK, NPWP, Phone, Kodepos, Rekening |
|
|
40
|
-
| **Error Handling** | `TypedError`, HTTP status codes, exception handler |
|
|
41
|
-
| **Logging** | Structured logger — WIB/WITA/WIT timezone |
|
|
42
|
-
|
|
43
|
-
### 🟡 Auth & Security
|
|
44
|
-
|
|
45
|
-
| Modul | Fitur |
|
|
46
|
-
|-------|-------|
|
|
47
|
-
| **Session Guard** | Login/logout via encrypted cookies (AES-256-GCM) |
|
|
48
|
-
| **Token Guard** | Bearer token authentication, abilities/permissions |
|
|
49
|
-
| **Gate** | Authorization policies — `Gate.define()`, `Gate.allows()` |
|
|
50
|
-
| **Middleware** | `authMiddleware()`, `guestMiddleware()`, `authorize()` |
|
|
51
|
-
| **Encryption** | AES-256-GCM encrypt/decrypt — Node.js `crypto` native |
|
|
52
|
-
| **Hashing** | scrypt (OWASP recommended) + PBKDF2 |
|
|
53
|
-
|
|
54
|
-
### 🟡 Database
|
|
55
|
-
|
|
56
|
-
| Modul | Fitur |
|
|
57
|
-
|-------|-------|
|
|
58
|
-
| **Query Builder** | Laravel-like chain: `.select()`, `.where()`, `.join()`, `.orderBy()` |
|
|
59
|
-
| **Pagination** | `.paginate(perPage)` — built-in di Query Builder |
|
|
60
|
-
| **Migrations** | `SchemaBuilder`, `TableBlueprint` (30+ column types) |
|
|
61
|
-
| **Seeding** | `Seeder` class — insert/truncate data awal |
|
|
62
|
-
| **Drivers** | MySQL (default), SQLite, PostgreSQL — runtime dynamic import |
|
|
63
|
-
|
|
64
|
-
### 🎨 Client (Signal-Based VDOM)
|
|
65
|
-
|
|
66
|
-
| Modul | Fitur |
|
|
67
|
-
|-------|-------|
|
|
68
|
-
| **Signals** | `signal()`, `computed()`, `effect()`, `batch()`, `untracked()` |
|
|
69
|
-
| **VDOM** | `h()`, `render()`, `patch()`, `hydrate()` — Virtual DOM sendiri |
|
|
70
|
-
| **SSR** | `renderToString()`, `renderToStream()`, `ServerRenderer` |
|
|
71
|
-
| **JSX** | Full JSX support via `jsxImportSource: \"@SpeexJS/vdom\"` |
|
|
72
|
-
| **Router** | File-based routing, guards, reactive current/params/query |
|
|
73
|
-
| **Adapters** | `FrameworkAdapter` interface — React/Vue integration |
|
|
74
|
-
|
|
75
|
-
### 🔗 RPC (Type-Safe)
|
|
76
|
-
|
|
77
|
-
| Modul | Fitur |
|
|
78
|
-
|-------|-------|
|
|
79
|
-
| **Server** | `rpc.create()` — definisi procedure + schema validation |
|
|
80
|
-
| **Client** | `createClient()` — auto-typed queries & mutations |
|
|
81
|
-
| **Batch** | Multiple procedures in one request |
|
|
82
|
-
|
|
83
|
-
### 🛠️ CLI (Zero Dependencies)
|
|
84
|
-
|
|
85
|
-
| Perintah | Fungsi |
|
|
86
|
-
|----------|--------|
|
|
87
|
-
| `speexjs init [name]` | Scaffold project baru (blank/fullstack/api-only) |
|
|
88
|
-
| `speexjs make:controller <name>` | Generate controller |
|
|
89
|
-
| `speexjs make:middleware <name>` | Generate middleware |
|
|
90
|
-
| `speexjs make:schema <name>` | Generate schema |
|
|
91
|
-
| `speexjs list-routes` | Lihat semua route yang terdaftar |
|
|
92
|
-
| `speexjs serve` | Jalankan development server |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 🚀 Quickstart
|
|
97
|
-
|
|
98
|
-
### 1. Install
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npm install speexjs
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 2. Buat File Server
|
|
9
|
+
## Quick Start
|
|
105
10
|
|
|
106
11
|
```typescript
|
|
107
|
-
// src/index.ts
|
|
108
12
|
import { speexjs } from 'speexjs/server'
|
|
109
13
|
|
|
110
14
|
const app = speexjs()
|
|
111
|
-
|
|
112
|
-
app.
|
|
113
|
-
return response.html('<h1>speexjs 🚀</h1>')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
app.get('/api/hello', async ({ response }) => {
|
|
117
|
-
return response.json({ message: 'Halo Dunia!' })
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
app.listen(3000, () => console.log('speexjs running on http://localhost:3000'))
|
|
15
|
+
app.get('/', ({ response }) => response.html('<h1>SpeexJS 🚀</h1>'))
|
|
16
|
+
app.listen(3000)
|
|
121
17
|
```
|
|
122
18
|
|
|
123
|
-
### 3. Jalankan
|
|
124
|
-
|
|
125
19
|
```bash
|
|
126
20
|
npx speexjs serve
|
|
127
|
-
# atau
|
|
128
|
-
node --loader ts-node src/index.ts
|
|
129
21
|
```
|
|
130
22
|
|
|
131
|
-
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
| Category | Description |
|
|
26
|
+
|----------|-------------|
|
|
27
|
+
| **Server** | Router, Middleware (10 built-in), Controller, DI Container |
|
|
28
|
+
| **Database** | Query Builder, Migrations, Pagination — MySQL/SQLite/PG |
|
|
29
|
+
| **Auth** | Session Guard, Token Guard, Gate Authorization |
|
|
30
|
+
| **Validation** | 25+ schema types — string, number, objects, arrays, and more |
|
|
31
|
+
| **Client** | Signals, VDOM, JSX, SSR — no React required |
|
|
32
|
+
| **RPC** | Type-safe server-client communication |
|
|
33
|
+
| **CLI** | `speexjs init`, `serve`, `make:*`, `list-routes` |
|
|
34
|
+
| **Zero Dep** | 100% native Node.js |
|
|
132
35
|
|
|
133
|
-
##
|
|
36
|
+
## Examples
|
|
134
37
|
|
|
135
|
-
###
|
|
38
|
+
### Routing
|
|
136
39
|
|
|
137
40
|
```typescript
|
|
138
41
|
import { speexjs } from 'speexjs/server'
|
|
139
42
|
|
|
140
43
|
const app = speexjs()
|
|
141
44
|
|
|
142
|
-
// Basic routes
|
|
143
|
-
app.get('/users', handler)
|
|
144
|
-
app.post('/users', handler)
|
|
145
|
-
app.put('/users/:id', handler)
|
|
146
|
-
app.delete('/users/:id', handler)
|
|
147
|
-
app.patch('/users/:id', handler)
|
|
148
|
-
|
|
149
|
-
// Route groups
|
|
150
45
|
app.group('/api', (router) => {
|
|
151
46
|
router.get('/users', [UserController, 'index'])
|
|
152
47
|
router.post('/users', [UserController, 'store'])
|
|
153
48
|
}).middleware(['auth', 'throttle'])
|
|
154
49
|
|
|
155
|
-
// Resource routes
|
|
156
50
|
app.router.resource('/posts', PostController)
|
|
157
|
-
// GET /posts → index
|
|
158
|
-
// GET /posts/create → create
|
|
159
|
-
// POST /posts → store
|
|
160
|
-
// GET /posts/:id → show
|
|
161
|
-
// GET /posts/:id/edit → edit
|
|
162
|
-
// PUT /posts/:id → update
|
|
163
|
-
// DELETE /posts/:id → destroy
|
|
164
|
-
|
|
165
|
-
// Named routes
|
|
166
|
-
app.get('/users/:id', handler).name('users.show')
|
|
167
|
-
// app.router.route('users.show', { id: 5 }) → '/users/5'
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Controller
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
import { Controller, get, post, put, del } from 'speexjs/server'
|
|
174
|
-
|
|
175
|
-
@controller('/api/users')
|
|
176
|
-
export class UserController extends Controller {
|
|
177
|
-
@get('/')
|
|
178
|
-
async index({ response }) {
|
|
179
|
-
const users = await UserService.all()
|
|
180
|
-
return response.json(users)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@post('/')
|
|
184
|
-
async store({ request, response }) {
|
|
185
|
-
const data = await request.validate(CreateUserSchema)
|
|
186
|
-
const user = await UserService.create(data)
|
|
187
|
-
return response.json(user, 201)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
@get('/:id')
|
|
191
|
-
async show({ params, response }) {
|
|
192
|
-
const user = await UserService.findOrFail(params.id)
|
|
193
|
-
return response.json(user)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
@put('/:id')
|
|
197
|
-
async update({ params, request, response }) {
|
|
198
|
-
const data = await request.validate(UpdateUserSchema)
|
|
199
|
-
const user = await UserService.update(params.id, data)
|
|
200
|
-
return response.json(user)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@del('/:id')
|
|
204
|
-
async destroy({ params, response }) {
|
|
205
|
-
await UserService.delete(params.id)
|
|
206
|
-
return response.noContent()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
51
|
```
|
|
210
52
|
|
|
211
|
-
###
|
|
53
|
+
### Validation
|
|
212
54
|
|
|
213
55
|
```typescript
|
|
214
|
-
import {
|
|
56
|
+
import { schema } from 'speexjs/schema'
|
|
215
57
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
email: s.string().email(),
|
|
221
|
-
age: s.number().min(17).max(120).optional(),
|
|
222
|
-
phone: s.phone(), // 🇮🇩 Nomor Indonesia
|
|
223
|
-
nik: s.nik().optional(), // 🇮🇩 NIK 16 digit
|
|
224
|
-
npwp: s.npwp().optional(), // 🇮🇩 NPWP
|
|
58
|
+
const UserSchema = schema.object({
|
|
59
|
+
name: schema.string().min(3).max(100),
|
|
60
|
+
email: schema.string().email(),
|
|
61
|
+
age: schema.number().min(17).optional(),
|
|
225
62
|
})
|
|
226
|
-
|
|
227
|
-
// Type inference
|
|
228
|
-
type User = s.Infer<typeof UserSchema>
|
|
229
|
-
|
|
230
|
-
// Parse & validate
|
|
231
|
-
const result = UserSchema.safeParse(input)
|
|
232
|
-
if (!result.success) {
|
|
233
|
-
console.log(result.error) // Pesan error Bahasa Indonesia
|
|
234
|
-
}
|
|
235
63
|
```
|
|
236
64
|
|
|
237
|
-
### Database
|
|
65
|
+
### Database
|
|
238
66
|
|
|
239
67
|
```typescript
|
|
240
|
-
import {
|
|
241
|
-
|
|
242
|
-
const db = new DatabaseConnection({
|
|
243
|
-
driver: 'mysql', // mysql | sqlite | postgresql
|
|
244
|
-
host: 'localhost',
|
|
245
|
-
database: 'myapp',
|
|
246
|
-
username: 'root',
|
|
247
|
-
password: 'secret',
|
|
248
|
-
})
|
|
68
|
+
import { db } from 'speexjs/server/database'
|
|
249
69
|
|
|
250
|
-
await db.connect()
|
|
70
|
+
await db.connect({ driver: 'mysql', database: 'myapp' })
|
|
251
71
|
|
|
252
|
-
// Query dengan chaining
|
|
253
72
|
const users = await db.table('users')
|
|
254
73
|
.select('id', 'name', 'email')
|
|
255
74
|
.where('age', '>', 18)
|
|
256
|
-
.
|
|
257
|
-
.orderByDesc('created_at')
|
|
258
|
-
.limit(10)
|
|
259
|
-
.get()
|
|
260
|
-
|
|
261
|
-
// Pagination
|
|
262
|
-
const result = await db.table('posts')
|
|
263
|
-
.where('published', true)
|
|
264
|
-
.paginate(15, 1)
|
|
265
|
-
// result.data, result.total, result.lastPage, ...
|
|
266
|
-
|
|
267
|
-
// Insert
|
|
268
|
-
const id = await db.table('users').insert({
|
|
269
|
-
name: 'John',
|
|
270
|
-
email: 'john@test.com',
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
// Update
|
|
274
|
-
await db.table('users')
|
|
275
|
-
.where('id', 1)
|
|
276
|
-
.update({ name: 'John Doe' })
|
|
277
|
-
|
|
278
|
-
// Raw SQL
|
|
279
|
-
const result = await db.raw('SELECT * FROM users WHERE age > ?', [18])
|
|
75
|
+
.paginate(10, 1)
|
|
280
76
|
```
|
|
281
77
|
|
|
282
|
-
###
|
|
78
|
+
### Auth
|
|
283
79
|
|
|
284
80
|
```typescript
|
|
285
|
-
import {
|
|
286
|
-
import { AuthManager, SessionGuard } from 'speexjs/server/auth'
|
|
287
|
-
|
|
288
|
-
const app = speexjs()
|
|
289
|
-
|
|
290
|
-
// Setup auth
|
|
291
|
-
const auth = new AuthManager()
|
|
292
|
-
auth.guard('session', new SessionGuard({
|
|
293
|
-
table: 'users',
|
|
294
|
-
cookieName: 'speexjs_session',
|
|
295
|
-
}))
|
|
81
|
+
import { auth } from 'speexjs/server/auth'
|
|
296
82
|
|
|
297
|
-
// Login route
|
|
298
83
|
app.post('/login', async ({ request, response }) => {
|
|
299
84
|
const { email, password } = await request.json()
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return response.json({ error: 'Login gagal' }, 401)
|
|
304
|
-
}
|
|
305
|
-
return response.json({ message: 'Login berhasil' })
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
// Protected route
|
|
309
|
-
app.get('/profile', async ({ response }, next) => {
|
|
310
|
-
const user = await auth.guard('session').user()
|
|
311
|
-
return response.json(user)
|
|
312
|
-
}).middleware(['auth'])
|
|
313
|
-
|
|
314
|
-
// Authorization Gate
|
|
315
|
-
import { Gate } from 'speexjs/server/gate'
|
|
316
|
-
|
|
317
|
-
const gate = new Gate()
|
|
318
|
-
gate.define('update-post', (user, post) => user.id === post.user_id)
|
|
319
|
-
|
|
320
|
-
app.put('/posts/:id', async ({ params, response }) => {
|
|
321
|
-
const post = await Post.find(params.id)
|
|
322
|
-
await gate.authorize('update-post', currentUser, post)
|
|
323
|
-
// ...
|
|
85
|
+
const ok = await auth.attempt({ email, password })
|
|
86
|
+
if (!ok) return response.json({ error: 'Login failed' }, 401)
|
|
87
|
+
return response.json({ message: 'Login successful' })
|
|
324
88
|
})
|
|
325
89
|
```
|
|
326
90
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
import { signal, computed, effect, h, render } from 'speexjs/client'
|
|
331
|
-
|
|
332
|
-
function Counter() {
|
|
333
|
-
const count = signal(0)
|
|
334
|
-
const doubled = computed(() => count.value * 2)
|
|
335
|
-
|
|
336
|
-
effect(() => console.log('Count:', count.value))
|
|
337
|
-
|
|
338
|
-
return h('div', { class: 'counter' },
|
|
339
|
-
h('p', {}, `Count: ${count.value}`),
|
|
340
|
-
h('p', {}, `Doubled: ${doubled.value}`),
|
|
341
|
-
h('button', { onClick: () => count.value++ }, '+'),
|
|
342
|
-
h('button', { onClick: () => count.value-- }, '-'),
|
|
343
|
-
)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Mount ke DOM
|
|
347
|
-
render(h(Counter), document.getElementById('root')!)
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
Dengan JSX (`tsconfig.json`):
|
|
351
|
-
```json
|
|
352
|
-
{
|
|
353
|
-
"compilerOptions": {
|
|
354
|
-
"jsx": "react-jsx",
|
|
355
|
-
"jsxImportSource": "@SpeexJS/vdom"
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
```tsx
|
|
361
|
-
function Counter() {
|
|
362
|
-
const count = signal(0)
|
|
363
|
-
return (
|
|
364
|
-
<div class="counter">
|
|
365
|
-
<p>Count: {count}</p>
|
|
366
|
-
<button onClick={() => count.value++}>+</button>
|
|
367
|
-
</div>
|
|
368
|
-
)
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Cache
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
import { Cache } from 'speexjs/server/cache'
|
|
376
|
-
|
|
377
|
-
const cache = new Cache({ store: 'memory', ttl: 3600 })
|
|
378
|
-
|
|
379
|
-
// Set & Get
|
|
380
|
-
await cache.set('user:1', { name: 'John' })
|
|
381
|
-
const user = await cache.get('user:1') // { name: 'John' }
|
|
382
|
-
|
|
383
|
-
// Remember (get or set)
|
|
384
|
-
const data = await cache.remember('expensive:data', 300, async () => {
|
|
385
|
-
return await fetchExpensiveData()
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// Increment/Decrement
|
|
389
|
-
await cache.increment('visits')
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### File Storage
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
import { createStorage } from 'speexjs/server/storage'
|
|
396
|
-
|
|
397
|
-
const storage = createStorage({
|
|
398
|
-
defaultDisk: 'local',
|
|
399
|
-
disks: {
|
|
400
|
-
local: { driver: 'local', root: './storage' },
|
|
401
|
-
public: { driver: 'local', root: './public/uploads', url: '/uploads' },
|
|
402
|
-
},
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
// Upload file
|
|
406
|
-
await storage.disk('public').put('images/photo.jpg', buffer)
|
|
407
|
-
|
|
408
|
-
// Get file URL
|
|
409
|
-
const url = storage.disk('public').url('images/photo.jpg')
|
|
410
|
-
|
|
411
|
-
// Check exists
|
|
412
|
-
const exists = await storage.exists('images/photo.jpg')
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### Events
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
import { event } from 'speexjs/server/events'
|
|
419
|
-
|
|
420
|
-
// Listen
|
|
421
|
-
event.on('user.registered', async (user) => {
|
|
422
|
-
await sendWelcomeEmail(user)
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
// Wildcard pattern
|
|
426
|
-
event.onPattern('user.*', async (eventName, data) => {
|
|
427
|
-
console.log(`User event: ${eventName}`, data)
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
// Emit
|
|
431
|
-
await event.emit('user.registered', { id: 1, email: 'john@test.com' })
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### RPC (Type-Safe)
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
// === SERVER ===
|
|
438
|
-
import { speexjs } from 'speexjs/server'
|
|
439
|
-
import { rpc } from 'speexjs/rpc'
|
|
440
|
-
import { s } from 'speexjs/schema'
|
|
441
|
-
|
|
442
|
-
const api = rpc.create({
|
|
443
|
-
procedures: {
|
|
444
|
-
'users.list': {
|
|
445
|
-
input: s.object({ page: s.number().default(1) }),
|
|
446
|
-
output: s.array(s.object({ id: s.number(), name: s.string() })),
|
|
447
|
-
handler: async ({ page }, ctx) => {
|
|
448
|
-
return await db.table('users').paginate(10, page).data
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
'echo': {
|
|
452
|
-
input: s.object({ message: s.string() }),
|
|
453
|
-
output: s.object({ reply: s.string() }),
|
|
454
|
-
handler: async ({ message }) => ({ reply: `You said: ${message}` }),
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
const app = speexjs()
|
|
460
|
-
app.post('/api/rpc', api.toHandler())
|
|
461
|
-
app.listen(3000)
|
|
462
|
-
|
|
463
|
-
// === CLIENT ===
|
|
464
|
-
import { createClient } from 'speexjs/rpc'
|
|
465
|
-
|
|
466
|
-
const client = createClient({ baseUrl: 'http://localhost:3000/api' })
|
|
467
|
-
const users = await client.call('users.list', { page: 1 })
|
|
468
|
-
// users typed as { id: number, name: string }[]
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
## 🇮🇩 Fitur Indonesia
|
|
474
|
-
|
|
475
|
-
| Fitur | Keterangan |
|
|
476
|
-
|-------|-----------|
|
|
477
|
-
| `s.nik()` | Validasi NIK 16 digit + tanggal lahir |
|
|
478
|
-
| `s.npwp()` | Validasi NPWP + checksum |
|
|
479
|
-
| `s.phone()` | Validasi nomor telepon Indonesia (+62, 08xx) |
|
|
480
|
-
| `s.kodepos()` | Validasi kode pos 5 digit |
|
|
481
|
-
| `s.rekening()` | Validasi nomor rekening bank |
|
|
482
|
-
| `s.alamat()` | Validasi alamat Indonesia |
|
|
483
|
-
| `Number.formatRupiah()` | Format mata uang Rupiah |
|
|
484
|
-
| `Number.terbilang()` | Angka ke kata Bahasa Indonesia |
|
|
485
|
-
| **Logger** | Timezone WIB/WITA/WIT |
|
|
486
|
-
| **Error messages** | Bahasa Indonesia default |
|
|
487
|
-
| **CLI** | Output dengan Bahasa Indonesia |
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
## 🏗️ Arsitektur
|
|
492
|
-
|
|
493
|
-
```
|
|
494
|
-
speexjs/
|
|
495
|
-
├── src/
|
|
496
|
-
│ ├── index.ts # Barrel export
|
|
497
|
-
│ ├── native/ # Zero-dep utilities
|
|
498
|
-
│ ├── schema/ # Validation (25+ types)
|
|
499
|
-
│ ├── server/
|
|
500
|
-
│ │ ├── http/ # SuperRequest/SuperResponse
|
|
501
|
-
│ │ ├── router/ # Routing engine
|
|
502
|
-
│ │ ├── middleware/ # 10 built-in middleware
|
|
503
|
-
│ │ ├── controller/ # Base controller
|
|
504
|
-
│ │ ├── container/ # Dependency injection
|
|
505
|
-
│ │ ├── engine/ # Swappable engine
|
|
506
|
-
│ │ ├── auth/ # Session + Token guards
|
|
507
|
-
│ │ ├── gate/ # Authorization
|
|
508
|
-
│ │ ├── cache/ # Caching system
|
|
509
|
-
│ │ ├── storage/ # File storage
|
|
510
|
-
│ │ ├── events/ # Event system
|
|
511
|
-
│ │ └── database/ # Query Builder + Migrations
|
|
512
|
-
│ ├── client/ # Signals + VDOM + SSR
|
|
513
|
-
│ ├── rpc/ # Type-safe RPC
|
|
514
|
-
│ └── cli/ # Native CLI (zero dep)
|
|
515
|
-
└── tests/
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## 📋 Perbandingan dengan Framework Lain
|
|
521
|
-
|
|
522
|
-
| Fitur | speexjs | Next.js | Express | AdonisJS | Laravel |
|
|
523
|
-
|-------|---------|---------|---------|----------|---------|
|
|
524
|
-
| Bahasa | TS/JS | TS/JS | JS | TS/JS | PHP |
|
|
525
|
-
| Dependencies | **0** | Ratusan | Puluhan | Puluhan | Ratusan |
|
|
526
|
-
| File size | ~175KB | >50MB | ~500KB | >10MB | >20MB |
|
|
527
|
-
| Routing | ✅ Laravel-like | ✅ File-based | ❌ Manual | ✅ Programmatic | ✅ Laravel |
|
|
528
|
-
| Database | ✅ Query Builder | ❌ Prisma | ❌ None | ✅ Lucid ORM | ✅ Eloquent |
|
|
529
|
-
| Auth | ✅ Session+Token | ✅ NextAuth | ❌ Manual | ✅ Built-in | ✅ Built-in |
|
|
530
|
-
| Validation | ✅ Schema (25+) | ✅ Zod | ❌ Manual | ✅ Vine | ✅ Form Request |
|
|
531
|
-
| VDOM/Signals | ✅ Built-in | ✅ React | ❌ | ❌ Edge | ❌ Blade |
|
|
532
|
-
| RPC | ✅ Type-safe | ❌ Server Actions | ❌ | ✅ Tuyau | ❌ |
|
|
533
|
-
| 🇮🇩 Indonesia | ✅ Built-in | ❌ | ❌ | ❌ | ❌ |
|
|
534
|
-
|
|
535
|
-
---
|
|
536
|
-
|
|
537
|
-
## 🗺️ Roadmap
|
|
538
|
-
|
|
539
|
-
| Version | Fitur |
|
|
540
|
-
|---------|-------|
|
|
541
|
-
| **v0.1.0** | ✅ Schema, Server (HTTP+Router+Middleware+Controller), Client (Signals+VDOM), RPC, CLI (commander) |
|
|
542
|
-
| **v0.2.0** | ✅ Zero dependencies, Native CLI, Auth (Session+Token), Gate, Database (Query Builder+Migrations+Pagination), Cache, Storage, Events, Encryption, Hashing, Helpers |
|
|
543
|
-
| **v0.3.0** | 🔜 File-based routing, Server Components, ORM, Queues |
|
|
544
|
-
| **v1.0.0** | 🔜 Adapter React/Vue, Dokumentasi lengkap, Publikasi global |
|
|
545
|
-
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
## ⚖️ License
|
|
549
|
-
|
|
550
|
-
MIT — bebas digunakan, dimodifikasi, dan didistribusikan.
|
|
91
|
+
## CLI
|
|
551
92
|
|
|
552
|
-
|
|
93
|
+
| Command | Description |
|
|
94
|
+
|---------|-------------|
|
|
95
|
+
| `speexjs init` | Create a new project |
|
|
96
|
+
| `speexjs serve` | Start development server |
|
|
97
|
+
| `speexjs make:controller User` | Generate a controller |
|
|
98
|
+
| `speexjs make:middleware Auth` | Generate a middleware |
|
|
99
|
+
| `speexjs make:schema User` | Generate a schema |
|
|
100
|
+
| `speexjs list-routes` | Display all registered routes |
|
|
553
101
|
|
|
554
|
-
|
|
555
|
-
**🇮🇩 Dibuat oleh developer Indonesia, untuk developer dunia.**
|
|
102
|
+
MIT
|