@happyvertical/smrt-properties 0.35.0 → 0.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +4 -4
- package/package.json +6 -6
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/prompts.ts","../src/models/Property.ts","../src/collections/Properties.ts","../src/models/Zone.ts","../src/collections/Zones.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Prompt registrations for the @happyvertical/smrt-properties package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-profiles` (see\n * `prompts.ts`) and `@happyvertical/smrt-content` (see `content-prompts.ts`).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n// Property summarization only uses non-PII property fields\n// (name, domain, description, status) plus aggregate zone information.\n// Internal/foreign-key fields like ownerId, repositoryId, tenantId, and the\n// extensible `metadata` blob are intentionally NOT passed to the AI\n// provider — they may link to identifying records or contain analytics IDs\n// and other configuration secrets. If a downstream tenant needs richer\n// context they can override the template via PromptOverride.\nexport const smrtPropertiesSummarizePrompt = definePrompt({\n key: 'smrtProperties.property.summarize',\n template: `Summarize this digital property:\nName: {propertyName}\nDomain: {propertyDomain}\nDescription: {propertyDescription}\nStatus: {propertyStatus}\nTotal zones: {totalZones}\nTop-level zones: {topLevelZones}`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * Property model - Digital property representation\n *\n * Represents a digital property (website, app, publication) that can contain\n * hierarchical zones for content/ad placement.\n */\n\nimport { crossPackageRef, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtPropertiesSummarizePrompt,\n} from '../prompts';\nimport type { PropertyOptions, PropertyStatus } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class Property extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Display name of the property\n */\n name: string = '';\n\n /**\n * Domain name (e.g., \"oakcreeknews.com\")\n */\n domain: string = '';\n\n /**\n * Full URL (e.g., \"https://oakcreeknews.com\")\n */\n url: string = '';\n\n /**\n * Property description\n */\n description: string = '';\n\n /**\n * Link to Repository (from smrt-projects)\n * Optional - used when property is backed by a git repository\n */\n @crossPackageRef('@happyvertical/smrt-projects:Repository')\n repositoryId: string | null = null;\n\n /**\n * Link to Profile (from smrt-profiles)\n * Optional - owner/publisher of the property\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n ownerId: string | null = null;\n\n /**\n * Property status\n */\n status: PropertyStatus = 'active';\n\n /**\n * Extensible metadata (analytics IDs, config, etc.)\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: PropertyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId as any;\n if (options.name !== undefined) this.name = options.name;\n if (options.domain !== undefined) this.domain = options.domain;\n if (options.url !== undefined) this.url = options.url;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.repositoryId !== undefined)\n this.repositoryId = options.repositoryId;\n if (options.ownerId !== undefined) this.ownerId = options.ownerId;\n if (options.status !== undefined) this.status = options.status;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Get all zones for this property\n */\n async getZones(): Promise<import('./Zone').Zone[]> {\n if (!this.id) return [];\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await (ZoneCollection as any).create(this.options);\n return await collection.findByProperty(this.id);\n }\n\n /**\n * Get zone tree for this property\n */\n async getZoneTree(): Promise<\n import('../types').ZoneTree<import('./Zone').Zone>\n > {\n if (!this.id) return { propertyId: '', roots: [] };\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await (ZoneCollection as any).create(this.options);\n return await collection.getTree(this.id);\n }\n\n /**\n * Create a zone on this property\n */\n async createZone(\n options: Omit<import('../types').ZoneOptions, 'propertyId'>,\n ): Promise<import('./Zone').Zone> {\n if (!this.id) {\n throw new Error('Property must be saved before creating zones');\n }\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await (ZoneCollection as any).create(this.options);\n const zone = await collection.create({\n ...options,\n propertyId: this.id,\n });\n await zone.save();\n return zone;\n }\n\n /**\n * Check if property is active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * AI-powered: Generate a summary of the property and its zones.\n *\n * Uses the `smrtProperties.property.summarize` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Only non-PII fields (name, domain, description, status) plus aggregate\n * zone information are sent to the AI provider. Internal foreign-key\n * fields and the extensible `metadata` blob are intentionally excluded.\n *\n * @returns Generated summary text\n */\n async summarize(): Promise<string> {\n const zones = await this.getZones();\n const topLevelZones = zones.filter((z) => z.parentId === null);\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias. SmrtClass maps `persistence → db` lazily during `initialize()`,\n // so on a freshly-constructed Property that has not yet been initialized,\n // `this.options.db` may be undefined while `this.options.persistence` is\n // set. Falling back here ensures stored app- and tenant-level prompt\n // overrides in `_smrt_prompt_overrides` are honored on the first call —\n // before `getAiClient()` triggers full initialization further below.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtPropertiesSummarizePrompt.key,\n {\n db,\n tenantId: this.tenantId,\n variables: {\n propertyName: this.name || '',\n propertyDomain: this.domain || '',\n propertyDescription: this.description || '',\n propertyStatus: this.status || '',\n totalZones: String(zones.length),\n topLevelZones: topLevelZones.map((z) => z.name).join(', '),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return response.trim();\n }\n}\n","/**\n * PropertyCollection - Collection manager for Property objects\n *\n * Provides querying and management operations for Property entities.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Property } from '../models/Property';\nimport type { PropertyStatus } from '../types';\n\nexport class PropertyCollection extends SmrtCollection<Property> {\n static readonly _itemClass = Property;\n\n /**\n * Find a property by domain\n *\n * @param domain - Domain name\n * @returns Property or null\n */\n async findByDomain(domain: string): Promise<Property | null> {\n const results = await this.list({\n where: { domain },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find properties by repository ID\n *\n * @param repositoryId - Repository ID\n * @returns Array of properties\n */\n async findByRepository(repositoryId: string): Promise<Property[]> {\n return await this.list({\n where: { repositoryId },\n });\n }\n\n /**\n * Find properties by owner ID\n *\n * @param ownerId - Owner profile ID\n * @returns Array of properties\n */\n async findByOwner(ownerId: string): Promise<Property[]> {\n return await this.list({\n where: { ownerId },\n });\n }\n\n /**\n * Find active properties\n *\n * @returns Array of active properties\n */\n async findActive(): Promise<Property[]> {\n return await this.list({\n where: { status: 'active' as PropertyStatus },\n });\n }\n\n /**\n * Find properties by status\n *\n * @param status - Property status\n * @returns Array of properties with the given status\n */\n async findByStatus(status: PropertyStatus): Promise<Property[]> {\n return await this.list({\n where: { status },\n });\n }\n\n /**\n * Get or create a property by domain\n *\n * @param domain - Domain name\n * @param defaults - Default values for creation\n * @returns Property (existing or newly created)\n */\n async getOrCreateByDomain(\n domain: string,\n defaults: {\n name?: string;\n url?: string;\n repositoryId?: string | null;\n ownerId?: string | null;\n } = {},\n ): Promise<Property> {\n let property = await this.findByDomain(domain);\n\n if (!property) {\n property = await this.create({\n domain,\n name: defaults.name || domain,\n url: defaults.url || `https://${domain}`,\n repositoryId: defaults.repositoryId ?? null,\n ownerId: defaults.ownerId ?? null,\n status: 'active',\n });\n await property.save();\n }\n\n return property;\n }\n\n /**\n * Count properties by status\n *\n * @returns Object with counts per status\n */\n async countByStatus(): Promise<Record<PropertyStatus, number>> {\n const all = await this.list({});\n const counts: Record<PropertyStatus, number> = {\n active: 0,\n inactive: 0,\n pending: 0,\n };\n\n for (const prop of all) {\n counts[prop.status]++;\n }\n\n return counts;\n }\n\n // ============================================\n // Tenant Helper Methods\n // ============================================\n\n /**\n * Find properties belonging to a specific tenant\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of properties for the tenant\n */\n async findByTenant(tenantId: string): Promise<Property[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global properties (no tenant association).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global properties\n */\n async findGlobal(): Promise<Property[]> {\n return queryGlobal<Property>(this);\n }\n\n /**\n * Find properties for a tenant plus all global properties.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of tenant-specific and global properties\n */\n async findWithGlobals(tenantId: string): Promise<Property[]> {\n return queryWithGlobals<Property>(\n this,\n tenantId,\n 'Property.findWithGlobals',\n );\n }\n}\n","/**\n * Zone model - Hierarchical placement area within a property\n *\n * Zones represent pages, sections, or slots within a digital property.\n * They can be nested arbitrarily using the parentId self-reference.\n */\n\nimport { foreignKey, SmrtHierarchical, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ZoneOptions, ZoneTreeNode } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class Zone extends SmrtHierarchical {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (required)\n */\n @foreignKey('Property')\n propertyId: string = '';\n\n // parentId inherited from SmrtHierarchical (null = top-level zone)\n\n /**\n * Display name\n */\n name: string = '';\n\n /**\n * Zone type (descriptive, not structural)\n * Common values: \"page\", \"section\", \"slot\", \"container\", \"widget\"\n */\n type: string = '';\n\n /**\n * URL path pattern (e.g., \"/\", \"/articles/*\")\n */\n path: string = '';\n\n /**\n * CSS selector for placement (e.g., \"#header\", \".sidebar\")\n */\n selector: string = '';\n\n /**\n * Position hint (e.g., \"top\", \"after-paragraph-3\")\n */\n position: string = '';\n\n /**\n * Width in pixels (null = fluid)\n */\n width: number | null = null;\n\n /**\n * Height in pixels (null = fluid)\n */\n height: number | null = null;\n\n /**\n * Allowed content/ad formats\n */\n allowedFormats: string[] = [];\n\n /**\n * Default content/asset ID for fallback\n */\n defaultContentId: string | null = null;\n\n /**\n * Extensible metadata\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: ZoneOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId as any;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.parentId !== undefined) this.parentId = options.parentId;\n if (options.name !== undefined) this.name = options.name;\n if (options.type !== undefined) this.type = options.type;\n if (options.path !== undefined) this.path = options.path;\n if (options.selector !== undefined) this.selector = options.selector;\n if (options.position !== undefined) this.position = options.position;\n if (options.width !== undefined) this.width = options.width;\n if (options.height !== undefined) this.height = options.height;\n if (options.allowedFormats !== undefined)\n this.allowedFormats = options.allowedFormats;\n if (options.defaultContentId !== undefined)\n this.defaultContentId = options.defaultContentId;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Check if this is a top-level zone (no parent)\n */\n isTopLevel(): boolean {\n return this.parentId === null;\n }\n\n /**\n * Check if this zone has dimensions set\n */\n hasDimensions(): boolean {\n return this.width !== null && this.height !== null;\n }\n\n /**\n * Get dimensions as string (e.g., \"728x90\")\n */\n getDimensionString(): string | null {\n if (!this.hasDimensions()) return null;\n return `${this.width}x${this.height}`;\n }\n\n /**\n * Get the parent property\n */\n async getProperty(): Promise<import('./Property').Property | null> {\n const { PropertyCollection } = await import('../collections/Properties');\n const collection = await (PropertyCollection as any).create(this.options);\n return await collection.get({ id: this.propertyId });\n }\n\n // Hierarchy traversal (getParent / getChildren / getAncestors /\n // getDescendants / getHierarchy / moveTo) provided by SmrtHierarchical.\n // The collection-level depth cache in `ZoneCollection.getAncestors` /\n // `getDescendants` remains available for callers that go through the\n // collection — the base-class methods do their own batched queries.\n\n /**\n * Get the full path from root to this zone\n */\n async getFullPath(): Promise<string> {\n const ancestors = await this.getAncestors();\n const names = [...ancestors.map((z) => z.name), this.name];\n return names.join(' > ');\n }\n\n /**\n * Get the depth in the hierarchy (0 = top-level)\n */\n async getDepth(): Promise<number> {\n const ancestors = await this.getAncestors();\n return ancestors.length;\n }\n\n /**\n * Create a child zone under this zone\n */\n async createChild(\n options: Omit<ZoneOptions, 'propertyId' | 'parentId'>,\n ): Promise<Zone> {\n if (!this.id) {\n throw new Error('Zone must be saved before creating children');\n }\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await (ZoneCollection as any).create(this.options);\n const zone = await collection.create({\n ...options,\n propertyId: this.propertyId,\n parentId: this.id,\n });\n await zone.save();\n return zone;\n }\n\n /**\n * Build tree node for this zone and its children\n */\n async toTreeNode(): Promise<ZoneTreeNode<Zone>> {\n const children = await this.getChildren();\n const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));\n return {\n zone: this,\n children: childNodes,\n };\n }\n\n /**\n * Check if a format is allowed in this zone\n */\n isFormatAllowed(format: string): boolean {\n if (this.allowedFormats.length === 0) return true; // No restrictions\n return this.allowedFormats.includes(format);\n }\n}\n","/**\n * ZoneCollection - Collection manager for Zone objects\n *\n * Provides querying and tree operations for Zone entities.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Zone } from '../models/Zone';\nimport type { ZoneTree, ZoneTreeNode } from '../types';\n\nexport class ZoneCollection extends SmrtCollection<Zone> {\n static readonly _itemClass = Zone;\n\n /**\n * Find all zones for a property\n *\n * @param propertyId - Property ID\n * @returns Array of zones\n */\n async findByProperty(propertyId: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId },\n });\n }\n\n /**\n * Find top-level zones for a property (no parent)\n *\n * @param propertyId - Property ID\n * @returns Array of top-level zones\n */\n async findTopLevel(propertyId: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, parentId: null },\n });\n }\n\n /**\n * Find direct children of a zone\n *\n * @param parentId - Parent zone ID\n * @returns Array of child zones\n */\n async findChildren(parentId: string): Promise<Zone[]> {\n return await this.list({\n where: { parentId },\n });\n }\n\n /**\n * Find zones by path pattern\n *\n * @param propertyId - Property ID\n * @param path - URL path to match\n * @returns Array of matching zones\n */\n async findByPath(propertyId: string, path: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, path },\n });\n }\n\n /**\n * Find zones by type\n *\n * @param propertyId - Property ID\n * @param type - Zone type\n * @returns Array of zones of the given type\n */\n async findByType(propertyId: string, type: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, type },\n });\n }\n\n /**\n * Get the complete zone tree for a property\n *\n * @param propertyId - Property ID\n * @returns ZoneTree structure\n */\n async getTree(propertyId: string): Promise<ZoneTree<Zone>> {\n const allZones = await this.findByProperty(propertyId);\n const zoneMap = new Map<string, Zone>();\n const childrenMap = new Map<string, Zone[]>();\n\n // Build lookup maps\n for (const zone of allZones) {\n if (zone.id) {\n zoneMap.set(zone.id, zone);\n }\n if (!childrenMap.has(zone.parentId || '')) {\n childrenMap.set(zone.parentId || '', []);\n }\n childrenMap.get(zone.parentId || '')?.push(zone);\n }\n\n // Build tree nodes recursively\n const buildNode = (zone: Zone): ZoneTreeNode<Zone> => {\n const children = (zone.id ? childrenMap.get(zone.id) : []) || [];\n return {\n zone,\n children: children.map(buildNode),\n };\n };\n\n // Get root nodes (no parent)\n const roots = childrenMap.get('') || [];\n\n return {\n propertyId,\n roots: roots.map(buildNode),\n };\n }\n\n /**\n * Get all ancestors of a zone (path to root)\n *\n * @param zoneId - Zone ID\n * @returns Array of ancestors (from root to parent)\n */\n async getAncestors(zoneId: string): Promise<Zone[]> {\n const ancestors: Zone[] = [];\n\n // Get the starting zone\n const startZone = await this.get({ id: zoneId });\n if (!startZone) return ancestors;\n\n let currentId: string | null = startZone.parentId;\n\n // Walk up the tree\n while (currentId) {\n const parent = await this.get({ id: currentId });\n if (!parent) break;\n ancestors.unshift(parent); // Add to front (root first)\n currentId = parent.parentId;\n }\n\n return ancestors;\n }\n\n /**\n * Get all descendants of a zone recursively\n *\n * @param zoneId - Zone ID\n * @returns Array of all descendant zones\n */\n async getDescendants(zoneId: string): Promise<Zone[]> {\n const descendants: Zone[] = [];\n const queue: string[] = [zoneId];\n\n while (queue.length > 0) {\n const currentId = queue.shift();\n if (!currentId) continue;\n\n const children = await this.findChildren(currentId);\n\n for (const child of children) {\n descendants.push(child);\n if (child.id) {\n queue.push(child.id);\n }\n }\n }\n\n return descendants;\n }\n\n /**\n * Get the depth of the deepest zone in a property\n *\n * @param propertyId - Property ID\n * @returns Maximum depth (0 = only top-level zones)\n */\n async getMaxDepth(propertyId: string): Promise<number> {\n const allZones = await this.findByProperty(propertyId);\n\n // Build a lookup map for efficient in-memory traversal\n const zonesById = new Map<string, Zone>();\n for (const zone of allZones) {\n if (zone.id) {\n zonesById.set(zone.id, zone);\n }\n }\n\n // Cache computed depths\n const depthCache = new Map<string, number>();\n\n const getDepth = (zone: Zone): number => {\n if (!zone.id) return 0;\n\n const cached = depthCache.get(zone.id);\n if (cached !== undefined) return cached;\n\n let depth = 0;\n let current: Zone | undefined = zone;\n const visited = new Set<string>();\n\n // Walk up the parent chain using in-memory lookup\n while (current?.id) {\n const parentId = current.parentId;\n if (!parentId) break;\n\n // Prevent infinite loops from cyclic data\n if (visited.has(parentId)) break;\n visited.add(parentId);\n\n const parent = zonesById.get(parentId);\n if (!parent) break;\n\n depth += 1;\n current = parent;\n }\n\n depthCache.set(zone.id, depth);\n return depth;\n };\n\n let maxDepth = 0;\n for (const zone of allZones) {\n maxDepth = Math.max(maxDepth, getDepth(zone));\n }\n\n return maxDepth;\n }\n\n /**\n * Find zones with specific dimensions\n *\n * @param propertyId - Property ID\n * @param width - Required width\n * @param height - Required height\n * @returns Array of matching zones\n */\n async findByDimensions(\n propertyId: string,\n width: number,\n height: number,\n ): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, width, height },\n });\n }\n\n /**\n * Find zones that allow a specific format\n *\n * @param propertyId - Property ID\n * @param format - Format to check\n * @returns Array of zones allowing the format\n */\n async findByAllowedFormat(\n propertyId: string,\n format: string,\n ): Promise<Zone[]> {\n const allZones = await this.findByProperty(propertyId);\n return allZones.filter((zone) => zone.isFormatAllowed(format));\n }\n\n /**\n * Move a zone to a new parent\n *\n * @param zoneId - Zone ID to move\n * @param newParentId - New parent ID (null for top-level)\n * @returns Updated zone\n */\n async moveZone(\n zoneId: string,\n newParentId: string | null,\n ): Promise<Zone | null> {\n const zone = await this.get({ id: zoneId });\n if (!zone) return null;\n\n // Prevent circular references\n if (newParentId) {\n const descendants = await this.getDescendants(zoneId);\n const descendantIds = descendants.map((d) => d.id);\n if (descendantIds.includes(newParentId)) {\n throw new Error('Cannot move zone into one of its descendants');\n }\n }\n\n zone.parentId = newParentId;\n await zone.save();\n return zone;\n }\n\n /**\n * Delete a zone and optionally its descendants\n *\n * @param zoneId - Zone ID to delete\n * @param cascade - Whether to delete descendants\n * @returns Number of zones deleted\n */\n async deleteZone(zoneId: string, cascade: boolean = false): Promise<number> {\n const zone = await this.get({ id: zoneId });\n if (!zone) return 0;\n\n let deleted = 0;\n\n if (cascade) {\n const descendants = await this.getDescendants(zoneId);\n for (const desc of descendants) {\n await desc.delete();\n deleted++;\n }\n } else {\n // Move children to parent\n const children = await this.findChildren(zoneId);\n for (const child of children) {\n child.parentId = zone.parentId;\n await child.save();\n }\n }\n\n await zone.delete();\n deleted++;\n\n return deleted;\n }\n\n // ============================================\n // Tenant Helper Methods\n // ============================================\n\n /**\n * Find zones belonging to a specific tenant\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of zones for the tenant\n */\n async findByTenant(tenantId: string): Promise<Zone[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global zones (no tenant association).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global zones\n */\n async findGlobal(): Promise<Zone[]> {\n return queryGlobal<Zone>(this);\n }\n\n /**\n * Find zones for a tenant plus all global zones.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of tenant-specific and global zones\n */\n async findWithGlobals(tenantId: string): Promise<Zone[]> {\n return queryWithGlobals<Zone>(this, tenantId, 'Zone.findWithGlobals');\n }\n}\n"],"names":["ZoneCollection","__decorateClass","tenantId","PropertyCollection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/prompts.ts","../src/models/Property.ts","../src/collections/Properties.ts","../src/models/Zone.ts","../src/collections/Zones.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Prompt registrations for the @happyvertical/smrt-properties package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-profiles` (see\n * `prompts.ts`) and `@happyvertical/smrt-content` (see `content-prompts.ts`).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n// Property summarization only uses non-PII property fields\n// (name, domain, description, status) plus aggregate zone information.\n// Internal/foreign-key fields like ownerId, repositoryId, tenantId, and the\n// extensible `metadata` blob are intentionally NOT passed to the AI\n// provider — they may link to identifying records or contain analytics IDs\n// and other configuration secrets. If a downstream tenant needs richer\n// context they can override the template via PromptOverride.\nexport const smrtPropertiesSummarizePrompt = definePrompt({\n key: 'smrtProperties.property.summarize',\n template: `Summarize this digital property:\nName: {propertyName}\nDomain: {propertyDomain}\nDescription: {propertyDescription}\nStatus: {propertyStatus}\nTotal zones: {totalZones}\nTop-level zones: {topLevelZones}`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * Property model - Digital property representation\n *\n * Represents a digital property (website, app, publication) that can contain\n * hierarchical zones for content/ad placement.\n */\n\nimport { crossPackageRef, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtPropertiesSummarizePrompt,\n} from '../prompts';\nimport type { PropertyOptions, PropertyStatus } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class Property extends SmrtObject {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Display name of the property\n */\n name: string = '';\n\n /**\n * Domain name (e.g., \"oakcreeknews.com\")\n */\n domain: string = '';\n\n /**\n * Full URL (e.g., \"https://oakcreeknews.com\")\n */\n url: string = '';\n\n /**\n * Property description\n */\n description: string = '';\n\n /**\n * Link to Repository (from smrt-projects)\n * Optional - used when property is backed by a git repository\n */\n @crossPackageRef('@happyvertical/smrt-projects:Repository')\n repositoryId: string | null = null;\n\n /**\n * Link to Profile (from smrt-profiles)\n * Optional - owner/publisher of the property\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n ownerId: string | null = null;\n\n /**\n * Property status\n */\n status: PropertyStatus = 'active';\n\n /**\n * Extensible metadata (analytics IDs, config, etc.)\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: PropertyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.domain !== undefined) this.domain = options.domain;\n if (options.url !== undefined) this.url = options.url;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.repositoryId !== undefined)\n this.repositoryId = options.repositoryId;\n if (options.ownerId !== undefined) this.ownerId = options.ownerId;\n if (options.status !== undefined) this.status = options.status;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Get all zones for this property\n */\n async getZones(): Promise<import('./Zone').Zone[]> {\n if (!this.id) return [];\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await ZoneCollection.create(this.options);\n return await collection.findByProperty(this.id);\n }\n\n /**\n * Get zone tree for this property\n */\n async getZoneTree(): Promise<\n import('../types').ZoneTree<import('./Zone').Zone>\n > {\n if (!this.id) return { propertyId: '', roots: [] };\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await ZoneCollection.create(this.options);\n return await collection.getTree(this.id);\n }\n\n /**\n * Create a zone on this property\n */\n async createZone(\n options: Omit<import('../types').ZoneOptions, 'propertyId'>,\n ): Promise<import('./Zone').Zone> {\n if (!this.id) {\n throw new Error('Property must be saved before creating zones');\n }\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await ZoneCollection.create(this.options);\n const zone = await collection.create({\n ...options,\n propertyId: this.id,\n });\n await zone.save();\n return zone;\n }\n\n /**\n * Check if property is active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * AI-powered: Generate a summary of the property and its zones.\n *\n * Uses the `smrtProperties.property.summarize` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Only non-PII fields (name, domain, description, status) plus aggregate\n * zone information are sent to the AI provider. Internal foreign-key\n * fields and the extensible `metadata` blob are intentionally excluded.\n *\n * @returns Generated summary text\n */\n async summarize(): Promise<string> {\n const zones = await this.getZones();\n const topLevelZones = zones.filter((z) => z.parentId === null);\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias. SmrtClass maps `persistence → db` lazily during `initialize()`,\n // so on a freshly-constructed Property that has not yet been initialized,\n // `this.options.db` may be undefined while `this.options.persistence` is\n // set. Falling back here ensures stored app- and tenant-level prompt\n // overrides in `_smrt_prompt_overrides` are honored on the first call —\n // before `getAiClient()` triggers full initialization further below.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtPropertiesSummarizePrompt.key,\n {\n db,\n tenantId: this.tenantId,\n variables: {\n propertyName: this.name || '',\n propertyDomain: this.domain || '',\n propertyDescription: this.description || '',\n propertyStatus: this.status || '',\n totalZones: String(zones.length),\n topLevelZones: topLevelZones.map((z) => z.name).join(', '),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return response.trim();\n }\n}\n","/**\n * PropertyCollection - Collection manager for Property objects\n *\n * Provides querying and management operations for Property entities.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Property } from '../models/Property';\nimport type { PropertyStatus } from '../types';\n\nexport class PropertyCollection extends SmrtCollection<Property> {\n static readonly _itemClass = Property;\n\n /**\n * Find a property by domain\n *\n * @param domain - Domain name\n * @returns Property or null\n */\n async findByDomain(domain: string): Promise<Property | null> {\n const results = await this.list({\n where: { domain },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find properties by repository ID\n *\n * @param repositoryId - Repository ID\n * @returns Array of properties\n */\n async findByRepository(repositoryId: string): Promise<Property[]> {\n return await this.list({\n where: { repositoryId },\n });\n }\n\n /**\n * Find properties by owner ID\n *\n * @param ownerId - Owner profile ID\n * @returns Array of properties\n */\n async findByOwner(ownerId: string): Promise<Property[]> {\n return await this.list({\n where: { ownerId },\n });\n }\n\n /**\n * Find active properties\n *\n * @returns Array of active properties\n */\n async findActive(): Promise<Property[]> {\n return await this.list({\n where: { status: 'active' as PropertyStatus },\n });\n }\n\n /**\n * Find properties by status\n *\n * @param status - Property status\n * @returns Array of properties with the given status\n */\n async findByStatus(status: PropertyStatus): Promise<Property[]> {\n return await this.list({\n where: { status },\n });\n }\n\n /**\n * Get or create a property by domain\n *\n * @param domain - Domain name\n * @param defaults - Default values for creation\n * @returns Property (existing or newly created)\n */\n async getOrCreateByDomain(\n domain: string,\n defaults: {\n name?: string;\n url?: string;\n repositoryId?: string | null;\n ownerId?: string | null;\n } = {},\n ): Promise<Property> {\n let property = await this.findByDomain(domain);\n\n if (!property) {\n property = await this.create({\n domain,\n name: defaults.name || domain,\n url: defaults.url || `https://${domain}`,\n repositoryId: defaults.repositoryId ?? null,\n ownerId: defaults.ownerId ?? null,\n status: 'active',\n });\n await property.save();\n }\n\n return property;\n }\n\n /**\n * Count properties by status\n *\n * @returns Object with counts per status\n */\n async countByStatus(): Promise<Record<PropertyStatus, number>> {\n const all = await this.list({});\n const counts: Record<PropertyStatus, number> = {\n active: 0,\n inactive: 0,\n pending: 0,\n };\n\n for (const prop of all) {\n counts[prop.status]++;\n }\n\n return counts;\n }\n\n // ============================================\n // Tenant Helper Methods\n // ============================================\n\n /**\n * Find properties belonging to a specific tenant\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of properties for the tenant\n */\n async findByTenant(tenantId: string): Promise<Property[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global properties (no tenant association).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global properties\n */\n async findGlobal(): Promise<Property[]> {\n return queryGlobal<Property>(this);\n }\n\n /**\n * Find properties for a tenant plus all global properties.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of tenant-specific and global properties\n */\n async findWithGlobals(tenantId: string): Promise<Property[]> {\n return queryWithGlobals<Property>(\n this,\n tenantId,\n 'Property.findWithGlobals',\n );\n }\n}\n","/**\n * Zone model - Hierarchical placement area within a property\n *\n * Zones represent pages, sections, or slots within a digital property.\n * They can be nested arbitrarily using the parentId self-reference.\n */\n\nimport { foreignKey, SmrtHierarchical, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { ZoneOptions, ZoneTreeNode } from '../types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class Zone extends SmrtHierarchical {\n /**\n * Tenant ID for multi-tenant isolation\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (required)\n */\n @foreignKey('Property')\n propertyId: string = '';\n\n // parentId inherited from SmrtHierarchical (null = top-level zone)\n\n /**\n * Display name\n */\n name: string = '';\n\n /**\n * Zone type (descriptive, not structural)\n * Common values: \"page\", \"section\", \"slot\", \"container\", \"widget\"\n */\n type: string = '';\n\n /**\n * URL path pattern (e.g., \"/\", \"/articles/*\")\n */\n path: string = '';\n\n /**\n * CSS selector for placement (e.g., \"#header\", \".sidebar\")\n */\n selector: string = '';\n\n /**\n * Position hint (e.g., \"top\", \"after-paragraph-3\")\n */\n position: string = '';\n\n /**\n * Width in pixels (null = fluid)\n */\n width: number | null = null;\n\n /**\n * Height in pixels (null = fluid)\n */\n height: number | null = null;\n\n /**\n * Allowed content/ad formats\n */\n allowedFormats: string[] = [];\n\n /**\n * Default content/asset ID for fallback\n */\n defaultContentId: string | null = null;\n\n /**\n * Extensible metadata\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: ZoneOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.parentId !== undefined) this.parentId = options.parentId;\n if (options.name !== undefined) this.name = options.name;\n if (options.type !== undefined) this.type = options.type;\n if (options.path !== undefined) this.path = options.path;\n if (options.selector !== undefined) this.selector = options.selector;\n if (options.position !== undefined) this.position = options.position;\n if (options.width !== undefined) this.width = options.width;\n if (options.height !== undefined) this.height = options.height;\n if (options.allowedFormats !== undefined)\n this.allowedFormats = options.allowedFormats;\n if (options.defaultContentId !== undefined)\n this.defaultContentId = options.defaultContentId;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Check if this is a top-level zone (no parent)\n */\n isTopLevel(): boolean {\n return this.parentId === null;\n }\n\n /**\n * Check if this zone has dimensions set\n */\n hasDimensions(): boolean {\n return this.width !== null && this.height !== null;\n }\n\n /**\n * Get dimensions as string (e.g., \"728x90\")\n */\n getDimensionString(): string | null {\n if (!this.hasDimensions()) return null;\n return `${this.width}x${this.height}`;\n }\n\n /**\n * Get the parent property\n */\n async getProperty(): Promise<import('./Property').Property | null> {\n const { PropertyCollection } = await import('../collections/Properties');\n const collection = await PropertyCollection.create(this.options);\n return await collection.get({ id: this.propertyId });\n }\n\n // Hierarchy traversal (getParent / getChildren / getAncestors /\n // getDescendants / getHierarchy / moveTo) provided by SmrtHierarchical.\n // The collection-level depth cache in `ZoneCollection.getAncestors` /\n // `getDescendants` remains available for callers that go through the\n // collection — the base-class methods do their own batched queries.\n\n /**\n * Get the full path from root to this zone\n */\n async getFullPath(): Promise<string> {\n const ancestors = await this.getAncestors();\n const names = [...ancestors.map((z) => z.name), this.name];\n return names.join(' > ');\n }\n\n /**\n * Get the depth in the hierarchy (0 = top-level)\n */\n async getDepth(): Promise<number> {\n const ancestors = await this.getAncestors();\n return ancestors.length;\n }\n\n /**\n * Create a child zone under this zone\n */\n async createChild(\n options: Omit<ZoneOptions, 'propertyId' | 'parentId'>,\n ): Promise<Zone> {\n if (!this.id) {\n throw new Error('Zone must be saved before creating children');\n }\n const { ZoneCollection } = await import('../collections/Zones');\n const collection = await ZoneCollection.create(this.options);\n const zone = await collection.create({\n ...options,\n propertyId: this.propertyId,\n parentId: this.id,\n });\n await zone.save();\n return zone;\n }\n\n /**\n * Build tree node for this zone and its children\n */\n async toTreeNode(): Promise<ZoneTreeNode<Zone>> {\n const children = await this.getChildren();\n const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));\n return {\n zone: this,\n children: childNodes,\n };\n }\n\n /**\n * Check if a format is allowed in this zone\n */\n isFormatAllowed(format: string): boolean {\n if (this.allowedFormats.length === 0) return true; // No restrictions\n return this.allowedFormats.includes(format);\n }\n}\n","/**\n * ZoneCollection - Collection manager for Zone objects\n *\n * Provides querying and tree operations for Zone entities.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Zone } from '../models/Zone';\nimport type { ZoneTree, ZoneTreeNode } from '../types';\n\nexport class ZoneCollection extends SmrtCollection<Zone> {\n static readonly _itemClass = Zone;\n\n /**\n * Find all zones for a property\n *\n * @param propertyId - Property ID\n * @returns Array of zones\n */\n async findByProperty(propertyId: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId },\n });\n }\n\n /**\n * Find top-level zones for a property (no parent)\n *\n * @param propertyId - Property ID\n * @returns Array of top-level zones\n */\n async findTopLevel(propertyId: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, parentId: null },\n });\n }\n\n /**\n * Find direct children of a zone\n *\n * @param parentId - Parent zone ID\n * @returns Array of child zones\n */\n async findChildren(parentId: string): Promise<Zone[]> {\n return await this.list({\n where: { parentId },\n });\n }\n\n /**\n * Find zones by path pattern\n *\n * @param propertyId - Property ID\n * @param path - URL path to match\n * @returns Array of matching zones\n */\n async findByPath(propertyId: string, path: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, path },\n });\n }\n\n /**\n * Find zones by type\n *\n * @param propertyId - Property ID\n * @param type - Zone type\n * @returns Array of zones of the given type\n */\n async findByType(propertyId: string, type: string): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, type },\n });\n }\n\n /**\n * Get the complete zone tree for a property\n *\n * @param propertyId - Property ID\n * @returns ZoneTree structure\n */\n async getTree(propertyId: string): Promise<ZoneTree<Zone>> {\n const allZones = await this.findByProperty(propertyId);\n const zoneMap = new Map<string, Zone>();\n const childrenMap = new Map<string, Zone[]>();\n\n // Build lookup maps\n for (const zone of allZones) {\n if (zone.id) {\n zoneMap.set(zone.id, zone);\n }\n if (!childrenMap.has(zone.parentId || '')) {\n childrenMap.set(zone.parentId || '', []);\n }\n childrenMap.get(zone.parentId || '')?.push(zone);\n }\n\n // Build tree nodes recursively\n const buildNode = (zone: Zone): ZoneTreeNode<Zone> => {\n const children = (zone.id ? childrenMap.get(zone.id) : []) || [];\n return {\n zone,\n children: children.map(buildNode),\n };\n };\n\n // Get root nodes (no parent)\n const roots = childrenMap.get('') || [];\n\n return {\n propertyId,\n roots: roots.map(buildNode),\n };\n }\n\n /**\n * Get all ancestors of a zone (path to root)\n *\n * @param zoneId - Zone ID\n * @returns Array of ancestors (from root to parent)\n */\n async getAncestors(zoneId: string): Promise<Zone[]> {\n const ancestors: Zone[] = [];\n\n // Get the starting zone\n const startZone = await this.get({ id: zoneId });\n if (!startZone) return ancestors;\n\n let currentId: string | null = startZone.parentId;\n\n // Walk up the tree\n while (currentId) {\n const parent = await this.get({ id: currentId });\n if (!parent) break;\n ancestors.unshift(parent); // Add to front (root first)\n currentId = parent.parentId;\n }\n\n return ancestors;\n }\n\n /**\n * Get all descendants of a zone recursively\n *\n * @param zoneId - Zone ID\n * @returns Array of all descendant zones\n */\n async getDescendants(zoneId: string): Promise<Zone[]> {\n const descendants: Zone[] = [];\n const queue: string[] = [zoneId];\n\n while (queue.length > 0) {\n const currentId = queue.shift();\n if (!currentId) continue;\n\n const children = await this.findChildren(currentId);\n\n for (const child of children) {\n descendants.push(child);\n if (child.id) {\n queue.push(child.id);\n }\n }\n }\n\n return descendants;\n }\n\n /**\n * Get the depth of the deepest zone in a property\n *\n * @param propertyId - Property ID\n * @returns Maximum depth (0 = only top-level zones)\n */\n async getMaxDepth(propertyId: string): Promise<number> {\n const allZones = await this.findByProperty(propertyId);\n\n // Build a lookup map for efficient in-memory traversal\n const zonesById = new Map<string, Zone>();\n for (const zone of allZones) {\n if (zone.id) {\n zonesById.set(zone.id, zone);\n }\n }\n\n // Cache computed depths\n const depthCache = new Map<string, number>();\n\n const getDepth = (zone: Zone): number => {\n if (!zone.id) return 0;\n\n const cached = depthCache.get(zone.id);\n if (cached !== undefined) return cached;\n\n let depth = 0;\n let current: Zone | undefined = zone;\n const visited = new Set<string>();\n\n // Walk up the parent chain using in-memory lookup\n while (current?.id) {\n const parentId = current.parentId;\n if (!parentId) break;\n\n // Prevent infinite loops from cyclic data\n if (visited.has(parentId)) break;\n visited.add(parentId);\n\n const parent = zonesById.get(parentId);\n if (!parent) break;\n\n depth += 1;\n current = parent;\n }\n\n depthCache.set(zone.id, depth);\n return depth;\n };\n\n let maxDepth = 0;\n for (const zone of allZones) {\n maxDepth = Math.max(maxDepth, getDepth(zone));\n }\n\n return maxDepth;\n }\n\n /**\n * Find zones with specific dimensions\n *\n * @param propertyId - Property ID\n * @param width - Required width\n * @param height - Required height\n * @returns Array of matching zones\n */\n async findByDimensions(\n propertyId: string,\n width: number,\n height: number,\n ): Promise<Zone[]> {\n return await this.list({\n where: { propertyId, width, height },\n });\n }\n\n /**\n * Find zones that allow a specific format\n *\n * @param propertyId - Property ID\n * @param format - Format to check\n * @returns Array of zones allowing the format\n */\n async findByAllowedFormat(\n propertyId: string,\n format: string,\n ): Promise<Zone[]> {\n const allZones = await this.findByProperty(propertyId);\n return allZones.filter((zone) => zone.isFormatAllowed(format));\n }\n\n /**\n * Move a zone to a new parent\n *\n * @param zoneId - Zone ID to move\n * @param newParentId - New parent ID (null for top-level)\n * @returns Updated zone\n */\n async moveZone(\n zoneId: string,\n newParentId: string | null,\n ): Promise<Zone | null> {\n const zone = await this.get({ id: zoneId });\n if (!zone) return null;\n\n // Prevent circular references\n if (newParentId) {\n const descendants = await this.getDescendants(zoneId);\n const descendantIds = descendants.map((d) => d.id);\n if (descendantIds.includes(newParentId)) {\n throw new Error('Cannot move zone into one of its descendants');\n }\n }\n\n zone.parentId = newParentId;\n await zone.save();\n return zone;\n }\n\n /**\n * Delete a zone and optionally its descendants\n *\n * @param zoneId - Zone ID to delete\n * @param cascade - Whether to delete descendants\n * @returns Number of zones deleted\n */\n async deleteZone(zoneId: string, cascade: boolean = false): Promise<number> {\n const zone = await this.get({ id: zoneId });\n if (!zone) return 0;\n\n let deleted = 0;\n\n if (cascade) {\n const descendants = await this.getDescendants(zoneId);\n for (const desc of descendants) {\n await desc.delete();\n deleted++;\n }\n } else {\n // Move children to parent\n const children = await this.findChildren(zoneId);\n for (const child of children) {\n child.parentId = zone.parentId;\n await child.save();\n }\n }\n\n await zone.delete();\n deleted++;\n\n return deleted;\n }\n\n // ============================================\n // Tenant Helper Methods\n // ============================================\n\n /**\n * Find zones belonging to a specific tenant\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of zones for the tenant\n */\n async findByTenant(tenantId: string): Promise<Zone[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global zones (no tenant association).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global zones\n */\n async findGlobal(): Promise<Zone[]> {\n return queryGlobal<Zone>(this);\n }\n\n /**\n * Find zones for a tenant plus all global zones.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID to filter by\n * @returns Array of tenant-specific and global zones\n */\n async findWithGlobals(tenantId: string): Promise<Zone[]> {\n return queryWithGlobals<Zone>(this, tenantId, 'Zone.findWithGlobals');\n }\n}\n"],"names":["ZoneCollection","__decorateClass","tenantId","PropertyCollection"],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1782340072775,
|
|
4
4
|
"packageName": "@happyvertical/smrt-properties",
|
|
5
|
-
"packageVersion": "0.35.
|
|
5
|
+
"packageVersion": "0.35.2",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-properties:PropertyCollection": {
|
|
8
8
|
"name": "propertycollection",
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-24T22:27:55.024Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-properties",
|
|
5
|
-
"packageVersion": "0.35.
|
|
5
|
+
"packageVersion": "0.35.2",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "4dddcc31bfa6bb4dff8def3f1af6c3c94dc73b8c594b7ce4d5cdebd896779c6e",
|
|
10
|
+
"packageJson": "51a439a4c7365f24e4ec05a23d28026b06703ed8eb2bd8b1fcf955c68109776b",
|
|
11
11
|
"agents": "a0f6fb35fa6c32a8c94158511c5f1722e1a96c2fcc01e790ec4b424d02845743"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-properties",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.2",
|
|
4
4
|
"description": "Digital property and zone management models for SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"./manifest.json": "./dist/manifest.json"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@happyvertical/smrt-core": "0.35.
|
|
23
|
-
"@happyvertical/smrt-prompts": "0.35.
|
|
24
|
-
"@happyvertical/smrt-tenancy": "0.35.
|
|
22
|
+
"@happyvertical/smrt-core": "0.35.2",
|
|
23
|
+
"@happyvertical/smrt-prompts": "0.35.2",
|
|
24
|
+
"@happyvertical/smrt-tenancy": "0.35.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@happyvertical/sql": "^0.74.7",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"typescript": "^5.9.3",
|
|
31
31
|
"vite": "^7.3.1",
|
|
32
32
|
"vitest": "^4.0.17",
|
|
33
|
-
"@happyvertical/smrt-
|
|
34
|
-
"@happyvertical/smrt-
|
|
33
|
+
"@happyvertical/smrt-cli": "0.35.2",
|
|
34
|
+
"@happyvertical/smrt-vitest": "0.35.2"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"ai",
|