@happyvertical/smrt-core

ORM, code generation, AI integration, and the DispatchBus. Everything else in the SMRT framework builds on this.

v0.20.44Core FoundationESM

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

bash
npm install @happyvertical/smrt-core

Dependencies

smrt-core builds on the HappyVertical SDK:

bash
npm install @happyvertical/ai @happyvertical/sql @happyvertical/files

Quick Start

Create your first SMRT object in under 5 minutes:

1. Define Your Object

typescript
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

typescript
// Create collection (lazy table creation on first DB op)
const products = await ProductCollection.create({
  db: 'products.db'  // SQLite database
});

3. CRUD Operations

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

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

text
┌─────────────────────────────────────────┐
│  @smrt Decorated Classes                │
│  (auto-register on instantiation)       │
└────────────┬────────────────────────────┘
             │
             ↓
┌─────────────────────────────────────────┐
│  ObjectRegistry (Global Singleton)      │
│  • Class constructors                   │
│  • Field definitions                    │
│  • Decorator configurations             │
│  • Collection instances (cached)        │
└────────────┬────────────────────────────┘
             │
    ┌────────┼────────┐
    ↓        ↓        ↓
 REST API  MCP Tools  CLI

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

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

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

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

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

typescript
const description = await product.describe();
// Returns professional description suitable for display

AI Tools & Function Calling

Objects expose methods as AI tools automatically:

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

Advanced Querying

Query Operators

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

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

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

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

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

MCP Server Generator

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

CLI Commands

bash
# 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:

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

typescript
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

typescript
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

typescript
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

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

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

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

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

  1. Use TypeScript types for simple properties -- let the framework infer the schema
  2. Use 0 for INTEGER, 0.0 for DECIMAL in field defaults
  3. Always define static readonly _itemClass on collection classes
  4. Never override toJSON() -- use transformJSON() instead (toJSON handles STI + meta fields)
  5. Cross-package FKs: use plain string IDs, not foreignKey() (avoids circular deps)
  6. Leverage eager loading to prevent N+1 query problems
  7. Use direct SQL via template literals for complex queries
  8. Manifest is build-time -- restart vitest after adding new @smrt() classes

Next Steps