@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/dist/index.js ADDED
@@ -0,0 +1,411 @@
1
+ import { ObjectRegistry, foreignKey, smrt, SmrtObject, SmrtCollection } from "@happyvertical/smrt-core";
2
+ import { tenantId, TenantScoped } from "@happyvertical/smrt-tenancy";
3
+ ObjectRegistry.registerPackageManifest(
4
+ new URL("./manifest.json", import.meta.url)
5
+ );
6
+ var __defProp$1 = Object.defineProperty;
7
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass$1 = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
10
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
+ if (decorator = decorators[i])
12
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
+ if (kind && result) __defProp$1(target, key, result);
14
+ return result;
15
+ };
16
+ let SiteAgentBinding = class extends SmrtObject {
17
+ tenantId = "";
18
+ siteId = "";
19
+ /** Agent class name, e.g. "Praeco" */
20
+ agentClass = "";
21
+ /** Whether this agent is enabled for the site */
22
+ enabled = true;
23
+ /** Per-site agent configuration overrides (JSON) */
24
+ config = null;
25
+ /** Sort priority (higher = first) */
26
+ priority = 0;
27
+ constructor(options = {}) {
28
+ super(options);
29
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
30
+ if (options.siteId !== void 0) this.siteId = options.siteId;
31
+ if (options.agentClass !== void 0) this.agentClass = options.agentClass;
32
+ if (options.enabled !== void 0) this.enabled = options.enabled;
33
+ if (options.priority !== void 0) this.priority = options.priority;
34
+ if (options.config !== void 0) {
35
+ if (typeof options.config === "string") {
36
+ try {
37
+ this.config = JSON.parse(options.config);
38
+ } catch {
39
+ this.config = null;
40
+ }
41
+ } else {
42
+ this.config = options.config;
43
+ }
44
+ }
45
+ }
46
+ };
47
+ __decorateClass$1([
48
+ tenantId()
49
+ ], SiteAgentBinding.prototype, "tenantId", 2);
50
+ __decorateClass$1([
51
+ foreignKey("Site")
52
+ ], SiteAgentBinding.prototype, "siteId", 2);
53
+ SiteAgentBinding = __decorateClass$1([
54
+ TenantScoped({ mode: "required" }),
55
+ smrt({
56
+ tableName: "site_agent_bindings",
57
+ api: { include: ["list", "get", "create", "update", "delete"] },
58
+ cli: { include: ["list", "get"] },
59
+ mcp: { include: ["list", "get"] },
60
+ conflictColumns: ["site_id", "agent_class"]
61
+ })
62
+ ], SiteAgentBinding);
63
+ class SiteAgentBindingCollection extends SmrtCollection {
64
+ static _itemClass = SiteAgentBinding;
65
+ /**
66
+ * Find all agent bindings for a site
67
+ */
68
+ async findBySite(siteId) {
69
+ return this.list({ where: { siteId } });
70
+ }
71
+ /**
72
+ * Find all sites using a specific agent class
73
+ */
74
+ async findByAgent(agentClass) {
75
+ return this.list({ where: { agentClass } });
76
+ }
77
+ /**
78
+ * Find enabled bindings for a site, ordered by priority descending
79
+ */
80
+ async findEnabled(siteId) {
81
+ const bindings = await this.list({
82
+ where: { siteId, enabled: true }
83
+ });
84
+ return bindings.sort((a, b) => b.priority - a.priority);
85
+ }
86
+ /**
87
+ * Find a specific site-agent binding
88
+ */
89
+ async findBySiteAndAgent(siteId, agentClass) {
90
+ const results = await this.list({
91
+ where: { siteId, agentClass }
92
+ });
93
+ return results[0] ?? null;
94
+ }
95
+ }
96
+ var __defProp = Object.defineProperty;
97
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
98
+ var __decorateClass = (decorators, target, key, kind) => {
99
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
100
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
101
+ if (decorator = decorators[i])
102
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
103
+ if (kind && result) __defProp(target, key, result);
104
+ return result;
105
+ };
106
+ let Site = class extends SmrtObject {
107
+ tenantId = "";
108
+ /** Display name, e.g. "Bentley Alberta" */
109
+ name = "";
110
+ /** Primary domain, e.g. "bentleyalberta.com" */
111
+ domain = "";
112
+ /** Lifecycle status */
113
+ status = "draft";
114
+ /** Pricing/feature tier */
115
+ tier = "free";
116
+ /** Site-specific database connection URL */
117
+ databaseUrl = "";
118
+ /** Database type: sqlite, postgres, libsql */
119
+ databaseType = "";
120
+ /** Theme, branding, navigation settings (JSON) */
121
+ portalConfig = "";
122
+ /** Git repository URL */
123
+ repositoryUrl = "";
124
+ /** Starter template name */
125
+ templateName = "";
126
+ /** Infrastructure provisioning status */
127
+ provisioningStatus = "";
128
+ /** When provisioning completed */
129
+ provisionedAt = null;
130
+ /** Last provisioning error message */
131
+ provisioningError = "";
132
+ /** Extensible key-value metadata (JSON) */
133
+ metadata = "";
134
+ createdAt = /* @__PURE__ */ new Date();
135
+ updatedAt = /* @__PURE__ */ new Date();
136
+ constructor(options = {}) {
137
+ super(options);
138
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
139
+ if (options.name !== void 0) this.name = options.name;
140
+ if (options.domain !== void 0) this.domain = options.domain;
141
+ if (options.status !== void 0) this.status = options.status;
142
+ if (options.tier !== void 0) this.tier = options.tier;
143
+ if (options.databaseUrl !== void 0)
144
+ this.databaseUrl = options.databaseUrl;
145
+ if (options.databaseType !== void 0)
146
+ this.databaseType = options.databaseType;
147
+ if (options.repositoryUrl !== void 0)
148
+ this.repositoryUrl = options.repositoryUrl;
149
+ if (options.templateName !== void 0)
150
+ this.templateName = options.templateName;
151
+ if (options.provisioningStatus !== void 0)
152
+ this.provisioningStatus = options.provisioningStatus;
153
+ if (options.provisionedAt !== void 0)
154
+ this.provisionedAt = options.provisionedAt;
155
+ if (options.provisioningError !== void 0)
156
+ this.provisioningError = options.provisioningError;
157
+ if (options.portalConfig !== void 0) {
158
+ if (typeof options.portalConfig === "string") {
159
+ this.portalConfig = options.portalConfig;
160
+ } else {
161
+ this.portalConfig = JSON.stringify(options.portalConfig);
162
+ }
163
+ }
164
+ if (options.metadata !== void 0) {
165
+ if (typeof options.metadata === "string") {
166
+ this.metadata = options.metadata;
167
+ } else {
168
+ this.metadata = JSON.stringify(options.metadata);
169
+ }
170
+ }
171
+ if (options.createdAt) this.createdAt = options.createdAt;
172
+ if (options.updatedAt) this.updatedAt = options.updatedAt;
173
+ }
174
+ /**
175
+ * Check if the site is active
176
+ */
177
+ isActive() {
178
+ return this.status === "active";
179
+ }
180
+ /**
181
+ * Check if the site is provisioned and ready
182
+ */
183
+ isReady() {
184
+ return this.provisioningStatus === "ready";
185
+ }
186
+ /**
187
+ * Get portal config as parsed object
188
+ */
189
+ getPortalConfig() {
190
+ if (!this.portalConfig) return {};
191
+ try {
192
+ return JSON.parse(this.portalConfig);
193
+ } catch {
194
+ return {};
195
+ }
196
+ }
197
+ /**
198
+ * Set portal config from object
199
+ */
200
+ setPortalConfig(config) {
201
+ this.portalConfig = JSON.stringify(config);
202
+ }
203
+ /**
204
+ * Get metadata as parsed object
205
+ */
206
+ getMetadata() {
207
+ if (!this.metadata) return {};
208
+ try {
209
+ return JSON.parse(this.metadata);
210
+ } catch {
211
+ return {};
212
+ }
213
+ }
214
+ /**
215
+ * Set metadata from object
216
+ */
217
+ setMetadata(data) {
218
+ this.metadata = JSON.stringify(data);
219
+ }
220
+ /**
221
+ * Update metadata by merging with existing values
222
+ */
223
+ updateMetadata(updates) {
224
+ const current = this.getMetadata();
225
+ this.setMetadata({ ...current, ...updates });
226
+ }
227
+ };
228
+ __decorateClass([
229
+ tenantId()
230
+ ], Site.prototype, "tenantId", 2);
231
+ Site = __decorateClass([
232
+ TenantScoped({ mode: "required" }),
233
+ smrt({
234
+ tableName: "sites",
235
+ api: { include: ["list", "get", "create", "update", "delete"] },
236
+ mcp: { include: ["list", "get", "create", "update"] },
237
+ cli: true,
238
+ conflictColumns: ["tenant_id", "domain"]
239
+ })
240
+ ], Site);
241
+ class SiteCollection extends SmrtCollection {
242
+ static _itemClass = Site;
243
+ /**
244
+ * Find a site by its primary domain
245
+ */
246
+ async findByDomain(domain) {
247
+ const results = await this.list({ where: { domain } });
248
+ return results[0] ?? null;
249
+ }
250
+ /**
251
+ * Find all sites belonging to a tenant
252
+ */
253
+ async findByTenant(tenantId2) {
254
+ return this.list({ where: { tenantId: tenantId2 } });
255
+ }
256
+ /**
257
+ * Find sites by lifecycle status
258
+ */
259
+ async findByStatus(status) {
260
+ return this.list({ where: { status } });
261
+ }
262
+ /**
263
+ * Find all active sites
264
+ */
265
+ async findActive() {
266
+ return this.findByStatus("active");
267
+ }
268
+ }
269
+ class SiteService {
270
+ sites;
271
+ bindings;
272
+ constructor(options) {
273
+ this.sites = options.sites;
274
+ this.bindings = options.bindings;
275
+ }
276
+ /**
277
+ * Create a new site in draft status
278
+ */
279
+ async createSite(tenantId2, data) {
280
+ const site = await this.sites.create({
281
+ tenantId: tenantId2,
282
+ name: data.name,
283
+ domain: data.domain,
284
+ status: "draft",
285
+ tier: data.tier ?? "free",
286
+ databaseUrl: data.databaseUrl ?? "",
287
+ databaseType: data.databaseType ?? "",
288
+ portalConfig: JSON.stringify(data.portalConfig ?? {}),
289
+ repositoryUrl: data.repositoryUrl ?? "",
290
+ templateName: data.templateName ?? "",
291
+ provisioningStatus: "pending",
292
+ metadata: JSON.stringify(data.metadata ?? {})
293
+ });
294
+ await site.save();
295
+ return site;
296
+ }
297
+ /**
298
+ * Activate a site — validates required fields then sets status to active
299
+ */
300
+ async activateSite(siteId) {
301
+ const site = await this.requireSite(siteId);
302
+ if (!site.name) {
303
+ throw new Error("Cannot activate site: name is required");
304
+ }
305
+ if (!site.domain) {
306
+ throw new Error("Cannot activate site: domain is required");
307
+ }
308
+ site.status = "active";
309
+ site.updatedAt = /* @__PURE__ */ new Date();
310
+ await site.save();
311
+ return site;
312
+ }
313
+ /**
314
+ * Suspend a site
315
+ */
316
+ async suspendSite(siteId) {
317
+ const site = await this.requireSite(siteId);
318
+ site.status = "suspended";
319
+ site.updatedAt = /* @__PURE__ */ new Date();
320
+ await site.save();
321
+ return site;
322
+ }
323
+ /**
324
+ * Archive a site
325
+ */
326
+ async archiveSite(siteId) {
327
+ const site = await this.requireSite(siteId);
328
+ site.status = "archived";
329
+ site.updatedAt = /* @__PURE__ */ new Date();
330
+ await site.save();
331
+ return site;
332
+ }
333
+ /**
334
+ * Mark site as provisioned and ready
335
+ */
336
+ async markProvisioned(siteId) {
337
+ const site = await this.requireSite(siteId);
338
+ site.provisioningStatus = "ready";
339
+ site.provisionedAt = /* @__PURE__ */ new Date();
340
+ site.provisioningError = "";
341
+ site.updatedAt = /* @__PURE__ */ new Date();
342
+ await site.save();
343
+ return site;
344
+ }
345
+ /**
346
+ * Mark site provisioning as failed
347
+ */
348
+ async markProvisioningFailed(siteId, error) {
349
+ const site = await this.requireSite(siteId);
350
+ site.provisioningStatus = "failed";
351
+ site.provisioningError = error;
352
+ site.updatedAt = /* @__PURE__ */ new Date();
353
+ await site.save();
354
+ return site;
355
+ }
356
+ /**
357
+ * Bind an agent to a site
358
+ */
359
+ async bindAgent(siteId, agentClass, config) {
360
+ const site = await this.requireSite(siteId);
361
+ const existing = await this.bindings.findBySiteAndAgent(siteId, agentClass);
362
+ if (existing) {
363
+ existing.enabled = true;
364
+ if (config !== void 0) existing.config = config;
365
+ await existing.save();
366
+ return existing;
367
+ }
368
+ const binding = await this.bindings.create({
369
+ tenantId: site.tenantId,
370
+ siteId,
371
+ agentClass,
372
+ enabled: true,
373
+ config: config ?? null
374
+ });
375
+ await binding.save();
376
+ return binding;
377
+ }
378
+ /**
379
+ * Unbind an agent from a site
380
+ */
381
+ async unbindAgent(siteId, agentClass) {
382
+ const binding = await this.bindings.findBySiteAndAgent(siteId, agentClass);
383
+ if (binding) {
384
+ await binding.delete();
385
+ }
386
+ }
387
+ /**
388
+ * Get all enabled agent bindings for a site
389
+ */
390
+ async getEnabledAgents(siteId) {
391
+ return this.bindings.findEnabled(siteId);
392
+ }
393
+ /**
394
+ * Load a site by ID, throwing if not found
395
+ */
396
+ async requireSite(siteId) {
397
+ const site = await this.sites.get({ id: siteId });
398
+ if (!site) {
399
+ throw new Error(`Site '${siteId}' not found`);
400
+ }
401
+ return site;
402
+ }
403
+ }
404
+ export {
405
+ Site,
406
+ SiteAgentBinding,
407
+ SiteAgentBindingCollection,
408
+ SiteCollection,
409
+ SiteService
410
+ };
411
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/models/SiteAgentBinding.ts","../src/collections/SiteAgentBindingCollection.ts","../src/models/Site.ts","../src/collections/SiteCollection.ts","../src/services/SiteService.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 * SiteAgentBinding - Junction between sites and agents\n *\n * Represents which agents are bound to a specific site,\n * with optional per-site agent configuration overrides.\n *\n * Each row binds an agent class to a site. The absence of a row\n * means the agent is not enabled for that site.\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { SiteAgentBindingOptions } from '../types';\n\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'site_agent_bindings',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n conflictColumns: ['site_id', 'agent_class'],\n})\nexport class SiteAgentBinding extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n /** FK to sites table */\n @foreignKey('Site')\n siteId: string = '';\n\n /** Agent class name, e.g. \"Praeco\" */\n agentClass: string = '';\n\n /** Whether this agent is enabled for the site */\n enabled: boolean = true;\n\n /** Per-site agent configuration overrides (JSON) */\n config: Record<string, unknown> | null = null;\n\n /** Sort priority (higher = first) */\n priority: number = 0;\n\n constructor(options: SiteAgentBindingOptions = {}) {\n super(options);\n\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.siteId !== undefined) this.siteId = options.siteId;\n if (options.agentClass !== undefined) this.agentClass = options.agentClass;\n if (options.enabled !== undefined) this.enabled = options.enabled;\n if (options.priority !== undefined) this.priority = options.priority;\n\n // Handle config — can be object, JSON string, or null\n if (options.config !== undefined) {\n if (typeof options.config === 'string') {\n try {\n this.config = JSON.parse(options.config);\n } catch {\n this.config = null;\n }\n } else {\n this.config = options.config;\n }\n }\n }\n}\n","/**\n * SiteAgentBindingCollection - Collection manager for SiteAgentBinding objects\n *\n * Provides queries for agent bindings by site, agent class, and enabled state.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { SiteAgentBinding } from '../models/SiteAgentBinding';\n\nexport class SiteAgentBindingCollection extends SmrtCollection<SiteAgentBinding> {\n static readonly _itemClass = SiteAgentBinding;\n\n /**\n * Find all agent bindings for a site\n */\n async findBySite(siteId: string): Promise<SiteAgentBinding[]> {\n return this.list({ where: { siteId } });\n }\n\n /**\n * Find all sites using a specific agent class\n */\n async findByAgent(agentClass: string): Promise<SiteAgentBinding[]> {\n return this.list({ where: { agentClass } });\n }\n\n /**\n * Find enabled bindings for a site, ordered by priority descending\n */\n async findEnabled(siteId: string): Promise<SiteAgentBinding[]> {\n const bindings = await this.list({\n where: { siteId, enabled: true },\n });\n return bindings.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Find a specific site-agent binding\n */\n async findBySiteAndAgent(\n siteId: string,\n agentClass: string,\n ): Promise<SiteAgentBinding | null> {\n const results = await this.list({\n where: { siteId, agentClass },\n });\n return results[0] ?? null;\n }\n}\n","/**\n * Site model - Represents a deployable website within a multi-tenant network\n *\n * Manages site lifecycle from draft through active/suspended/archived states,\n * with infrastructure provisioning tracking and portal configuration.\n *\n * Tenancy: Sites are tenant-scoped with required mode — every site\n * belongs to exactly one tenant.\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n ProvisioningStatus,\n SiteOptions,\n SitePortalConfig,\n SiteStatus,\n SiteTier,\n} from '../types';\n\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'sites',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: true,\n conflictColumns: ['tenant_id', 'domain'],\n})\nexport class Site extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n /** Display name, e.g. \"Bentley Alberta\" */\n name: string = '';\n\n /** Primary domain, e.g. \"bentleyalberta.com\" */\n domain: string = '';\n\n /** Lifecycle status */\n status: SiteStatus = 'draft';\n\n /** Pricing/feature tier */\n tier: SiteTier = 'free';\n\n /** Site-specific database connection URL */\n databaseUrl: string = '';\n\n /** Database type: sqlite, postgres, libsql */\n databaseType: string = '';\n\n /** Theme, branding, navigation settings (JSON) */\n portalConfig: string = '';\n\n /** Git repository URL */\n repositoryUrl: string = '';\n\n /** Starter template name */\n templateName: string = '';\n\n /** Infrastructure provisioning status */\n provisioningStatus: ProvisioningStatus | '' = '';\n\n /** When provisioning completed */\n provisionedAt: Date | null = null;\n\n /** Last provisioning error message */\n provisioningError: string = '';\n\n /** Extensible key-value metadata (JSON) */\n metadata: string = '';\n\n createdAt: Date = new Date();\n updatedAt: Date = new Date();\n\n constructor(options: SiteOptions = {}) {\n super(options);\n\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.status !== undefined) this.status = options.status;\n if (options.tier !== undefined) this.tier = options.tier;\n if (options.databaseUrl !== undefined)\n this.databaseUrl = options.databaseUrl;\n if (options.databaseType !== undefined)\n this.databaseType = options.databaseType;\n if (options.repositoryUrl !== undefined)\n this.repositoryUrl = options.repositoryUrl;\n if (options.templateName !== undefined)\n this.templateName = options.templateName;\n if (options.provisioningStatus !== undefined)\n this.provisioningStatus = options.provisioningStatus;\n if (options.provisionedAt !== undefined)\n this.provisionedAt = options.provisionedAt;\n if (options.provisioningError !== undefined)\n this.provisioningError = options.provisioningError;\n\n // Handle portalConfig — can be object or JSON string\n if (options.portalConfig !== undefined) {\n if (typeof options.portalConfig === 'string') {\n this.portalConfig = options.portalConfig;\n } else {\n this.portalConfig = JSON.stringify(options.portalConfig);\n }\n }\n\n // Handle metadata — can be object or JSON string\n if (options.metadata !== undefined) {\n if (typeof options.metadata === 'string') {\n this.metadata = options.metadata;\n } else {\n this.metadata = JSON.stringify(options.metadata);\n }\n }\n\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n /**\n * Check if the site is active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if the site is provisioned and ready\n */\n isReady(): boolean {\n return this.provisioningStatus === 'ready';\n }\n\n /**\n * Get portal config as parsed object\n */\n getPortalConfig(): SitePortalConfig {\n if (!this.portalConfig) return {};\n try {\n return JSON.parse(this.portalConfig);\n } catch {\n return {};\n }\n }\n\n /**\n * Set portal config from object\n */\n setPortalConfig(config: SitePortalConfig): void {\n this.portalConfig = JSON.stringify(config);\n }\n\n /**\n * Get metadata as parsed object\n */\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n return JSON.parse(this.metadata);\n } catch {\n return {};\n }\n }\n\n /**\n * Set metadata from object\n */\n setMetadata(data: Record<string, unknown>): void {\n this.metadata = JSON.stringify(data);\n }\n\n /**\n * Update metadata by merging with existing values\n */\n updateMetadata(updates: Record<string, unknown>): void {\n const current = this.getMetadata();\n this.setMetadata({ ...current, ...updates });\n }\n}\n","/**\n * SiteCollection - Collection manager for Site objects\n *\n * Provides domain lookup, tenant filtering, and status queries.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Site } from '../models/Site';\nimport type { SiteStatus } from '../types';\n\nexport class SiteCollection extends SmrtCollection<Site> {\n static readonly _itemClass = Site;\n\n /**\n * Find a site by its primary domain\n */\n async findByDomain(domain: string): Promise<Site | null> {\n const results = await this.list({ where: { domain } });\n return results[0] ?? null;\n }\n\n /**\n * Find all sites belonging to a tenant\n */\n async findByTenant(tenantId: string): Promise<Site[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find sites by lifecycle status\n */\n async findByStatus(status: SiteStatus): Promise<Site[]> {\n return this.list({ where: { status } });\n }\n\n /**\n * Find all active sites\n */\n async findActive(): Promise<Site[]> {\n return this.findByStatus('active');\n }\n}\n","/**\n * SiteService - High-level API for site lifecycle management\n *\n * Provides stateless operations for creating, activating, suspending,\n * and archiving sites, as well as managing agent bindings.\n */\n\nimport type { SiteAgentBindingCollection } from '../collections/SiteAgentBindingCollection';\nimport type { SiteCollection } from '../collections/SiteCollection';\nimport type { Site } from '../models/Site';\nimport type { SiteAgentBinding } from '../models/SiteAgentBinding';\nimport type { CreateSiteData } from '../types';\n\n/**\n * Options for creating a SiteService\n */\nexport interface SiteServiceOptions {\n sites: SiteCollection;\n bindings: SiteAgentBindingCollection;\n}\n\nexport class SiteService {\n private sites: SiteCollection;\n private bindings: SiteAgentBindingCollection;\n\n constructor(options: SiteServiceOptions) {\n this.sites = options.sites;\n this.bindings = options.bindings;\n }\n\n /**\n * Create a new site in draft status\n */\n async createSite(tenantId: string, data: CreateSiteData): Promise<Site> {\n const site = await this.sites.create({\n tenantId,\n name: data.name,\n domain: data.domain,\n status: 'draft',\n tier: data.tier ?? 'free',\n databaseUrl: data.databaseUrl ?? '',\n databaseType: data.databaseType ?? '',\n portalConfig: JSON.stringify(data.portalConfig ?? {}),\n repositoryUrl: data.repositoryUrl ?? '',\n templateName: data.templateName ?? '',\n provisioningStatus: 'pending',\n metadata: JSON.stringify(data.metadata ?? {}),\n });\n await site.save();\n return site;\n }\n\n /**\n * Activate a site — validates required fields then sets status to active\n */\n async activateSite(siteId: string): Promise<Site> {\n const site = await this.requireSite(siteId);\n\n if (!site.name) {\n throw new Error('Cannot activate site: name is required');\n }\n if (!site.domain) {\n throw new Error('Cannot activate site: domain is required');\n }\n\n site.status = 'active';\n site.updatedAt = new Date();\n await site.save();\n return site;\n }\n\n /**\n * Suspend a site\n */\n async suspendSite(siteId: string): Promise<Site> {\n const site = await this.requireSite(siteId);\n site.status = 'suspended';\n site.updatedAt = new Date();\n await site.save();\n return site;\n }\n\n /**\n * Archive a site\n */\n async archiveSite(siteId: string): Promise<Site> {\n const site = await this.requireSite(siteId);\n site.status = 'archived';\n site.updatedAt = new Date();\n await site.save();\n return site;\n }\n\n /**\n * Mark site as provisioned and ready\n */\n async markProvisioned(siteId: string): Promise<Site> {\n const site = await this.requireSite(siteId);\n site.provisioningStatus = 'ready';\n site.provisionedAt = new Date();\n site.provisioningError = '';\n site.updatedAt = new Date();\n await site.save();\n return site;\n }\n\n /**\n * Mark site provisioning as failed\n */\n async markProvisioningFailed(siteId: string, error: string): Promise<Site> {\n const site = await this.requireSite(siteId);\n site.provisioningStatus = 'failed';\n site.provisioningError = error;\n site.updatedAt = new Date();\n await site.save();\n return site;\n }\n\n /**\n * Bind an agent to a site\n */\n async bindAgent(\n siteId: string,\n agentClass: string,\n config?: Record<string, unknown>,\n ): Promise<SiteAgentBinding> {\n const site = await this.requireSite(siteId);\n\n const existing = await this.bindings.findBySiteAndAgent(siteId, agentClass);\n if (existing) {\n existing.enabled = true;\n if (config !== undefined) existing.config = config;\n await existing.save();\n return existing;\n }\n\n const binding = await this.bindings.create({\n tenantId: site.tenantId,\n siteId,\n agentClass,\n enabled: true,\n config: config ?? null,\n });\n await binding.save();\n return binding;\n }\n\n /**\n * Unbind an agent from a site\n */\n async unbindAgent(siteId: string, agentClass: string): Promise<void> {\n const binding = await this.bindings.findBySiteAndAgent(siteId, agentClass);\n if (binding) {\n await binding.delete();\n }\n }\n\n /**\n * Get all enabled agent bindings for a site\n */\n async getEnabledAgents(siteId: string): Promise<SiteAgentBinding[]> {\n return this.bindings.findEnabled(siteId);\n }\n\n /**\n * Load a site by ID, throwing if not found\n */\n private async requireSite(siteId: string): Promise<Site> {\n const site = await this.sites.get({ id: siteId });\n if (!site) {\n throw new Error(`Site '${siteId}' not found`);\n }\n return site;\n }\n}\n"],"names":["__decorateClass","tenantId"],"mappings":"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"}