@happyvertical/smrt-properties 0.37.2 → 0.37.3

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 CHANGED
@@ -1,788 +1,732 @@
1
- import { ObjectRegistry, crossPackageRef, smrt, SmrtObject, SmrtCollection, foreignKey, SmrtHierarchical } from "@happyvertical/smrt-core";
1
+ import { ObjectRegistry, SmrtCollection, SmrtHierarchical, SmrtObject, crossPackageRef, foreignKey, smrt } from "@happyvertical/smrt-core";
2
2
  import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
3
- import { tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
4
- ObjectRegistry.registerPackageManifest(
5
- new URL("./manifest.json", import.meta.url)
6
- );
7
- const smrtPropertiesSummarizePrompt = definePrompt({
8
- key: "smrtProperties.property.summarize",
9
- template: `Summarize this digital property:
3
+ import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
4
+ //#region \0rolldown/runtime.js
5
+ var __defProp$2 = Object.defineProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp$2(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp$2(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ //#endregion
16
+ //#region src/__smrt-register__.ts
17
+ ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
18
+ //#endregion
19
+ //#region src/prompts.ts
20
+ var smrtPropertiesSummarizePrompt = definePrompt({
21
+ key: "smrtProperties.property.summarize",
22
+ template: `Summarize this digital property:
10
23
  Name: {propertyName}
11
24
  Domain: {propertyDomain}
12
25
  Description: {propertyDescription}
13
26
  Status: {propertyStatus}
14
27
  Total zones: {totalZones}
15
28
  Top-level zones: {topLevelZones}`,
16
- editable: {
17
- template: true,
18
- profile: true,
19
- model: true,
20
- params: true
21
- }
29
+ editable: {
30
+ template: true,
31
+ profile: true,
32
+ model: true,
33
+ params: true
34
+ }
22
35
  });
23
36
  function promptMessageOptions(ai) {
24
- return {
25
- ...ai.params || {},
26
- ...ai.model ? { model: ai.model } : {},
27
- ...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
28
- ...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
29
- };
37
+ return {
38
+ ...ai.params || {},
39
+ ...ai.model ? { model: ai.model } : {},
40
+ ...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
41
+ ...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
42
+ };
30
43
  }
44
+ //#endregion
45
+ //#region src/models/Property.ts
31
46
  var __defProp$1 = Object.defineProperty;
32
47
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
33
48
  var __decorateClass$1 = (decorators, target, key, kind) => {
34
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
35
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
36
- if (decorator = decorators[i])
37
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
38
- if (kind && result) __defProp$1(target, key, result);
39
- return result;
49
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
50
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
51
+ if (kind && result) __defProp$1(target, key, result);
52
+ return result;
40
53
  };
41
- let Property = class extends SmrtObject {
42
- tenantId = null;
43
- /**
44
- * Display name of the property
45
- */
46
- name = "";
47
- /**
48
- * Domain name (e.g., "oakcreeknews.com")
49
- */
50
- domain = "";
51
- /**
52
- * Full URL (e.g., "https://oakcreeknews.com")
53
- */
54
- url = "";
55
- /**
56
- * Property description
57
- */
58
- description = "";
59
- repositoryId = null;
60
- ownerId = null;
61
- /**
62
- * Property status
63
- */
64
- status = "active";
65
- /**
66
- * Extensible metadata (analytics IDs, config, etc.)
67
- */
68
- metadata = {};
69
- constructor(options = {}) {
70
- super(options);
71
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
72
- if (options.name !== void 0) this.name = options.name;
73
- if (options.domain !== void 0) this.domain = options.domain;
74
- if (options.url !== void 0) this.url = options.url;
75
- if (options.description !== void 0)
76
- this.description = options.description;
77
- if (options.repositoryId !== void 0)
78
- this.repositoryId = options.repositoryId;
79
- if (options.ownerId !== void 0) this.ownerId = options.ownerId;
80
- if (options.status !== void 0) this.status = options.status;
81
- if (options.metadata !== void 0) this.metadata = options.metadata;
82
- }
83
- /**
84
- * Get all zones for this property
85
- */
86
- async getZones() {
87
- if (!this.id) return [];
88
- const { ZoneCollection: ZoneCollection2 } = await Promise.resolve().then(() => Zones);
89
- const collection = await ZoneCollection2.create(this.options);
90
- return await collection.findByProperty(this.id);
91
- }
92
- /**
93
- * Get zone tree for this property
94
- */
95
- async getZoneTree() {
96
- if (!this.id) return { propertyId: "", roots: [] };
97
- const { ZoneCollection: ZoneCollection2 } = await Promise.resolve().then(() => Zones);
98
- const collection = await ZoneCollection2.create(this.options);
99
- return await collection.getTree(this.id);
100
- }
101
- /**
102
- * Create a zone on this property
103
- */
104
- async createZone(options) {
105
- if (!this.id) {
106
- throw new Error("Property must be saved before creating zones");
107
- }
108
- const { ZoneCollection: ZoneCollection2 } = await Promise.resolve().then(() => Zones);
109
- const collection = await ZoneCollection2.create(this.options);
110
- const zone = await collection.create({
111
- ...options,
112
- propertyId: this.id
113
- });
114
- await zone.save();
115
- return zone;
116
- }
117
- /**
118
- * Check if property is active
119
- */
120
- isActive() {
121
- return this.status === "active";
122
- }
123
- /**
124
- * AI-powered: Generate a summary of the property and its zones.
125
- *
126
- * Uses the `smrtProperties.property.summarize` prompt registered via
127
- * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
128
- * overrides of the template, model, and parameters at runtime.
129
- *
130
- * Only non-PII fields (name, domain, description, status) plus aggregate
131
- * zone information are sent to the AI provider. Internal foreign-key
132
- * fields and the extensible `metadata` blob are intentionally excluded.
133
- *
134
- * @returns Generated summary text
135
- */
136
- async summarize() {
137
- const zones = await this.getZones();
138
- const topLevelZones = zones.filter((z) => z.parentId === null);
139
- const db = this.options.db ?? this.options.persistence;
140
- const resolvedPrompt = await resolvePrompt(
141
- smrtPropertiesSummarizePrompt.key,
142
- {
143
- db,
144
- tenantId: this.tenantId,
145
- variables: {
146
- propertyName: this.name || "",
147
- propertyDomain: this.domain || "",
148
- propertyDescription: this.description || "",
149
- propertyStatus: this.status || "",
150
- totalZones: String(zones.length),
151
- topLevelZones: topLevelZones.map((z) => z.name).join(", ")
152
- }
153
- }
154
- );
155
- const ai = await this.getAiClient();
156
- const response = await ai.message(
157
- resolvedPrompt.text,
158
- promptMessageOptions(resolvedPrompt.ai)
159
- );
160
- return response.trim();
161
- }
54
+ var Property = class extends SmrtObject {
55
+ tenantId = null;
56
+ /**
57
+ * Display name of the property
58
+ */
59
+ name = "";
60
+ /**
61
+ * Domain name (e.g., "oakcreeknews.com")
62
+ */
63
+ domain = "";
64
+ /**
65
+ * Full URL (e.g., "https://oakcreeknews.com")
66
+ */
67
+ url = "";
68
+ /**
69
+ * Property description
70
+ */
71
+ description = "";
72
+ repositoryId = null;
73
+ ownerId = null;
74
+ /**
75
+ * Property status
76
+ */
77
+ status = "active";
78
+ /**
79
+ * Extensible metadata (analytics IDs, config, etc.)
80
+ */
81
+ metadata = {};
82
+ constructor(options = {}) {
83
+ super(options);
84
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
85
+ if (options.name !== void 0) this.name = options.name;
86
+ if (options.domain !== void 0) this.domain = options.domain;
87
+ if (options.url !== void 0) this.url = options.url;
88
+ if (options.description !== void 0) this.description = options.description;
89
+ if (options.repositoryId !== void 0) this.repositoryId = options.repositoryId;
90
+ if (options.ownerId !== void 0) this.ownerId = options.ownerId;
91
+ if (options.status !== void 0) this.status = options.status;
92
+ if (options.metadata !== void 0) this.metadata = options.metadata;
93
+ }
94
+ /**
95
+ * Get all zones for this property
96
+ */
97
+ async getZones() {
98
+ if (!this.id) return [];
99
+ const { ZoneCollection } = await Promise.resolve().then(() => Zones_exports);
100
+ return await (await ZoneCollection.create(this.options)).findByProperty(this.id);
101
+ }
102
+ /**
103
+ * Get zone tree for this property
104
+ */
105
+ async getZoneTree() {
106
+ if (!this.id) return {
107
+ propertyId: "",
108
+ roots: []
109
+ };
110
+ const { ZoneCollection } = await Promise.resolve().then(() => Zones_exports);
111
+ return await (await ZoneCollection.create(this.options)).getTree(this.id);
112
+ }
113
+ /**
114
+ * Create a zone on this property
115
+ */
116
+ async createZone(options) {
117
+ if (!this.id) throw new Error("Property must be saved before creating zones");
118
+ const { ZoneCollection } = await Promise.resolve().then(() => Zones_exports);
119
+ const zone = await (await ZoneCollection.create(this.options)).create({
120
+ ...options,
121
+ propertyId: this.id
122
+ });
123
+ await zone.save();
124
+ return zone;
125
+ }
126
+ /**
127
+ * Check if property is active
128
+ */
129
+ isActive() {
130
+ return this.status === "active";
131
+ }
132
+ /**
133
+ * AI-powered: Generate a summary of the property and its zones.
134
+ *
135
+ * Uses the `smrtProperties.property.summarize` prompt registered via
136
+ * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
137
+ * overrides of the template, model, and parameters at runtime.
138
+ *
139
+ * Only non-PII fields (name, domain, description, status) plus aggregate
140
+ * zone information are sent to the AI provider. Internal foreign-key
141
+ * fields and the extensible `metadata` blob are intentionally excluded.
142
+ *
143
+ * @returns Generated summary text
144
+ */
145
+ async summarize() {
146
+ const zones = await this.getZones();
147
+ const topLevelZones = zones.filter((z) => z.parentId === null);
148
+ const db = this.options.db ?? this.options.persistence;
149
+ const resolvedPrompt = await resolvePrompt(smrtPropertiesSummarizePrompt.key, {
150
+ db,
151
+ tenantId: this.tenantId,
152
+ variables: {
153
+ propertyName: this.name || "",
154
+ propertyDomain: this.domain || "",
155
+ propertyDescription: this.description || "",
156
+ propertyStatus: this.status || "",
157
+ totalZones: String(zones.length),
158
+ topLevelZones: topLevelZones.map((z) => z.name).join(", ")
159
+ }
160
+ });
161
+ return (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
162
+ }
162
163
  };
163
- __decorateClass$1([
164
- tenantId({ nullable: true })
165
- ], Property.prototype, "tenantId", 2);
166
- __decorateClass$1([
167
- crossPackageRef("@happyvertical/smrt-projects:Repository")
168
- ], Property.prototype, "repositoryId", 2);
169
- __decorateClass$1([
170
- crossPackageRef("@happyvertical/smrt-profiles:Profile")
171
- ], Property.prototype, "ownerId", 2);
172
- Property = __decorateClass$1([
173
- TenantScoped({ mode: "optional" }),
174
- smrt({
175
- tableStrategy: "sti",
176
- api: { include: ["list", "get", "create", "update", "delete"] },
177
- mcp: { include: ["list", "get", "create"] },
178
- cli: true
179
- })
180
- ], Property);
181
- class PropertyCollection extends SmrtCollection {
182
- static _itemClass = Property;
183
- /**
184
- * Find a property by domain
185
- *
186
- * @param domain - Domain name
187
- * @returns Property or null
188
- */
189
- async findByDomain(domain) {
190
- const results = await this.list({
191
- where: { domain },
192
- limit: 1
193
- });
194
- return results[0] || null;
195
- }
196
- /**
197
- * Find properties by repository ID
198
- *
199
- * @param repositoryId - Repository ID
200
- * @returns Array of properties
201
- */
202
- async findByRepository(repositoryId) {
203
- return await this.list({
204
- where: { repositoryId }
205
- });
206
- }
207
- /**
208
- * Find properties by owner ID
209
- *
210
- * @param ownerId - Owner profile ID
211
- * @returns Array of properties
212
- */
213
- async findByOwner(ownerId) {
214
- return await this.list({
215
- where: { ownerId }
216
- });
217
- }
218
- /**
219
- * Find active properties
220
- *
221
- * @returns Array of active properties
222
- */
223
- async findActive() {
224
- return await this.list({
225
- where: { status: "active" }
226
- });
227
- }
228
- /**
229
- * Find properties by status
230
- *
231
- * @param status - Property status
232
- * @returns Array of properties with the given status
233
- */
234
- async findByStatus(status) {
235
- return await this.list({
236
- where: { status }
237
- });
238
- }
239
- /**
240
- * Get or create a property by domain
241
- *
242
- * @param domain - Domain name
243
- * @param defaults - Default values for creation
244
- * @returns Property (existing or newly created)
245
- */
246
- async getOrCreateByDomain(domain, defaults = {}) {
247
- let property = await this.findByDomain(domain);
248
- if (!property) {
249
- property = await this.create({
250
- domain,
251
- name: defaults.name || domain,
252
- url: defaults.url || `https://${domain}`,
253
- repositoryId: defaults.repositoryId ?? null,
254
- ownerId: defaults.ownerId ?? null,
255
- status: "active"
256
- });
257
- await property.save();
258
- }
259
- return property;
260
- }
261
- /**
262
- * Count properties by status
263
- *
264
- * @returns Object with counts per status
265
- */
266
- async countByStatus() {
267
- const all = await this.list({});
268
- const counts = {
269
- active: 0,
270
- inactive: 0,
271
- pending: 0
272
- };
273
- for (const prop of all) {
274
- counts[prop.status]++;
275
- }
276
- return counts;
277
- }
278
- // ============================================
279
- // Tenant Helper Methods
280
- // ============================================
281
- /**
282
- * Find properties belonging to a specific tenant
283
- *
284
- * @param tenantId - Tenant ID to filter by
285
- * @returns Array of properties for the tenant
286
- */
287
- async findByTenant(tenantId2) {
288
- return this.list({ where: { tenantId: tenantId2 } });
289
- }
290
- /**
291
- * Find all global properties (no tenant association).
292
- *
293
- * Routes through the shared tenant-global helper so it does not throw under
294
- * an active tenant context (an explicit `tenant_id IS NULL` filter would be
295
- * flagged as an isolation violation). (#1600)
296
- *
297
- * @returns Array of global properties
298
- */
299
- async findGlobal() {
300
- return queryGlobal(this);
301
- }
302
- /**
303
- * Find properties for a tenant plus all global properties.
304
- *
305
- * Fails closed if an active tenant context requests a different tenant's
306
- * rows; the admin/system path keeps the cross-tenant capability. (#1600)
307
- *
308
- * @param tenantId - Tenant ID to filter by
309
- * @returns Array of tenant-specific and global properties
310
- */
311
- async findWithGlobals(tenantId2) {
312
- return queryWithGlobals(
313
- this,
314
- tenantId2,
315
- "Property.findWithGlobals"
316
- );
317
- }
318
- }
319
- const Properties = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
320
- __proto__: null,
321
- PropertyCollection
322
- }, Symbol.toStringTag, { value: "Module" }));
164
+ __decorateClass$1([tenantId({ nullable: true })], Property.prototype, "tenantId", 2);
165
+ __decorateClass$1([crossPackageRef("@happyvertical/smrt-projects:Repository")], Property.prototype, "repositoryId", 2);
166
+ __decorateClass$1([crossPackageRef("@happyvertical/smrt-profiles:Profile")], Property.prototype, "ownerId", 2);
167
+ Property = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
168
+ tableStrategy: "sti",
169
+ api: { include: [
170
+ "list",
171
+ "get",
172
+ "create",
173
+ "update",
174
+ "delete"
175
+ ] },
176
+ mcp: { include: [
177
+ "list",
178
+ "get",
179
+ "create"
180
+ ] },
181
+ cli: true
182
+ })], Property);
183
+ //#endregion
184
+ //#region src/collections/Properties.ts
185
+ var Properties_exports = /* @__PURE__ */ __exportAll({ PropertyCollection: () => PropertyCollection });
186
+ var PropertyCollection = class extends SmrtCollection {
187
+ static _itemClass = Property;
188
+ /**
189
+ * Find a property by domain
190
+ *
191
+ * @param domain - Domain name
192
+ * @returns Property or null
193
+ */
194
+ async findByDomain(domain) {
195
+ return (await this.list({
196
+ where: { domain },
197
+ limit: 1
198
+ }))[0] || null;
199
+ }
200
+ /**
201
+ * Find properties by repository ID
202
+ *
203
+ * @param repositoryId - Repository ID
204
+ * @returns Array of properties
205
+ */
206
+ async findByRepository(repositoryId) {
207
+ return await this.list({ where: { repositoryId } });
208
+ }
209
+ /**
210
+ * Find properties by owner ID
211
+ *
212
+ * @param ownerId - Owner profile ID
213
+ * @returns Array of properties
214
+ */
215
+ async findByOwner(ownerId) {
216
+ return await this.list({ where: { ownerId } });
217
+ }
218
+ /**
219
+ * Find active properties
220
+ *
221
+ * @returns Array of active properties
222
+ */
223
+ async findActive() {
224
+ return await this.list({ where: { status: "active" } });
225
+ }
226
+ /**
227
+ * Find properties by status
228
+ *
229
+ * @param status - Property status
230
+ * @returns Array of properties with the given status
231
+ */
232
+ async findByStatus(status) {
233
+ return await this.list({ where: { status } });
234
+ }
235
+ /**
236
+ * Get or create a property by domain
237
+ *
238
+ * @param domain - Domain name
239
+ * @param defaults - Default values for creation
240
+ * @returns Property (existing or newly created)
241
+ */
242
+ async getOrCreateByDomain(domain, defaults = {}) {
243
+ let property = await this.findByDomain(domain);
244
+ if (!property) {
245
+ property = await this.create({
246
+ domain,
247
+ name: defaults.name || domain,
248
+ url: defaults.url || `https://${domain}`,
249
+ repositoryId: defaults.repositoryId ?? null,
250
+ ownerId: defaults.ownerId ?? null,
251
+ status: "active"
252
+ });
253
+ await property.save();
254
+ }
255
+ return property;
256
+ }
257
+ /**
258
+ * Count properties by status
259
+ *
260
+ * @returns Object with counts per status
261
+ */
262
+ async countByStatus() {
263
+ const all = await this.list({});
264
+ const counts = {
265
+ active: 0,
266
+ inactive: 0,
267
+ pending: 0
268
+ };
269
+ for (const prop of all) counts[prop.status]++;
270
+ return counts;
271
+ }
272
+ /**
273
+ * Find properties belonging to a specific tenant
274
+ *
275
+ * @param tenantId - Tenant ID to filter by
276
+ * @returns Array of properties for the tenant
277
+ */
278
+ async findByTenant(tenantId) {
279
+ return this.list({ where: { tenantId } });
280
+ }
281
+ /**
282
+ * Find all global properties (no tenant association).
283
+ *
284
+ * Routes through the shared tenant-global helper so it does not throw under
285
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
286
+ * flagged as an isolation violation). (#1600)
287
+ *
288
+ * @returns Array of global properties
289
+ */
290
+ async findGlobal() {
291
+ return queryGlobal(this);
292
+ }
293
+ /**
294
+ * Find properties for a tenant plus all global properties.
295
+ *
296
+ * Fails closed if an active tenant context requests a different tenant's
297
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
298
+ *
299
+ * @param tenantId - Tenant ID to filter by
300
+ * @returns Array of tenant-specific and global properties
301
+ */
302
+ async findWithGlobals(tenantId) {
303
+ return queryWithGlobals(this, tenantId, "Property.findWithGlobals");
304
+ }
305
+ };
306
+ //#endregion
307
+ //#region src/models/Zone.ts
323
308
  var __defProp = Object.defineProperty;
