@happyvertical/smrt-types
Shared TypeScript types and enums for the SMRT framework. Prevents circular dependencies by centralizing types that multiple packages need. Zero runtime code except enums.
Overview
@happyvertical/smrt-types provides shared TypeScript definitions that are used across
the entire SMRT ecosystem. By centralizing these types, we avoid circular dependencies between packages
and ensure consistent interfaces throughout the SDK.
This package exports three categories: Signal System types (method tracking and observability), Module UI types (module registration and admin panels), and User/Tenant Status enums (lifecycle status values with runtime code).
Installation
pnpm add @happyvertical/smrt-typesNote: This package is typically installed as a dependency of other SMRT packages. You rarely need to install it directly.
Signal System
The Signal System provides automatic observability into SMRT method execution, enabling logging, metrics, pub/sub updates, and distributed tracing without manual instrumentation.
Signal Interface
import type { Signal } from '@happyvertical/smrt-types';
// Signal structure
interface Signal {
// Execution tracking
id: string; // Unique execution ID
objectId: string; // SMRT object instance ID
className: string; // SMRT class name
method: string; // Method being executed
// Lifecycle stage
type: 'start' | 'step' | 'end' | 'error';
// Optional progress tracking
step?: string; // Custom step label
// Execution data
args?: any[]; // Sanitized arguments
result?: any; // Return value (on 'end')
error?: Error; // Error (on 'error')
duration?: number; // Execution time in ms
// Metadata
timestamp: Date;
metadata?: Record<string, any>;
}SignalType
Four lifecycle stages for method execution:
start- Method execution startedstep- Manual progress step (optional)end- Method completed successfullyerror- Method failed with error
type SignalType = 'start' | 'step' | 'end' | 'error';SignalAdapter Interface
Adapters consume signals for specific purposes (logging, metrics, tracing, etc.):
import type { SignalAdapter, Signal } from '@happyvertical/smrt-types';
class MyAdapter implements SignalAdapter {
async handle(signal: Signal): Promise<void> {
// Process the signal
// Errors are caught by SignalBus
}
}
// Common adapter use cases:
// - Logging: Write to console, file, or logging service
// - Metrics: Track execution counts, durations, errors
// - Pub/Sub: Broadcast real-time updates to clients
// - Tracing: Send spans to distributed tracing systemsModule UI Types
Types for module registration and admin panel UI slots. Used by packages that register UI components for admin interfaces.
import type {
SmrtModuleMeta, // Module metadata (name, version, description)
ModuleUISlot, // UI slot definition for module admin panels
ModuleComponentType, // Component type classification
ModuleUIBaseProps, // Base props interface for module UI components
ModuleUIRegistryInterface // Registry for module UI registration
} from '@happyvertical/smrt-types';User/Tenant Status Enums
Runtime enum values for user and tenant lifecycle status. These are the only exports
with runtime code -- use regular imports (not import type).
import {
UserStatus, // User lifecycle status
TenantStatus, // Tenant lifecycle status
MembershipStatus, // Membership lifecycle status
SessionStatus, // Session lifecycle status
OverrideEffect, // Permission override effect
TenantPermissionEffect // Tenant-level permission effect
} from '@happyvertical/smrt-types';Usage in SMRT Objects
SMRT objects automatically emit signals during method execution. You can listen to these signals using the internal signal bus:
import { SmrtObject } from '@happyvertical/smrt-core';
import type { Signal } from '@happyvertical/smrt-types';
class Product extends SmrtObject {
async analyze() {
// Signals automatically emitted:
// - 'start' when method begins
// - 'end' when method completes
// - 'error' if method throws
}
}
// Listen to signals
const product = new Product();
const signalBus = product._signalBus;
signalBus?.on('method:before', (signal: Signal) => {
console.log(`Starting: ${signal.method}`);
});
signalBus?.on('method:after', (signal: Signal) => {
console.log(`Completed: ${signal.method} in ${signal.duration}ms`);
});
signalBus?.on('method:error', (signal: Signal) => {
console.error(`Failed: ${signal.method}`, signal.error);
});
await product.analyze();Custom Step Signals
Emit manual progress steps within methods:
class Document extends SmrtObject {
async process() {
this._signalBus?.emit({
id: 'exec-123',
objectId: this.id,
className: 'Document',
method: 'process',
type: 'step',
step: 'downloading',
timestamp: new Date()
});
await this.download();
this._signalBus?.emit({
...
step: 'parsing',
...
});
await this.parse();
}
}Signal Adapters
Example: Logging Adapter
import type { SignalAdapter, Signal } from '@happyvertical/smrt-types';
class LoggingAdapter implements SignalAdapter {
async handle(signal: Signal): Promise<void> {
const log = {
time: signal.timestamp,
class: signal.className,
method: signal.method,
type: signal.type
};
if (signal.type === 'end') {
log.duration = signal.duration;
console.log('✓', log);
} else if (signal.type === 'error') {
log.error = signal.error?.message;
console.error('✗', log);
} else if (signal.type === 'start') {
console.log('→', log);
}
}
}Example: Metrics Adapter
class MetricsAdapter implements SignalAdapter {
private metrics = new Map<string, number[]>();
async handle(signal: Signal): Promise<void> {
if (signal.type === 'end' && signal.duration) {
const key = `${signal.className}.${signal.method}`;
const durations = this.metrics.get(key) || [];
durations.push(signal.duration);
this.metrics.set(key, durations);
}
}
getAverageDuration(className: string, method: string): number {
const key = `${className}.${method}`;
const durations = this.metrics.get(key) || [];
return durations.reduce((a, b) => a + b, 0) / durations.length;
}
}Example: Pub/Sub Adapter
class WebSocketAdapter implements SignalAdapter {
constructor(private ws: WebSocket) {}
async handle(signal: Signal): Promise<void> {
// Broadcast progress to connected clients
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
event: 'method:progress',
data: {
class: signal.className,
method: signal.method,
type: signal.type,
progress: signal.step,
timestamp: signal.timestamp
}
}));
}
}
}Why Centralize Types?
- Prevent Circular Dependencies - Shared types can be imported without creating dependency cycles
- Single Source of Truth - Type definitions maintained in one place
- Version Synchronization - All packages use the same type version
- Type Safety - TypeScript ensures consistency across the SDK
- Tree Shaking - Zero runtime overhead (types only)
API Reference
Exports
// Signal System (types — no runtime code)
import type { Signal, SignalType, SignalAdapter } from '@happyvertical/smrt-types';
// Module UI (types — no runtime code)
import type {
SmrtModuleMeta, ModuleUISlot, ModuleComponentType,
ModuleUIBaseProps, ModuleUIRegistryInterface
} from '@happyvertical/smrt-types';
// User/Tenant Status (enums — have runtime values, use regular import)
import {
UserStatus, TenantStatus, MembershipStatus,
SessionStatus, OverrideEffect, TenantPermissionEffect
} from '@happyvertical/smrt-types';Signal Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique execution identifier |
objectId | string | SMRT object instance ID |
className | string | SMRT class name |
method | string | Method being executed |
type | SignalType | Lifecycle stage |
step | string? | Custom progress label |
args | any[]? | Sanitized arguments |
result | any? | Return value (on 'end') |
error | Error? | Error (on 'error') |
duration | number? | Execution time (ms) |
timestamp | Date | When signal was emitted |
metadata | Record? | Additional context |
Best Practices
- Use
import typefor non-enum imports to avoid unnecessary runtime dependencies - Use regular
importfor enums (UserStatus, TenantStatus, etc.) which need runtime values - Add shared types here if two or more packages need the same type definition
- Handle adapter errors - SignalAdapter.handle() should catch its own errors
- Sanitize sensitive data - Don't include passwords or keys in signal.args
- Keep adapters lightweight - Signals are emitted frequently; avoid heavy operations