speexjs 0.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 +555 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1017 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +73 -0
- package/dist/client/index.js +927 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/signals/index.d.ts +62 -0
- package/dist/client/signals/index.js +248 -0
- package/dist/client/signals/index.js.map +1 -0
- package/dist/client/vdom/index.d.ts +50 -0
- package/dist/client/vdom/index.js +540 -0
- package/dist/client/vdom/index.js.map +1 -0
- package/dist/client/vdom/jsx-runtime.d.ts +9 -0
- package/dist/client/vdom/jsx-runtime.js +203 -0
- package/dist/client/vdom/jsx-runtime.js.map +1 -0
- package/dist/index-CMkhSDh7.d.ts +97 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +6402 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-DGrnv8QB.d.ts +8 -0
- package/dist/response-Ca8KWK5_.d.ts +173 -0
- package/dist/rpc/index.d.ts +70 -0
- package/dist/rpc/index.js +136 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/schema/index.d.ts +231 -0
- package/dist/schema/index.js +1160 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +61 -0
- package/dist/server/auth/index.js +462 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/cache/index.d.ts +45 -0
- package/dist/server/cache/index.js +238 -0
- package/dist/server/cache/index.js.map +1 -0
- package/dist/server/container/index.d.ts +20 -0
- package/dist/server/container/index.js +62 -0
- package/dist/server/container/index.js.map +1 -0
- package/dist/server/controller/index.d.ts +37 -0
- package/dist/server/controller/index.js +139 -0
- package/dist/server/controller/index.js.map +1 -0
- package/dist/server/database/index.d.ts +461 -0
- package/dist/server/database/index.js +1977 -0
- package/dist/server/database/index.js.map +1 -0
- package/dist/server/events/index.d.ts +29 -0
- package/dist/server/events/index.js +159 -0
- package/dist/server/events/index.js.map +1 -0
- package/dist/server/gate/index.d.ts +36 -0
- package/dist/server/gate/index.js +169 -0
- package/dist/server/gate/index.js.map +1 -0
- package/dist/server/http/index.d.ts +45 -0
- package/dist/server/http/index.js +871 -0
- package/dist/server/http/index.js.map +1 -0
- package/dist/server/index.d.ts +79 -0
- package/dist/server/index.js +4185 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware/index.d.ts +5 -0
- package/dist/server/middleware/index.js +416 -0
- package/dist/server/middleware/index.js.map +1 -0
- package/dist/server/router/index.d.ts +5 -0
- package/dist/server/router/index.js +231 -0
- package/dist/server/router/index.js.map +1 -0
- package/dist/server/storage/index.d.ts +66 -0
- package/dist/server/storage/index.js +244 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/session-guard-CZeN87L9.d.ts +48 -0
- package/dist/types-CXH8hPei.d.ts +38 -0
- package/package.json +138 -0
package/README.md
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# speedx 🚀
|
|
2
|
+
|
|
3
|
+
**Fullstack JavaScript/TypeScript Framework — Server + Client + RPC + Schema + Database + Auth**
|
|
4
|
+
**🇮🇩 Indonesia First, Built for the World**
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install speedx
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
> Zero external dependencies. Only Node.js built-in modules.
|
|
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
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// src/index.ts
|
|
108
|
+
import { speedx } from 'speedx/server'
|
|
109
|
+
|
|
110
|
+
const app = speedx()
|
|
111
|
+
|
|
112
|
+
app.get('/', async ({ response }) => {
|
|
113
|
+
return response.html('<h1>speedx 🚀</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('speedx running on http://localhost:3000'))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Jalankan
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx speedx serve
|
|
127
|
+
# atau
|
|
128
|
+
node --loader ts-node src/index.ts
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 📖 Dokumentasi Lengkap
|
|
134
|
+
|
|
135
|
+
### Server Routing
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { speedx } from 'speedx/server'
|
|
139
|
+
|
|
140
|
+
const app = speedx()
|
|
141
|
+
|
|
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
|
+
app.group('/api', (router) => {
|
|
151
|
+
router.get('/users', [UserController, 'index'])
|
|
152
|
+
router.post('/users', [UserController, 'store'])
|
|
153
|
+
}).middleware(['auth', 'throttle'])
|
|
154
|
+
|
|
155
|
+
// Resource routes
|
|
156
|
+
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 'speedx/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
|
+
```
|
|
210
|
+
|
|
211
|
+
### Schema Validation
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { s } from 'speedx/schema'
|
|
215
|
+
|
|
216
|
+
// Buat schema
|
|
217
|
+
const UserSchema = s.object({
|
|
218
|
+
id: s.number(),
|
|
219
|
+
name: s.string().min(3).max(100),
|
|
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
|
|
225
|
+
})
|
|
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
|
+
```
|
|
236
|
+
|
|
237
|
+
### Database Query Builder
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { DatabaseConnection } from 'speedx/server/database'
|
|
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
|
+
})
|
|
249
|
+
|
|
250
|
+
await db.connect()
|
|
251
|
+
|
|
252
|
+
// Query dengan chaining
|
|
253
|
+
const users = await db.table('users')
|
|
254
|
+
.select('id', 'name', 'email')
|
|
255
|
+
.where('age', '>', 18)
|
|
256
|
+
.whereLike('name', '%john%')
|
|
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])
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Authentication
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { speedx } from 'speedx/server'
|
|
286
|
+
import { AuthManager, SessionGuard } from 'speedx/server/auth'
|
|
287
|
+
|
|
288
|
+
const app = speedx()
|
|
289
|
+
|
|
290
|
+
// Setup auth
|
|
291
|
+
const auth = new AuthManager()
|
|
292
|
+
auth.guard('session', new SessionGuard({
|
|
293
|
+
table: 'users',
|
|
294
|
+
cookieName: 'speedx_session',
|
|
295
|
+
}))
|
|
296
|
+
|
|
297
|
+
// Login route
|
|
298
|
+
app.post('/login', async ({ request, response }) => {
|
|
299
|
+
const { email, password } = await request.json()
|
|
300
|
+
const authenticated = await auth.guard('session').attempt({ email, password })
|
|
301
|
+
|
|
302
|
+
if (!authenticated) {
|
|
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 '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
|
+
```
|
|
326
|
+
|
|
327
|
+
### Client — Signals + VDOM
|
|
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
|
+
```
|
|
349
|
+
|
|
350
|
+
Dengan JSX (`tsconfig.json`):
|
|
351
|
+
```json
|
|
352
|
+
{
|
|
353
|
+
"compilerOptions": {
|
|
354
|
+
"jsx": "react-jsx",
|
|
355
|
+
"jsxImportSource": "@SpeedX/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 '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' })
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### RPC (Type-Safe)
|
|
435
|
+
|
|
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
|
+
```
|
|
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
|
+
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/
|
|
516
|
+
```
|
|
517
|
+
|
|
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
|
+
---
|
|
553
|
+
|
|
554
|
+
**speedx — Fullstack JavaScript/TypeScript Framework**
|
|
555
|
+
**🇮🇩 Dibuat oleh developer Indonesia, untuk developer dunia.**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|