bunderstack 0.23.4 → 0.24.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/CHANGELOG.md +67 -0
- package/README.md +7 -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/auth.d.ts +20 -9
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +3 -8
- package/dist/auth.js.map +1 -1
- 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 +23 -25
- 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/provision-internals.d.ts +1 -1
- package/dist/provision-internals.js +1 -1
- package/dist/provision-internals.js.map +1 -1
- package/dist/provision-runtime.d.ts +7 -0
- package/dist/provision-runtime.d.ts.map +1 -0
- package/dist/provision-runtime.js +50 -0
- package/dist/provision-runtime.js.map +1 -0
- package/dist/provision-schema.d.ts +16 -0
- package/dist/provision-schema.d.ts.map +1 -0
- package/dist/provision-schema.js +63 -0
- package/dist/provision-schema.js.map +1 -0
- package/dist/provision.d.ts +4 -21
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +10 -111
- package/dist/provision.js.map +1 -1
- package/dist/runtime.d.ts +17 -14
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +20 -33
- 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 +3 -3
- package/dist/testing/fixture.d.ts.map +1 -1
- package/dist/testing/fixture.js +12 -10
- 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/llms-full.txt +416 -226
- package/llms.txt +4 -3
- package/package.json +10 -2
- package/skills/creating-bunderstack-apps/references/application-structure.md +16 -7
- package/skills/creating-bunderstack-apps/references/verification.md +7 -6
- package/skills/migrating-to-bunderstack/SKILL.md +75 -51
- package/skills/migrating-to-bunderstack/references/audit-checklist.md +2 -2
- package/skills/migrating-to-bunderstack/references/runtime-replacements.md +20 -15
package/llms.txt
CHANGED
|
@@ -41,9 +41,10 @@ MINIMAL APP
|
|
|
41
41
|
|
|
42
42
|
The blueprint imports the backend declaration and never starts the runtime.
|
|
43
43
|
Database adapters are imported from their own entry points: libsql(),
|
|
44
|
-
pglite(), bunSql(), postgresJs().
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
pglite(), bunSql(), postgresJs(). Production provisioning imports
|
|
45
|
+
`provision(app)` from `bunderstack/provision` and requires committed migrations;
|
|
46
|
+
that entrypoint never imports Drizzle Kit. Development schema push imports the
|
|
47
|
+
same function name from `bunderstack/provision-schema` instead.
|
|
47
48
|
|
|
48
49
|
DECLARING AN API
|
|
49
50
|
|
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.1",
|
|
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",
|
|
@@ -77,6 +77,10 @@
|
|
|
77
77
|
"types": "./dist/provision.d.ts",
|
|
78
78
|
"default": "./dist/provision.js"
|
|
79
79
|
},
|
|
80
|
+
"./provision-schema": {
|
|
81
|
+
"types": "./dist/provision-schema.d.ts",
|
|
82
|
+
"default": "./dist/provision-schema.js"
|
|
83
|
+
},
|
|
80
84
|
"./testing": {
|
|
81
85
|
"types": "./dist/testing.d.ts",
|
|
82
86
|
"default": "./dist/testing.js"
|
|
@@ -113,6 +117,10 @@
|
|
|
113
117
|
"types": "./dist/email/smtp.d.ts",
|
|
114
118
|
"default": "./dist/email/smtp.js"
|
|
115
119
|
},
|
|
120
|
+
"./messaging": {
|
|
121
|
+
"types": "./dist/messaging/index.d.ts",
|
|
122
|
+
"default": "./dist/messaging/index.js"
|
|
123
|
+
},
|
|
116
124
|
"./api": {
|
|
117
125
|
"types": "./dist/api/types.d.ts",
|
|
118
126
|
"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
|
|
|
@@ -17,9 +17,10 @@ the configured Bunderstack entry. Set `package.json#bunderstack.entry` when the
|
|
|
17
17
|
entry is not `src/bunderstack.ts`. `bun run blueprint:check` must pass in CI so
|
|
18
18
|
the committed declaration matches the application.
|
|
19
19
|
|
|
20
|
-
Before production, generate and commit the Drizzle `migrations/` folder.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
Before production, generate and commit the Drizzle `migrations/` folder.
|
|
21
|
+
`provision(app)` from `bunderstack/provision` only applies committed
|
|
22
|
+
migrations and never imports drizzle-kit. For the local schema-push loop, import
|
|
23
|
+
`provision` from `bunderstack/provision-schema`; that development-only
|
|
24
|
+
entrypoint requires drizzle-kit. Keep the generated migrations, blueprint,
|
|
25
|
+
tests, worker entry, API mount, and deployment scripts under version control;
|
|
26
|
+
never commit secrets, databases, uploads, or build output.
|
|
@@ -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,26 @@ 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`
|
|
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/
|
|
72
|
-
| `bunderstack/
|
|
73
|
-
| `bunderstack/
|
|
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` | Production provisioning from committed migrations |
|
|
74
|
+
| `bunderstack/provision-schema` | Development-only schema push through Drizzle Kit |
|
|
75
|
+
| `bunderstack/testing` | Test fixture helpers |
|
|
76
|
+
| `bunderstack/schema` | Internal system tables (`export * from 'bunderstack/schema'`) |
|
|
77
|
+
| `bunderstack/typeid` | TypeID column types & generators |
|
|
74
78
|
|
|
75
79
|
---
|
|
76
80
|
|
|
@@ -79,6 +83,7 @@ All Bunderstack capabilities are imported directly from single-segment subpaths
|
|
|
79
83
|
### Scale Decision: Flat vs. Modular
|
|
80
84
|
|
|
81
85
|
1. **Flat Layout (MVP / Small Service: < 5 tables, < 5 procedures, 1 job):**
|
|
86
|
+
|
|
82
87
|
```
|
|
83
88
|
src/
|
|
84
89
|
├── bunderstack.ts # backend declaration & app start
|
|
@@ -159,14 +164,16 @@ export const protectedProcedure = o.protected.use(async ({ context, next }) => {
|
|
|
159
164
|
return next()
|
|
160
165
|
})
|
|
161
166
|
|
|
162
|
-
export const adminProcedure = protectedProcedure.use(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
export const adminProcedure = protectedProcedure.use(
|
|
168
|
+
async ({ context, next, errors }) => {
|
|
169
|
+
if (context.user.role !== 'admin') {
|
|
170
|
+
throw errors.FORBIDDEN({ message: 'Admin privileges required' })
|
|
171
|
+
}
|
|
172
|
+
return next()
|
|
173
|
+
},
|
|
174
|
+
)
|
|
168
175
|
|
|
169
|
-
// Graph-wide observability middleware (registered
|
|
176
|
+
// Graph-wide observability middleware (registered as `middleware: [instrumentation]`)
|
|
170
177
|
export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
171
178
|
const startedAt = performance.now()
|
|
172
179
|
try {
|
|
@@ -176,7 +183,9 @@ export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
|
176
183
|
const duration = Math.round(performance.now() - startedAt)
|
|
177
184
|
// context.peekSession() reads resolved session without triggering forced auth on public/webhooks
|
|
178
185
|
const userId = context.peekSession()?.user?.id
|
|
179
|
-
console.log(
|
|
186
|
+
console.log(
|
|
187
|
+
`[oRPC] ${path.join('.')} - ${duration}ms - User: ${userId ?? 'anon'}`,
|
|
188
|
+
)
|
|
180
189
|
}
|
|
181
190
|
})
|
|
182
191
|
```
|
|
@@ -184,6 +193,7 @@ export const instrumentation = o.middleware(async ({ context, next, path }) => {
|
|
|
184
193
|
### Rule: Circular Boot-Time Import Prevention
|
|
185
194
|
|
|
186
195
|
`src/bunderstack/auth.ts` and `api/base.ts` must **NEVER** import `app` or `src/bunderstack/index.ts` at module top-level.
|
|
196
|
+
|
|
187
197
|
- 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
198
|
- In `api/*.ts`: Consume `context.db`, `context.env`, `context.jobs`, `context.storage`, `context.auth` from handler parameters.
|
|
189
199
|
|
|
@@ -235,15 +245,16 @@ export const access = defineAccess(schema, {
|
|
|
235
245
|
await client.posts.update.call({ id: 'post_1', title: 'Updated Title' })
|
|
236
246
|
```
|
|
237
247
|
- **Custom List Procedures**: Use `listSpec` to give custom endpoints the same pagination and filtering behavior:
|
|
248
|
+
|
|
238
249
|
```ts
|
|
239
250
|
import { listSpec } from 'bunderstack'
|
|
240
|
-
|
|
251
|
+
|
|
241
252
|
const logSpec = listSpec(schema.auditLogs, {
|
|
242
253
|
filterable: ['level', 'userId'],
|
|
243
254
|
sortable: ['createdAt'],
|
|
244
255
|
defaultSort: { column: 'createdAt', order: 'desc' },
|
|
245
256
|
})
|
|
246
|
-
|
|
257
|
+
|
|
247
258
|
export const logsProcedure = adminProcedure
|
|
248
259
|
.input(logSpec.input)
|
|
249
260
|
.handler(logSpec.handler)
|
|
@@ -251,7 +262,7 @@ export const access = defineAccess(schema, {
|
|
|
251
262
|
|
|
252
263
|
### 3.2 Authentication (`authConfig`)
|
|
253
264
|
|
|
254
|
-
Export a clean Better Auth config and pass it
|
|
265
|
+
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
266
|
|
|
256
267
|
```ts
|
|
257
268
|
// src/bunderstack/auth.ts
|
|
@@ -277,12 +288,15 @@ import * as v from 'valibot'
|
|
|
277
288
|
export const defineJobs = (jobs) =>
|
|
278
289
|
jobs.define({
|
|
279
290
|
sendWelcomeEmail: jobs.job({
|
|
280
|
-
input: v.object({
|
|
291
|
+
input: v.object({
|
|
292
|
+
userId: v.string(),
|
|
293
|
+
email: v.pipe(v.string(), v.email()),
|
|
294
|
+
}),
|
|
281
295
|
concurrency: 5,
|
|
282
296
|
timeout: 30_000,
|
|
283
297
|
retries: 3,
|
|
284
298
|
handler: async ({ userId, email }, ctx) => {
|
|
285
|
-
await ctx.email.send({
|
|
299
|
+
await ctx.messaging.email.send({
|
|
286
300
|
to: email,
|
|
287
301
|
subject: 'Welcome!',
|
|
288
302
|
html: '<h1>Welcome to our service</h1>',
|
|
@@ -360,6 +374,7 @@ Raise typed errors in procedures using `errors`:
|
|
|
360
374
|
```
|
|
361
375
|
|
|
362
376
|
Outside procedures (e.g. in background jobs or domain services):
|
|
377
|
+
|
|
363
378
|
```ts
|
|
364
379
|
import { BunderstackError } from 'bunderstack'
|
|
365
380
|
|
|
@@ -373,17 +388,19 @@ throw new BunderstackError('FORBIDDEN', 'Quota exceeded')
|
|
|
373
388
|
### Development vs. Production Lifecycle
|
|
374
389
|
|
|
375
390
|
1. **Local Development (No Migrations Folder):**
|
|
376
|
-
-
|
|
391
|
+
- Import `provision` from `bunderstack/provision-schema`; it pushes the schema to the SQLite/libSQL/Postgres database.
|
|
377
392
|
- Developers can rapidly prototype and iterate on table schemas without generating migrations on every change.
|
|
378
393
|
|
|
379
394
|
2. **Production & Bunderhost Deployments (MANDATORY Migrations):**
|
|
380
395
|
- **Committed migrations are strictly mandatory for production deployments.**
|
|
396
|
+
- Import `provision` from `bunderstack/provision`; it contains no Drizzle Kit import edge and fails clearly when the migration journal is missing.
|
|
381
397
|
- Bunderhost will **NOT** run schema push in production; deployment will fail if committed migrations in `migrations/` are missing or out of date.
|
|
382
398
|
|
|
383
399
|
### CRITICAL MIGRATION RULES
|
|
384
400
|
|
|
385
401
|
> [!CAUTION]
|
|
386
402
|
> **ALL MIGRATIONS MUST BE GENERATED EXCLUSIVELY VIA DRIZZLE-KIT CLI.**
|
|
403
|
+
>
|
|
387
404
|
> - Always run: `bunx drizzle-kit generate` (or `bun run db:generate`).
|
|
388
405
|
> - **NEVER** hand-edit generated migration SQL files.
|
|
389
406
|
> - **NEVER** let an LLM agent write or modify `.sql` files in `migrations/`.
|
|
@@ -410,7 +427,7 @@ export const Route = createFileRoute('/api/$')({
|
|
|
410
427
|
})
|
|
411
428
|
```
|
|
412
429
|
|
|
413
|
-
|
|
430
|
+
_Note: Remove any separate `/api/auth/$`, `/api/trpc/$`, or `/api/cron/_` route files.\*
|
|
414
431
|
|
|
415
432
|
### Dedicated Production Worker (`src/worker.ts`)
|
|
416
433
|
|
|
@@ -429,6 +446,7 @@ await app.runWorker()
|
|
|
429
446
|
```
|
|
430
447
|
|
|
431
448
|
Add worker script in `package.json`:
|
|
449
|
+
|
|
432
450
|
```json
|
|
433
451
|
{
|
|
434
452
|
"scripts": {
|
|
@@ -470,14 +488,17 @@ test('creates and retrieves a post', async () => {
|
|
|
470
488
|
// Typed in-process oRPC client
|
|
471
489
|
const client = t.client(identity)
|
|
472
490
|
|
|
473
|
-
const created = await client.posts.create({
|
|
491
|
+
const created = await client.posts.create({
|
|
492
|
+
title: 'New Post',
|
|
493
|
+
content: 'Hello',
|
|
494
|
+
})
|
|
474
495
|
expect(created.title).toBe('New Post')
|
|
475
496
|
|
|
476
497
|
// Run all queued background jobs deterministically
|
|
477
498
|
await t.jobs.runUntilIdle()
|
|
478
499
|
|
|
479
500
|
// Inspect sent emails
|
|
480
|
-
expect(t.email.sent).toHaveLength(0)
|
|
501
|
+
expect(t.messaging.email.sent).toHaveLength(0)
|
|
481
502
|
})
|
|
482
503
|
```
|
|
483
504
|
|
|
@@ -515,6 +536,7 @@ Bunderhost monitors application deployment status via `GET /api/readiness`, whic
|
|
|
515
536
|
### Official Bunderstack Documentation for LLMs
|
|
516
537
|
|
|
517
538
|
When working on Bunderstack projects, consult the dedicated LLM references:
|
|
539
|
+
|
|
518
540
|
- **Web Documentation**: [https://bunderstack.kcrz.dev/docs](https://bunderstack.kcrz.dev/docs)
|
|
519
541
|
- **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
542
|
- **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 +546,12 @@ When working on Bunderstack projects, consult the dedicated LLM references:
|
|
|
524
546
|
Bunderhost provides a Model Context Protocol (MCP) server that allows coding agents to inspect, manage, and deploy projects.
|
|
525
547
|
|
|
526
548
|
#### Connecting to Bunderhost MCP:
|
|
549
|
+
|
|
527
550
|
1. Generate an Agent Access Token in Bunderhost: **Organization → Agent Access → Issue Token**.
|
|
528
551
|
2. Connect your MCP client to `https://<bunderhost-host>/mcp` using the token as a `Bearer` credential.
|
|
529
552
|
|
|
530
553
|
#### Key MCP Tools:
|
|
554
|
+
|
|
531
555
|
- `list_projects`: List all projects in the organization.
|
|
532
556
|
- `get_project`: Retrieve project configuration, active deployments, and blueprint status.
|
|
533
557
|
- `get_project_readiness`: Check database reachability, migration state, and queue backlog.
|
|
@@ -537,6 +561,7 @@ Bunderhost provides a Model Context Protocol (MCP) server that allows coding age
|
|
|
537
561
|
- `get_runtime_logs`: Stream runtime container logs.
|
|
538
562
|
|
|
539
563
|
#### Agent Safety Rules for Bunderhost:
|
|
564
|
+
|
|
540
565
|
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
566
|
2. **Mutations Require Confirmation**: Creating projects or deploying revisions require explicit user approval in the MCP client before execution.
|
|
542
567
|
|
|
@@ -544,15 +569,14 @@ Bunderhost provides a Model Context Protocol (MCP) server that allows coding age
|
|
|
544
569
|
|
|
545
570
|
## 9. Quick Reference & Common Mistakes
|
|
546
571
|
|
|
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
|
-
|
|
572
|
+
| Anti-Pattern (Don't Do This) | Canonical Pattern (Do This) |
|
|
573
|
+
| ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
574
|
+
| Creating separate `/api/auth/$` and `/api/trpc/$` routes | Single catch-all `src/routes/api/$.ts` with `createApiHandlers(app)` |
|
|
575
|
+
| Creating multiple Drizzle instances in `src/lib/db.ts` | Use `app.db` and `context.db`; export types with `BunderstackDb<typeof schema>` |
|
|
576
|
+
| Constructing `ORPCError` manually | Use `errors.CODE({ message })` or `new BunderstackError('CODE', message)` |
|
|
577
|
+
| Calling `getSession()` inside global middleware | Use `context.peekSession()` for non-blocking observability |
|
|
578
|
+
| Editing `.sql` files in `migrations/` by hand | Always generate with `bunx drizzle-kit generate` and commit untouched |
|
|
579
|
+
| Deploying to Bunderhost with schema push only | Generate and commit Drizzle migrations before deploying |
|
|
580
|
+
| Starting workers inside the web server process in prod | Run dedicated `src/worker.ts` with `app.runWorker()` |
|
|
581
|
+
| Hand-written HTTP `/api/cron/*` endpoints | Use `jobs.cron({ schedule, handler })` |
|
|
582
|
+
| 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,
|
|
@@ -251,9 +255,10 @@ with names and safe placeholders only.
|
|
|
251
255
|
|
|
252
256
|
## Provisioning, migrations, and blueprint
|
|
253
257
|
|
|
254
|
-
`provision(app)`
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
`provision(app)` from `bunderstack/provision` applies committed migrations
|
|
259
|
+
without importing Drizzle Kit and fails when the journal is absent. During
|
|
260
|
+
local prototyping, import it from `bunderstack/provision-schema` to use the
|
|
261
|
+
development schema-push loop. Generate and commit migrations before production:
|
|
257
262
|
|
|
258
263
|
```json
|
|
259
264
|
{
|