The HAVE SDK
The HAppy VErtical SDK is the ~30-package adapter layer the SMRT framework builds on — and that you can use entirely on its own. Every
package exposes a single getX(config) factory returning a stable interface, so the
provider behind a database, a model, or a cache is a configuration detail, not a code dependency.
The getX() adapter philosophy
The SDK isolates fundamental operations — querying a database, reading a file, generating an embedding — behind stable interfaces, so downstream code is insulated from provider deprecation, API churn, and shifting pricing. You state what you need; the adapter handles how.
import { getAI } from '@happyvertical/ai';
import { getDatabase } from '@happyvertical/sql';
// Adapters accept explicit config...
const ai = await getAI({ type: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY });
const db = await getDatabase({ type: 'postgres', url: process.env.DATABASE_URL });
// ...or auto-configure from HAVE_* environment variables.
// const ai = await getAI();
// const db = await getDatabase();
// When requirements change, swap providers without touching this code.
const summary = await ai.message('Summarize the latest trends in AI agents.');
await db.insert('summaries', { content: summary.content, created_at: new Date() });Adapter pattern by default
getAI(), getDatabase(), getFilesystem(), getCache(), and getSecretStore() share one architectural shape:
call the factory with a config object, get back an interface.
Environment-variable driven
Packages read from HAVE_<PACKAGE>_* prefixes. The same config works
whether it sits in a local .env or is injected by a secrets manager — pass it
explicitly or let the factory auto-configure.
Pay for what you use
Third-party vendor SDKs are optional peer dependencies. Install only the providers you actually use; the adapter interface stays the same either way.
The "Day 2" swap
Prototype on a managed API on Day 1; when throughput spikes, moving (say) managed Redis to a self-hosted instance becomes a dependency swap, not a refactor.
ai — one interface, 10 providers
@happyvertical/ai is a multi-provider client. One interface covers chat, message, streaming, embeddings, function calling, image operations, and
text-to-speech where the provider supports them — pick the backend with type:
import { getAI } from '@happyvertical/ai';
// OpenAI (default when type is omitted)
const openai = await getAI({ apiKey: process.env.OPENAI_API_KEY });
// Anthropic Claude
const claude = await getAI({ type: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY });
// Google Gemini
const gemini = await getAI({ type: 'gemini', apiKey: process.env.GEMINI_API_KEY });
// Ollama (local model, no key)
const ollama = await getAI({ type: 'ollama', baseUrl: 'http://localhost:11434' });
// Claude CLI (uses a Claude subscription, no API key)
const cli = await getAI({ type: 'claude-cli', defaultModel: 'sonnet' });
// Same interface regardless of provider:
const reply = await claude.message('Explain generics in one sentence');
for await (const chunk of ollama.stream([{ role: 'user', content: 'Hi' }])) {
process.stdout.write(chunk.content ?? '');
}type | Provider |
|---|---|
'openai' | Default when type is omitted; chat, embeddings, vision, image, TTS. |
'litellm' | OpenAI-compatible gateway. |
'bifrost' | OpenAI-compatible gateway with governance admin APIs (ai.admin). |
'ollama' | Local-by-default models; remote/cloud hosts via baseUrl + key. |
'anthropic' | Anthropic Claude. |
'gemini' | Google Gemini. |
'bedrock' | AWS Bedrock. |
'huggingface' | Hugging Face Inference (apiToken). |
'claude-cli' | Drives a local Claude CLI / subscription; no API key. |
'qwen3-tts' | Text-to-speech only; local token-bucket pacing. |
Rate-limit pacing & usage tracking
Use rateLimit to pace calls that share a provider budget (it respects Retry-After, falling back to a configurable delay), and onUsage to
observe every call — tokens, cost, latency — with optional usageTags to correlate
usage with features or users:
// Pace requests that share a provider budget, and observe every call.
const ai = await getAI({
type: 'gemini',
apiKey: process.env.GEMINI_API_KEY,
defaultModel: 'gemini-2.5-flash',
rateLimit: { key: 'gemini:shared-batch', requestsPerMinute: 60 },
usageTags: { app: 'indagator', team: 'news' },
onUsage: (event) => {
// event => { provider, model, operation, usage?, duration, timestamp, tags? }
console.log(
`[${event.provider}/${event.model}] ${event.operation}: ` +
`${event.usage?.totalTokens} tokens in ${event.duration}ms`,
);
},
});The UsageEvent carries provider, model, operation, optional usage ({ promptTokens, completionTokens, totalTokens }), duration, timestamp, and merged tags. Errors thrown inside onUsage are swallowed, so instrumentation never breaks a request.
sql — one database interface, four backends
@happyvertical/sql gives every backend the same DatabaseInterface:
template-literal queries, CRUD helpers, transactions, schema synchronization, and vector
search. Interpolated values in a tagged query are always parameterized — never
string-concatenated:
import { getDatabase } from '@happyvertical/sql';
const db = await getDatabase({ type: 'sqlite', url: 'file:./app.db' });
// Template-literal queries: interpolated values are ALWAYS parameterized
// (never string-concatenated), with per-adapter placeholder handling.
const status = 'active';
const users = await db.many`
SELECT id, email FROM users WHERE status = ${status} LIMIT 50
`;
// CRUD helpers + transactions.
await db.transaction(async (tx) => {
const id = await tx.insert('users', { email: 'a@b.com', status });
await tx.update('profiles', { user_id: id }, { onboarded: true });
});| Backend | type | Driver | Notes |
|---|---|---|---|
| SQLite | 'sqlite' | LibSQL (@libsql/client) | :memory:, file, and remote Turso URLs (authToken). |
| PostgreSQL | 'postgres' | pg Pool | Connection pooling; pgvector via db.vector. |
| DuckDB | 'duckdb' | @duckdb/node-api | JSON file auto-registration, write-back strategies. |
| JSON | 'json' | DuckDB in-memory | Reads/writes JSON files as tables. |
Vector search (pgvector)
PostgreSQL adapters expose db.vector when pgvector is available — semantic search
lives behind the same interface as the rest of your queries:
// PostgreSQL exposes db.vector when pgvector is available.
const pg = await getDatabase({ type: 'postgres', url: process.env.DATABASE_URL });
if (pg.vector) {
await pg.vector.ensureColumn('documents', 'embedding', 1536);
const neighbors = await pg.vector.search(
'documents',
'embedding',
queryEmbedding,
{ limit: 5 },
);
}cache — four backends, one interface
@happyvertical/cache implements the same CacheProvider across Memory,
File, Redis, and S3, with TTL, eviction policies, batch operations, and optional gzip
compression. TTL is always in seconds:
import { getCache } from '@happyvertical/cache';
const cache = await getCache({
provider: 'redis',
host: 'localhost',
port: 6379,
namespace: 'app',
enableCompression: true,
});
await cache.set('user:123', { name: 'Alice' }, 3600); // TTL in seconds
const user = await cache.get('user:123');
// Same CacheProvider interface for memory, file, redis, and s3 — swap the
// provider, keep the code. Batch ops cut round-trips:
await cache.setMany([{ key: 'a', value: 1 }, { key: 'b', value: 2, ttl: 60 }]);| Backend | provider | Notes |
|---|---|---|
| Memory | 'memory' | In-process; LRU/LFU/FIFO eviction, maxSize. |
| File | 'file' | On-disk, persists across restarts; optional gzip. |
| Redis | 'redis' | Distributed via the redis client; optional gzip. |
| S3 | 's3' | S3 objects; compression on by default (CI caches). |
secrets — envelope encryption, pluggable backends
@happyvertical/secrets does per-tenant secret management with a two-tier key
hierarchy: an Application Master Key (AMK) wraps per-tenant Data Encryption Keys (TDEKs), which
encrypt secret values with AES-256-GCM. Only wrapped keys are stored, and tenant keys rotate
without re-encrypting every secret:
import { getSecretStore } from '@happyvertical/secrets';
import { getDatabase } from '@happyvertical/sql';
const db = await getDatabase({ type: 'sqlite', url: ':memory:' });
// Envelope encryption: an Application Master Key wraps per-tenant Data
// Encryption Keys, which encrypt the secret values (AES-256-GCM).
const store = await getSecretStore({
type: 'database',
db,
amk: { provider: 'env', keyEnvVar: 'MY_SECRET_KEY', keyId: 'amk-v1' },
});
await store.createTenantKey('tenant-123');
const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
const value = await store.decrypt('tenant-123', envelope);
// Rotate a tenant key without re-encrypting every secret or downtime.
await store.rotateTenantKey('tenant-123');type | Backend |
|---|---|
'database' | DB-backed wrapped-key storage (only wrapped keys hit the DB). |
'aws-kms' | AWS KMS as the master-key provider. |
'vault' | HashiCorp Vault. |
'azure-keyvault' | Azure Key Vault. |
files — filesystem abstraction
@happyvertical/files abstracts file storage across local disk, Google Drive, and
S3 behind one interface, and also ships rate-limited HTTP fetch utilities:
import { getFilesystem } from '@happyvertical/files';
// Local disk, Google Drive, or S3 — one interface.
const fs = await getFilesystem({ type: 'local', basePath: '/app/data' });
await fs.write('reports/q3.txt', 'hello');
const buf = await fs.read('reports/q3.txt');
const entries = await fs.list('reports/');type | Provider |
|---|---|
'local' | Local disk under a basePath. |
'gdrive' | Google Drive. |
's3' | S3-compatible object storage. |
The rest of the SDK
The five adapters above are the core SMRT leans on, but the SDK spans roughly 30 packages. A representative slice:
@happyvertical/utils IDs, date parsing, URLs, string conversion, sandboxing, error classes.@happyvertical/logger Structured logging; SMRT signal adapter; optional Sentry.@happyvertical/json Drop-in JSON.parse/stringify with SIMD acceleration + JS fallback.@happyvertical/documents PDF / HTML / Markdown extraction; auto-detects CMS sources.@happyvertical/encryption PGP/OpenPGP, NaCl/libsodium, Node crypto: encrypt, sign, keys.@happyvertical/auth Keycloak (OIDC/OAuth2), AWS Cognito, Nostr identity.@happyvertical/translator Google Translate, DeepL, LibreTranslate; detection + batch.@happyvertical/geo Geocoding and static maps (Google, OSM/Nominatim, Mapbox).@happyvertical/weather Environment Canada (free) and OpenWeatherMap.@happyvertical/social Publish to YouTube, Threads, X, Bluesky; analytics.@happyvertical/repos GitHub issues, PRs, labels, comments, search.@happyvertical/sdk-mcp MCP server routing dev queries to package docs.Every package ships its own README and an AGENT.md for AI-assisted
development. Full source: github.com/happyvertical/sdk →