bunbase 0.0.9 → 1.0.1

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,25 +1,718 @@
1
- # basalt
1
+ # bunbase
2
2
 
3
- Type-safe components for scalable applications
3
+ A type-safe, batteries-included backend framework for [Bun](https://bun.sh) that makes building APIs delightful.
4
+
5
+ ## Why Bunbase?
6
+
7
+ - **🎯 Type-safe by default** - Full end-to-end type safety with TypeBox schemas
8
+ - **⚡ Built for Bun** - Leverages Bun's native performance and APIs
9
+ - **🔒 Authorization first** - Composable guards with RBAC and multi-tenancy built-in
10
+ - **🚀 Zero boilerplate** - Define actions, not routes. File-based discovery just works.
11
+ - **🔄 Job queue & scheduler** - Postgres-backed queue with cron support included
12
+ - **📡 Multiple triggers** - API, events, cron, webhooks, and MCP tools from one action
13
+ - **🎨 HTTP field mapping** - Route fields to body, headers, query, cookies, path automatically
14
+ - **📊 Built-in observability** - Action logs, runs tracking, and Studio dashboard
15
+ - **🔌 Optional Redis** - Drop-in Redis support for KV store and distributed rate limiting
4
16
 
5
17
  ## Installation
6
18
 
7
19
  ```bash
8
- bun add basalt
20
+ bun add bunbase
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Initialize a new project
26
+
27
+ ```bash
28
+ bunbase init my-app
29
+ cd my-app
30
+ ```
31
+
32
+ ### 2. Configure your database
33
+
34
+ Create a `bunbase.config.ts`:
35
+
36
+ ```typescript
37
+ import { defineConfig } from 'bunbase'
38
+
39
+ export default defineConfig({
40
+ db: {
41
+ url: process.env.DATABASE_URL || 'postgresql://localhost:5432/myapp',
42
+ },
43
+ server: {
44
+ port: 3000,
45
+ },
46
+ })
47
+ ```
48
+
49
+ ### 3. Create your first action
50
+
51
+ ```typescript
52
+ // src/tasks/create-task.action.ts
53
+ import { action, t, triggers } from 'bunbase'
54
+
55
+ export const createTask = action({
56
+ name: 'create-task',
57
+ description: 'Create a new task',
58
+ input: t.Object({
59
+ title: t.String({ minLength: 1, maxLength: 200 }),
60
+ description: t.Optional(t.String()),
61
+ }),
62
+ output: t.Object({
63
+ id: t.String(),
64
+ title: t.String(),
65
+ createdAt: t.String(),
66
+ }),
67
+ triggers: [triggers.api('POST', '/tasks')],
68
+ }, async ({ input, ctx }) => {
69
+ const task = await ctx.db
70
+ .from('tasks')
71
+ .insert({ title: input.title, description: input.description })
72
+ .returning('id', 'title', 'created_at')
73
+ .single()
74
+
75
+ return {
76
+ id: task.id,
77
+ title: task.title,
78
+ createdAt: task.created_at,
79
+ }
80
+ })
81
+ ```
82
+
83
+ ### 4. Run your server
84
+
85
+ ```bash
86
+ bunbase dev
87
+ ```
88
+
89
+ Your API is now live at `http://localhost:3000/tasks`! 🎉
90
+
91
+ ## Core Concepts
92
+
93
+ ### Actions
94
+
95
+ Actions are the fundamental building blocks of Bunbase. They are reusable, validated functions that represent atomic units of work.
96
+
97
+ ```typescript
98
+ import { action, t, triggers, guards } from 'bunbase'
99
+
100
+ export const updateProfile = action({
101
+ name: 'update-profile',
102
+ description: 'Update user profile',
103
+
104
+ // TypeBox input schema with validation
105
+ input: t.Object({
106
+ name: t.String({ minLength: 2 }),
107
+ bio: t.Optional(t.String({ maxLength: 500 })),
108
+ }),
109
+
110
+ // TypeBox output schema
111
+ output: t.Object({
112
+ id: t.String(),
113
+ name: t.String(),
114
+ bio: t.Union([t.String(), t.Null()]),
115
+ }),
116
+
117
+ // How this action can be invoked
118
+ triggers: [
119
+ triggers.api('PATCH', '/profile'),
120
+ ],
121
+
122
+ // Authorization checks
123
+ guards: [guards.authenticated()],
124
+
125
+ // Optional retry configuration
126
+ retry: {
127
+ maxAttempts: 3,
128
+ backoff: 'exponential',
129
+ backoffMs: 1000,
130
+ },
131
+ }, async ({ input, ctx }) => {
132
+ // ctx provides: db, logger, auth, event, queue, scheduler
133
+ const user = await ctx.db
134
+ .from('users')
135
+ .eq('id', ctx.auth.userId)
136
+ .update({ name: input.name, bio: input.bio })
137
+ .returning('id', 'name', 'bio')
138
+ .single()
139
+
140
+ return user
141
+ })
142
+ ```
143
+
144
+ ### Modules
145
+
146
+ Modules group related actions with shared configuration:
147
+
148
+ ```typescript
149
+ // src/billing/_module.ts
150
+ import { module, guards } from 'bunbase'
151
+ import { createSubscription } from './create-subscription.action.ts'
152
+ import { cancelSubscription } from './cancel-subscription.action.ts'
153
+ import { getInvoices } from './get-invoices.action.ts'
154
+
155
+ export default module({
156
+ name: 'billing',
157
+ description: 'Subscription and billing management',
158
+ apiPrefix: '/billing', // All action routes prefixed with this
159
+ guards: [guards.authenticated(), guards.hasFeature('billing')],
160
+ actions: [createSubscription, cancelSubscription, getInvoices],
161
+ })
162
+ ```
163
+
164
+ ### Triggers
165
+
166
+ Actions can be invoked through multiple triggers:
167
+
168
+ ```typescript
169
+ // API endpoint
170
+ triggers.api('POST', '/tasks')
171
+
172
+ // Scheduled cron job
173
+ triggers.cron('0 0 * * *', { timezone: 'America/New_York' })
174
+
175
+ // Event-driven
176
+ triggers.event('task.completed')
177
+
178
+ // Webhook with signature verification
179
+ triggers.webhook('/webhooks/stripe', {
180
+ verify: async (req) => verifyStripeSignature(req)
181
+ })
182
+
183
+ // MCP tool (for AI assistants)
184
+ triggers.mcp({
185
+ description: 'Create a new task',
186
+ parameters: { /* ... */ }
187
+ })
188
+ ```
189
+
190
+ ## Database
191
+
192
+ Bunbase includes a type-safe query builder with fluent API:
193
+
194
+ ### Query Building
195
+
196
+ ```typescript
197
+ // Select with filters
198
+ const tasks = await ctx.db
199
+ .from('tasks')
200
+ .eq('status', 'active')
201
+ .gt('priority', 5)
202
+ .orderBy('created_at', 'desc')
203
+ .limit(10)
204
+ .exec()
205
+
206
+ // Single record
207
+ const user = await ctx.db
208
+ .from('users')
209
+ .eq('email', 'user@example.com')
210
+ .single() // Throws if not found
211
+
212
+ // Maybe single (returns null if not found)
213
+ const task = await ctx.db
214
+ .from('tasks')
215
+ .eq('id', taskId)
216
+ .maybeSingle()
217
+
218
+ // Insert
219
+ const newTask = await ctx.db
220
+ .from('tasks')
221
+ .insert({ title: 'New task', status: 'pending' })
222
+ .returning('id', 'title', 'created_at')
223
+ .single()
224
+
225
+ // Update
226
+ await ctx.db
227
+ .from('tasks')
228
+ .eq('id', taskId)
229
+ .update({ status: 'completed', completed_at: new Date().toISOString() })
230
+ .exec()
231
+
232
+ // Delete
233
+ await ctx.db
234
+ .from('tasks')
235
+ .eq('status', 'archived')
236
+ .delete()
237
+ .exec()
238
+
239
+ // Count
240
+ const { count } = await ctx.db
241
+ .from('tasks')
242
+ .eq('status', 'active')
243
+ .count()
244
+ ```
245
+
246
+ ### Type Generation
247
+
248
+ Generate TypeScript types from your PostgreSQL database:
249
+
250
+ ```bash
251
+ bunbase typegen:db
9
252
  ```
10
253
 
11
- ## Usage
254
+ This creates `.bunbase/database.d.ts` with Row/Insert/Update types for all tables. The types are automatically picked up by the database client via module augmentation.
12
255
 
13
256
  ```typescript
14
- import { greet } from 'basalt';
257
+ // Types are automatically inferred!
258
+ const user = await ctx.db.from('users').eq('id', userId).single()
259
+ // ^? { id: string; email: string; name: string; ... }
260
+ ```
261
+
262
+ ## Guards & Authorization
15
263
 
16
- console.log(greet('World')); // Hello, World!
264
+ Guards are composable authorization functions that run before action handlers:
265
+
266
+ ### Built-in Guards
267
+
268
+ ```typescript
269
+ import { guards } from 'bunbase'
270
+
271
+ // Require authenticated user
272
+ guards.authenticated()
273
+
274
+ // Role-based access control
275
+ guards.hasRole('admin')
276
+ guards.hasPermission('tasks:delete')
277
+
278
+ // Multi-tenant SaaS
279
+ guards.inOrg() // User must be in an organization
280
+ guards.hasFeature('advanced-analytics') // Org must have feature
281
+ guards.trialActiveOrPaid() // Org must have active trial or paid plan
282
+
283
+ // Rate limiting
284
+ guards.rateLimit({
285
+ points: 100, // 100 requests
286
+ duration: 60000, // per 60 seconds
287
+ keyPrefix: 'api',
288
+ blockDuration: 300000, // Block for 5 minutes if exceeded
289
+ })
290
+ ```
291
+
292
+ ### Custom Guards
293
+
294
+ ```typescript
295
+ import { type GuardFn, GuardError } from 'bunbase'
296
+
297
+ function isTaskOwner(): GuardFn {
298
+ return async ({ input, ctx }) => {
299
+ const task = await ctx.db
300
+ .from('tasks')
301
+ .eq('id', input.taskId)
302
+ .single()
303
+
304
+ if (task.created_by !== ctx.auth.userId) {
305
+ throw new GuardError('Not authorized to access this task', 403)
306
+ }
307
+ }
308
+ }
309
+
310
+ // Use in action
311
+ export const deleteTask = action({
312
+ guards: [guards.authenticated(), isTaskOwner()],
313
+ // ...
314
+ })
315
+ ```
316
+
317
+ ## HTTP Field Mapping
318
+
319
+ Route fields to different HTTP locations automatically:
320
+
321
+ ```typescript
322
+ import { action, t, triggers, http } from 'bunbase'
323
+
324
+ export const advancedLogin = action({
325
+ name: 'advanced-login',
326
+ input: t.Object({
327
+ // Regular fields go to JSON body
328
+ email: t.String({ format: 'email' }),
329
+ password: t.String(),
330
+
331
+ // Map to HTTP locations
332
+ apiKey: http.Header(t.String(), 'X-API-Key'),
333
+ remember: http.Query(t.Boolean()),
334
+ deviceId: http.Cookie(t.String()),
335
+ }),
336
+ output: t.Object({
337
+ user: t.Object({ id: t.String(), email: t.String() }),
338
+ token: t.String(),
339
+
340
+ // Response headers and cookies
341
+ userId: http.Header(t.String(), 'X-User-ID'),
342
+ refreshToken: http.Cookie(t.String(), 'refresh_token', {
343
+ httpOnly: true,
344
+ secure: true,
345
+ sameSite: 'strict',
346
+ maxAge: 7 * 24 * 60 * 60, // 7 days
347
+ }),
348
+ }),
349
+ triggers: [triggers.api('POST', '/auth/login')],
350
+ }, async ({ input, ctx }) => {
351
+ // All fields are available in input
352
+ // HTTP routing happens automatically
353
+ const user = await authenticateUser(input.email, input.password)
354
+ const token = generateToken(user.id)
355
+
356
+ return {
357
+ user: { id: user.id, email: user.email },
358
+ token,
359
+ userId: user.id, // Will be in X-User-ID response header
360
+ refreshToken: 'refresh_token_value', // Will be in Set-Cookie header
361
+ }
362
+ })
17
363
  ```
18
364
 
365
+ When using [@bunbase/react](../react), the client automatically handles all HTTP field routing based on your backend schema.
366
+
367
+ ## Job Queue
368
+
369
+ Postgres-backed job queue with priorities, retries, and dead letter queue:
370
+
371
+ ```typescript
372
+ // Push a job
373
+ await ctx.queue.push('send-email', {
374
+ to: 'user@example.com',
375
+ subject: 'Welcome!',
376
+ body: 'Thanks for signing up',
377
+ }, {
378
+ priority: 10, // Higher priority = processed first
379
+ delay: 5000, // Delay 5 seconds
380
+ })
381
+
382
+ // Define job handler
383
+ export const sendEmail = action({
384
+ name: 'send-email',
385
+ triggers: [triggers.job()],
386
+ input: t.Object({
387
+ to: t.String({ format: 'email' }),
388
+ subject: t.String(),
389
+ body: t.String(),
390
+ }),
391
+ output: t.Object({
392
+ messageId: t.String(),
393
+ }),
394
+ retry: {
395
+ maxAttempts: 5,
396
+ backoff: 'exponential',
397
+ backoffMs: 1000,
398
+ maxBackoffMs: 30000,
399
+ },
400
+ }, async ({ input, ctx }) => {
401
+ const messageId = await emailService.send(input)
402
+ return { messageId }
403
+ })
404
+ ```
405
+
406
+ ## Scheduler
407
+
408
+ Schedule actions to run at specific times or intervals:
409
+
410
+ ```typescript
411
+ // Cron-based scheduling
412
+ export const dailyReport = action({
413
+ name: 'daily-report',
414
+ triggers: [
415
+ triggers.cron('0 9 * * *', { timezone: 'America/New_York' })
416
+ ],
417
+ }, async ({ ctx }) => {
418
+ const report = await generateDailyReport()
419
+ await ctx.event.emit('report.generated', { report })
420
+ })
421
+
422
+ // One-time delayed execution
423
+ await ctx.scheduler.schedule(
424
+ 'send-reminder',
425
+ { taskId: task.id },
426
+ new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours from now
427
+ )
428
+ ```
429
+
430
+ ## Event Bus
431
+
432
+ In-memory event emitter for action-to-action communication:
433
+
434
+ ```typescript
435
+ // Emit event
436
+ await ctx.event.emit('task.created', {
437
+ taskId: task.id,
438
+ title: task.title,
439
+ createdBy: ctx.auth.userId,
440
+ })
441
+
442
+ // Listen for event
443
+ export const onTaskCreated = action({
444
+ name: 'on-task-created',
445
+ triggers: [triggers.event('task.created')],
446
+ input: t.Object({
447
+ taskId: t.String(),
448
+ title: t.String(),
449
+ createdBy: t.String(),
450
+ }),
451
+ }, async ({ input, ctx }) => {
452
+ // Send notifications, update analytics, etc.
453
+ await ctx.queue.push('send-notification', {
454
+ userId: input.createdBy,
455
+ message: `Task "${input.title}" created`,
456
+ })
457
+ })
458
+ ```
459
+
460
+ ## Redis Support (Optional)
461
+
462
+ Add Redis for high-performance KV operations and distributed rate limiting:
463
+
464
+ ```typescript
465
+ // bunbase.config.ts
466
+ export default defineConfig({
467
+ redis: {
468
+ url: process.env.REDIS_URL || 'redis://localhost:6379',
469
+ connectionTimeout: 5000,
470
+ autoReconnect: true,
471
+ maxRetries: 10,
472
+ tls: false,
473
+ },
474
+ db: { /* ... */ },
475
+ })
476
+ ```
477
+
478
+ When Redis is configured:
479
+ - **KV Store**: ~10-100x faster than Postgres for key-value operations
480
+ - **Rate Limiting**: Distributed rate limiting across multiple server instances
481
+ - **Auto-fallback**: Falls back to Postgres/in-memory if Redis unavailable
482
+
483
+ ```typescript
484
+ // KV operations (uses Redis if configured, Postgres otherwise)
485
+ await ctx.kv.set('user:123:preferences', { theme: 'dark' })
486
+ const prefs = await ctx.kv.get('user:123:preferences')
487
+ await ctx.kv.delete('user:123:preferences')
488
+
489
+ // Rate limiting automatically uses Redis when available
490
+ guards.rateLimit({ points: 100, duration: 60000 })
491
+ ```
492
+
493
+ ## CLI Commands
494
+
495
+ ```bash
496
+ # Initialize new project
497
+ bunbase init <project-name>
498
+
499
+ # Generate action or module
500
+ bunbase generate action <name>
501
+ bunbase generate module <name>
502
+
503
+ # Database migrations
504
+ bunbase migrate # Run pending migrations
505
+ bunbase migrate new <name> # Create new migration
506
+
507
+ # Type generation
508
+ bunbase typegen:db # PostgreSQL → TypeScript
509
+ bunbase typegen:react --url <backend-url> # Backend schema → React types
510
+
511
+ # Development server
512
+ bunbase dev
513
+
514
+ # Production build
515
+ bun run build
516
+ ```
517
+
518
+ ## Configuration
519
+
520
+ Complete `bunbase.config.ts` example:
521
+
522
+ ```typescript
523
+ import { defineConfig } from 'bunbase'
524
+
525
+ export default defineConfig({
526
+ // Database configuration (required)
527
+ db: {
528
+ url: process.env.DATABASE_URL!,
529
+ },
530
+
531
+ // Server configuration
532
+ server: {
533
+ port: 3000,
534
+ hostname: '0.0.0.0',
535
+ cors: {
536
+ origin: ['http://localhost:3000', 'https://myapp.com'],
537
+ credentials: true,
538
+ },
539
+ },
540
+
541
+ // Redis configuration (optional)
542
+ redis: {
543
+ url: process.env.REDIS_URL,
544
+ connectionTimeout: 5000,
545
+ idleTimeout: 30000,
546
+ autoReconnect: true,
547
+ maxRetries: 10,
548
+ tls: false,
549
+ },
550
+
551
+ // Session configuration
552
+ session: {
553
+ secret: process.env.SESSION_SECRET!,
554
+ cookieName: 'my_session',
555
+ maxAge: 7 * 24 * 60 * 60, // 7 days
556
+ },
557
+
558
+ // Logging configuration
559
+ logging: {
560
+ level: 'info',
561
+ pretty: true,
562
+ },
563
+
564
+ // Write buffer for action logs/runs
565
+ writeBuffer: {
566
+ flushInterval: 2000, // Flush every 2 seconds
567
+ maxSize: 500, // Or when 500 entries buffered
568
+ },
569
+
570
+ // Queue configuration
571
+ queue: {
572
+ pollInterval: 1000, // Check for jobs every second
573
+ maxConcurrency: 10, // Process up to 10 jobs concurrently
574
+ },
575
+ })
576
+ ```
577
+
578
+ ## Studio Dashboard
579
+
580
+ Bunbase includes a built-in development dashboard at `http://localhost:3000/_studio`:
581
+
582
+ - 📊 View all registered actions and modules
583
+ - 🔍 Browse action execution logs and runs
584
+ - ⚡ Monitor performance metrics
585
+ - 🎯 Test actions directly from the UI
586
+ - 📈 Success rates and average duration stats
587
+
588
+ ## React Integration
589
+
590
+ Generate fully-typed React client with automatic HTTP field routing:
591
+
592
+ ```bash
593
+ bunbase typegen:react --url http://localhost:3000
594
+ ```
595
+
596
+ ```typescript
597
+ import { createBunbaseClient } from '@bunbase/react'
598
+ import type { BunbaseAPI } from './.bunbase/api'
599
+ import { bunbaseAPISchema } from './.bunbase/api'
600
+
601
+ export const bunbase = createBunbaseClient<BunbaseAPI>({
602
+ baseUrl: 'http://localhost:3000',
603
+ schema: bunbaseAPISchema, // Enables automatic field routing
604
+ })
605
+
606
+ // Use in components
607
+ function TaskList() {
608
+ const { data, isLoading } = bunbase.useQuery('list-tasks', {
609
+ status: 'active'
610
+ })
611
+
612
+ const createTask = bunbase.useMutation('create-task')
613
+
614
+ // Full type safety and automatic HTTP field routing!
615
+ }
616
+ ```
617
+
618
+ See [@bunbase/react](../react) for complete documentation.
619
+
620
+ ## Error Handling
621
+
622
+ ```typescript
623
+ import { GuardError, NonRetriableError } from 'bunbase'
624
+
625
+ // Guard errors (401/403/429)
626
+ throw new GuardError('Not authorized', 403)
627
+
628
+ // Non-retriable errors (won't retry even if retry config exists)
629
+ throw new NonRetriableError('Invalid payment method')
630
+
631
+ // Regular errors (will retry based on action retry config)
632
+ throw new Error('External API timeout')
633
+ ```
634
+
635
+ ## Multi-Tenant SaaS
636
+
637
+ Built-in support for multi-tenant applications:
638
+
639
+ ```typescript
640
+ import { guards } from 'bunbase'
641
+
642
+ // Ensure user is in an organization
643
+ guards.inOrg()
644
+
645
+ // Check organization has feature
646
+ guards.hasFeature('advanced-analytics')
647
+
648
+ // Check subscription status
649
+ guards.trialActiveOrPaid()
650
+
651
+ // Access current org in action
652
+ async ({ ctx }) => {
653
+ const orgId = ctx.auth.orgId
654
+ const org = await ctx.db.from('organizations').eq('id', orgId).single()
655
+ }
656
+ ```
657
+
658
+ ## Observability
659
+
660
+ Every action execution is automatically tracked:
661
+
662
+ ```typescript
663
+ // Access action run info
664
+ async ({ ctx }) => {
665
+ ctx.logger.info('Processing task', { taskId: '123' })
666
+
667
+ // Current retry attempt (if using retry)
668
+ if (ctx.retry.attempt > 1) {
669
+ ctx.logger.warn(`Retry attempt ${ctx.retry.attempt}/${ctx.retry.maxAttempts}`)
670
+ }
671
+ }
672
+ ```
673
+
674
+ Action logs and runs are stored in the database and visible in Studio.
675
+
676
+ ## TypeScript
677
+
678
+ Bunbase is written in TypeScript with strict mode and provides:
679
+
680
+ - Full type inference for database queries
681
+ - TypeBox schemas for runtime validation and type generation
682
+ - Generated types from database schema
683
+ - End-to-end type safety with React client
684
+
685
+ ## Examples
686
+
687
+ - [Basic Example](../../examples/basic) - Complete working app with all features
688
+ - [AMANTRA Control Panel](../../examples/amantra-cpanel) - Real-world SaaS application
689
+
690
+ ## Architecture
691
+
692
+ Bunbase follows these principles:
693
+
694
+ - **Composition over inheritance** - Guards, triggers, and actions are composable functions
695
+ - **Registry pattern** - Single ActionRegistry holds all discovered actions
696
+ - **Builder pattern** - Fluent APIs for actions, modules, triggers, queries
697
+ - **Write buffering** - Batched writes to avoid database bombardment
698
+ - **Convention over configuration** - File-based discovery, sensible defaults
699
+
700
+ ## Performance
701
+
702
+ - Built on Bun's native HTTP server and SQLite/PostgreSQL client
703
+ - Automatic request batching with write buffer
704
+ - Optional Redis for high-performance operations
705
+ - Efficient query builder with minimal overhead
706
+ - Zero-copy cookie parsing with Bun's CookieMap
707
+
19
708
  ## Contributing
20
709
 
21
- Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution guidelines.
710
+ Issues and PRs welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md).
22
711
 
23
712
  ## License
24
713
 
25
714
  MIT
715
+
716
+ ---
717
+
718
+ Built with ❤️ using [Bun](https://bun.sh)