324
309
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
325
310
  var __decorateClass = (decorators, target, key, kind) => {
326
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
327
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
328
- if (decorator = decorators[i])
329
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
330
- if (kind && result) __defProp(target, key, result);
331
- return result;
311
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
312
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
313
+ if (kind && result) __defProp(target, key, result);
314
+ return result;
332
315
  };
333
- let Zone = class extends SmrtHierarchical {
334
- tenantId = null;
335
- propertyId = "";
336
- // parentId inherited from SmrtHierarchical (null = top-level zone)
337
- /**
338
- * Display name
339
- */
340
- name = "";
341
- /**
342
- * Zone type (descriptive, not structural)
343
- * Common values: "page", "section", "slot", "container", "widget"
344
- */
345
- type = "";
346
- /**
347
- * URL path pattern (e.g., "/", "/articles/*")
348
- */
349
- path = "";
350
- /**
351
- * CSS selector for placement (e.g., "#header", ".sidebar")
352
- */
353
- selector = "";
354
- /**
355
- * Position hint (e.g., "top", "after-paragraph-3")
356
- */
357
- position = "";
358
- /**
359
- * Width in pixels (null = fluid)
360
- */
361
- width = null;
362
- /**
363
- * Height in pixels (null = fluid)
364
- */
365
- height = null;
366
- /**
367
- * Allowed content/ad formats
368
- */
369
- allowedFormats = [];
370
- /**
371
- * Default content/asset ID for fallback
372
- */
373
- defaultContentId = null;
374
- /**
375
- * Extensible metadata
376
- */
377
- metadata = {};
378
- constructor(options = {}) {
379
- super(options);
380
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
381
- if (options.propertyId !== void 0) this.propertyId = options.propertyId;
382
- if (options.parentId !== void 0) this.parentId = options.parentId;
383
- if (options.name !== void 0) this.name = options.name;
384
- if (options.type !== void 0) this.type = options.type;
385
- if (options.path !== void 0) this.path = options.path;
386
- if (options.selector !== void 0) this.selector = options.selector;
387
- if (options.position !== void 0) this.position = options.position;
388
- if (options.width !== void 0) this.width = options.width;
389
- if (options.height !== void 0) this.height = options.height;
390
- if (options.allowedFormats !== void 0)
391
- this.allowedFormats = options.allowedFormats;
392
- if (options.defaultContentId !== void 0)
393
- this.defaultContentId = options.defaultContentId;
394
- if (options.metadata !== void 0) this.metadata = options.metadata;
395
- }
396
- /**
397
- * Check if this is a top-level zone (no parent)
398
- */
399
- isTopLevel() {
400
- return this.parentId === null;
401
- }
402
- /**
403
- * Check if this zone has dimensions set
404
- */
405
- hasDimensions() {
406
- return this.width !== null && this.height !== null;
407
- }
408
- /**
409
- * Get dimensions as string (e.g., "728x90")
410
- */
411
- getDimensionString() {
412
- if (!this.hasDimensions()) return null;
413
- return `${this.width}x${this.height}`;
414
- }
415
- /**
416
- * Get the parent property
417
- */
418
- async getProperty() {
419
- const { PropertyCollection: PropertyCollection2 } = await Promise.resolve().then(() => Properties);
420
- const collection = await PropertyCollection2.create(this.options);
421
- return await collection.get({ id: this.propertyId });
422
- }
423
- // Hierarchy traversal (getParent / getChildren / getAncestors /
424
- // getDescendants / getHierarchy / moveTo) provided by SmrtHierarchical.
425
- // The collection-level depth cache in `ZoneCollection.getAncestors` /
426
- // `getDescendants` remains available for callers that go through the
427
- // collection — the base-class methods do their own batched queries.
428
- /**
429
- * Get the full path from root to this zone
430
- */
431
- async getFullPath() {
432
- const ancestors = await this.getAncestors();
433
- const names = [...ancestors.map((z) => z.name), this.name];
434
- return names.join(" > ");
435
- }
436
- /**
437
- * Get the depth in the hierarchy (0 = top-level)
438
- */
439
- async getDepth() {
440
- const ancestors = await this.getAncestors();
441
- return ancestors.length;
442
- }
443
- /**
444
- * Create a child zone under this zone
445
- */
446
- async createChild(options) {
447
- if (!this.id) {
448
- throw new Error("Zone must be saved before creating children");
449
- }
450
- const { ZoneCollection: ZoneCollection2 } = await Promise.resolve().then(() => Zones);
451
- const collection = await ZoneCollection2.create(this.options);
452
- const zone = await collection.create({
453
- ...options,
454
- propertyId: this.propertyId,
455
- parentId: this.id
456
- });
457
- await zone.save();
458
- return zone;
459
- }
460
- /**
461
- * Build tree node for this zone and its children
462
- */
463
- async toTreeNode() {
464
- const children = await this.getChildren();
465
- const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));
466
- return {
467
- zone: this,
468
- children: childNodes
469
- };
470
- }
471
- /**
472
- * Check if a format is allowed in this zone
473
- */
474
- isFormatAllowed(format) {
475
- if (this.allowedFormats.length === 0) return true;
476
- return this.allowedFormats.includes(format);
477
- }
316
+ var Zone = class extends SmrtHierarchical {
317
+ tenantId = null;
318
+ propertyId = "";
319
+ /**
320
+ * Display name
321
+ */
322
+ name = "";
323
+ /**
324
+ * Zone type (descriptive, not structural)
325
+ * Common values: "page", "section", "slot", "container", "widget"
326
+ */
327
+ type = "";
328
+ /**
329
+ * URL path pattern (e.g., "/", "/articles/*")
330
+ */
331
+ path = "";
332
+ /**
333
+ * CSS selector for placement (e.g., "#header", ".sidebar")
334
+ */
335
+ selector = "";
336
+ /**
337
+ * Position hint (e.g., "top", "after-paragraph-3")
338
+ */
339
+ position = "";
340
+ /**
341
+ * Width in pixels (null = fluid)
342
+ */
343
+ width = null;
344
+ /**
345
+ * Height in pixels (null = fluid)
346
+ */
347
+ height = null;
348
+ /**
349
+ * Allowed content/ad formats
350
+ */
351
+ allowedFormats = [];
352
+ /**
353
+ * Default content/asset ID for fallback
354
+ */
355
+ defaultContentId = null;
356
+ /**
357
+ * Extensible metadata
358
+ */
359
+ metadata = {};
360
+ constructor(options = {}) {
361
+ super(options);
362
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
363
+ if (options.propertyId !== void 0) this.propertyId = options.propertyId;
364
+ if (options.parentId !== void 0) this.parentId = options.parentId;
365
+ if (options.name !== void 0) this.name = options.name;
366
+ if (options.type !== void 0) this.type = options.type;
367
+ if (options.path !== void 0) this.path = options.path;
368
+ if (options.selector !== void 0) this.selector = options.selector;
369
+ if (options.position !== void 0) this.position = options.position;
370
+ if (options.width !== void 0) this.width = options.width;
371
+ if (options.height !== void 0) this.height = options.height;
372
+ if (options.allowedFormats !== void 0) this.allowedFormats = options.allowedFormats;
373
+ if (options.defaultContentId !== void 0) this.defaultContentId = options.defaultContentId;
374
+ if (options.metadata !== void 0) this.metadata = options.metadata;
375
+ }
376
+ /**
377
+ * Check if this is a top-level zone (no parent)
378
+ */
379
+ isTopLevel() {
380
+ return this.parentId === null;
381
+ }
382
+ /**
383
+ * Check if this zone has dimensions set
384
+ */
385
+ hasDimensions() {
386
+ return this.width !== null && this.height !== null;
387
+ }
388
+ /**
389
+ * Get dimensions as string (e.g., "728x90")
390
+ */
391
+ getDimensionString() {
392
+ if (!this.hasDimensions()) return null;
393
+ return `${this.width}x${this.height}`;
394
+ }
395
+ /**
396
+ * Get the parent property
397
+ */
398
+ async getProperty() {
399
+ const { PropertyCollection } = await Promise.resolve().then(() => Properties_exports);
400
+ return await (await PropertyCollection.create(this.options)).get({ id: this.propertyId });
401
+ }
402
+ /**
403
+ * Get the full path from root to this zone
404
+ */
405
+ async getFullPath() {
406
+ return [...(await this.getAncestors()).map((z) => z.name), this.name].join(" > ");
407
+ }
408
+ /**
409
+ * Get the depth in the hierarchy (0 = top-level)
410
+ */
411
+ async getDepth() {
412
+ return (await this.getAncestors()).length;
413
+ }
414
+ /**
415
+ * Create a child zone under this zone
416
+ */
417
+ async createChild(options) {
418
+ if (!this.id) throw new Error("Zone must be saved before creating children");
419
+ const { ZoneCollection } = await Promise.resolve().then(() => Zones_exports);
420
+ const zone = await (await ZoneCollection.create(this.options)).create({
421
+ ...options,
422
+ propertyId: this.propertyId,
423
+ parentId: this.id
424
+ });
425
+ await zone.save();
426
+ return zone;
427
+ }
428
+ /**
429
+ * Build tree node for this zone and its children
430
+ */
431
+ async toTreeNode() {
432
+ const children = await this.getChildren();
433
+ const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));
434
+ return {
435
+ zone: this,
436
+ children: childNodes
437
+ };
438
+ }
439
+ /**
440
+ * Check if a format is allowed in this zone
441
+ */
442
+ isFormatAllowed(format) {
443
+ if (this.allowedFormats.length === 0) return true;
444
+ return this.allowedFormats.includes(format);
445
+ }
478
446
  };
