Skip to content

fringe4life/dollar-holler

Repository files navigation

Dollar Holler

SvelteKit Svelte TypeScript Drizzle ORM Better Auth Neon Elysia TailwindCSS

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.

Prerequisites

  • Bun (required)
  • Neon account for PostgreSQL database
  • Vercel account for deployment (optional)

Getting Started

  1. Clone and install dependencies:

    git clone <repository-url>
    cd dollar-holler
    bun install
  2. Set up environment variables (Varlock): The committed .env.schema is the source of truth for variable names, validation, and (optional) Bitwarden Secrets Manager lookups.

    • Bun: 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 / SvelteKit: vite.config.ts uses @varlock/vite-integration with ssrInjectMode: "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.local as BITWARDEN_ACCESS_TOKEN=.... In .env.schema, replace the placeholder UUIDs in bitwarden("...") with your real secret IDs (Bitwarden plugin).
    • Without Bitwarden (e.g. quick local setup): Set DATABASE_URL, BETTER_AUTH_SECRET, and PUBLIC_BASE_URL in .env or .env.local. Host and CI variables still override resolved values when set.
    • Types: After changing .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.

  3. 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
  4. Start the development server:

    bun run dev
  5. Optional: Preview production build

    bun run build && bun run preview

Available Scripts

  • bun run dev - Start development server (Vite 8)
  • bun run build - Build for production
  • bun run preview - Preview production build
  • bun run check - Run Ultracite checks
  • bun run check:watch - svelte-kit sync then svelte-check --watch
  • bun run env:typegen - Regenerate types from .env.schema (Varlock)
  • bun run db:generate - Generate Drizzle migrations
  • bun run db:migrate - Run database migrations
  • bun run db:seed - Seed database with sample data
  • bun run db:clear - Clear application data from the database
  • bun run db:studio - Open Drizzle Studio
  • bun run db:push - Push schema directly to the database
  • bun run format - Format source with Prettier
  • bun run lint - Run Prettier check and ESLint
  • bun run lint:fix - Auto-fix lint and formatting issues
  • bun run ultracite:upgrade - Re-run Ultracite init/upgrade for this stack
  • bun run fix - Run Ultracite fix (ultracite fix)
  • bun run fallow - Run Fallow (project graph and analysis)
  • bun run fallow:dead-code - Fallow dead-code pass
  • bun run fallow:boundaries - List configured boundaries
  • bun run fallow:boundary-violations - Dead-code with boundary violations

Tech Stack

  • Framework: SvelteKit 5 with Svelte 5 runes
  • API layer: ElysiaJS in src/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)
  • 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() via create-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)

Project Structure

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

Database Schema

The application uses the following main tables:

  • user - Better Auth user accounts
  • session - User sessions
  • account - OAuth accounts
  • verification - Email verification tokens
  • clients - Client information (client_status: active, archive)
  • invoices - Invoice records (invoice_status: draft, sent, paid; optional discount)
  • line_items - Invoice line items
  • settings - 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.

Features

  • 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

Deployment

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, 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 at build time: Add BITWARDEN_ACCESS_TOKEN to Vercel so the build can resolve bitwarden(...) entries in .env.schema (Varlock Vite SSR options).

Notes

  • Uses Vite 8 (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.
  • ESLint configuration is in 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.
  • The project uses Svelte 5's @attach directive for modern component patterns and the Spring class for smooth animations.
  • Better Auth is configured in auth.server.ts to 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).

License

MIT

About

Dollar Holler is a SvelteKit invoice and client manager with auth, Elysia API, Drizzle/Postgres and a UI for creating invoices, tracking payments, managing clients, and line items, plus settings and printable views. Built with TypeScript and Pandacss

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors