s-m-r-t

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

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

Related Modules