Wrappers: Capabilities
Wrappers are how Happen maintains its core philosophy of radical simplicity while enabling powerful extensions. The core framework stays focused on just Nodes and Events, while wrappers provide specialized functionality built on top of these primitives.
Why Wrappers?
Happen's core is intentionally minimal - just two primitives (Nodes and Events) with causality tracking. This design choice enables:
Minimal dependencies in the core framework
Universal compatibility across environments
Predictable behavior regardless of extensions
Optional complexity - only pay for what you use
Wrappers let you add sophisticated functionality while preserving this simplicity.
How Wrappers Work
Wrappers are just specialized Node factories that create nodes with specific behaviors. They don't change how Happen works - they build on existing patterns.
Basic Wrapper Pattern
// A wrapper is just a function that creates configured nodes
function createDatabaseNode(config) {
const node = createNode(config.name)
// Add specialized event handlers
node.on('db.query', async (event) => {
const result = await database.query(event.payload.sql)
node.broadcast({
type: 'db.result',
payload: result,
correlationId: event.correlationId
})
})
node.on('db.insert', async (event) => {
// Handle inserts...
})
return node
}
// Usage - still just regular Happen patterns
const userDB = createDatabaseNode({ name: 'user-db' })
system.broadcast({
type: 'db.query',
payload: { sql: 'SELECT * FROM users WHERE active = true' }
})The wrapper provides convenience, but underneath it's just Nodes handling Events.
The happen-agents Wrapper
The agents wrapper we discussed is a perfect example:
import { createAgentNode, createContextNode } from 'happen-agents'
// Creates a node that handles the agent event patterns
const researcher = createAgentNode({
llm: openai,
name: 'research-agent'
})
// Creates a node that composes context dynamically
const context = createContextNode({
compose: {
expertise: () => system.state.get('domain-knowledge'),
constraints: () => system.state.get('compliance-rules')
}
})
// Still just regular event flow
system.broadcast({
type: 'agent.research',
payload: {
goal: "analyze competitor pricing",
contexts: [context.id]
}
})The wrapper handles the LLM integration complexity, but the interface remains pure Happen.
Building Your Own Wrapper
Creating a wrapper is straightforward. Here's a template:
export function createMyWrapper(config) {
const node = createNode(config.name)
// Initialize any external dependencies
const externalService = new SomeAPI(config.apiKey)
// Add specialized event handlers
node.on('my-wrapper.action', async (event) => {
try {
// Do something with external service
const result = await externalService.process(event.payload)
// Emit results as events
node.broadcast({
type: 'my-wrapper.complete',
payload: result,
correlationId: event.correlationId
})
} catch (error) {
// Error handling through events too
node.broadcast({
type: 'my-wrapper.error',
payload: { error: error.message },
correlationId: event.correlationId
})
}
})
// Add helper methods if needed
node.customMethod = () => {
// But still emit events for actual work
node.broadcast({ type: 'my-wrapper.custom' })
}
return node
}Wrapper Best Practices
Keep the interface event-based: Don't expose complex APIs. Let everything flow through events.
Maintain causality: Always use correlationId to preserve causal chains.
Handle errors as events: Don't throw exceptions - emit error events.
Stay stateless when possible: Store state in the node, not in closure variables.
Document event types: Make it clear what events your wrapper handles and emits.
Wrapper Ecosystem
The wrapper pattern enables a rich ecosystem:
Data & Storage
happen-postgres- PostgreSQL integrationhappen-redis- Redis pub/sub and cachinghappen-s3- AWS S3 file operationshappen-elasticsearch- Full-text search
AI & ML
happen-agents- LLM-based intelligent agentshappen-embeddings- Vector similarity searchhappen-vision- Image analysis and generationhappen-speech- Speech-to-text and text-to-speech
Communication
happen-slack- Slack bot integrationhappen-email- SMTP email sendinghappen-websocket- Real-time client connectionshappen-webhook- HTTP webhook handling
Monitoring
happen-metrics- Prometheus metrics collectionhappen-logging- Structured logginghappen-tracing- Distributed tracinghappen-alerts- Alert management
Multiple Wrappers Together
Wrappers compose naturally:
import { createAgentNode } from 'happen-agents'
import { createDatabaseNode } from 'happen-postgres'
import { createSlackNode } from 'happen-slack'
const analyst = createAgentNode({ llm: openai })
const database = createDatabaseNode({ connectionString: "..." })
const slack = createSlackNode({ botToken: "..." })
// Agent can request data
analyst.on('agent.complete', (event) => {
database.broadcast({
type: 'db.insert',
payload: { table: 'reports', data: event.payload }
})
})
// Database can trigger notifications
database.on('db.complete', (event) => {
slack.broadcast({
type: 'slack.message',
payload: {
channel: '#reports',
text: 'New analysis complete!'
}
})
})Each wrapper handles its specialty, but they coordinate through Happen's event system.
Wrapper Development Guidelines
Installation
npm install happen happen-your-wrapperPeer Dependencies
List happen as a peer dependency, not a regular dependency:
{
"peerDependencies": {
"happen": "^1.0.0"
}
}TypeScript Support
Provide types for your event payloads:
interface DatabaseQueryEvent {
type: 'db.query'
payload: {
sql: string
params?: any[]
}
}
interface DatabaseResultEvent {
type: 'db.result'
payload: {
rows: any[]
rowCount: number
}
}Testing
Test your wrapper with regular Happen patterns:
test('handles query events', async () => {
const db = createDatabaseNode({ name: 'test-db' })
const resultPromise = new Promise(resolve => {
db.on('db.result', resolve)
})
db.broadcast({
type: 'db.query',
payload: { sql: 'SELECT 1' }
})
const result = await resultPromise
expect(result.payload.rows).toHaveLength(1)
})The Wrapper Philosophy
Wrappers embody Happen's core philosophy:
Simple interface, powerful behavior: Complex functionality accessible through simple event patterns.
Composable by design: Any wrapper works with any other wrapper through events.
Causality preserved: All wrapper interactions maintain full causal tracking.
Environment agnostic: Same wrapper works in browser, server, worker, edge.
Optional complexity: Use only the wrappers you need, when you need them.
Wrappers let Happen grow without compromising its essential simplicity. They're how we achieve the goal of being infinitely extensible while remaining fundamentally understandable.
Last updated