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: "auto-load" so dev and SSR resolve the same schema (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 reads secrets via SvelteKit’s $env/static/private and $env/static/public (for example DATABASE_URL in src/lib/db/index.ts).
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/db/schema.ts and writes migrations under ./src/lib/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 checksbun run check:watch - Run Svelte check in watch modebun 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 format - Format source with Prettierbun run lint - Run Prettier check and ESLintbun run lint:fix - Auto-fix lint and formatting issuesbun run ultracite:upgrade - Re-run Ultracite init/upgrade for this stackbun run fix - Run Ultracite fix (ultracite fix)src/lib/server route modules), mounted at /api via src/routes/api/[...slugs]/+server.ts, Eden Treaty client in src/lib/client.tsPool)src/lib/auth.ts, Drizzle adapter)Bun.randomUUIDv7() via src/lib/features/pagination/utils/create-uuidv7.ts (cursor-friendly IDs)src/
├── lib/
│ ├── auth.ts # Better Auth configuration (Drizzle adapter, UUIDv7 user IDs)
│ ├── auth-client.ts # Client-side auth helpers
│ ├── client.ts # Eden Treaty client for the Elysia API
│ ├── 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
│ │ └── migrate.ts
│ ├── server/
│ │ ├── auth-plugin.ts # Better Auth integration for Elysia
│ │ ├── better-auth-openapi.ts
│ │ ├── api-response-schemas.ts
│ │ └── routes/ # API modules (clients, invoices, settings)
│ ├── features/ # Domain features: components, stores, schemas, list helpers
│ │ ├── auth/
│ │ ├── clients/
│ │ ├── invoices/
│ │ ├── line-items/
│ │ ├── pagination/ # Cursor pagination, list-query utilities, UUIDv7 helper
│ │ └── settings/
│ ├── components/ # Shared UI (icons under components/icons/)
│ ├── attachments/ # Svelte 5 @attach helpers (e.g. swipe, clickOutside)
│ ├── runes/ # Shared rune modules (ItemPanel, etc.)
│ ├── stores/ # Shared list-store bases, dashboard context
│ ├── utils/
│ └── validators.ts
├── routes/
│ ├── (auth)/ # Login, signup, password reset, logout
│ ├── (dashboard)/
│ │ ├── (shell)/ # App shell: clients, invoice list, settings
│ │ └── invoices/ # Invoice detail, line items, thanks
│ ├── 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.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. Choose one approach:
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. If the host cannot run Varlock the same way as local dev, you may need ssrInjectMode: "resolved-env" in vite.config.ts for production builds (Varlock Vite SSR options).vite in package.json). Production builds use rolldownOptions in vite.config.ts (for example dropConsole). If issues arise with third-party plugins, see Vite's documentation for compatibility.eslint.config.mjs and uses Svelte 5 rules and Prettier integration. Prettier is configured in prettier.config.mjs. Use bun run format before bun run lint.@attach directive for modern component patterns and the Spring class for smooth animations.svelte.config.ts (Vercel adapter, preprocess, Svelte 5 async compiler option).MIT