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 CHANGED
@@ -1,555 +1,102 @@
1
- # speexjs 🚀
1
+ # SpeexJS
2
2
 
3
- **Fullstack JavaScript/TypeScript Framework — Server + Client + RPC + Schema + Database + Auth**
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
- > Zero external dependencies. Only Node.js built-in modules.
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.get('/', async ({ response }) => {
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
- ## 📖 Dokumentasi Lengkap
36
+ ## Examples
134
37
 
135
- ### Server Routing
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
- ### Schema Validation
53
+ ### Validation
212
54
 
213
55
  ```typescript
214
- import { s } from 'speexjs/schema'
56
+ import { schema } from 'speexjs/schema'
215
57
 
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
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 Query Builder
65
+ ### Database
238
66
 
239
67
  ```typescript
240
- import { DatabaseConnection } from 'speexjs/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
- })
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
- .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])
75
+ .paginate(10, 1)
280
76
  ```
281
77
 
282
- ### Authentication
78
+ ### Auth
283
79
 
284
80
  ```typescript
285
- import { speexjs } from 'speexjs/server'
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 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 '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
- ### Client — Signals + VDOM
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
- **speexjs — Fullstack JavaScript/TypeScript Framework**
555
- **🇮🇩 Dibuat oleh developer Indonesia, untuk developer dunia.**
102
+ MIT