speexjs 0.2.1 → 0.2.3
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 +56 -481
- package/dist/cli/index.js +48 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/server/auth/index.js +1 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/index.js +2 -2
- package/dist/server/middleware/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,152 +1,52 @@
|
|
|
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
|
-
npm install
|
|
6
|
+
npm install speexjs
|
|
8
7
|
```
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 📦 Kenapa speedx?
|
|
15
|
-
|
|
16
|
-
| Masalah | Solusi speedx |
|
|
17
|
-
|---------|---------------|
|
|
18
|
-
| Next.js terlalu berat & vendor lock-in | speedx ringan, zero-dep, engine-swappable |
|
|
19
|
-
| Express terlalu minimalis | speedx Laravel-like: routing, middleware, controller, DI, ORM |
|
|
20
|
-
| Laravel pakai PHP | speedx 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: \"@SpeedX/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
|
-
| `speedx init [name]` | Scaffold project baru (blank/fullstack/api-only) |
|
|
88
|
-
| `speedx make:controller <name>` | Generate controller |
|
|
89
|
-
| `speedx make:middleware <name>` | Generate middleware |
|
|
90
|
-
| `speedx make:schema <name>` | Generate schema |
|
|
91
|
-
| `speedx list-routes` | Lihat semua route yang terdaftar |
|
|
92
|
-
| `speedx serve` | Jalankan development server |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 🚀 Quickstart
|
|
97
|
-
|
|
98
|
-
### 1. Install
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npm install speedx
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 2. Buat File Server
|
|
9
|
+
## Quick Start
|
|
105
10
|
|
|
106
11
|
```typescript
|
|
107
|
-
|
|
108
|
-
import { speedx } from 'speedx/server'
|
|
12
|
+
import { speexjs } from 'speexjs/server'
|
|
109
13
|
|
|
110
|
-
const app =
|
|
14
|
+
const app = speexjs()
|
|
111
15
|
|
|
112
16
|
app.get('/', async ({ response }) => {
|
|
113
|
-
return response.html('<h1>
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
app.get('/api/hello', async ({ response }) => {
|
|
117
|
-
return response.json({ message: 'Halo Dunia!' })
|
|
17
|
+
return response.html('<h1>SpeexJS 🚀</h1>')
|
|
118
18
|
})
|
|
119
19
|
|
|
120
|
-
app.listen(3000
|
|
20
|
+
app.listen(3000)
|
|
121
21
|
```
|
|
122
22
|
|
|
123
|
-
### 3. Jalankan
|
|
124
|
-
|
|
125
23
|
```bash
|
|
126
|
-
npx
|
|
127
|
-
# atau
|
|
128
|
-
node --loader ts-node src/index.ts
|
|
24
|
+
npx speexjs serve
|
|
129
25
|
```
|
|
130
26
|
|
|
131
|
-
|
|
27
|
+
## Feature Overview
|
|
132
28
|
|
|
133
|
-
|
|
29
|
+
| Category | Features |
|
|
30
|
+
|----------|----------|
|
|
31
|
+
| **Server** | Router, Middleware (10 built-in), Controller, DI Container, Laravel-like API |
|
|
32
|
+
| **Database** | Query Builder, Migrations, Pagination, MySQL/SQLite/PostgreSQL |
|
|
33
|
+
| **Auth** | Session Guard, Token Guard, Gate Authorization, Encryption, Hashing |
|
|
34
|
+
| **Validation** | 25+ schema types, NIK/NPWP/Phone/Kodepos/Rupiah |
|
|
35
|
+
| **Client** | Signals, VDOM, JSX, SSR — no React dependency |
|
|
36
|
+
| **RPC** | Type-safe server-client communication |
|
|
37
|
+
| **CLI** | `speexjs init`, `serve`, `make:*`, `list-routes` |
|
|
38
|
+
| **Zero Dep** | 100% native Node.js — zero external dependencies |
|
|
134
39
|
|
|
135
|
-
|
|
40
|
+
## Examples
|
|
136
41
|
|
|
137
|
-
|
|
138
|
-
import { speedx } from 'speedx/server'
|
|
42
|
+
### Routing
|
|
139
43
|
|
|
140
|
-
|
|
44
|
+
```typescript
|
|
45
|
+
import { speexjs, Controller, get, post, controller } from 'speexjs/server'
|
|
141
46
|
|
|
142
|
-
|
|
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)
|
|
47
|
+
const app = speexjs()
|
|
148
48
|
|
|
149
|
-
// Route groups
|
|
49
|
+
// Route groups with middleware
|
|
150
50
|
app.group('/api', (router) => {
|
|
151
51
|
router.get('/users', [UserController, 'index'])
|
|
152
52
|
router.post('/users', [UserController, 'store'])
|
|
@@ -154,402 +54,77 @@ app.group('/api', (router) => {
|
|
|
154
54
|
|
|
155
55
|
// Resource routes
|
|
156
56
|
app.router.resource('/posts', PostController)
|
|
157
|
-
// GET
|
|
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'
|
|
57
|
+
// GET /posts, POST /posts, GET /posts/:id, PUT /posts/:id, DELETE /posts/:id
|
|
168
58
|
```
|
|
169
59
|
|
|
170
|
-
###
|
|
60
|
+
### Validation
|
|
171
61
|
|
|
172
62
|
```typescript
|
|
173
|
-
import {
|
|
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
|
-
```
|
|
210
|
-
|
|
211
|
-
### Schema Validation
|
|
63
|
+
import { s } from 'speexjs/schema'
|
|
212
64
|
|
|
213
|
-
```typescript
|
|
214
|
-
import { s } from 'speedx/schema'
|
|
215
|
-
|
|
216
|
-
// Buat schema
|
|
217
65
|
const UserSchema = s.object({
|
|
218
|
-
id: s.number(),
|
|
219
66
|
name: s.string().min(3).max(100),
|
|
220
67
|
email: s.string().email(),
|
|
221
68
|
age: s.number().min(17).max(120).optional(),
|
|
222
|
-
phone: s.phone(),
|
|
223
|
-
nik: s.nik().optional(),
|
|
224
|
-
npwp: s.npwp().optional(), // 🇮🇩 NPWP
|
|
69
|
+
phone: s.phone(), // Nomor Indonesia
|
|
70
|
+
nik: s.nik().optional(), // NIK 16 digit
|
|
225
71
|
})
|
|
226
72
|
|
|
227
|
-
// Type inference
|
|
228
73
|
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
74
|
```
|
|
236
75
|
|
|
237
|
-
### Database
|
|
76
|
+
### Database
|
|
238
77
|
|
|
239
78
|
```typescript
|
|
240
|
-
import { DatabaseConnection } from '
|
|
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
|
-
})
|
|
79
|
+
import { DatabaseConnection } from 'speexjs/server/database'
|
|
249
80
|
|
|
81
|
+
const db = new DatabaseConnection({ driver: 'mysql', database: 'myapp' })
|
|
250
82
|
await db.connect()
|
|
251
83
|
|
|
252
|
-
// Query dengan chaining
|
|
253
84
|
const users = await db.table('users')
|
|
254
85
|
.select('id', 'name', 'email')
|
|
255
86
|
.where('age', '>', 18)
|
|
256
|
-
.whereLike('name', '%john%')
|
|
257
87
|
.orderByDesc('created_at')
|
|
258
|
-
.
|
|
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])
|
|
88
|
+
.paginate(10, 1)
|
|
280
89
|
```
|
|
281
90
|
|
|
282
|
-
###
|
|
91
|
+
### Auth
|
|
283
92
|
|
|
284
93
|
```typescript
|
|
285
|
-
import {
|
|
286
|
-
import { AuthManager, SessionGuard } from 'speedx/server/auth'
|
|
94
|
+
import { AuthManager, SessionGuard } from 'speexjs/server/auth'
|
|
287
95
|
|
|
288
|
-
const app = speedx()
|
|
289
|
-
|
|
290
|
-
// Setup auth
|
|
291
96
|
const auth = new AuthManager()
|
|
292
|
-
auth.guard('session', new SessionGuard({
|
|
293
|
-
table: 'users',
|
|
294
|
-
cookieName: 'speedx_session',
|
|
295
|
-
}))
|
|
97
|
+
auth.guard('session', new SessionGuard({ table: 'users' }))
|
|
296
98
|
|
|
297
|
-
// Login route
|
|
298
99
|
app.post('/login', async ({ request, response }) => {
|
|
299
100
|
const { email, password } = await request.json()
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
if (!authenticated) {
|
|
303
|
-
return response.json({ error: 'Login gagal' }, 401)
|
|
304
|
-
}
|
|
101
|
+
const ok = await auth.guard('session').attempt({ email, password })
|
|
102
|
+
if (!ok) return response.json({ error: 'Login gagal' }, 401)
|
|
305
103
|
return response.json({ message: 'Login berhasil' })
|
|
306
104
|
})
|
|
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 'speedx/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
|
-
// ...
|
|
324
|
-
})
|
|
325
105
|
```
|
|
326
106
|
|
|
327
|
-
###
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
import { signal, computed, effect, h, render } from 'speedx/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
|
-
```
|
|
107
|
+
### CLI
|
|
349
108
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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 'speedx/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 'speedx/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 'speedx/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' })
|
|
109
|
+
```bash
|
|
110
|
+
speexjs init my-app # Buat project baru
|
|
111
|
+
speexjs serve # Jalankan dev server
|
|
112
|
+
speexjs make:controller User # Generate controller
|
|
113
|
+
speexjs make:middleware Auth # Generate middleware
|
|
114
|
+
speexjs list-routes # Lihat semua route
|
|
432
115
|
```
|
|
433
116
|
|
|
434
|
-
|
|
117
|
+
## Architecture
|
|
435
118
|
|
|
436
|
-
```typescript
|
|
437
|
-
// === SERVER ===
|
|
438
|
-
import { speedx } from 'speedx/server'
|
|
439
|
-
import { rpc } from 'speedx/rpc'
|
|
440
|
-
import { s } from 'speedx/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 = speedx()
|
|
460
|
-
app.post('/api/rpc', api.toHandler())
|
|
461
|
-
app.listen(3000)
|
|
462
|
-
|
|
463
|
-
// === CLIENT ===
|
|
464
|
-
import { createClient } from 'speedx/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
119
|
```
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
speedx/
|
|
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/
|
|
120
|
+
speexjs/
|
|
121
|
+
├── server/ (HTTP, Router, Middleware, Auth, Database, Cache, Storage, Events)
|
|
122
|
+
├── client/ (Signals, VDOM, SSR, JSX)
|
|
123
|
+
├── schema/ (25+ validation types)
|
|
124
|
+
├── rpc/ (Type-safe RPC)
|
|
125
|
+
└── cli/ (speexjs init, serve, make:*)
|
|
516
126
|
```
|
|
517
127
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
## 📋 Perbandingan dengan Framework Lain
|
|
521
|
-
|
|
522
|
-
| Fitur | speedx | 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.
|
|
551
|
-
|
|
552
|
-
---
|
|
128
|
+
## License
|
|
553
129
|
|
|
554
|
-
|
|
555
|
-
**🇮🇩 Dibuat oleh developer Indonesia, untuk developer dunia.**
|
|
130
|
+
MIT
|