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.

~30 packages ESM-only Node 24+ Adapter pattern

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.

typescript
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:

typescript
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 ?? '');
}
typeProvider
'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:

typescript
// 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:

typescript
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 });
});
BackendtypeDriverNotes
SQLite'sqlite'LibSQL (@libsql/client):memory:, file, and remote Turso URLs (authToken).
PostgreSQL'postgres'pg PoolConnection pooling; pgvector via db.vector.
DuckDB'duckdb'@duckdb/node-apiJSON file auto-registration, write-back strategies.
JSON'json'DuckDB in-memoryReads/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:

typescript
// 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:

typescript
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 }]);
BackendproviderNotes
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:

typescript
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');
typeBackend
'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:

typescript
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/');
typeProvider
'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.