workerstack
v0.1.7
Published
Full-stack framework for Cloudflare Workers: one blueprint declares the API, auth, storage, jobs, cron, and realtime, and SSR with TanStack Start runs next to them.
Maintainers
Readme
workerstack
A full-stack framework for Cloudflare Workers. One declaration describes the database, auth, API, file buckets, jobs, cron, realtime, and rate limits; the TanStack Start app renders on the server in the same Worker.
Workerstack started as the Workers rewrite of Bunderstack. Bunderstack 0.x stays the choice for apps that need a long-running Bun process (a headless browser, native database drivers, large files in memory).
bun add workerstack better-auth drizzle-orm valibot @libsql/client
bun add -d vite wrangler @cloudflare/vite-plugin drizzle-kitThe backend
// src/workerstack.ts
import { workerstack } from 'workerstack'
import { defineAccess } from 'workerstack/access'
import { libsql } from 'workerstack/libsql'
import * as v from 'valibot'
import * as schema from './schema'
export const backend = workerstack({
schema,
database: { adapter: libsql() },
access: defineAccess(schema, {
notes: { crud: true, list: 'authenticated', create: 'authenticated' },
}),
auth: ({ env }) => ({
baseURL: env.APP_URL,
emailAndPassword: { enabled: true },
}),
storage: { buckets: { media: { upload: { maxSize: '5mb' } } } },
realtime: true,
rateLimit: { windowMs: 60_000, max: 100 },
jobs: (j) =>
j.define({
noteCreated: j.job({
input: v.object({ noteId: v.string() }),
handler: async (input, ctx) => {
/* ... */
},
}),
nightly: j.cron({ schedule: '0 3 * * *', handler: async () => {} }),
}),
api: (o) => ({
ping: o.public
.route({ method: 'GET', path: '/api/ping' })
.handler(() => ({ ok: true })),
}),
})
export type App = Awaited<ReturnType<typeof backend.start>>// vite.config.ts — SSR with TanStack Start by default.
import viteReact from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { workerstack } from 'workerstack/vite'
export default defineConfig({ plugins: [workerstack(), viteReact()] })// src/api.ts — one client for loaders, server functions, and the browser.
import { workerstackStart } from 'workerstack/start'
import type { App } from './workerstack'
export const { createQueryClient, createApi } = workerstackStart<App>()On the server the client calls the backend inside the same isolate and
forwards the request cookie; in the browser it calls /api over HTTP.
Commands
bunx workerstack dev # sqld + Vite on workerd, the production runtime
bunx workerstack build # dist/server/index.js and dist/client
bunx workerstack blueprint # writes workerstack.blueprint.yaml
bunx workerstack wrangler # writes wrangler.json from the blueprintworkerstack.blueprint.yaml is the single source of truth for hosting: the
database, buckets, Durable Objects (Scheduler, RealtimeHub, RateLimiter), cron,
and the Worker entry. wrangler.json is generated from it. Commit the
blueprint and the Drizzle migrations; a host deploys from them.
Runtime
- Database: SQLite through libsql. Locally
workerstack devstarts sqld; hosted apps use Turso. - Files: R2 bindings, one per declared bucket.
- Jobs and cron: a Scheduler Durable Object wakes on alarms; there is no worker process.
- Realtime: a RealtimeHub Durable Object fans writes out to subscribers.
- Rate limits: a RateLimiter Durable Object.
Code runs in a Worker isolate: no file system, no child processes, no native modules, and CPU time per request is limited.
Package subpaths
workerstack—workerstack(), the backend declarationworkerstack/access,workerstack/schema,workerstack/typeid,workerstack/envworkerstack/libsql— the database adapterworkerstack/vite,workerstack/start,workerstack/start-auth— the Vite plugin and TanStack Start integrationworkerstack/workers— Worker entry helpers and the Durable Object classesworkerstack/client*,workerstack/query*,workerstack/sync,workerstack/live— clientsworkerstack/messaging,workerstack/email-smtp— messaging channelsworkerstack/testing—backend.test()fixtures forbun testworkerstack/blueprint— the blueprint schema, for hosts
Examples
examples/ssr-probe— SSR with TanStack Startexamples/workers-probe— SPAexamples/agent-chat— an agent chat with durable streamingexamples/todo-solid-native— Solid
Development
bun install
bun run test # package and repository tests
bun run test:workers # builds the probes and runs them on celld (-- --runtime workerd for workerd)
bun run typecheck:allLicense
MIT
Managed identity on Bunderhost
Applications with Better Auth user, session, account and verification tables
can enable managed providers in their Bunderhost environment settings. The
framework declares its capability in the blueprint and installs the adapter
when Bunderhost supplies APP_URL and the sensitive, server-only
BUNDERHOST_AUTH_CONFIG. Provider credentials stay in the central identity
service. Set auth: { managedIdentity: false } to use a custom adapter.
import { createStartAuthClient } from 'workerstack/start-auth'
const auth = createStartAuthClient()
const { providers } = await auth.managed.providers()
// Render these providers, then call the selected provider:
await auth.signIn.managed({ provider: providers[0]!.id, callbackURL: '/' })Discovery returns only enabled, configured providers. Refresh discovery to
reflect environment policy changes without rebuilding the application.
workerstack/managed-auth exposes the same server adapter for other Better
Auth integrations; workerstack/managed-auth/client contains only HTTP browser
helpers. Existing application account-linking policies continue to apply.
