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.schemais the source of truth for variable names, validation, and (optional) Bitwarden Secrets Manager lookups.- Bun:
bunfig.tomlsetsenv = falseandpreload = ["varlock/auto-load"]so Bun does not load.envon its own before Varlock (see Varlock + Bun). - Vite / SvelteKit:
vite.config.tsuses@varlock/vite-integrationwithssrInjectMode: "resolved-env"(Varlock + Vite). - Bitwarden: Install the app deps (already in
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.localasBITWARDEN_ACCESS_TOKEN=.... In.env.schema, replace the placeholder UUIDs inbitwarden("...")with your real secret IDs (Bitwarden plugin). - Without Bitwarden (e.g. quick local setup): Set
DATABASE_URL,BETTER_AUTH_SECRET, andPUBLIC_BASE_URLin.envor.env.local. Host and CI variables still override resolved values when set. - Types: After changing
.env.schema, runbun run env:typegento refreshsrc/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) readsDATABASE_URLfrom SvelteKit$env/static/private(populated by the Varlock Vite integration in dev/build). SvelteKit$env/static/*remains available where used. - Bun:
-
Set up the database: The
bun run db:*scripts wrap Drizzle Kit withvarlock runsoDATABASE_URLis resolved the same way as the app (see.env.schema).drizzle.config.tspoints at./src/lib/server/db/schema.tsand 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 checksbun run check:watch-svelte-kit syncthensvelte-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 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)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 violations
- Framework: SvelteKit 5 with Svelte 5 runes
- API layer: ElysiaJS in
src/lib/server/app.ts(OpenAPI/Scalar in dev viaopenapi-plugin.ts, auth macros inauth-plugin.ts, list-query helpers inlist-query-plugin.ts, domain routes), mounted at/apiviasrc/routes/api/[...slugs]/+server.ts, Eden Treaty clientapiClient(@elysiajs/eden/treaty2) - Database: PostgreSQL with Neon serverless
- ORM: Drizzle ORM (beta) with Neon serverless driver (WebSocket
Pool) - Authentication: Better Auth with email/password (
src/lib/auth.server.ts, Drizzle adapter, bearer + OpenAPI plugins) - ID generation:
Bun.randomUUIDv7()viacreate-uuidv7.server.ts(cursor-friendly IDs) - Deployment: Vercel adapter
- Package manager: Bun
- Validation: ArkType for runtime-safe form validation
- Bundler: Vite 8 for dev and production builds
- UI components: Bits UI with Tailwind CSS
- Styling: Tailwind CSS 4 with Tailwind Variants
- Lint/format: Ultracite (ESLint, Prettier, Stylelint)
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)
│ ├── features/ # Domain features: components, stores, schemas, list helpers
│ │ ├── auth/
│ │ ├── clients/ # includes server queries (list, options, verify)
│ │ ├── invoices/ # includes server queries (list, verify)
│ │ ├── line-items/
│ │ ├── pagination/ # PaginatedList, base filters, UUIDv7 `createId` (server module)
│ │ └── 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/
├── 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 settings
Primary 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.
- Modern Authentication: Better Auth with email/password support
- Type-Safe Database: Drizzle ORM with full TypeScript support
- Serverless Ready: Neon serverless driver (WebSocket pool) for Vercel deployment
- Resilient IDs: Bun UUIDv7 for cursor-based navigation and performance
- Recent Data: Seed script generates realistic data from the last 6 months
- Multi-User Support: Data is distributed randomly among users
- Auth Flows: Reset password supported; token is read from URL and validated
- Modern UI: Bits UI components with Tailwind CSS 4
- Svelte 5 Runes: Uses @attach directives and reactive patterns
- Responsive Design: Mobile-first with swipe gestures
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.
- Platform env vars: Set
DATABASE_URL,BETTER_AUTH_SECRET, andPUBLIC_BASE_URLin the Vercel project (and any other keys your schema requires). You can rely on Varlock for validation while storing values only in Vercel. - Bitwarden at build time: Add
BITWARDEN_ACCESS_TOKENto Vercel so the build can resolvebitwarden(...)entries in.env.schema(Varlock Vite SSR options).
- Uses Vite 8 (
viteinpackage.json). Varlock’s Vite plugin usesssrInjectMode: "resolved-env". Production builds userolldownOptionsinvite.config.ts(for exampledropConsole). If issues arise with third-party plugins, see Vite's documentation for compatibility. - ESLint configuration is in
eslint.config.mjsand uses Svelte 5 rules and Prettier integration. Prettier is configured inprettier.config.mjs. Usebun run formatbeforebun run lint. - The project uses Svelte 5's
@attachdirective for modern component patterns and the Spring class for smooth animations. - Better Auth is configured in
auth.server.tsto use Bun UUIDv7 for user ID generation and includes session caching for performance. - App configuration lives in
svelte.config.ts(Vercel adapter, preprocess, Svelte 5 async compiler option).
MIT