@happyvertical/smrt-sites 0.30.0

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/AGENTS.md ADDED
@@ -0,0 +1,18 @@
1
+ # @happyvertical/smrt-sites
2
+
3
+ Multi-tenant site lifecycle management with agent bindings.
4
+
5
+ ## Models
6
+
7
+ - **Site**: `domain` (unique per tenant), `tier` (free/standard/premium), `portalConfig` JSON, database connection, `provisioningStatus`/`provisioningTimestamp`. Status: draft/active/suspended/archived.
8
+ - **SiteAgentBinding**: junction linking sites to agent classes. Per-site `config` overrides and `priority` ordering. `conflictColumns: ['site_id', 'agent_class']`.
9
+
10
+ ## SiteService
11
+
12
+ Stateless lifecycle ops: `activate()`, `suspend()`, `archive()`. `bindAgent()` handles upsert (update config if exists, create if not).
13
+
14
+ ## Gotchas
15
+
16
+ - **Required tenancy**: both models use `@TenantScoped({ mode: 'required' })` — must have tenant context
17
+ - **portalConfig accepts both object and JSON string** in constructor
18
+ - **SiteAgentBinding.config is nullable**: auto-parsed from JSON on init
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @happyvertical/smrt-sites
2
+
3
+ Site lifecycle management for multi-tenant SMRT networks. Manages deployable websites with provisioning status, tier classification, portal configuration, and agent bindings with priority ordering.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-sites
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import {
15
+ Site, SiteCollection,
16
+ SiteAgentBinding, SiteAgentBindingCollection,
17
+ SiteService
18
+ } from '@happyvertical/smrt-sites';
19
+
20
+ // Create collections
21
+ const sites = await SiteCollection.create({ db });
22
+ const bindings = await SiteAgentBindingCollection.create({ db });
23
+
24
+ // Use SiteService for lifecycle operations
25
+ const service = new SiteService({ sites, bindings });
26
+
27
+ // Create a site (starts in draft status)
28
+ const site = await service.createSite('tenant-123', {
29
+ name: 'Community Hub',
30
+ domain: 'hub.example.com',
31
+ tier: 'standard',
32
+ });
33
+
34
+ // Activate after validation (requires name + domain)
35
+ await service.activateSite(site.id);
36
+
37
+ // Mark infrastructure as provisioned
38
+ await service.markProvisioned(site.id);
39
+
40
+ // Bind agents with priority ordering (higher = first)
41
+ await service.bindAgent(site.id, 'Praeco', { schedule: '0 * * * *' });
42
+ await service.bindAgent(site.id, 'Caelus', { maxArticles: 50 });
43
+
44
+ // Get enabled agents sorted by priority descending
45
+ const agents = await service.getEnabledAgents(site.id);
46
+
47
+ // Suspend or archive a site
48
+ await service.suspendSite(site.id);
49
+ await service.archiveSite(site.id);
50
+ ```
51
+
52
+ ### Site lifecycle
53
+
54
+ Sites progress through four statuses: `draft` -> `active` -> `suspended` -> `archived`. The `SiteService` enforces validation on activation (name and domain required) and tracks provisioning state separately via `provisioningStatus` (pending/provisioning/ready/failed). Sites are uniquely identified by `(tenant_id, domain)`.
55
+
56
+ ### Agent bindings
57
+
58
+ `SiteAgentBinding` is a junction table linking sites to agent classes. Each binding has a `priority` (higher values execute first), an `enabled` flag, and optional per-site `config` overrides stored as JSON. The `bindAgent()` method upserts -- updating config if a binding already exists, creating one if not. `findEnabled()` returns bindings sorted by priority descending.
59
+
60
+ ### Portal configuration
61
+
62
+ Sites store theme, branding, and navigation settings in `portalConfig` as JSON. Use `getPortalConfig()`/`setPortalConfig()` for type-safe access. Both models require tenant context (`@TenantScoped({ mode: 'required' })`).
63
+
64
+ ## API
65
+
66
+ ### Models
67
+
68
+ | Export | Description |
69
+ |--------|------------|
70
+ | `Site` | Site record with domain, status, tier, provisioning tracking, and portal config |
71
+ | `SiteAgentBinding` | Junction binding an agent class to a site with priority and config overrides |
72
+
73
+ ### Collections
74
+
75
+ | Export | Description |
76
+ |--------|------------|
77
+ | `SiteCollection` | CRUD and queries for Site records |
78
+ | `SiteAgentBindingCollection` | Queries by site, agent class, and enabled state with priority sorting |
79
+
80
+ ### Services
81
+
82
+ | Export | Description |
83
+ |--------|------------|
84
+ | `SiteService` | High-level lifecycle ops: create, activate, suspend, archive, bind/unbind agents |
85
+
86
+ ### Key Types
87
+
88
+ `SiteOptions`, `SiteStatus`, `SiteTier`, `ProvisioningStatus`, `SitePortalConfig`, `SiteServiceOptions`, `SiteAgentBindingOptions`, `CreateSiteData`
89
+
90
+ ## Dependencies
91
+
92
+ - `@happyvertical/smrt-core` -- ORM and code generation
93
+ - `@happyvertical/smrt-tenancy` -- required multi-tenant scoping
94
+ - Peer: `@happyvertical/smrt-agents` (optional)
@@ -0,0 +1,258 @@
1
+ import { SmrtCollection } from '@happyvertical/smrt-core';
2
+ import { SmrtObject } from '@happyvertical/smrt-core';
3
+
4
+ /**
5
+ * Data required to create a new site
6
+ */
7
+ export declare interface CreateSiteData {
8
+ name: string;
9
+ domain: string;
10
+ tier?: SiteTier;
11
+ databaseUrl?: string;
12
+ databaseType?: string;
13
+ portalConfig?: SitePortalConfig;
14
+ repositoryUrl?: string;
15
+ templateName?: string;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ /**
20
+ * Infrastructure provisioning status
21
+ */
22
+ export declare type ProvisioningStatus = 'pending' | 'provisioning' | 'ready' | 'failed';
23
+
24
+ export declare class Site extends SmrtObject {
25
+ tenantId: string;
26
+ /** Display name, e.g. "Bentley Alberta" */
27
+ name: string;
28
+ /** Primary domain, e.g. "bentleyalberta.com" */
29
+ domain: string;
30
+ /** Lifecycle status */
31
+ status: SiteStatus;
32
+ /** Pricing/feature tier */
33
+ tier: SiteTier;
34
+ /** Site-specific database connection URL */
35
+ databaseUrl: string;
36
+ /** Database type: sqlite, postgres, libsql */
37
+ databaseType: string;
38
+ /** Theme, branding, navigation settings (JSON) */
39
+ portalConfig: string;
40
+ /** Git repository URL */
41
+ repositoryUrl: string;
42
+ /** Starter template name */
43
+ templateName: string;
44
+ /** Infrastructure provisioning status */
45
+ provisioningStatus: ProvisioningStatus | '';
46
+ /** When provisioning completed */
47
+ provisionedAt: Date | null;
48
+ /** Last provisioning error message */
49
+ provisioningError: string;
50
+ /** Extensible key-value metadata (JSON) */
51
+ metadata: string;
52
+ createdAt: Date;
53
+ updatedAt: Date;
54
+ constructor(options?: SiteOptions);
55
+ /**
56
+ * Check if the site is active
57
+ */
58
+ isActive(): boolean;
59
+ /**
60
+ * Check if the site is provisioned and ready
61
+ */
62
+ isReady(): boolean;
63
+ /**
64
+ * Get portal config as parsed object
65
+ */
66
+ getPortalConfig(): SitePortalConfig;
67
+ /**
68
+ * Set portal config from object
69
+ */
70
+ setPortalConfig(config: SitePortalConfig): void;
71
+ /**
72
+ * Get metadata as parsed object
73
+ */
74
+ getMetadata(): Record<string, unknown>;
75
+ /**
76
+ * Set metadata from object
77
+ */
78
+ setMetadata(data: Record<string, unknown>): void;
79
+ /**
80
+ * Update metadata by merging with existing values
81
+ */
82
+ updateMetadata(updates: Record<string, unknown>): void;
83
+ }
84
+
85
+ export declare class SiteAgentBinding extends SmrtObject {
86
+ tenantId: string;
87
+ /** FK to sites table */
88
+ siteId: string;
89
+ /** Agent class name, e.g. "Praeco" */
90
+ agentClass: string;
91
+ /** Whether this agent is enabled for the site */
92
+ enabled: boolean;
93
+ /** Per-site agent configuration overrides (JSON) */
94
+ config: Record<string, unknown> | null;
95
+ /** Sort priority (higher = first) */
96
+ priority: number;
97
+ constructor(options?: SiteAgentBindingOptions);
98
+ }
99
+
100
+ export declare class SiteAgentBindingCollection extends SmrtCollection<SiteAgentBinding> {
101
+ static readonly _itemClass: typeof SiteAgentBinding;
102
+ /**
103
+ * Find all agent bindings for a site
104
+ */
105
+ findBySite(siteId: string): Promise<SiteAgentBinding[]>;
106
+ /**
107
+ * Find all sites using a specific agent class
108
+ */
109
+ findByAgent(agentClass: string): Promise<SiteAgentBinding[]>;
110
+ /**
111
+ * Find enabled bindings for a site, ordered by priority descending
112
+ */
113
+ findEnabled(siteId: string): Promise<SiteAgentBinding[]>;
114
+ /**
115
+ * Find a specific site-agent binding
116
+ */
117
+ findBySiteAndAgent(siteId: string, agentClass: string): Promise<SiteAgentBinding | null>;
118
+ }
119
+
120
+ /**
121
+ * Options for constructing a SiteAgentBinding instance
122
+ */
123
+ export declare interface SiteAgentBindingOptions {
124
+ id?: string;
125
+ tenantId?: string;
126
+ siteId?: string;
127
+ agentClass?: string;
128
+ enabled?: boolean;
129
+ config?: Record<string, unknown> | string | null;
130
+ priority?: number;
131
+ createdAt?: Date;
132
+ updatedAt?: Date;
133
+ }
134
+
135
+ export declare class SiteCollection extends SmrtCollection<Site> {
136
+ static readonly _itemClass: typeof Site;
137
+ /**
138
+ * Find a site by its primary domain
139
+ */
140
+ findByDomain(domain: string): Promise<Site | null>;
141
+ /**
142
+ * Find all sites belonging to a tenant
143
+ */
144
+ findByTenant(tenantId: string): Promise<Site[]>;
145
+ /**
146
+ * Find sites by lifecycle status
147
+ */
148
+ findByStatus(status: SiteStatus): Promise<Site[]>;
149
+ /**
150
+ * Find all active sites
151
+ */
152
+ findActive(): Promise<Site[]>;
153
+ }
154
+
155
+ /**
156
+ * Options for constructing a Site instance
157
+ */
158
+ export declare interface SiteOptions {
159
+ id?: string;
160
+ tenantId?: string;
161
+ name?: string;
162
+ domain?: string;
163
+ status?: SiteStatus;
164
+ tier?: SiteTier;
165
+ databaseUrl?: string;
166
+ databaseType?: string;
167
+ portalConfig?: SitePortalConfig | string;
168
+ repositoryUrl?: string;
169
+ templateName?: string;
170
+ provisioningStatus?: ProvisioningStatus;
171
+ provisionedAt?: Date | null;
172
+ provisioningError?: string;
173
+ metadata?: Record<string, unknown> | string;
174
+ createdAt?: Date;
175
+ updatedAt?: Date;
176
+ }
177
+
178
+ /**
179
+ * Portal configuration for theming, branding, and navigation
180
+ */
181
+ export declare interface SitePortalConfig {
182
+ theme?: string;
183
+ branding?: Record<string, unknown>;
184
+ navigation?: Array<{
185
+ label: string;
186
+ path: string;
187
+ icon?: string;
188
+ }>;
189
+ }
190
+
191
+ export declare class SiteService {
192
+ private sites;
193
+ private bindings;
194
+ constructor(options: SiteServiceOptions);
195
+ /**
196
+ * Create a new site in draft status
197
+ */
198
+ createSite(tenantId: string, data: CreateSiteData): Promise<Site>;
199
+ /**
200
+ * Activate a site — validates required fields then sets status to active
201
+ */
202
+ activateSite(siteId: string): Promise<Site>;
203
+ /**
204
+ * Suspend a site
205
+ */
206
+ suspendSite(siteId: string): Promise<Site>;
207
+ /**
208
+ * Archive a site
209
+ */
210
+ archiveSite(siteId: string): Promise<Site>;
211
+ /**
212
+ * Mark site as provisioned and ready
213
+ */
214
+ markProvisioned(siteId: string): Promise<Site>;
215
+ /**
216
+ * Mark site provisioning as failed
217
+ */
218
+ markProvisioningFailed(siteId: string, error: string): Promise<Site>;
219
+ /**
220
+ * Bind an agent to a site
221
+ */
222
+ bindAgent(siteId: string, agentClass: string, config?: Record<string, unknown>): Promise<SiteAgentBinding>;
223
+ /**
224
+ * Unbind an agent from a site
225
+ */
226
+ unbindAgent(siteId: string, agentClass: string): Promise<void>;
227
+ /**
228
+ * Get all enabled agent bindings for a site
229
+ */
230
+ getEnabledAgents(siteId: string): Promise<SiteAgentBinding[]>;
231
+ /**
232
+ * Load a site by ID, throwing if not found
233
+ */
234
+ private requireSite;
235
+ }
236
+
237
+ /**
238
+ * Options for creating a SiteService
239
+ */
240
+ export declare interface SiteServiceOptions {
241
+ sites: SiteCollection;
242
+ bindings: SiteAgentBindingCollection;
243
+ }
244
+
245
+ /**
246
+ * Types for smrt-sites package
247
+ */
248
+ /**
249
+ * Site lifecycle status
250
+ */
251
+ export declare type SiteStatus = 'draft' | 'active' | 'suspended' | 'archived';
252
+
253
+ /**
254
+ * Site pricing/feature tier
255
+ */
256
+ export declare type SiteTier = 'free' | 'standard' | 'premium';
257
+
258
+ export { }