@happyvertical/smrt-core
ORM, code generation, AI integration, and the DispatchBus. Everything else in the SMRT framework builds on this.
Overview
@happyvertical/smrt-core is the heart of the SMRT framework. It provides:
- SmrtObject - Persistent entities with save, delete, is(), do(), loadFromId/Slug
- SmrtCollection - CRUD collection with list, get, create, upsert, advanced querying
- Code Generators - Auto-generate REST APIs, MCP servers, and CLI commands from
@smrt() - DispatchBus - Inter-agent messaging with persistent subscriptions and wildcards
- Single Table Inheritance - Polymorphic object hierarchies in a single table
- Context Memory - remember/recall with hierarchical scoped storage
- Embeddings - Built-in semantic search via AI provider or local models
- GlobalInterceptors - Plugin hooks for beforeList/Get/Save/Delete
Installation
npm install @happyvertical/smrt-coreDependencies
smrt-core builds on the HappyVertical SDK:
npm install @happyvertical/ai @happyvertical/sql @happyvertical/filesQuick Start
Create your first SMRT object in under 5 minutes:
1. Define Your Object
import { smrt, SmrtObject, SmrtCollection, foreignKey } from '@happyvertical/smrt-core';
@smrt({ api: true, cli: true, mcp: true })
class Product extends SmrtObject {
name: string = '';
price: number = 0.0; // DECIMAL (has decimal point)
quantity: number = 0; // INTEGER (no decimal point)
isPublished: boolean = false;
categoryId = foreignKey(Category);
}
class ProductCollection extends SmrtCollection<Product> {
static readonly _itemClass = Product;
}2. Initialize Collection
// Create collection (lazy table creation on first DB op)
const products = await ProductCollection.create({
db: 'products.db' // SQLite database
});3. CRUD Operations
// Create
const product = await products.create({ name: 'Widget', price: 9.99 });
await product.save();
// Query
const results = await products.list({
where: { isPublished: true, price: { op: '>', value: 5 } },
orderBy: 'price DESC',
limit: 20,
});
// Read
const one = await products.get(product.id);
// Update
product.price = 24.99;
await product.save();
// Delete
await product.delete();4. AI Operations
// Ask yes/no questions about your objects
const isExpensive = await product.is('costs more than the average product');
// Perform AI-powered actions
const description = await product.do('Write a short marketing description');Core Concepts
Architecture
smrt-core uses a registry-driven design:
┌─────────────────────────────────────────┐
│ @smrt Decorated Classes │
│ (auto-register on instantiation) │
└────────────┬────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ ObjectRegistry (Global Singleton) │
│ • Class constructors │
│ • Field definitions │
│ • Decorator configurations │
│ • Collection instances (cached) │
└────────────┬────────────────────────────┘
│
┌────────┼────────┐
↓ ↓ ↓
REST API MCP Tools CLICore Classes
- SmrtClass - Foundation with database, filesystem, and AI client access
- SmrtObject - Persistent entities with save, delete, is(), do(), context memory
- SmrtCollection<T> - CRUD collection with list, get, create, upsert, getOrUpsert
- ObjectRegistry - Global singleton (globalThis) for class metadata, fields, STI chains
- DispatchBus - Inter-agent messaging with emit, subscribe (persistent), process
Field System
Use TypeScript types for automatic schema inference:
class Product extends SmrtObject {
name: string = ''; // → TEXT
quantity: number = 0; // → INTEGER (no decimal)
price: number = 0.0; // → DECIMAL (has decimal)
active: boolean = true; // → BOOLEAN
tags: string[] = []; // → JSON
createdAt: Date = new Date(); // → DATETIME
}Or use field helpers when constraints are needed:
import { field, foreignKey } from '@happyvertical/smrt-core';
class Product extends SmrtObject {
@field({ required: true })
name: string = '';
price: number = 0.0;
categoryId = foreignKey(Category);
}AI Integration
The is() Method
Ask yes/no questions about your objects:
const isHighQuality = await document.is(`
- Contains more than 500 words
- Has clear structure and headings
- Uses professional language
`);
if (isHighQuality) {
await document.publish();
}The do() Method
Perform AI-powered actions:
const summary = await document.do(`
Create a 2-sentence summary of this document.
Focus on the key points and main conclusions.
`);
const translation = await document.do(`
Translate the title to Spanish.
`);The describe() Method
Generate human-readable descriptions:
const description = await product.describe();
// Returns professional description suitable for displayAI Tools & Function Calling
Objects expose methods as AI tools automatically:
class Document extends SmrtObject {
async summarize() { /* ... */ }
async analyze() { /* ... */ }
async translate(language: string) { /* ... */ }
}
// AI can call these methods during do() operations
const result = await document.do(`
Analyze this document and translate the summary to Spanish.
`);
// AI will call analyze() and translate() as neededAdvanced Querying
Query Operators
const products = await collection.list({
where: {
isPublished: true, // Equals (default)
price: { op: '>', value: 10 }, // Object syntax
'price <=': 100, // String-operator syntax
'name like': '%widget%', // Pattern matching
category: ['A', 'B', 'C'], // Arrays auto-detect IN
'deletedAt !=': null // Not equal
},
orderBy: 'price DESC',
limit: 20,
offset: 0
});Eager Loading (Prevent N+1 Queries)
// Load relationships efficiently with SQL JOINs
const orders = await orderCollection.list({
limit: 100,
include: ['customerId', 'productId'] // Pre-load relationships
});
// Access without additional queries
for (const order of orders) {
const customer = order.getRelated('customerId'); // Already loaded!
const product = order.getRelated('productId');
}Direct SQL Access
// Template literal safety (SQL injection prevention)
const expensive = await collection.db.many`
SELECT * FROM products
WHERE price > ${100}
ORDER BY price DESC
`;
const count = await collection.db.pluck`
SELECT COUNT(*) FROM products WHERE category = ${'electronics'}
`;Code Generation
The @smrt Decorator
Control what gets generated for each object:
@smrt({
api: { include: ['list', 'get', 'create', 'update', 'delete'] },
mcp: { include: ['list', 'get'] }, // Read-only for AI
cli: true,
swagger: true
})
export class Product extends SmrtObject { }REST API Generator
import { APIGenerator } from '@happyvertical/smrt-core';
const generator = new APIGenerator({
basePath: '/api/v1'
});
generator.registerCollection('products', productCollection);
// Generated OpenAPI-compliant endpoints:
// GET /api/v1/products - List
// POST /api/v1/products - Create
// GET /api/v1/products/:id - Get
// PUT /api/v1/products/:id - Update
// DELETE /api/v1/products/:id - DeleteMCP Server Generator
import { MCPGenerator } from '@happyvertical/smrt-core';
const generator = new MCPGenerator({
name: 'smrt-mcp-server',
version: '1.0.0'
});
generator.registerCollection('products', productCollection);
// Generated MCP tools:
// list_products, get_product_by_id, create_product,
// update_product, delete_productCLI Commands
# Auto-generated from @smrt({ cli: true })
npx smrt products list
npx smrt products get <id>
npx smrt products create --name "Widget" --price 29.99
npx smrt products update <id> --price 24.99
npx smrt products delete <id>Context Memory System
Store and retrieve learned patterns:
// Store learned patterns
await object.remember({
scope: 'parser/html/domain.com',
key: 'article-selector',
value: '.main-article',
confidence: 0.95,
metadata: { discoveredAt: new Date() }
});
// Retrieve with ancestor fallback
const context = await object.recall({
scope: 'parser/html/domain.com/news',
key: 'article-selector',
includeAncestors: true // Falls back to parent scopes
});
// Batch retrieval
const allContexts = await object.recallAll({
scope: 'config/processing',
includeDescendants: true
});
// Cleanup
await object.forget({ scope, key });
await object.forgetScope({ scope, includeDescendants: true });Use Case: Web Scraper Learning
class WebScraper extends SmrtObject {
async discoverSelector(url: string) {
const hostname = new URL(url).hostname;
// Try to recall learned selector
const remembered = await this.recall({
scope: `parser/${hostname}`,
key: 'main-content',
includeAncestors: true
});
if (remembered) return remembered.value;
// Discover and remember
const selector = await this.do(`Find CSS selector for main content`);
await this.remember({
scope: `parser/${hostname}`,
key: 'main-content',
value: selector,
confidence: 0.9
});
return selector;
}
}Relationships
Foreign Keys
import { foreignKey } from '@happyvertical/smrt-core';
class Order extends SmrtObject {
customerId = foreignKey(Customer);
productId = foreignKey(Product);
total: number = 0.0;
}
// Load relationship (lazy, cached in _loadedRelationships Map)
await order.loadRelated('customerId');
const customer = order.getRelated('customerId');One-to-Many
import { oneToMany } from '@happyvertical/smrt-core';
class Customer extends SmrtObject {
orders = oneToMany(Order, { foreignKey: 'customerId' });
}
// Access related records
const orders = await customer.loadRelated('orders');Many-to-Many
import { manyToMany } from '@happyvertical/smrt-core';
class Product extends SmrtObject {
relatedProducts = manyToMany(Product, {
through: 'product_relations'
});
}
// Access related products
const related = await product.loadRelated('relatedProducts');Single Table Inheritance
Polymorphic object hierarchies in a single database table:
import { smrt, SmrtObject, meta } from '@happyvertical/smrt-core';
@smrt({ tableStrategy: 'sti' })
class Event extends SmrtObject {
title: string = ''; // Base table column
startTime: Date = new Date(); // Base table column
}
@smrt()
class Meeting extends Event {
location: string = ''; // Base table column
@meta() roomNumber: string = ''; // Stored in _meta_data JSONB
@meta() attendees: string[] = [];
}
@smrt()
class Concert extends Event {
venue: string = ''; // Base table column
@meta() artist: string = ''; // Stored in _meta_data JSONB
@meta() ticketPrice: number = 0;
}
// Polymorphic queries — collection loads correct subclass automatically
const events = await eventCollection.list();
events.forEach(event => {
if (event instanceof Meeting) {
console.log(`Meeting at ${event.location}`);
} else if (event instanceof Concert) {
console.log(`Concert by ${event.artist}`);
}
});Vite Plugin
Auto-generate virtual modules during development:
// vite.config.ts
import { smrtPlugin } from '@happyvertical/smrt-core';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [smrtPlugin()],
// Required for @smrt() decorators
esbuild: {
tsconfigRaw: {
compilerOptions: {
experimentalDecorators: true,
emitDecoratorMetadata: true
}
}
}
});Generated Output
The Vite plugin generates virtual modules for routes, clients, and manifests at dev time. Code generators produce OpenAPI REST endpoints, Commander CLI commands, and MCP server tools from your @smrt() configuration.
Database Support
Supported Databases
- SQLite -
{ type: 'sqlite', url: 'app.db' } - PostgreSQL -
{ type: 'postgres', url: 'postgres://...' } - DuckDB -
{ type: 'duckdb', url: 'data.db' } - JSON -
{ type: 'json', url: 'data.json' }(testing only)
Configuration
// String shortcut (auto-detects type)
const collection = await ProductCollection.create({
db: 'products.db'
});
// Config object
const collection = await ProductCollection.create({
db: {
type: 'sqlite',
url: 'products.db'
}
});
// DatabaseInterface instance
import { getDatabase } from '@happyvertical/sql';
const db = await getDatabase({ type: 'postgres', url: '...' });
const collection = await ProductCollection.create({ db });Best Practices
- Use TypeScript types for simple properties -- let the framework infer the schema
- Use
0for INTEGER,0.0for DECIMAL in field defaults - Always define
static readonly _itemClasson collection classes - Never override
toJSON()-- usetransformJSON()instead (toJSON handles STI + meta fields) - Cross-package FKs: use plain string IDs, not
foreignKey()(avoids circular deps) - Leverage eager loading to prevent N+1 query problems
- Use direct SQL via template literals for complex queries
- Manifest is build-time -- restart vitest after adding new
@smrt()classes