A modern invoice management application built with SvelteKit 5, featuring Better Auth authentication, Drizzle ORM with Neon database, and Bun UUIDv7 for resilient ID generation.
Clone and install dependencies:
git clone <repository-url>
cd dollar-holler
bun install
Set up environment variables (Varlock): The committed .env.schema is the source of truth for variable names, validation, and (optional) Bitwarden Secrets Manager lookups.
bunfig.toml sets env = false and preload = ["varlock/auto-load"] so Bun does not load .env on its own before Varlock (see Varlock + Bun).vite.config.ts uses @varlock/vite-integration with ssrInjectMode: "resolved-env" (Varlock + Vite).package.json), then in Bitwarden Secrets Manager create a machine account, copy its access token once, and grant it read access to the secrets you need. Put the token in a gitignored file such as .env.local as BITWARDEN_ACCESS_TOKEN=.... In .env.schema, replace the placeholder UUIDs in bitwarden("...") with your real secret IDs (Bitwarden plugin).DATABASE_URL, BETTER_AUTH_SECRET, and PUBLIC_BASE_URL in .env or .env.local. Host and CI variables still override resolved values when set..env.schema, run bun run env:typegen to refresh src/env-varlock.d.ts.The app resolves configuration from Varlock (varlock/env) for Drizzle Kit (drizzle.config.ts), auth (src/lib/auth.server.ts), and the Eden client (src/lib/api.ts). The database pool (src/lib/server/db/index.ts) reads DATABASE_URL from SvelteKit $env/static/private (populated by the Varlock Vite integration in dev/build). SvelteKit $env/static/* remains available where used.
Set up the database: The bun run db:* scripts wrap Drizzle Kit with varlock run so DATABASE_URL is resolved the same way as the app (see .env.schema). drizzle.config.ts points at ./src/lib/server/db/schema.ts and writes migrations under ./src/lib/server/db/migrations.
# Generate migrations
bun run db:generate
# Run migrations
bun run db:migrate
# Seed the database with sample data
bun run db:seed
Start the development server:
bun run dev
Optional: Preview production build
bun run build && bun run preview
bun run dev - Start development server (Vite 8)bun run build - Build for productionbun run preview - Preview production buildbun run check - Run Ultracite (Biome) checksbun run check:watch - svelte-kit sync then svelte-check --watchbun run env:typegen - Regenerate types from .env.schema (Varlock)bun run db:generate - Generate Drizzle migrationsbun run db:migrate - Run database migrationsbun run db:seed - Seed database with sample databun run db:clear - Clear application data from the databasebun run db:studio - Open Drizzle Studiobun run db:push - Push schema directly to the databasebun run ultracite:upgrade - Re-run Ultracite init/upgrade for this stackbun run fix - Run Ultracite fix (ultracite fix)bun run fallow - Run Fallow (project graph and analysis)bun run fallow:dead-code - Fallow dead-code passbun run fallow:boundaries - List configured boundariesbun run fallow:boundary-violations - Dead-code with boundary violationsbun run fallow:dupes - Fallow duplicate detectionbun run prepare (auto) - panda codegen and svelte-kit sync after installsrc/lib/server/app.ts (OpenAPI/Scalar in dev via openapi-plugin.ts, auth macros in auth-plugin.ts, list-query helpers in list-query-plugin.ts, domain routes), mounted at /api via src/routes/api/[...slugs]/+server.ts, Eden Treaty client apiClient (@elysiajs/eden/treaty2)Pool)src/lib/auth.server.ts, Drizzle adapter, bearer + OpenAPI plugins)Bun.randomUUIDv7() via create-uuidv7.server.ts (cursor-friendly IDs)@ark-ui/svelte)styled-system (see panda.config.ts, PostCSS, prepare script)biome.jsonc)src/
├── lib/
│ ├── auth.server.ts # Centralized Better Auth configuration (Drizzle adapter, UUIDv7 IDs)
│ ├── api.ts # Eden Treaty client (`apiClient`)
│ ├── server/
│ │ ├── db/
│ │ │ ├── index.ts # Database connection (Neon serverless WebSocket pool)
│ │ │ ├── schema.ts # Drizzle tables and enums
│ │ │ ├── types.ts # Enum-derived types (e.g. client/invoice status)
│ │ │ ├── relations.ts # Drizzle relations v2 (`defineRelations`)
│ │ │ ├── seed.ts # Database seeding
│ │ │ └── clear-app-data.ts
│ │ ├── app.ts # Elysia app: OpenAPI plugin, auth mount, API routes, error mapping
│ │ ├── plugins/ # OpenAPI (dev), auth, list-query
│ │ ├── schemas.ts # Shared API response shapes
│ │ ├── utils/ # better-auth-openapi, api-error-body, errors
│ │ └── routes/ # API modules (clients, invoices, settings)
│ ├── client/ # Client-only: @attach helpers, shared runes (ItemPanel, etc.)
│ ├── features/ # Domain features: components, stores, schemas, list helpers
│ │ ├── auth/
│ │ ├── clients/ # includes server queries (list, options, verify)
│ │ ├── invoices/ # includes server queries (list, verify)
│ │ ├── landing-page/ # Marketing sections, nav, copy constants
│ │ ├── line-items/
│ │ ├── pagination/ # PaginatedList, search, blank states, UUIDv7 `createId` (server module)
│ │ └── settings/
│ ├── components/ # Shared UI (navbar/, icons under components/icons/, ui/)
│ ├── stores/ # Shared list-store bases, dashboard context
│ ├── styles.ts # Shared class names / style recipes
│ └── utils/
├── routes/
│ ├── (auth)/ # Login, signup, password reset, logout
│ ├── (dashboard)/
│ │ ├── (shell)/ # App shell: clients, invoice list, settings, thanks
│ │ └── invoices/ # Invoice detail, line items
│ ├── api/ # Elysia catch-all API handler
│ ├── +layout.svelte
│ └── +page.svelte # Landing page
└── app.html
The application uses the following main tables:
user - Better Auth user accountssession - User sessionsaccount - OAuth accountsverification - Email verification tokensclients - Client information (client_status: active, archive)invoices - Invoice records (invoice_status: draft, sent, paid; optional discount)line_items - Invoice line itemssettings - User settingsPrimary keys use PostgreSQL uuid columns; IDs are Bun UUIDv7 strings from createId (including Better Auth generateId in src/lib/auth.server.ts). Foreign keys use cascade deletes where appropriate.
The application uses Drizzle's relations v2 (defineRelations) to simplify nested queries (e.g., db.query.invoices.findMany({ with: { client: true, lineItems: true } })) and avoid manual joins in API routes.
The application is configured for Vercel deployment with the Vercel adapter. vercel.json runs the production build as varlock run -- bun --bun run build so Varlock resolves secrets the same way as local db:* scripts.
DATABASE_URL, BETTER_AUTH_SECRET, and PUBLIC_BASE_URL in the Vercel project (and any other keys your schema requires). You can rely on Varlock for validation while storing values only in Vercel.BITWARDEN_ACCESS_TOKEN to Vercel so the build can resolve bitwarden(...) entries in .env.schema (Varlock Vite SSR options).vite in package.json). Varlock’s Vite plugin uses ssrInjectMode: "resolved-env". Production builds use rolldownOptions in vite.config.ts (for example dropConsole). If issues arise with third-party plugins, see Vite's documentation for compatibility.bun run check, bun run fix) with Biome rules extended from ultracite/biome for core and Svelte.styled-system/; svelte.config.ts aliases styled-system and includes it in TypeScript. After dependency install, prepare runs codegen for Panda.@attach directive for modern component patterns and the Spring class for smooth animations.auth.server.ts to use Bun UUIDv7 for user ID generation and includes session caching for performance.svelte.config.ts (Vercel adapter, preprocess, Svelte 5 async compiler option).MIT