TypeScript 96%, Node.js runtime, Drizzle ORM (type-safe queries, migrations), PGlite (embedded Wasm PostgreSQL for dev), Vite + React 19 frontend, and pnpm workspace.
Every major technology in the Paperclip stack, why it was chosen, and what you need to know about each one before writing any code.
Paperclip is 96% TypeScript. TypeScript adds static typing to JavaScript, which means the compiler catches type mismatches before they become runtime errors.
For Paperclip specifically, TypeScript is critical because:
- The @paperclip/shared package defines types used by both server and frontend — a change to an API response type breaks the frontend immediately at compile time, not at runtime
- Adapter interfaces are typed contracts — you can't implement a broken adapter without the compiler telling you
# Check types across the whole monorepo
pnpm typecheck
Node.js 20 is the current Long-Term Support release. Paperclip uses:
pnpm (Performant npm) uses a content-addressable store where each package version is stored once on disk and hard-linked into each project's node_modules. This means:
pnpm install is faster than npm install (especially after the first install)pnpm install # install all workspace dependencies
pnpm --filter server dev # run dev only in apps/server
pnpm -r typecheck # run typecheck in all workspaces
Express is the minimal HTTP framework for the API server. Paperclip keeps it simple:
src/routes/router.get(), router.post(), etc.This makes the code easy to follow and easy to extend with new routes.
Drizzle ORM is a TypeScript-first ORM that generates type-safe queries from a schema definition file.
// Schema definition (apps/server/src/db/schema.ts)
export const agents = pgTable('agents', {
id: uuid('id').primaryKey().defaultRandom(),
companyId: uuid('company_id').references(() => companies.id),
name: text('name').notNull(),
status: text('status', { enum: ['active', 'paused', 'terminated'] }).notNull(),
// ...
});
// Type-safe query — TypeScript knows the exact shape of the result
const activeAgents = await db
.select()
.from(agents)
.where(eq(agents.status, 'active'));
// activeAgents is typed as typeof agents.$inferSelect[]
Drizzle generates SQL migration files when you change the schema:
pnpm db:generate # generate migration from schema diff
pnpm db:migrate # apply pending migrations
PGlite is a PostgreSQL engine compiled to WebAssembly that runs in-process. Paperclip uses it automatically in development when DATABASE_URL is not set.
Key facts:
- Stores data in a single file (.paperclip/dev.db)
- Full PostgreSQL SQL compatibility (same queries as production)
- Zero setup — no Docker, no service to start
- Not suitable for production (single-process, no network access)
The dashboard is a React 19 SPA. Notable choices:
| Technology | Why |
|---|---|
| Vite | Near-instant HMR (hot module replacement) during development |
| React Router v6 | Client-side routing for the dashboard pages |
| React Query (TanStack) | Server state management — handles caching, refetch, loading states |
| Recharts | Cost and run timeline charts |
| Tool | Purpose |
|---|---|
| ESLint | Code style enforcement |
| Prettier | Code formatting |
| Vitest | Unit testing (same syntax as Jest) |
| tsx | Runs TypeScript files directly in Node without a compile step |
@paperclip/shared package specifically?