@happyvertical/smrt-properties
Digital property and zone management for websites, apps, and advertising inventory.
v0.19.0PropertiesZones
Overview
smrt-properties manages digital properties (websites, applications) and their hierarchical zones (pages, sections, ad slots). It provides tree-structured organization with flexible metadata, dimension tracking, and format validation.
Installation
bash
npm install @happyvertical/smrt-propertiesQuick Start
typescript
import { PropertyCollection, ZoneCollection } from '@happyvertical/smrt-properties';
// Initialize collections
const properties = await PropertyCollection.create({ db: {...} });
const zones = await ZoneCollection.create({ db: {...} });
// Create property
const site = await properties.create({
name: 'Oak Creek News',
domain: 'oakcreeknews.com',
url: 'https://oakcreeknews.com',
status: 'active'
});
await site.save();
// Create page zone
const homePage = await zones.create({
propertyId: site.id,
name: 'Home Page',
type: 'page',
path: '/'
});
await homePage.save();
// Create ad slot
const headerSlot = await zones.create({
propertyId: site.id,
parentId: homePage.id,
name: 'Header Leaderboard',
type: 'slot',
width: 728,
height: 90,
allowedFormats: ['display', 'video']
});
await headerSlot.save();
// Get zone tree
const tree = await zones.getTree(site.id);
console.log(`Property has ${tree.roots.length} top-level zones`);Core Concepts
Property Model
typescript
class Property extends SmrtObject {
name: string
domain: string
url: string
status: 'active' | 'inactive' | 'pending'
ownerId?: string // Optional profile link
repositoryId?: string // Optional project link
metadata?: Record<string, any>
async getZones(): Promise<Zone[]>
async getZoneTree(): Promise<ZoneTree>
async createZone(options): Promise<Zone>
async summarize(): Promise<string> // AI-powered
}Zone Model
typescript
class Zone extends SmrtObject {
propertyId: string
parentId?: string // Self-referencing hierarchy
name: string
type: string // 'page', 'section', 'slot', etc.
path?: string
selector?: string // CSS selector
width?: number
height?: number
allowedFormats?: string[] // ['display', 'video', 'native']
metadata?: Record<string, any>
async getProperty(): Promise<Property>
async getParent(): Promise<Zone | null>
async getChildren(): Promise<Zone[]>
async getAncestors(): Promise<Zone[]>
async getDescendants(): Promise<Zone[]>
async getFullPath(): string
async getDepth(): number
isFormatAllowed(format: string): boolean
hasDimensions(): boolean
getDimensionString(): string
}API Reference
PropertyCollection
typescript
await properties.findByDomain(domain: string)
await properties.findByOwner(ownerId: string)
await properties.findActive()
await properties.getOrCreateByDomain(domain, defaults)
await properties.countByStatus()ZoneCollection
typescript
await zones.findByProperty(propertyId: string)
await zones.findTopLevel(propertyId: string)
await zones.getTree(propertyId: string): Promise<ZoneTree>
await zones.getAncestors(zoneId: string)
await zones.getDescendants(zoneId: string)
await zones.moveZone(zoneId: string, newParentId?: string)
await zones.deleteZone(zoneId: string, cascade: boolean)Examples
Example 1: Multi-Zone Website
typescript
// Create property
const site = await properties.create({
name: 'News Site',
domain: 'news.com',
status: 'active'
});
await site.save();
// Create pages
const home = await zones.create({
propertyId: site.id,
name: 'Home',
type: 'page',
path: '/'
});
await home.save();
const articles = await zones.create({
propertyId: site.id,
name: 'Articles',
type: 'page',
path: '/articles/*'
});
await articles.save();
// Create slots
const headerAd = await zones.create({
propertyId: site.id,
parentId: home.id,
name: 'Header Ad',
type: 'slot',
width: 728,
height: 90
});
await headerAd.save();Example 2: Zone Traversal
typescript
// Get tree structure
const tree = await zones.getTree(site.id);
// Get full path
const path = await headerAd.getFullPath(); // "Home > Header Ad"
// Get ancestors
const ancestors = await headerAd.getAncestors(); // [home]
// Get all descendants of page
const slots = await home.getDescendants();Example 3: Format Validation
typescript
// Configure allowed formats
const videoSlot = await zones.create({
propertyId: site.id,
name: 'Video Player',
allowedFormats: ['video', 'native']
});
await videoSlot.save();
// Check compatibility
if (videoSlot.isFormatAllowed('video')) {
console.log('Video ads allowed');
}
// Find zones by dimensions
const leaderboards = await zones.list({
where: {
propertyId: site.id,
width: 728,
height: 90
}
});Best Practices
✓ DOs
- Save properties before creating zones
- Cache zone trees in-memory for performance
- Use moveZone() for reparenting (prevents cycles)
- Validate formats with isFormatAllowed() before assignment
- Use findByDimensions() to pre-filter ad slots
✗ DON'Ts
- Don't manually set parentId to a descendant (causes cycles)
- Don't delete properties without handling zones first
- Don't assume unlimited nesting without depth checks
- Don't query zones repeatedly in loops (use batch operations)
- Don't skip save() after creation (id requires persistence)