bunderstack 0.17.0-beta.8 → 0.17.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.
@@ -0,0 +1,16 @@
1
+ /** The skills this package ships, copied from `.agents/skills` at build time. */
2
+ export declare const SHIPPED_SKILLS: readonly ["creating-bunderstack-apps", "migrating-to-bunderstack"];
3
+ export type SkillsOptions = {
4
+ /** Project root the skills are installed into. */
5
+ cwd: string;
6
+ /** Destination, relative to `cwd`. */
7
+ directory?: string;
8
+ /** Report drift instead of writing. Exits non-zero when anything differs. */
9
+ check?: boolean;
10
+ };
11
+ export type SkillsIo = {
12
+ stdout(message: string): void;
13
+ stderr(message: string): void;
14
+ };
15
+ export declare function installSkills(options: SkillsOptions, io: SkillsIo): Promise<number>;
16
+ //# sourceMappingURL=cli-skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-skills.d.ts","sourceRoot":"","sources":["../src/cli-skills.ts"],"names":[],"mappings":"AAIA,iFAAiF;AACjF,eAAO,MAAM,cAAc,oEAGjB,CAAA;AAwBV,MAAM,MAAM,aAAa,GAAG;IAC1B,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAA;IACX,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,CAAA;AAuDD,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,QAAQ,GACX,OAAO,CAAC,MAAM,CAAC,CAiDjB"}
@@ -0,0 +1,116 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { cp, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ /** The skills this package ships, copied from `.agents/skills` at build time. */
5
+ export const SHIPPED_SKILLS = [
6
+ 'creating-bunderstack-apps',
7
+ 'migrating-to-bunderstack',
8
+ ];
9
+ const MARKER_START = '<!-- bunderstack:skills -->';
10
+ const MARKER_END = '<!-- /bunderstack:skills -->';
11
+ /**
12
+ * Agents read AGENTS.md before they search for anything, so the pointer is
13
+ * what actually gets the skills into context. The markers let a later
14
+ * `bunderstack skills` run replace the block without touching the rest.
15
+ */
16
+ function agentsBlock(directory) {
17
+ return `${MARKER_START}
18
+ ## Bunderstack
19
+
20
+ This project uses Bunderstack. Before changing the server API, access rules,
21
+ jobs, storage, or realtime, read \`${directory}/creating-bunderstack-apps/SKILL.md\`.
22
+ When replacing existing infrastructure, read
23
+ \`${directory}/migrating-to-bunderstack/SKILL.md\` instead.
24
+
25
+ \`node_modules/bunderstack/llms.txt\` is a dense plain-text reference for the
26
+ whole framework.
27
+ ${MARKER_END}`;
28
+ }
29
+ /** Where this build keeps its copy of the skills. */
30
+ function packagedSkillsDir() {
31
+ return join(new URL('..', import.meta.url).pathname, 'skills');
32
+ }
33
+ async function filesUnder(dir) {
34
+ const entries = await readdir(dir, { withFileTypes: true, recursive: true });
35
+ return entries
36
+ .filter((entry) => entry.isFile())
37
+ .map((entry) => join(entry.parentPath, entry.name));
38
+ }
39
+ async function differs(from, to) {
40
+ if (!existsSync(to))
41
+ return true;
42
+ const sources = await filesUnder(from);
43
+ for (const source of sources) {
44
+ const target = join(to, source.slice(from.length + 1));
45
+ if (!existsSync(target))
46
+ return true;
47
+ if ((await readFile(source, 'utf8')) !== (await readFile(target, 'utf8'))) {
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ }
53
+ async function writeAgentsPointer(cwd, directory) {
54
+ const path = join(cwd, 'AGENTS.md');
55
+ const block = agentsBlock(directory);
56
+ if (!existsSync(path)) {
57
+ await writeFile(path, `# Agent guide\n\n${block}\n`);
58
+ return 'created';
59
+ }
60
+ const current = await readFile(path, 'utf8');
61
+ const start = current.indexOf(MARKER_START);
62
+ const end = current.indexOf(MARKER_END);
63
+ if (start !== -1 && end !== -1) {
64
+ const replaced = current.slice(0, start) + block + current.slice(end + MARKER_END.length);
65
+ if (replaced === current)
66
+ return 'current';
67
+ await writeFile(path, replaced);
68
+ return 'updated';
69
+ }
70
+ await writeFile(path, `${current.trimEnd()}\n\n${block}\n`);
71
+ return 'updated';
72
+ }
73
+ export async function installSkills(options, io) {
74
+ const source = packagedSkillsDir();
75
+ if (!existsSync(source)) {
76
+ io.stderr('[bunderstack] this build ships no skills; reinstall the package or run bun run build');
77
+ return 1;
78
+ }
79
+ const directory = options.directory ?? '.agents/skills';
80
+ const destination = join(options.cwd, directory);
81
+ const stale = [];
82
+ for (const skill of SHIPPED_SKILLS) {
83
+ const from = join(source, skill);
84
+ const to = join(destination, skill);
85
+ if (!(await differs(from, to)))
86
+ continue;
87
+ stale.push(skill);
88
+ if (options.check)
89
+ continue;
90
+ await mkdir(destination, { recursive: true });
91
+ await cp(from, to, { recursive: true });
92
+ }
93
+ if (options.check) {
94
+ const pointer = await readFile(join(options.cwd, 'AGENTS.md'), 'utf8').catch(() => '');
95
+ const pointerStale = !pointer.includes(MARKER_START);
96
+ if (stale.length === 0 && !pointerStale) {
97
+ io.stdout(`${directory} is current`);
98
+ return 0;
99
+ }
100
+ for (const skill of stale) {
101
+ io.stderr(`[bunderstack] ${directory}/${skill} is missing or outdated`);
102
+ }
103
+ if (pointerStale)
104
+ io.stderr('[bunderstack] AGENTS.md has no Bunderstack block');
105
+ io.stderr('[bunderstack] run: bunx bunderstack skills');
106
+ return 1;
107
+ }
108
+ const pointer = await writeAgentsPointer(options.cwd, directory);
109
+ io.stdout(stale.length === 0
110
+ ? `${directory} is current`
111
+ : `Installed ${stale.length} skill(s) into ${directory}: ${stale.join(', ')}`);
112
+ if (pointer !== 'current')
113
+ io.stdout(`AGENTS.md ${pointer}`);
114
+ return 0;
115
+ }
116
+ //# sourceMappingURL=cli-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-skills.js","sourceRoot":"","sources":["../src/cli-skills.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAEL,iBAAiB,EAElB,MAAM,uBAAuB,CAAA;AAE9B,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,CAAA;AAOD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,KAAK,EACT,QAAQ,GAAE,OAAO,iBAAqC,GACrD,OAAO,CAAC,MAAM,CAAC,CA4DjB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAEL,iBAAiB,EAElB,MAAM,uBAAuB,CAAA;AAG9B,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,CAAA;AAcD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,KAAK,EACT,QAAQ,GAAE,OAAO,iBAAqC,GACrD,OAAO,CAAC,MAAM,CAAC,CAmFjB"}
package/dist/cli.js CHANGED
@@ -1,9 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
  import { BlueprintCheckError, generateBlueprint, } from './blueprint-generator.js';
3
- const help = `Usage: bunderstack blueprint [directory] [--entry <path>] [--output <path>] [--check]
3
+ import { installSkills } from './cli-skills.js';
4
+ const help = `Usage:
5
+ bunderstack blueprint [directory] [--entry <path>] [--output <path>] [--check]
6
+ bunderstack skills [--dir <path>] [--check]
4
7
 
5
- Generate a committed deployment declaration for a TanStack Start application.
6
- Entry precedence: --entry, package.json#bunderstack.entry, src/bunderstack.ts.`;
8
+ blueprint Generate a committed deployment declaration for a TanStack Start
9
+ application. Entry precedence: --entry,
10
+ package.json#bunderstack.entry, src/bunderstack.ts.
11
+
12
+ skills Install the Bunderstack agent skills that match this version into
13
+ .agents/skills, and point AGENTS.md at them so an agent loads them
14
+ before touching the API. --check reports drift without writing.`;
7
15
  export async function runCli(args, io, generate = generateBlueprint) {
8
16
  if (args[0] === '--help' || args[0] === '-h') {
9
17
  io.stdout(help);
@@ -13,8 +21,32 @@ export async function runCli(args, io, generate = generateBlueprint) {
13
21
  io.stdout((await Bun.file(new URL('../package.json', import.meta.url)).json()).version);
14
22
  return 0;
15
23
  }
24
+ if (args[0] === 'skills') {
25
+ const options = {
26
+ cwd: process.cwd(),
27
+ };
28
+ for (let index = 1; index < args.length; index++) {
29
+ const argument = args[index];
30
+ if (argument === '--check') {
31
+ options.check = true;
32
+ continue;
33
+ }
34
+ if (argument === '--dir') {
35
+ const value = args[++index];
36
+ if (!value || value.startsWith('--')) {
37
+ io.stderr('[bunderstack] missing value for --dir');
38
+ return 2;
39
+ }
40
+ options.directory = value;
41
+ continue;
42
+ }
43
+ io.stderr(`[bunderstack] unknown option: ${argument}`);
44
+ return 2;
45
+ }
46
+ return installSkills(options, io);
47
+ }
16
48
  if (args[0] !== 'blueprint') {
17
- io.stderr('Usage: bunderstack blueprint [directory] [--entry <path>] [--output <path>] [--check]');
49
+ io.stderr(help);
18
50
  return 2;
19
51
  }
20
52
  const options = { directory: process.cwd() };
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/llms.txt ADDED
@@ -0,0 +1,360 @@
1
+ BUNDERSTACK
2
+
3
+ A batteries-included backend framework for Bun. Version 0.17 (beta).
4
+ Docs: https://bunderstack.dev/docs
5
+ This file is written for coding agents. It is dense on purpose.
6
+
7
+ WHAT IT IS
8
+
9
+ createBunderstack() takes a Drizzle schema and returns an app whose handler is
10
+ a single Web-Standard Request -> Response function. From the schema it
11
+ generates secured CRUD procedures. Your own procedures, file storage, realtime
12
+ subscriptions, and a health check live in the same oRPC graph, reachable both
13
+ as typed RPC and as ordinary HTTP.
14
+
15
+ Stack: Bun, Drizzle (+ drizzle-kit), Better Auth, oRPC v2, libSQL or Postgres,
16
+ sharp. Validation accepts any Standard Schema library; generated schemas use
17
+ Valibot. There is no Hono, no tRPC, and no Zod requirement.
18
+
19
+ Packages: bunderstack (server), bunderstack-query (client + TanStack Query),
20
+ bunderstack-sync (TanStack DB collections), bunderstack-start (TanStack Start
21
+ integration).
22
+
23
+ MINIMAL APP
24
+
25
+ import { createBunderstack } from 'bunderstack'
26
+ import { libsql } from 'bunderstack/database/libsql'
27
+ import * as schema from './schema'
28
+
29
+ export const app = await createBunderstack({
30
+ schema,
31
+ database: { adapter: libsql(), url: 'file:./data.db' },
32
+ access: { posts: { list: 'public', get: 'public' } },
33
+ })
34
+
35
+ export type App = typeof app
36
+ Bun.serve({ fetch: app.handler })
37
+
38
+ Database adapters are imported from their own entry points: libsql(),
39
+ pglite(), bunSql(), postgresJs(). Provisioning: `await provision(app)` pushes
40
+ the schema in development and applies committed migrations once a migrations/
41
+ folder exists.
42
+
43
+ DECLARING AN API
44
+
45
+ Declare the builder once at module scope. defineApi infers the types from the
46
+ values you pass, so you never write the generic parameters yourself. It reads
47
+ nothing at runtime.
48
+
49
+ // src/api/base.ts
50
+ import { defineApi } from 'bunderstack'
51
+ import { envSchema } from '../env'
52
+ import { schema } from '../schema'
53
+
54
+ export const o = defineApi({ schema, env: envSchema })
55
+ export const publicProcedure = o.public
56
+ export const protectedProcedure = o.protected
57
+
58
+ Router modules are plain objects that import the base they need:
59
+
60
+ // src/api/boards.ts
61
+ import { protectedProcedure } from './base'
62
+
63
+ export const boardsRouter = {
64
+ stats: protectedProcedure
65
+ .route({ method: 'GET', path: '/api/board-stats' })
66
+ .input(v.object({ boardId: v.string() }))
67
+ .handler(async ({ context, input }) => countTodos(context.db, input.boardId)),
68
+ }
69
+
70
+ // src/api/index.ts
71
+ export const api = { boards: boardsRouter }
72
+
73
+ // config
74
+ createBunderstack({ schema, database, api })
75
+
76
+ Do NOT write a factory that receives a bag of procedures. That pattern exists
77
+ only because the api option used to be a callback. The callback form still
78
+ works — api: (o) => ({ ... }) — for a router that must be built from the
79
+ framework builder at configuration time, but the object form is the default.
80
+
81
+ BASES
82
+
83
+ o.public session resolved only if the handler calls context.getSession()
84
+ o.protected resolves the session, narrows context.user to non-null
85
+ o.webhook public, and preserves the exact raw request body
86
+ o.middleware(fn) a standalone middleware typed over the request context
87
+
88
+ Extend a base with .use(). Whatever you pass to next({ context }) is merged and
89
+ typed downstream:
90
+
91
+ export const adminProcedure = o.protected.use(async ({ context, next, errors }) => {
92
+ if (context.user.role !== 'admin') throw errors.FORBIDDEN({ message: 'Admin only' })
93
+ return next()
94
+ })
95
+
96
+ HANDLER CONTEXT
97
+
98
+ db typed Drizzle instance for your schema
99
+ env validated environment, typed from the env schema
100
+ storage StorageFacade: delete, bucket, sweep, getUrl, upload
101
+ email send()
102
+ jobs enqueue()
103
+ realtime publish()
104
+ auth the Better Auth instance
105
+ request the original Request
106
+ resHeaders response headers you can set
107
+ getSession() resolves the session, memoized per request
108
+ peekSession() the already-resolved session or undefined; never resolves
109
+ getRawBody() the exact bytes, memoized; safe for signature checks
110
+
111
+ o.protected additionally guarantees context.user and
112
+ context.session.activeOrganizationId.
113
+
114
+ MIDDLEWARE
115
+
116
+ Two placements, and the difference is the thing people get wrong.
117
+
118
+ .use() on a base reaches only procedures built from that base. Use it for
119
+ rules about a group of procedures: role checks, organization scope, quotas.
120
+
121
+ middleware: [...] in createBunderstack reaches EVERY procedure, including the
122
+ generated CRUD, storage, realtime, and health. Use it for observability. A
123
+ tracing middleware attached to a base leaves generated CRUD unmeasured,
124
+ because the framework builds those procedures itself and they never pass
125
+ through an application base.
126
+
127
+ export const instrumentation = o.middleware(async ({ context, next, path }) => {
128
+ const name = path.join('.')
129
+ const startedAt = performance.now()
130
+ try {
131
+ const result = await next()
132
+ metrics.record(name, performance.now() - startedAt)
133
+ return result
134
+ } catch (error) {
135
+ metrics.error(name, error)
136
+ throw error
137
+ }
138
+ })
139
+
140
+ createBunderstack({ schema, database, middleware: [instrumentation], api })
141
+
142
+ Rules for graph-wide middleware:
143
+ - It runs before authentication. context.user does not exist there.
144
+ - Read the caller with context.peekSession(), after await next(). Do not call
145
+ getSession(): that forces resolution on every request and makes signed
146
+ webhooks pay for authentication they do not need.
147
+ - peekSession() is for observability only, never for authorization. An
148
+ anonymous request and an unresolved session look identical.
149
+ - A realtime subscription is one long-lived call. Code after await next() runs
150
+ when the stream closes, not when it starts. Filter by path[0] === 'realtime'
151
+ when that matters.
152
+ - Middlewares run outermost first, in array order.
153
+
154
+ TYPED ERRORS
155
+
156
+ Declared codes: BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT,
157
+ PAYLOAD_TOO_LARGE, TOO_MANY_REQUESTS. Each maps to its standard HTTP status.
158
+
159
+ Inside a handler or middleware, raise from the errors argument:
160
+
161
+ throw errors.NOT_FOUND({ message: 'Board not found' })
162
+ throw errors.CONFLICT({ message: 'Already running', data: { details: { id } } })
163
+
164
+ Outside a handler — a service function, a job — there is no errors argument.
165
+ Throw BunderstackError; the framework maps it to the same typed error:
166
+
167
+ import { BunderstackError } from 'bunderstack'
168
+ throw new BunderstackError('FORBIDDEN', 'Insufficient credits')
169
+
170
+ Do not construct ORPCError by hand. Clients narrow on these with the oRPC
171
+ isDefinedError helpers.
172
+
173
+ GENERATED CRUD
174
+
175
+ Per exposed table: list, get, create, update, delete. HTTP equivalents are
176
+ GET /api/:table, GET /api/:table/:id, POST /api/:table, PATCH /api/:table/:id,
177
+ DELETE /api/:table/:id.
178
+
179
+ List parameters, identical over RPC and query string: limit (default 20, capped
180
+ at 200), offset, sort, order, q, count, cursor, filters. filters[col]=value is
181
+ equality, filters[col][]=a&filters[col][]=b is IN, filters[col]=null is IS
182
+ NULL. A bare ?col=value is not a filter and returns 400. cursor and offset
183
+ cannot be combined.
184
+
185
+ List response: { items, limit, offset, hasMore, total, sort, order, nextCursor }.
186
+ total is present only with count: true.
187
+
188
+ Exposure and rules come from the access option:
189
+
190
+ access: defineAccess(schema, {
191
+ posts: {
192
+ list: 'authenticated',
193
+ get: 'owner',
194
+ create: 'authenticated',
195
+ update: 'owner',
196
+ delete: 'owner',
197
+ filterableColumns: ['authorId'],
198
+ sortableColumns: ['createdAt'],
199
+ defaultSort: { column: 'createdAt', order: 'desc' },
200
+ scope: { read: (ctx) => ({ userId: ctx.user?.id ?? '' }) },
201
+ },
202
+ auditLog: { crud: false },
203
+ })
204
+
205
+ Rules are 'public', 'authenticated', 'owner', 'deny', or a predicate. Without
206
+ an access entry, a table with a userId column is exposed by convention. Owner
207
+ checks use ownerColumn, detected as userId unless stated.
208
+
209
+ LIST ENDPOINTS OUTSIDE CRUD
210
+
211
+ listSpec gives a procedure you write the same list contract, for tables that
212
+ are not exposed as CRUD or that need a different policy:
213
+
214
+ import { listSpec } from 'bunderstack'
215
+
216
+ const logsList = listSpec(appLogs, {
217
+ filterable: ['level', 'userId'],
218
+ sortable: ['createdAt'],
219
+ defaultSort: { column: 'createdAt', order: 'desc' },
220
+ })
221
+
222
+ export const adminRouter = {
223
+ logs: adminProcedure.input(logsList.input).handler(logsList.handler),
224
+ }
225
+
226
+ It returns the schema and the handler separately, not a finished procedure.
227
+ That is deliberate: the base procedure must stay concrete at the call site, or
228
+ TypeScript resolves the builder through a generic constraint and the row type
229
+ is erased. listSpec reads no access configuration; the base carries the policy.
230
+
231
+ TYPING HELPERS
232
+
233
+ A service function in its own module cannot use typeof app.db without an import
234
+ cycle. Use the exported types:
235
+
236
+ import type { BunderstackDb, BunderstackTx } from 'bunderstack'
237
+ import type { schema } from './schema'
238
+
239
+ type Db = BunderstackDb<typeof schema>
240
+ type Tx = BunderstackTx<typeof schema>
241
+
242
+ WEBHOOKS AND HTTP
243
+
244
+ A webhook is an ordinary procedure with a route. getRawBody() returns the exact
245
+ bytes, so signature verification is correct, and the session is never resolved
246
+ unless the handler asks for it:
247
+
248
+ stripeWebhook: o.webhook
249
+ .route({ method: 'POST', path: '/webhooks/stripe' })
250
+ .handler(async ({ context }) => {
251
+ const raw = await context.getRawBody()
252
+ verify(raw, context.request.headers.get('stripe-signature'), context.env.STRIPE_SECRET)
253
+ return { received: true }
254
+ })
255
+
256
+ For typed headers, query parameters, status codes, or response headers, use
257
+ oRPC inputStructure: 'detailed' and outputStructure.
258
+
259
+ ENV
260
+
261
+ env: { server: { STRIPE_KEY: v.string() }, client: { PUBLIC_NAME: v.string() } }
262
+
263
+ Server variables must not start with PUBLIC_; client variables must. Validated
264
+ at boot; app.env and context.env are typed from the schema. Declare the schema
265
+ in its own module so both createBunderstack and defineApi can use it.
266
+
267
+ AUTH
268
+
269
+ auth takes Better Auth options directly, or defineAuth(schema, ({ db, env }) =>
270
+ options) when database hooks need the app's own connection. Better Auth owns
271
+ /api/auth/*. context.user carries id, email, name, and role. authResolver
272
+ replaces session reading with your own implementation.
273
+
274
+ BACKGROUND JOBS
275
+
276
+ One table, one loop. A cron is a job created on a schedule.
277
+
278
+ jobs: (j) => j.define({
279
+ sendEmail: j.job({
280
+ input: v.object({ userId: v.string() }),
281
+ retries: 3,
282
+ handler: async ({ userId }, ctx) => { /* ctx.db, ctx.email, ... */ },
283
+ }),
284
+ daily: j.cron({ schedule: '0 9 * * *', handler: async (_inv, ctx) => {} }),
285
+ })
286
+
287
+ Enqueue with app.jobs.enqueue(name, input, { dedupeKey }). The background loop
288
+ starts on its own, gated by BUNDERSTACK_ROLE: all (default), web, or worker.
289
+
290
+ STORAGE
291
+
292
+ storage: {
293
+ local: './uploads',
294
+ defaultBucket: 'files',
295
+ buckets: {
296
+ avatars: {
297
+ visibility: 'public',
298
+ access: { create: 'authenticated', get: 'public', delete: 'owner' },
299
+ upload: { maxSize: '2mb', accept: ['image/jpeg', 'image/png'] },
300
+ transforms: true,
301
+ },
302
+ },
303
+ }
304
+
305
+ Canonical URL: /api/files/{bucket}/{+path}. transforms: true enables on-the-fly
306
+ image derivatives through sharp. app.storage exposes delete, bucket, sweep,
307
+ getUrl, and upload for server-generated files.
308
+
309
+ REALTIME
310
+
311
+ realtime: true uses an in-memory publisher; { redis: url } fans out across
312
+ processes. Generated writes publish automatically. After a custom write,
313
+ publish the complete returned row:
314
+
315
+ await context.realtime.publish(schema.posts, 'update', post)
316
+
317
+ Clients subscribe through the same graph; there is no separate SSE transport to
318
+ configure.
319
+
320
+ CLIENT
321
+
322
+ import { createClient } from 'bunderstack-query'
323
+ import type { App } from './bunderstack'
324
+
325
+ export const api = createClient<App>({ queryClient, realtime: true })
326
+
327
+ await api.posts.list.call({ limit: 20 })
328
+ useQuery(api.posts.list.queryOptions({ input: { limit: 20 } }))
329
+ useMutation(api.posts.create.mutationOptions())
330
+ await api.files.avatars.upload.call(file)
331
+ api.files.avatars.url(fileId, { width: 320, format: 'webp' })
332
+
333
+ App is a type-only import, so server code never enters the browser bundle.
334
+ bunderstack-sync layers TanStack DB collections over the same procedures.
335
+
336
+ CONFIG KEYS
337
+
338
+ schema (required), database (required: adapter, url), access, auth,
339
+ authResolver, storage, email, env, jobs, api, middleware, realtime, rateLimit,
340
+ idempotency, background, openapi, processEnv.
341
+
342
+ OPENAPI
343
+
344
+ openapi: true serves /api/openapi.json. RPC types remain the source of truth; a
345
+ procedure without .output() has an unspecified response body. OpenAPI failures
346
+ never break normal boot.
347
+
348
+ COMMON MISTAKES
349
+
350
+ - Writing router factories that take a procedure bag. Use module-scope bases.
351
+ - Declaring middleware with os.$context<...>(). Use o.middleware(...).
352
+ - Attaching observability to a base and expecting it to cover generated CRUD.
353
+ Use the middleware config option.
354
+ - Calling getSession() in graph-wide middleware. Use peekSession().
355
+ - Constructing ORPCError by hand. Use errors.CODE() or BunderstackError.
356
+ - Using any for a db parameter. Use BunderstackDb<typeof schema>.
357
+ - Importing the app from a module the app imports. Both the api router and any
358
+ module it pulls in are evaluated at import time, so a cycle through the app
359
+ breaks initialization. Load the app lazily there instead.
360
+ - Reading env from an imported module inside a handler. Use context.env.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunderstack",
3
- "version": "0.17.0-beta.8",
3
+ "version": "0.17.0",
4
4
  "description": "Batteries-included backend framework for Bun: type-safe oRPC APIs, auth, storage, realtime, jobs, email, and validated env from one config.",
5
5
  "keywords": [
6
6
  "backend",
@@ -25,6 +25,8 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
+ "skills",
29
+ "llms.txt",
28
30
  "CHANGELOG.md",
29
31
  "README.md",
30
32
  "LICENSE"
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: creating-bunderstack-apps
3
+ description: Use when working in a repository that depends on bunderstack - starting or structuring the application, adding or changing oRPC procedures, bases, middleware, access rules, jobs, storage, or realtime, choosing a runtime integration, or preparing it for production.
4
+ ---
5
+
6
+ # Creating Bunderstack Apps
7
+
8
+ ## Workflow
9
+
10
+ 1. Inspect the product brief and target runtime.
11
+ 2. Choose the layout from the table below.
12
+ 3. For a full TanStack Start SaaS, copy `templates/tanstack-start-saas/`.
13
+ 4. Configure schema, access, auth, env, storage, jobs, realtime, and the oRPC API graph.
14
+ 5. Mount the single `app.handler` integration.
15
+ 6. Add committed migrations and a deployment blueprint before production.
16
+ 7. Run the verification contract.
17
+
18
+ | Condition | Layout |
19
+ | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
20
+ | Small API with short configuration | `src/bunderstack.ts` |
21
+ | Auth, access, jobs, env, or custom oRPC procedures need independent modules | `src/bunderstack/` |
22
+ | Full SaaS | Copy `templates/tanstack-start-saas/` and keep its modular layout |
23
+
24
+ ## Runtime decision recipe
25
+
26
+ Choose the process boundary before listing files. A TanStack Start SaaS keeps
27
+ the API mount in Start. A standalone Bun API has `src/bunderstack.ts` (or the
28
+ modular `src/bunderstack/` entry) and a Bun process that delegates to
29
+ `app.handler`.
30
+
31
+ For a React SPA brief, use the browser-only layout: an `api/` project contains
32
+ the Bunderstack entry and Bun API process, and a separate `frontend/` project
33
+ contains the React build. The API process owns `app.handler`; the browser
34
+ client receives a configured API base URL and calls that process. Include both
35
+ processes and the configured browser-to-API path in the proposed structure.
36
+
37
+ `node_modules/bunderstack/llms.txt` is a dense plain-text reference for the
38
+ whole framework. Read it when a detail is not covered here.
39
+
40
+ Read [application structure](references/application-structure.md) before placing
41
+ the Bunderstack entry, schemas, authorization, or configuration modules, and
42
+ before declaring procedures, bases, middleware, or errors.
43
+ Read [runtime integrations](references/runtime-integrations.md) before mounting
44
+ HTTP, adding a worker, or choosing a framework adapter. Read the
45
+ [verification contract](references/verification.md) after adding the app
46
+ scripts and again before handoff or deployment.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Creating Bunderstack Apps"
3
+ short_description: "Start production-ready Bunderstack applications"
4
+ default_prompt: "Use $creating-bunderstack-apps to start a new Bunderstack application."