bunderstack 0.23.4 → 0.24.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/CHANGELOG.md +56 -0
- package/README.md +6 -4
- package/dist/api/builder.d.ts +13 -11
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/builder.js.map +1 -1
- package/dist/api/context.d.ts +6 -6
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +1 -1
- package/dist/api/context.js.map +1 -1
- package/dist/api/crud-router.d.ts +6 -6
- package/dist/api/realtime-router.d.ts +1 -1
- package/dist/api/storage-router.d.ts +5 -5
- package/dist/backend-internals.d.ts +5 -13
- package/dist/backend-internals.d.ts.map +1 -1
- package/dist/backend-internals.js.map +1 -1
- package/dist/backend.d.ts +39 -5
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +37 -46
- package/dist/backend.js.map +1 -1
- package/dist/blueprint-generator.d.ts +1 -0
- package/dist/blueprint-generator.d.ts.map +1 -1
- package/dist/blueprint-generator.js +29 -1
- package/dist/blueprint-generator.js.map +1 -1
- package/dist/blueprint.d.ts +2 -1
- package/dist/blueprint.d.ts.map +1 -1
- package/dist/blueprint.js +9 -2
- package/dist/blueprint.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +13 -19
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/email/smtp.d.ts +7 -1
- package/dist/email/smtp.d.ts.map +1 -1
- package/dist/email/smtp.js +5 -1
- package/dist/email/smtp.js.map +1 -1
- package/dist/email.d.ts +3 -29
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +1 -194
- package/dist/email.js.map +1 -1
- package/dist/env-probe.d.ts +9 -0
- package/dist/env-probe.d.ts.map +1 -0
- package/dist/env-probe.js +73 -0
- package/dist/env-probe.js.map +1 -0
- package/dist/env.d.ts +2 -5
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +2 -9
- package/dist/env.js.map +1 -1
- package/dist/hosted-contract.d.ts +11 -0
- package/dist/hosted-contract.d.ts.map +1 -0
- package/dist/hosted-contract.js +46 -0
- package/dist/hosted-contract.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +15 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +43 -0
- package/dist/inspect.js.map +1 -0
- package/dist/internal-tables-pg.d.ts +43 -94
- package/dist/internal-tables-pg.d.ts.map +1 -1
- package/dist/internal-tables-pg.js +14 -17
- package/dist/internal-tables-pg.js.map +1 -1
- package/dist/internal-tables.d.ts +213 -488
- package/dist/internal-tables.d.ts.map +1 -1
- package/dist/internal-tables.js +33 -33
- package/dist/internal-tables.js.map +1 -1
- package/dist/jobs/define.d.ts +20 -20
- package/dist/jobs/define.d.ts.map +1 -1
- package/dist/jobs/define.js.map +1 -1
- package/dist/manifest-diff.d.ts +6 -0
- package/dist/manifest-diff.d.ts.map +1 -0
- package/dist/manifest-diff.js +42 -0
- package/dist/manifest-diff.js.map +1 -0
- package/dist/manifest.d.ts +10 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +27 -29
- package/dist/manifest.js.map +1 -1
- package/dist/messaging/email.d.ts +16 -0
- package/dist/messaging/email.d.ts.map +1 -0
- package/dist/messaging/email.js +8 -0
- package/dist/messaging/email.js.map +1 -0
- package/dist/messaging/index.d.ts +8 -0
- package/dist/messaging/index.d.ts.map +1 -0
- package/dist/messaging/index.js +4 -0
- package/dist/messaging/index.js.map +1 -0
- package/dist/messaging/journal.d.ts +4 -0
- package/dist/messaging/journal.d.ts.map +1 -0
- package/dist/messaging/journal.js +19 -0
- package/dist/messaging/journal.js.map +1 -0
- package/dist/messaging/runtime.d.ts +16 -0
- package/dist/messaging/runtime.d.ts.map +1 -0
- package/dist/messaging/runtime.js +213 -0
- package/dist/messaging/runtime.js.map +1 -0
- package/dist/messaging/standalone.d.ts +27 -0
- package/dist/messaging/standalone.d.ts.map +1 -0
- package/dist/messaging/standalone.js +18 -0
- package/dist/messaging/standalone.js.map +1 -0
- package/dist/messaging/telegram.d.ts +16 -0
- package/dist/messaging/telegram.d.ts.map +1 -0
- package/dist/messaging/telegram.js +5 -0
- package/dist/messaging/telegram.js.map +1 -0
- package/dist/messaging/types.d.ts +22 -0
- package/dist/messaging/types.d.ts.map +1 -0
- package/dist/messaging/types.js +17 -0
- package/dist/messaging/types.js.map +1 -0
- package/dist/runtime.d.ts +11 -10
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +19 -31
- package/dist/runtime.js.map +1 -1
- package/dist/schema-export-pg.d.ts +1 -1
- package/dist/schema-export-pg.d.ts.map +1 -1
- package/dist/schema-export-pg.js +1 -1
- package/dist/schema-export-pg.js.map +1 -1
- package/dist/schema-export.d.ts +1 -1
- package/dist/schema-export.d.ts.map +1 -1
- package/dist/schema-export.js +1 -1
- package/dist/schema-export.js.map +1 -1
- package/dist/testing/fixture.d.ts +2 -2
- package/dist/testing/fixture.d.ts.map +1 -1
- package/dist/testing/fixture.js +11 -9
- package/dist/testing/fixture.js.map +1 -1
- package/dist/testing/messaging.d.ts +21 -0
- package/dist/testing/messaging.d.ts.map +1 -0
- package/dist/testing/messaging.js +34 -0
- package/dist/testing/messaging.js.map +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js.map +1 -1
- package/package.json +6 -2
- package/skills/creating-bunderstack-apps/references/application-structure.md +16 -7
- package/skills/migrating-to-bunderstack/SKILL.md +72 -50
- package/skills/migrating-to-bunderstack/references/audit-checklist.md +2 -2
- package/skills/migrating-to-bunderstack/references/runtime-replacements.md +16 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunderstack",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Batteries-included backend framework for Bun: type-safe oRPC APIs, auth, storage, realtime, jobs,
|
|
3
|
+
"version": "0.24.0",
|
|
4
|
+
"description": "Batteries-included backend framework for Bun: type-safe oRPC APIs, auth, storage, realtime, jobs, messaging, and validated env from one declaration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backend",
|
|
7
7
|
"better-auth",
|
|
@@ -113,6 +113,10 @@
|
|
|
113
113
|
"types": "./dist/email/smtp.d.ts",
|
|
114
114
|
"default": "./dist/email/smtp.js"
|
|
115
115
|
},
|
|
116
|
+
"./messaging": {
|
|
117
|
+
"types": "./dist/messaging/index.d.ts",
|
|
118
|
+
"default": "./dist/messaging/index.js"
|
|
119
|
+
},
|
|
116
120
|
"./api": {
|
|
117
121
|
"types": "./dist/api/types.d.ts",
|
|
118
122
|
"default": "./dist/api/types.js"
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
## Separate declaration from runtime
|
|
4
4
|
|
|
5
5
|
`src/bunderstack/backend.ts` synchronously constructs and exports `backend =
|
|
6
|
-
bunderstack({...})`.
|
|
7
|
-
`
|
|
8
|
-
|
|
6
|
+
bunderstack({...})`. The declaration is one object; `database`, `storage`,
|
|
7
|
+
`messaging`, and `realtime` also accept a function of the validated
|
|
8
|
+
environment, and `auth` accepts a builder over `{ db, env }`. It is pure:
|
|
9
|
+
`backend.inspect({ env })` resolves it and returns a manifest without
|
|
10
|
+
connecting to infrastructure, and the blueprint imports this declaration
|
|
11
|
+
without starting the application.
|
|
9
12
|
|
|
10
13
|
`src/bunderstack/index.ts` owns the production runtime: it imports `backend`,
|
|
11
14
|
calls `await backend.start()`, exports `app`, and calls `provision(app)` when the
|
|
@@ -122,7 +125,12 @@ const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
|
122
125
|
}
|
|
123
126
|
})
|
|
124
127
|
|
|
125
|
-
bunderstack({
|
|
128
|
+
bunderstack({
|
|
129
|
+
schema,
|
|
130
|
+
database,
|
|
131
|
+
middleware: [instrumentation],
|
|
132
|
+
api,
|
|
133
|
+
})
|
|
126
134
|
```
|
|
127
135
|
|
|
128
136
|
Three rules apply to a graph-wide middleware. It runs before authentication, so
|
|
@@ -186,9 +194,10 @@ Commit `.env.example` with names and safe placeholders only. Keep production
|
|
|
186
194
|
secrets, database URLs, storage credentials, and auth secrets in the runtime
|
|
187
195
|
environment.
|
|
188
196
|
|
|
189
|
-
Use local libSQL storage and
|
|
190
|
-
|
|
191
|
-
environment rather than
|
|
197
|
+
Use local libSQL storage for development, and let a messaging channel without
|
|
198
|
+
credentials capture instead of sending; declare production adapters and
|
|
199
|
+
credentials through the declaration and the runtime environment rather than
|
|
200
|
+
hard-coding them.
|
|
192
201
|
|
|
193
202
|
## Publish direct writes
|
|
194
203
|
|
|
@@ -8,6 +8,7 @@ description: Use when building, structuring, or migrating an application on Bund
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
10
|
Bunderstack is a batteries-included full-stack backend framework for Bun unifying:
|
|
11
|
+
|
|
11
12
|
- **Drizzle ORM** (libSQL / SQLite / Postgres)
|
|
12
13
|
- **Better Auth** (authentication & session management)
|
|
13
14
|
- **oRPC v2** (unified type-safe RPC & OpenAPI/REST procedures with Standard Schema / Valibot)
|
|
@@ -31,11 +32,13 @@ import { schema } from './schema'
|
|
|
31
32
|
import { access } from './access'
|
|
32
33
|
import { api } from './api'
|
|
33
34
|
|
|
34
|
-
// 1. Pure synchronous declaration (does NO I/O, no DB connection
|
|
35
|
+
// 1. Pure synchronous declaration (does NO I/O, no DB connection)
|
|
36
|
+
// One object. `database`, `storage`, `messaging`, and `realtime` also take
|
|
37
|
+
// a function of the validated env when they need a key or a URL.
|
|
35
38
|
export const backend = bunderstack({
|
|
36
39
|
schema,
|
|
37
40
|
access,
|
|
38
|
-
database: { adapter: libsql(), url
|
|
41
|
+
database: { adapter: libsql() }, // url defaults to DATABASE_URL
|
|
39
42
|
api,
|
|
40
43
|
})
|
|
41
44
|
|
|
@@ -44,7 +47,7 @@ export const app = await backend.start()
|
|
|
44
47
|
export type App = typeof app
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
- `backend.
|
|
50
|
+
- `backend.inspect({ env })` resolves the declaration and returns its manifest for tools (such as blueprint generators) without starting the app or connecting to a database.
|
|
48
51
|
- `app = await backend.start()` explicitly boots the runtime.
|
|
49
52
|
- `backend.test()` creates an isolated, lexically owned test fixture.
|
|
50
53
|
|
|
@@ -52,25 +55,25 @@ export type App = typeof app
|
|
|
52
55
|
|
|
53
56
|
All Bunderstack capabilities are imported directly from single-segment subpaths of `bunderstack`:
|
|
54
57
|
|
|
55
|
-
| Subpath Import
|
|
56
|
-
|
|
|
57
|
-
| `bunderstack`
|
|
58
|
-
| `bunderstack/libsql`
|
|
59
|
-
| `bunderstack/postgres-js`
|
|
60
|
-
| `bunderstack/bun-sql`
|
|
61
|
-
| `bunderstack/pglite`
|
|
62
|
-
| `bunderstack/client`
|
|
63
|
-
| `bunderstack/client-react` | React LiveView hook (`useLiveView`)
|
|
64
|
-
| `bunderstack/client-rest`
|
|
65
|
-
| `bunderstack/query`
|
|
66
|
-
| `bunderstack/query-react`
|
|
67
|
-
| `bunderstack/sync`
|
|
68
|
-
| `bunderstack/start`
|
|
69
|
-
| `bunderstack/start-auth`
|
|
70
|
-
| `bunderstack/provision`
|
|
71
|
-
| `bunderstack/testing`
|
|
72
|
-
| `bunderstack/schema`
|
|
73
|
-
| `bunderstack/typeid`
|
|
58
|
+
| Subpath Import | Purpose |
|
|
59
|
+
| -------------------------- | ------------------------------------------------------------------------------------- |
|
|
60
|
+
| `bunderstack` | Core backend builder (`bunderstack`, `defineApi`, `defineAccess`, `BunderstackError`) |
|
|
61
|
+
| `bunderstack/libsql` | libSQL / SQLite database adapter |
|
|
62
|
+
| `bunderstack/postgres-js` | postgres.js database adapter |
|
|
63
|
+
| `bunderstack/bun-sql` | `Bun.sql` Postgres adapter |
|
|
64
|
+
| `bunderstack/pglite` | PGlite in-memory / embedded Postgres adapter |
|
|
65
|
+
| `bunderstack/client` | Framework-neutral typed client & `createLiveView` |
|
|
66
|
+
| `bunderstack/client-react` | React LiveView hook (`useLiveView`) |
|
|
67
|
+
| `bunderstack/client-rest` | Type-safe REST client |
|
|
68
|
+
| `bunderstack/query` | TanStack Query integration (`createClient`, `syncRealtime`) |
|
|
69
|
+
| `bunderstack/query-react` | React-specific query helpers |
|
|
70
|
+
| `bunderstack/sync` | TanStack DB realtime sync collections |
|
|
71
|
+
| `bunderstack/start` | TanStack Start integration (`createApiHandlers`) |
|
|
72
|
+
| `bunderstack/start-auth` | Better Auth client for TanStack Start |
|
|
73
|
+
| `bunderstack/provision` | Database schema provisioning (`provision(app)`) |
|
|
74
|
+
| `bunderstack/testing` | Test fixture helpers |
|
|
75
|
+
| `bunderstack/schema` | Internal system tables (`export * from 'bunderstack/schema'`) |
|
|
76
|
+
| `bunderstack/typeid` | TypeID column types & generators |
|
|
74
77
|
|
|
75
78
|
---
|
|
76
79
|
|
|
@@ -79,6 +82,7 @@ All Bunderstack capabilities are imported directly from single-segment subpaths
|
|
|
79
82
|
### Scale Decision: Flat vs. Modular
|
|
80
83
|
|
|
81
84
|
1. **Flat Layout (MVP / Small Service: < 5 tables, < 5 procedures, 1 job):**
|
|
85
|
+
|
|
82
86
|
```
|
|
83
87
|
src/
|
|
84
88
|
├── bunderstack.ts # backend declaration & app start
|
|
@@ -159,14 +163,16 @@ export const protectedProcedure = o.protected.use(async ({ context, next }) => {
|
|
|
159
163
|
return next()
|
|
160
164
|
})
|
|
161
165
|
|
|
162
|
-
export const adminProcedure = protectedProcedure.use(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
export const adminProcedure = protectedProcedure.use(
|
|
167
|
+
async ({ context, next, errors }) => {
|
|
168
|
+
if (context.user.role !== 'admin') {
|
|
169
|
+
throw errors.FORBIDDEN({ message: 'Admin privileges required' })
|
|
170
|
+
}
|
|
171
|
+
return next()
|
|
172
|
+
},
|
|
173
|
+
)
|
|
168
174
|
|
|
169
|
-
// Graph-wide observability middleware (registered
|
|
175
|
+
// Graph-wide observability middleware (registered as `middleware: [instrumentation]`)
|
|
170
176
|
export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
171
177
|
const startedAt = performance.now()
|
|
172
178
|
try {
|
|
@@ -176,7 +182,9 @@ export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
|
176
182
|
const duration = Math.round(performance.now() - startedAt)
|
|
177
183
|
// context.peekSession() reads resolved session without triggering forced auth on public/webhooks
|
|
178
184
|
const userId = context.peekSession()?.user?.id
|
|
179
|
-
console.log(
|
|
185
|
+
console.log(
|
|
186
|
+
`[oRPC] ${path.join('.')} - ${duration}ms - User: ${userId ?? 'anon'}`,
|
|
187
|
+
)
|
|
180
188
|
}
|
|
181
189
|
})
|
|
182
190
|
```
|
|
@@ -184,6 +192,7 @@ export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
|
184
192
|
### Rule: Circular Boot-Time Import Prevention
|
|
185
193
|
|
|
186
194
|
`src/bunderstack/auth.ts` and `api/base.ts` must **NEVER** import `app` or `src/bunderstack/index.ts` at module top-level.
|
|
195
|
+
|
|
187
196
|
- In `auth.ts`: Read `process.env` directly for secret keys. If an async hook (like email sending) needs the initialized app, use dynamic `import('./index')` inside the callback.
|
|
188
197
|
- In `api/*.ts`: Consume `context.db`, `context.env`, `context.jobs`, `context.storage`, `context.auth` from handler parameters.
|
|
189
198
|
|
|
@@ -235,15 +244,16 @@ export const access = defineAccess(schema, {
|
|
|
235
244
|
await client.posts.update.call({ id: 'post_1', title: 'Updated Title' })
|
|
236
245
|
```
|
|
237
246
|
- **Custom List Procedures**: Use `listSpec` to give custom endpoints the same pagination and filtering behavior:
|
|
247
|
+
|
|
238
248
|
```ts
|
|
239
249
|
import { listSpec } from 'bunderstack'
|
|
240
|
-
|
|
250
|
+
|
|
241
251
|
const logSpec = listSpec(schema.auditLogs, {
|
|
242
252
|
filterable: ['level', 'userId'],
|
|
243
253
|
sortable: ['createdAt'],
|
|
244
254
|
defaultSort: { column: 'createdAt', order: 'desc' },
|
|
245
255
|
})
|
|
246
|
-
|
|
256
|
+
|
|
247
257
|
export const logsProcedure = adminProcedure
|
|
248
258
|
.input(logSpec.input)
|
|
249
259
|
.handler(logSpec.handler)
|
|
@@ -251,7 +261,7 @@ export const access = defineAccess(schema, {
|
|
|
251
261
|
|
|
252
262
|
### 3.2 Authentication (`authConfig`)
|
|
253
263
|
|
|
254
|
-
Export a clean Better Auth config and pass it
|
|
264
|
+
Export a clean Better Auth config and pass it as `auth: authConfig`; use the `({ db, env })` builder form when a hook needs the database or an env value:
|
|
255
265
|
|
|
256
266
|
```ts
|
|
257
267
|
// src/bunderstack/auth.ts
|
|
@@ -277,12 +287,15 @@ import * as v from 'valibot'
|
|
|
277
287
|
export const defineJobs = (jobs) =>
|
|
278
288
|
jobs.define({
|
|
279
289
|
sendWelcomeEmail: jobs.job({
|
|
280
|
-
input: v.object({
|
|
290
|
+
input: v.object({
|
|
291
|
+
userId: v.string(),
|
|
292
|
+
email: v.pipe(v.string(), v.email()),
|
|
293
|
+
}),
|
|
281
294
|
concurrency: 5,
|
|
282
295
|
timeout: 30_000,
|
|
283
296
|
retries: 3,
|
|
284
297
|
handler: async ({ userId, email }, ctx) => {
|
|
285
|
-
await ctx.email.send({
|
|
298
|
+
await ctx.messaging.email.send({
|
|
286
299
|
to: email,
|
|
287
300
|
subject: 'Welcome!',
|
|
288
301
|
html: '<h1>Welcome to our service</h1>',
|
|
@@ -360,6 +373,7 @@ Raise typed errors in procedures using `errors`:
|
|
|
360
373
|
```
|
|
361
374
|
|
|
362
375
|
Outside procedures (e.g. in background jobs or domain services):
|
|
376
|
+
|
|
363
377
|
```ts
|
|
364
378
|
import { BunderstackError } from 'bunderstack'
|
|
365
379
|
|
|
@@ -384,6 +398,7 @@ throw new BunderstackError('FORBIDDEN', 'Quota exceeded')
|
|
|
384
398
|
|
|
385
399
|
> [!CAUTION]
|
|
386
400
|
> **ALL MIGRATIONS MUST BE GENERATED EXCLUSIVELY VIA DRIZZLE-KIT CLI.**
|
|
401
|
+
>
|
|
387
402
|
> - Always run: `bunx drizzle-kit generate` (or `bun run db:generate`).
|
|
388
403
|
> - **NEVER** hand-edit generated migration SQL files.
|
|
389
404
|
> - **NEVER** let an LLM agent write or modify `.sql` files in `migrations/`.
|
|
@@ -410,7 +425,7 @@ export const Route = createFileRoute('/api/$')({
|
|
|
410
425
|
})
|
|
411
426
|
```
|
|
412
427
|
|
|
413
|
-
|
|
428
|
+
_Note: Remove any separate `/api/auth/$`, `/api/trpc/$`, or `/api/cron/_` route files.\*
|
|
414
429
|
|
|
415
430
|
### Dedicated Production Worker (`src/worker.ts`)
|
|
416
431
|
|
|
@@ -429,6 +444,7 @@ await app.runWorker()
|
|
|
429
444
|
```
|
|
430
445
|
|
|
431
446
|
Add worker script in `package.json`:
|
|
447
|
+
|
|
432
448
|
```json
|
|
433
449
|
{
|
|
434
450
|
"scripts": {
|
|
@@ -470,14 +486,17 @@ test('creates and retrieves a post', async () => {
|
|
|
470
486
|
// Typed in-process oRPC client
|
|
471
487
|
const client = t.client(identity)
|
|
472
488
|
|
|
473
|
-
const created = await client.posts.create({
|
|
489
|
+
const created = await client.posts.create({
|
|
490
|
+
title: 'New Post',
|
|
491
|
+
content: 'Hello',
|
|
492
|
+
})
|
|
474
493
|
expect(created.title).toBe('New Post')
|
|
475
494
|
|
|
476
495
|
// Run all queued background jobs deterministically
|
|
477
496
|
await t.jobs.runUntilIdle()
|
|
478
497
|
|
|
479
498
|
// Inspect sent emails
|
|
480
|
-
expect(t.email.sent).toHaveLength(0)
|
|
499
|
+
expect(t.messaging.email.sent).toHaveLength(0)
|
|
481
500
|
})
|
|
482
501
|
```
|
|
483
502
|
|
|
@@ -515,6 +534,7 @@ Bunderhost monitors application deployment status via `GET /api/readiness`, whic
|
|
|
515
534
|
### Official Bunderstack Documentation for LLMs
|
|
516
535
|
|
|
517
536
|
When working on Bunderstack projects, consult the dedicated LLM references:
|
|
537
|
+
|
|
518
538
|
- **Web Documentation**: [https://bunderstack.kcrz.dev/docs](https://bunderstack.kcrz.dev/docs)
|
|
519
539
|
- **Compact LLM Context (`llms.txt`)**: [https://bunderstack.kcrz.dev/docs/llms.txt](https://bunderstack.kcrz.dev/docs/llms.txt) (or local `node_modules/bunderstack/llms.txt`)
|
|
520
540
|
- **Complete LLM Knowledge Base (`llms-full.txt`)**: [https://bunderstack.kcrz.dev/docs/llms-full.txt](https://bunderstack.kcrz.dev/docs/llms-full.txt)
|
|
@@ -524,10 +544,12 @@ When working on Bunderstack projects, consult the dedicated LLM references:
|
|
|
524
544
|
Bunderhost provides a Model Context Protocol (MCP) server that allows coding agents to inspect, manage, and deploy projects.
|
|
525
545
|
|
|
526
546
|
#### Connecting to Bunderhost MCP:
|
|
547
|
+
|
|
527
548
|
1. Generate an Agent Access Token in Bunderhost: **Organization → Agent Access → Issue Token**.
|
|
528
549
|
2. Connect your MCP client to `https://<bunderhost-host>/mcp` using the token as a `Bearer` credential.
|
|
529
550
|
|
|
530
551
|
#### Key MCP Tools:
|
|
552
|
+
|
|
531
553
|
- `list_projects`: List all projects in the organization.
|
|
532
554
|
- `get_project`: Retrieve project configuration, active deployments, and blueprint status.
|
|
533
555
|
- `get_project_readiness`: Check database reachability, migration state, and queue backlog.
|
|
@@ -537,6 +559,7 @@ Bunderhost provides a Model Context Protocol (MCP) server that allows coding age
|
|
|
537
559
|
- `get_runtime_logs`: Stream runtime container logs.
|
|
538
560
|
|
|
539
561
|
#### Agent Safety Rules for Bunderhost:
|
|
562
|
+
|
|
540
563
|
1. **Secrets Are Never Exposed**: Database passwords, encryption keys, and environment values are never returned by MCP tools. When a new secret is needed, the agent must create a setup session (`create_setup_session`), and the user types the secret in the Bunderhost UI.
|
|
541
564
|
2. **Mutations Require Confirmation**: Creating projects or deploying revisions require explicit user approval in the MCP client before execution.
|
|
542
565
|
|
|
@@ -544,15 +567,14 @@ Bunderhost provides a Model Context Protocol (MCP) server that allows coding age
|
|
|
544
567
|
|
|
545
568
|
## 9. Quick Reference & Common Mistakes
|
|
546
569
|
|
|
547
|
-
| Anti-Pattern (Don't Do This)
|
|
548
|
-
|
|
|
549
|
-
| Creating separate `/api/auth/$` and `/api/trpc/$` routes
|
|
550
|
-
| Creating multiple Drizzle instances in `src/lib/db.ts`
|
|
551
|
-
| Constructing `ORPCError` manually
|
|
552
|
-
| Calling `getSession()` inside global middleware
|
|
553
|
-
| Editing `.sql` files in `migrations/` by hand
|
|
554
|
-
| Deploying to Bunderhost with schema push only
|
|
555
|
-
| Starting workers inside the web server process in prod
|
|
556
|
-
| Hand-written HTTP `/api/cron/*` endpoints
|
|
557
|
-
| Top-level import of `app` inside `auth.ts` or `api/base.ts` | Consume `context` in handlers or use dynamic `import('./index')` in callbacks
|
|
558
|
-
|
|
570
|
+
| Anti-Pattern (Don't Do This) | Canonical Pattern (Do This) |
|
|
571
|
+
| ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
572
|
+
| Creating separate `/api/auth/$` and `/api/trpc/$` routes | Single catch-all `src/routes/api/$.ts` with `createApiHandlers(app)` |
|
|
573
|
+
| Creating multiple Drizzle instances in `src/lib/db.ts` | Use `app.db` and `context.db`; export types with `BunderstackDb<typeof schema>` |
|
|
574
|
+
| Constructing `ORPCError` manually | Use `errors.CODE({ message })` or `new BunderstackError('CODE', message)` |
|
|
575
|
+
| Calling `getSession()` inside global middleware | Use `context.peekSession()` for non-blocking observability |
|
|
576
|
+
| Editing `.sql` files in `migrations/` by hand | Always generate with `bunx drizzle-kit generate` and commit untouched |
|
|
577
|
+
| Deploying to Bunderhost with schema push only | Generate and commit Drizzle migrations before deploying |
|
|
578
|
+
| Starting workers inside the web server process in prod | Run dedicated `src/worker.ts` with `app.runWorker()` |
|
|
579
|
+
| Hand-written HTTP `/api/cron/*` endpoints | Use `jobs.cron({ schedule, handler })` |
|
|
580
|
+
| Top-level import of `app` inside `auth.ts` or `api/base.ts` | Consume `context` in handlers or use dynamic `import('./index')` in callbacks |
|
|
@@ -12,12 +12,12 @@ output or a file reference, not an assertion.
|
|
|
12
12
|
| API mounting | Hand-written handler maps; separate `/api/auth/$`, `/api/trpc/$` | `createApiHandlers(app)` on one `/api/$` | Auth and oRPC requests succeed with only the catch-all present | Shadowing route files deleted |
|
|
13
13
|
| Custom API routes | Route files doing CRUD the framework can generate | Generated CRUD plus `defineAccess`, or an `o.protected` procedure | Access rules cover each exposed table; a cross-owner request is denied | Route file has no client callers |
|
|
14
14
|
| Access control | Per-endpoint session checks and hand-written SQL filters | `defineAccess(schema, rules)` with `scope.read` / `scope.write` | A test asserts a second user cannot read or write the first user's rows | Manual filter helpers unused |
|
|
15
|
-
| Jobs | BullMQ or a bespoke queue module | `jobs.define({ ... })` and `app.jobs.enqueue(...)` | Job appears in `backend.
|
|
15
|
+
| Jobs | BullMQ or a bespoke queue module | `jobs.define({ ... })` and `app.jobs.enqueue(...)` | Job appears in `backend.inspect().background.jobs` | No queue library importer; package uninstalled |
|
|
16
16
|
| Cron | `/api/cron/*` guarded by a shared secret | `jobs.cron({ schedule, handler })` | Cron task appears in the blueprint | Cron route file and its secret removed from env |
|
|
17
17
|
| Worker topology | `startWorker()` or a queue bootstrap in the web entry | `src/worker.ts` calling `app.runWorker()`, run as its own process | Web entry starts no worker; the worker command exists in deployment config | Worker process is deployed before the embedded call is removed |
|
|
18
18
|
| Realtime | Custom WebSocket server, manual pub/sub, channel-and-payload publishing | `realtime` config plus `ctx.realtime.publish(table, event, row)` after commit | A direct write reaches a subscriber with the complete row | Custom transport deleted; shared Redis configured for multi-process |
|
|
19
19
|
| Storage | AWS or Tigris SDK wrapper, custom multipart upload route | Declared buckets and `app.storage` | Upload, signed URL, and delete work through the facade | Wrapper deleted and SDK uninstalled |
|
|
20
|
-
|
|
|
20
|
+
| Messaging | Resend, SMTP, or Telegram SDK wrapper | `messaging` channels and `app.messaging.<channel>.send(...)` | A send succeeds through the configured provider | Wrapper deleted and SDK uninstalled |
|
|
21
21
|
| Env | `createEnv()` beside the app, `dotenv`, unchecked `process.env` reads | `env` schema in `bunderstack()`; source in `backend.start()`; `app.env` / `ctx.env` | Boot fails with a clear message when a required variable is missing | Legacy env module unused; `.env.example` lists names only |
|
|
22
22
|
| API declaration | Router factories taking a bag of procedures; hand-written builder generics | `defineApi({ schema, env })` bases in one module, plain router objects, `api` object | A router module imports its base and exports an object; no factory remains | `BunderstackApiBuilder<...>` and `os.$context<...>()` deleted |
|
|
23
23
|
| Observability | Tracing or logging attached to an application procedure base | `middleware: [...]` in the config, which also reaches the generated CRUD | A generated CRUD request produces a span or log line | Per-base instrumentation removed |
|
|
@@ -23,12 +23,14 @@ export const backend = bunderstack({
|
|
|
23
23
|
schema,
|
|
24
24
|
access,
|
|
25
25
|
env: envSchema,
|
|
26
|
-
database: {
|
|
27
|
-
adapter: libsql(),
|
|
28
|
-
url: 'file:./data.db',
|
|
29
|
-
},
|
|
26
|
+
database: { adapter: libsql() },
|
|
30
27
|
auth: authConfig,
|
|
31
|
-
|
|
28
|
+
messaging: (env) => ({
|
|
29
|
+
email: resend({
|
|
30
|
+
apiKey: env.RESEND_API_KEY,
|
|
31
|
+
from: 'App <no-reply@example.com>',
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
32
34
|
storage: {
|
|
33
35
|
local: './uploads',
|
|
34
36
|
defaultBucket: 'files',
|
|
@@ -69,7 +71,7 @@ await provision(app)
|
|
|
69
71
|
|
|
70
72
|
The database adapter is imported explicitly; there is no implicit driver. Keep
|
|
71
73
|
unrelated external side effects out of the backend import graph. The blueprint
|
|
72
|
-
imports the declaration and
|
|
74
|
+
imports the declaration and calls `backend.inspect({ env })`; it never starts the app,
|
|
73
75
|
connects to a queue, or needs a special environment flag.
|
|
74
76
|
|
|
75
77
|
Aggregate every domain, Better Auth, plugin, and internal table in the schema
|
|
@@ -230,19 +232,21 @@ access rules. Delete the AWS or Tigris wrapper and uninstall the SDK. A custom
|
|
|
230
232
|
multipart upload route is replaced by the bucket's own upload route unless it
|
|
231
233
|
performs domain work that cannot move into a job.
|
|
232
234
|
|
|
233
|
-
##
|
|
235
|
+
## Messaging
|
|
234
236
|
|
|
235
237
|
```ts
|
|
236
|
-
await app.email.send({ to, subject, html })
|
|
238
|
+
await app.messaging.email.send({ to, subject, html })
|
|
237
239
|
```
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
Declare named channels: `messaging: (env) => ({ email: resend({ apiKey: env.RESEND_API_KEY, from }) })`. A
|
|
242
|
+
channel whose credentials are absent or empty captures to the message journal
|
|
243
|
+
instead of sending, and prints to the console locally. Telegram is a provider
|
|
244
|
+
too, with its own message type. The facade uses Web Standard `fetch`, so the
|
|
245
|
+
`resend` package is uninstalled.
|
|
242
246
|
|
|
243
247
|
## Env
|
|
244
248
|
|
|
245
|
-
Pass `envSchema`
|
|
249
|
+
Pass `envSchema` as the `env` key of the declaration and read `app.env`
|
|
246
250
|
or `ctx.env`. Remove `@t3-oss/env-core` `createEnv()` calls and `dotenv`; Bun
|
|
247
251
|
loads `.env` itself. Server variables must not use the `PUBLIC_` prefix, and
|
|
248
252
|
browser-safe variables must. Declared env appears in the deployment blueprint,
|