479
- __decorateClass([
480
- tenantId({ nullable: true })
481
- ], Zone.prototype, "tenantId", 2);
482
- __decorateClass([
483
- foreignKey("Property")
484
- ], Zone.prototype, "propertyId", 2);
485
- Zone = __decorateClass([
486
- TenantScoped({ mode: "optional" }),
487
- smrt({
488
- api: { include: ["list", "get", "create", "update", "delete"] },
489
- mcp: { include: ["list", "get", "create"] },
490
- cli: true
491
- })
492
- ], Zone);
493
- class ZoneCollection extends SmrtCollection {
494
- static _itemClass = Zone;
495
- /**
496
- * Find all zones for a property
497
- *
498
- * @param propertyId - Property ID
499
- * @returns Array of zones
500
- */
501
- async findByProperty(propertyId) {
502
- return await this.list({
503
- where: { propertyId }
504
- });
505
- }
506
- /**
507
- * Find top-level zones for a property (no parent)
508
- *
509
- * @param propertyId - Property ID
510
- * @returns Array of top-level zones
511
- */
512
- async findTopLevel(propertyId) {
513
- return await this.list({
514
- where: { propertyId, parentId: null }
515
- });
516
- }
517
- /**
518
- * Find direct children of a zone
519
- *
520
- * @param parentId - Parent zone ID
521
- * @returns Array of child zones
522
- */
523
- async findChildren(parentId) {
524
- return await this.list({
525
- where: { parentId }
526
- });
527
- }
528
- /**
529
- * Find zones by path pattern
530
- *
531
- * @param propertyId - Property ID
532
- * @param path - URL path to match
533
- * @returns Array of matching zones
534
- */
535
- async findByPath(propertyId, path) {
536
- return await this.list({
537
- where: { propertyId, path }
538
- });
539
- }
540
- /**
541
- * Find zones by type
542
- *
543
- * @param propertyId - Property ID
544
- * @param type - Zone type
545
- * @returns Array of zones of the given type
546
- */
547
- async findByType(propertyId, type) {
548
- return await this.list({
549
- where: { propertyId, type }
550
- });
551
- }
552
- /**
553
- * Get the complete zone tree for a property
554
- *
555
- * @param propertyId - Property ID
556
- * @returns ZoneTree structure
557
- */
558
- async getTree(propertyId) {
559
- const allZones = await this.findByProperty(propertyId);
560
- const zoneMap = /* @__PURE__ */ new Map();
561
- const childrenMap = /* @__PURE__ */ new Map();
562
- for (const zone of allZones) {
563
- if (zone.id) {
564
- zoneMap.set(zone.id, zone);
565
- }
566
- if (!childrenMap.has(zone.parentId || "")) {
567
- childrenMap.set(zone.parentId || "", []);
568
- }
569
- childrenMap.get(zone.parentId || "")?.push(zone);
570
- }
571
- const buildNode = (zone) => {
572
- const children = (zone.id ? childrenMap.get(zone.id) : []) || [];
573
- return {
574
- zone,
575
- children: children.map(buildNode)
576
- };
577
- };
578
- const roots = childrenMap.get("") || [];
579
- return {
580
- propertyId,
581
- roots: roots.map(buildNode)
582
- };
583
- }
584
- /**
585
- * Get all ancestors of a zone (path to root)
586
- *
587
- * @param zoneId - Zone ID
588
- * @returns Array of ancestors (from root to parent)
589
- */
590
- async getAncestors(zoneId) {
591
- const ancestors = [];
592
- const startZone = await this.get({ id: zoneId });
593
- if (!startZone) return ancestors;
594
- let currentId = startZone.parentId;
595
- while (currentId) {
596
- const parent = await this.get({ id: currentId });
597
- if (!parent) break;
598
- ancestors.unshift(parent);
599
- currentId = parent.parentId;
600
- }
601
- return ancestors;
602
- }
603
- /**
604
- * Get all descendants of a zone recursively
605
- *
606
- * @param zoneId - Zone ID
607
- * @returns Array of all descendant zones
608
- */
609
- async getDescendants(zoneId) {
610
- const descendants = [];
611
- const queue = [zoneId];
612
- while (queue.length > 0) {
613
- const currentId = queue.shift();
614
- if (!currentId) continue;
615
- const children = await this.findChildren(currentId);
616
- for (const child of children) {
617
- descendants.push(child);
618
- if (child.id) {
619
- queue.push(child.id);
620
- }
621
- }
622
- }
623
- return descendants;
624
- }
625
- /**
626
- * Get the depth of the deepest zone in a property
627
- *
628
- * @param propertyId - Property ID
629
- * @returns Maximum depth (0 = only top-level zones)
630
- */
631
- async getMaxDepth(propertyId) {
632
- const allZones = await this.findByProperty(propertyId);
633
- const zonesById = /* @__PURE__ */ new Map();
634
- for (const zone of allZones) {
635
- if (zone.id) {
636
- zonesById.set(zone.id, zone);
637
- }
638
- }
639
- const depthCache = /* @__PURE__ */ new Map();
640
- const getDepth = (zone) => {
641
- if (!zone.id) return 0;
642
- const cached = depthCache.get(zone.id);
643
- if (cached !== void 0) return cached;
644
- let depth = 0;
645
- let current = zone;
646
- const visited = /* @__PURE__ */ new Set();
647
- while (current?.id) {
648
- const parentId = current.parentId;
649
- if (!parentId) break;
650
- if (visited.has(parentId)) break;
651
- visited.add(parentId);
652
- const parent = zonesById.get(parentId);
653
- if (!parent) break;
654
- depth += 1;
655
- current = parent;
656
- }
657
- depthCache.set(zone.id, depth);
658
- return depth;
659
- };
660
- let maxDepth = 0;
661
- for (const zone of allZones) {
662
- maxDepth = Math.max(maxDepth, getDepth(zone));
663
- }
664
- return maxDepth;
665
- }
666
- /**
667
- * Find zones with specific dimensions
668
- *
669
- * @param propertyId - Property ID
670
- * @param width - Required width
671
- * @param height - Required height
672
- * @returns Array of matching zones
673
- */
674
- async findByDimensions(propertyId, width, height) {
675
- return await this.list({
676
- where: { propertyId, width, height }
677
- });
678
- }
679
- /**
680
- * Find zones that allow a specific format
681
- *
682
- * @param propertyId - Property ID
683
- * @param format - Format to check
684
- * @returns Array of zones allowing the format
685
- */
686
- async findByAllowedFormat(propertyId, format) {
687
- const allZones = await this.findByProperty(propertyId);
688
- return allZones.filter((zone) => zone.isFormatAllowed(format));
689
- }
690
- /**
691
- * Move a zone to a new parent
692
- *
693
- * @param zoneId - Zone ID to move
694
- * @param newParentId - New parent ID (null for top-level)
695
- * @returns Updated zone
696
- */
697
- async moveZone(zoneId, newParentId) {
698
- const zone = await this.get({ id: zoneId });
699
- if (!zone) return null;
700
- if (newParentId) {
701
- const descendants = await this.getDescendants(zoneId);
702
- const descendantIds = descendants.map((d) => d.id);
703
- if (descendantIds.includes(newParentId)) {
704
- throw new Error("Cannot move zone into one of its descendants");
705
- }
706
- }
707
- zone.parentId = newParentId;
708
- await zone.save();
709
- return zone;
710
- }
711
- /**
712
- * Delete a zone and optionally its descendants
713
- *
714
- * @param zoneId - Zone ID to delete
715
- * @param cascade - Whether to delete descendants
716
- * @returns Number of zones deleted
717
- */
718
- async deleteZone(zoneId, cascade = false) {
719
- const zone = await this.get({ id: zoneId });
720
- if (!zone) return 0;
721
- let deleted = 0;
722
- if (cascade) {
723
- const descendants = await this.getDescendants(zoneId);
724
- for (const desc of descendants) {
725
- await desc.delete();
726
- deleted++;
727
- }
728
- } else {
729
- const children = await this.findChildren(zoneId);
730
- for (const child of children) {
731
- child.parentId = zone.parentId;
732
- await child.save();
733
- }
734
- }
735
- await zone.delete();
736
- deleted++;
737
- return deleted;
738
- }
739
- // ============================================
740
- // Tenant Helper Methods
741
- // ============================================
742
- /**
743
- * Find zones belonging to a specific tenant
744
- *
745
- * @param tenantId - Tenant ID to filter by
746
- * @returns Array of zones for the tenant
747
- */
748
- async findByTenant(tenantId2) {
749
- return this.list({ where: { tenantId: tenantId2 } });
750
- }
751
- /**
752
- * Find all global zones (no tenant association).
753
- *
754
- * Routes through the shared tenant-global helper so it does not throw under
755
- * an active tenant context (an explicit `tenant_id IS NULL` filter would be
756
- * flagged as an isolation violation). (#1600)
757
- *
758
- * @returns Array of global zones
759
- */
760
- async findGlobal() {
761
- return queryGlobal(this);
762
- }
763
- /**
764
- * Find zones for a tenant plus all global zones.
765
- *
766
- * Fails closed if an active tenant context requests a different tenant's
767
- * rows; the admin/system path keeps the cross-tenant capability. (#1600)
768
- *
769
- * @param tenantId - Tenant ID to filter by
770
- * @returns Array of tenant-specific and global zones
771
- */
772
- async findWithGlobals(tenantId2) {
773
- return queryWithGlobals(this, tenantId2, "Zone.findWithGlobals");
774
- }
775
- }
776
- const Zones = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
777
- __proto__: null,
778
- ZoneCollection
779
- }, Symbol.toStringTag, { value: "Module" }));
780
- export {
781
- Property,
782
- PropertyCollection,
783
- Zone,
784
- ZoneCollection,
785
- promptMessageOptions,
786
- smrtPropertiesSummarizePrompt
447
+ __decorateClass([tenantId({ nullable: true })], Zone.prototype, "tenantId", 2);
448
+ __decorateClass([foreignKey("Property")], Zone.prototype, "propertyId", 2);
449
+ Zone = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
450
+ api: { include: [
451
+ "list",
452
+ "get",
453
+ "create",
454
+ "update",
455
+ "delete"
456
+ ] },
457
+ mcp: { include: [
458
+ "list",
459
+ "get",
460
+ "create"
461
+ ] },
462
+ cli: true
463
+ })], Zone);
464
+ //#endregion
465
+ //#region src/collections/Zones.ts
466
+ var Zones_exports = /* @__PURE__ */ __exportAll({ ZoneCollection: () => ZoneCollection });
467
+ var ZoneCollection = class extends SmrtCollection {
468
+ static _itemClass = Zone;
469
+ /**
470
+ * Find all zones for a property
471
+ *
472
+ * @param propertyId - Property ID
473
+ * @returns Array of zones
474
+ */
475
+ async findByProperty(propertyId) {
476
+ return await this.list({ where: { propertyId } });
477
+ }
478
+ /**
479
+ * Find top-level zones for a property (no parent)
480
+ *
481
+ * @param propertyId - Property ID
482
+ * @returns Array of top-level zones
483
+ */
484
+ async findTopLevel(propertyId) {
485
+ return await this.list({ where: {
486
+ propertyId,
487
+ parentId: null
488
+ } });
489
+ }
490
+ /**
491
+ * Find direct children of a zone
492
+ *
493
+ * @param parentId - Parent zone ID
494
+ * @returns Array of child zones
495
+ */
496
+ async findChildren(parentId) {
497
+ return await this.list({ where: { parentId } });
498
+ }
499
+ /**
500
+ * Find zones by path pattern
501
+ *
502
+ * @param propertyId - Property ID
503
+ * @param path - URL path to match
504
+ * @returns Array of matching zones
505
+ */
506
+ async findByPath(propertyId, path) {
507
+ return await this.list({ where: {
508
+ propertyId,
509
+ path
510
+ } });
511
+ }
512
+ /**
513
+ * Find zones by type
514
+ *
515
+ * @param propertyId - Property ID
516
+ * @param type - Zone type
517
+ * @returns Array of zones of the given type
518
+ */
519
+ async findByType(propertyId, type) {
520
+ return await this.list({ where: {
521
+ propertyId,
522
+ type
523
+ } });
524
+ }
525
+ /**
526
+ * Get the complete zone tree for a property
527
+ *
528
+ * @param propertyId - Property ID
529
+ * @returns ZoneTree structure
530
+ */
531
+ async getTree(propertyId) {
532
+ const allZones = await this.findByProperty(propertyId);
533
+ const zoneMap = /* @__PURE__ */ new Map();
534
+ const childrenMap = /* @__PURE__ */ new Map();
535
+ for (const zone of allZones) {
536
+ if (zone.id) zoneMap.set(zone.id, zone);
537
+ if (!childrenMap.has(zone.parentId || "")) childrenMap.set(zone.parentId || "", []);
538
+ childrenMap.get(zone.parentId || "")?.push(zone);
539
+ }
540
+ const buildNode = (zone) => {
541
+ return {
542
+ zone,
543
+ children: ((zone.id ? childrenMap.get(zone.id) : []) || []).map(buildNode)
544
+ };
545
+ };
546
+ return {
547
+ propertyId,
548
+ roots: (childrenMap.get("") || []).map(buildNode)
549
+ };
550
+ }
551
+ /**
552
+ * Get all ancestors of a zone (path to root)
553
+ *
554
+ * @param zoneId - Zone ID
555
+ * @returns Array of ancestors (from root to parent)
556
+ */
557
+ async getAncestors(zoneId) {
558
+ const ancestors = [];
559
+ const startZone = await this.get({ id: zoneId });
560
+ if (!startZone) return ancestors;
561
+ let currentId = startZone.parentId;
562
+ while (currentId) {
563
+ const parent = await this.get({ id: currentId });
564
+ if (!parent) break;
565
+ ancestors.unshift(parent);
566
+ currentId = parent.parentId;
567
+ }
568
+ return ancestors;
569
+ }
570
+ /**
571
+ * Get all descendants of a zone recursively
572
+ *
573
+ * @param zoneId - Zone ID
574
+ * @returns Array of all descendant zones
575
+ */
576
+ async getDescendants(zoneId) {
577
+ const descendants = [];
578
+ const queue = [zoneId];
579
+ while (queue.length > 0) {
580
+ const currentId = queue.shift();
581
+ if (!currentId) continue;
582
+ const children = await this.findChildren(currentId);
583
+ for (const child of children) {
584
+ descendants.push(child);
585
+ if (child.id) queue.push(child.id);
586
+ }
587
+ }
588
+ return descendants;
589
+ }
590
+ /**
591
+ * Get the depth of the deepest zone in a property
592
+ *
593
+ * @param propertyId - Property ID
594
+ * @returns Maximum depth (0 = only top-level zones)
595
+ */
596
+ async getMaxDepth(propertyId) {
597
+ const allZones = await this.findByProperty(propertyId);
598
+ const zonesById = /* @__PURE__ */ new Map();
599
+ for (const zone of allZones) if (zone.id) zonesById.set(zone.id, zone);
600
+ const depthCache = /* @__PURE__ */ new Map();
601
+ const getDepth = (zone) => {
602
+ if (!zone.id) return 0;
603
+ const cached = depthCache.get(zone.id);
604
+ if (cached !== void 0) return cached;
605
+ let depth = 0;
606
+ let current = zone;
607
+ const visited = /* @__PURE__ */ new Set();
608
+ while (current?.id) {
609
+ const parentId = current.parentId;
610
+ if (!parentId) break;
611
+ if (visited.has(parentId)) break;
612
+ visited.add(parentId);
613
+ const parent = zonesById.get(parentId);
614
+ if (!parent) break;
615
+ depth += 1;
616
+ current = parent;
617
+ }
618
+ depthCache.set(zone.id, depth);
619
+ return depth;
620
+ };
621
+ let maxDepth = 0;
622
+ for (const zone of allZones) maxDepth = Math.max(maxDepth, getDepth(zone));
623
+ return maxDepth;
624
+ }
625
+ /**
626
+ * Find zones with specific dimensions
627
+ *
628
+ * @param propertyId - Property ID
629
+ * @param width - Required width
630
+ * @param height - Required height
631
+ * @returns Array of matching zones
632
+ */
633
+ async findByDimensions(propertyId, width, height) {
634
+ return await this.list({ where: {
635
+ propertyId,
636
+ width,
637
+ height
638
+ } });
639
+ }
640
+ /**
641
+ * Find zones that allow a specific format
642
+ *
643
+ * @param propertyId - Property ID
644
+ * @param format - Format to check
645
+ * @returns Array of zones allowing the format
646
+ */
647
+ async findByAllowedFormat(propertyId, format) {
648
+ return (await this.findByProperty(propertyId)).filter((zone) => zone.isFormatAllowed(format));
649
+ }
650
+ /**
651
+ * Move a zone to a new parent
652
+ *
653
+ * @param zoneId - Zone ID to move
654
+ * @param newParentId - New parent ID (null for top-level)
655
+ * @returns Updated zone
656
+ */
657
+ async moveZone(zoneId, newParentId) {
658
+ const zone = await this.get({ id: zoneId });
659
+ if (!zone) return null;
660
+ if (newParentId) {
661
+ if ((await this.getDescendants(zoneId)).map((d) => d.id).includes(newParentId)) throw new Error("Cannot move zone into one of its descendants");
662
+ }
663
+ zone.parentId = newParentId;
664
+ await zone.save();
665
+ return zone;
666
+ }
667
+ /**
668
+ * Delete a zone and optionally its descendants
669
+ *
670
+ * @param zoneId - Zone ID to delete
671
+ * @param cascade - Whether to delete descendants
672
+ * @returns Number of zones deleted
673
+ */
674
+ async deleteZone(zoneId, cascade = false) {
675
+ const zone = await this.get({ id: zoneId });
676
+ if (!zone) return 0;
677
+ let deleted = 0;
678
+ if (cascade) {
679
+ const descendants = await this.getDescendants(zoneId);
680
+ for (const desc of descendants) {
681
+ await desc.delete();
682
+ deleted++;
683
+ }
684
+ } else {
685
+ const children = await this.findChildren(zoneId);
686
+ for (const child of children) {
687
+ child.parentId = zone.parentId;
688
+ await child.save();
689
+ }
690
+ }
691
+ await zone.delete();
692
+ deleted++;
693
+ return deleted;
694
+ }
695
+ /**
696
+ * Find zones belonging to a specific tenant
697
+ *
698
+ * @param tenantId - Tenant ID to filter by
699
+ * @returns Array of zones for the tenant
700
+ */
701
+ async findByTenant(tenantId) {
702
+ return this.list({ where: { tenantId } });
703
+ }
704
+ /**
705
+ * Find all global zones (no tenant association).
706
+ *
707
+ * Routes through the shared tenant-global helper so it does not throw under
708
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
709
+ * flagged as an isolation violation). (#1600)
710
+ *
711
+ * @returns Array of global zones
712
+ */
713
+ async findGlobal() {
714
+ return queryGlobal(this);
715
+ }
716
+ /**
717
+ * Find zones for a tenant plus all global zones.
718
+ *
719
+ * Fails closed if an active tenant context requests a different tenant's
720
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
721
+ *
722
+ * @param tenantId - Tenant ID to filter by
723
+ * @returns Array of tenant-specific and global zones
724
+ */
725
+ async findWithGlobals(tenantId) {
726
+ return queryWithGlobals(this, tenantId, "Zone.findWithGlobals");
727
+ }
787
728
  };
788
- //# sourceMappingURL=index.js.map
729
+ //#endregion
730
+ export { Property, PropertyCollection, Zone, ZoneCollection, promptMessageOptions, smrtPropertiesSummarizePrompt };
731
+
732
+ //# sourceMappingURL=index.js.map