@happyvertical/smrt-properties 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 +38 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +99 -0
- package/dist/index.d.ts +431 -0
- package/dist/index.js +776 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1612 -0
- package/dist/smrt-knowledge.json +755 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import { ObjectRegistry, crossPackageRef, smrt, SmrtObject, SmrtCollection, foreignKey, SmrtHierarchical } from "@happyvertical/smrt-core";
|
|
2
|
+
import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
|
|
3
|
+
import { tenantId, TenantScoped } 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:
|
|
10
|
+
Name: {propertyName}
|
|
11
|
+
Domain: {propertyDomain}
|
|
12
|
+
Description: {propertyDescription}
|
|
13
|
+
Status: {propertyStatus}
|
|
14
|
+
Total zones: {totalZones}
|
|
15
|
+
Top-level zones: {topLevelZones}`,
|
|
16
|
+
editable: {
|
|
17
|
+
template: true,
|
|
18
|
+
profile: true,
|
|
19
|
+
model: true,
|
|
20
|
+
params: true
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
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
|
+
};
|
|
30
|
+
}
|
|
31
|
+
var __defProp$1 = Object.defineProperty;
|
|
32
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
33
|
+
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;
|
|
40
|
+
};
|
|
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
|
+
}
|
|
162
|
+
};
|
|
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 global properties (no tenant association)
|
|
292
|
+
*
|
|
293
|
+
* @returns Array of global properties
|
|
294
|
+
*/
|
|
295
|
+
async findGlobal() {
|
|
296
|
+
return this.list({ where: { tenantId: null } });
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Find properties for a tenant including global properties
|
|
300
|
+
*
|
|
301
|
+
* @param tenantId - Tenant ID to filter by
|
|
302
|
+
* @returns Array of tenant-specific and global properties
|
|
303
|
+
*/
|
|
304
|
+
async findWithGlobals(tenantId2) {
|
|
305
|
+
return this.query(
|
|
306
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
307
|
+
[tenantId2]
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const Properties = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
312
|
+
__proto__: null,
|
|
313
|
+
PropertyCollection
|
|
314
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
315
|
+
var __defProp = Object.defineProperty;
|
|
316
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
317
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
318
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
319
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
320
|
+
if (decorator = decorators[i])
|
|
321
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
322
|
+
if (kind && result) __defProp(target, key, result);
|
|
323
|
+
return result;
|
|
324
|
+
};
|
|
325
|
+
let Zone = class extends SmrtHierarchical {
|
|
326
|
+
tenantId = null;
|
|
327
|
+
propertyId = "";
|
|
328
|
+
// parentId inherited from SmrtHierarchical (null = top-level zone)
|
|
329
|
+
/**
|
|
330
|
+
* Display name
|
|
331
|
+
*/
|
|
332
|
+
name = "";
|
|
333
|
+
/**
|
|
334
|
+
* Zone type (descriptive, not structural)
|
|
335
|
+
* Common values: "page", "section", "slot", "container", "widget"
|
|
336
|
+
*/
|
|
337
|
+
type = "";
|
|
338
|
+
/**
|
|
339
|
+
* URL path pattern (e.g., "/", "/articles/*")
|
|
340
|
+
*/
|
|
341
|
+
path = "";
|
|
342
|
+
/**
|
|
343
|
+
* CSS selector for placement (e.g., "#header", ".sidebar")
|
|
344
|
+
*/
|
|
345
|
+
selector = "";
|
|
346
|
+
/**
|
|
347
|
+
* Position hint (e.g., "top", "after-paragraph-3")
|
|
348
|
+
*/
|
|
349
|
+
position = "";
|
|
350
|
+
/**
|
|
351
|
+
* Width in pixels (null = fluid)
|
|
352
|
+
*/
|
|
353
|
+
width = null;
|
|
354
|
+
/**
|
|
355
|
+
* Height in pixels (null = fluid)
|
|
356
|
+
*/
|
|
357
|
+
height = null;
|
|
358
|
+
/**
|
|
359
|
+
* Allowed content/ad formats
|
|
360
|
+
*/
|
|
361
|
+
allowedFormats = [];
|
|
362
|
+
/**
|
|
363
|
+
* Default content/asset ID for fallback
|
|
364
|
+
*/
|
|
365
|
+
defaultContentId = null;
|
|
366
|
+
/**
|
|
367
|
+
* Extensible metadata
|
|
368
|
+
*/
|
|
369
|
+
metadata = {};
|
|
370
|
+
constructor(options = {}) {
|
|
371
|
+
super(options);
|
|
372
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
373
|
+
if (options.propertyId !== void 0) this.propertyId = options.propertyId;
|
|
374
|
+
if (options.parentId !== void 0) this.parentId = options.parentId;
|
|
375
|
+
if (options.name !== void 0) this.name = options.name;
|
|
376
|
+
if (options.type !== void 0) this.type = options.type;
|
|
377
|
+
if (options.path !== void 0) this.path = options.path;
|
|
378
|
+
if (options.selector !== void 0) this.selector = options.selector;
|
|
379
|
+
if (options.position !== void 0) this.position = options.position;
|
|
380
|
+
if (options.width !== void 0) this.width = options.width;
|
|
381
|
+
if (options.height !== void 0) this.height = options.height;
|
|
382
|
+
if (options.allowedFormats !== void 0)
|
|
383
|
+
this.allowedFormats = options.allowedFormats;
|
|
384
|
+
if (options.defaultContentId !== void 0)
|
|
385
|
+
this.defaultContentId = options.defaultContentId;
|
|
386
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check if this is a top-level zone (no parent)
|
|
390
|
+
*/
|
|
391
|
+
isTopLevel() {
|
|
392
|
+
return this.parentId === null;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Check if this zone has dimensions set
|
|
396
|
+
*/
|
|
397
|
+
hasDimensions() {
|
|
398
|
+
return this.width !== null && this.height !== null;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get dimensions as string (e.g., "728x90")
|
|
402
|
+
*/
|
|
403
|
+
getDimensionString() {
|
|
404
|
+
if (!this.hasDimensions()) return null;
|
|
405
|
+
return `${this.width}x${this.height}`;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get the parent property
|
|
409
|
+
*/
|
|
410
|
+
async getProperty() {
|
|
411
|
+
const { PropertyCollection: PropertyCollection2 } = await Promise.resolve().then(() => Properties);
|
|
412
|
+
const collection = await PropertyCollection2.create(this.options);
|
|
413
|
+
return await collection.get({ id: this.propertyId });
|
|
414
|
+
}
|
|
415
|
+
// Hierarchy traversal (getParent / getChildren / getAncestors /
|
|
416
|
+
// getDescendants / getHierarchy / moveTo) provided by SmrtHierarchical.
|
|
417
|
+
// The collection-level depth cache in `ZoneCollection.getAncestors` /
|
|
418
|
+
// `getDescendants` remains available for callers that go through the
|
|
419
|
+
// collection — the base-class methods do their own batched queries.
|
|
420
|
+
/**
|
|
421
|
+
* Get the full path from root to this zone
|
|
422
|
+
*/
|
|
423
|
+
async getFullPath() {
|
|
424
|
+
const ancestors = await this.getAncestors();
|
|
425
|
+
const names = [...ancestors.map((z) => z.name), this.name];
|
|
426
|
+
return names.join(" > ");
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get the depth in the hierarchy (0 = top-level)
|
|
430
|
+
*/
|
|
431
|
+
async getDepth() {
|
|
432
|
+
const ancestors = await this.getAncestors();
|
|
433
|
+
return ancestors.length;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Create a child zone under this zone
|
|
437
|
+
*/
|
|
438
|
+
async createChild(options) {
|
|
439
|
+
if (!this.id) {
|
|
440
|
+
throw new Error("Zone must be saved before creating children");
|
|
441
|
+
}
|
|
442
|
+
const { ZoneCollection: ZoneCollection2 } = await Promise.resolve().then(() => Zones);
|
|
443
|
+
const collection = await ZoneCollection2.create(this.options);
|
|
444
|
+
const zone = await collection.create({
|
|
445
|
+
...options,
|
|
446
|
+
propertyId: this.propertyId,
|
|
447
|
+
parentId: this.id
|
|
448
|
+
});
|
|
449
|
+
await zone.save();
|
|
450
|
+
return zone;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Build tree node for this zone and its children
|
|
454
|
+
*/
|
|
455
|
+
async toTreeNode() {
|
|
456
|
+
const children = await this.getChildren();
|
|
457
|
+
const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));
|
|
458
|
+
return {
|
|
459
|
+
zone: this,
|
|
460
|
+
children: childNodes
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Check if a format is allowed in this zone
|
|
465
|
+
*/
|
|
466
|
+
isFormatAllowed(format) {
|
|
467
|
+
if (this.allowedFormats.length === 0) return true;
|
|
468
|
+
return this.allowedFormats.includes(format);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
__decorateClass([
|
|
472
|
+
tenantId({ nullable: true })
|
|
473
|
+
], Zone.prototype, "tenantId", 2);
|
|
474
|
+
__decorateClass([
|
|
475
|
+
foreignKey("Property")
|
|
476
|
+
], Zone.prototype, "propertyId", 2);
|
|
477
|
+
Zone = __decorateClass([
|
|
478
|
+
TenantScoped({ mode: "optional" }),
|
|
479
|
+
smrt({
|
|
480
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
481
|
+
mcp: { include: ["list", "get", "create"] },
|
|
482
|
+
cli: true
|
|
483
|
+
})
|
|
484
|
+
], Zone);
|
|
485
|
+
class ZoneCollection extends SmrtCollection {
|
|
486
|
+
static _itemClass = Zone;
|
|
487
|
+
/**
|
|
488
|
+
* Find all zones for a property
|
|
489
|
+
*
|
|
490
|
+
* @param propertyId - Property ID
|
|
491
|
+
* @returns Array of zones
|
|
492
|
+
*/
|
|
493
|
+
async findByProperty(propertyId) {
|
|
494
|
+
return await this.list({
|
|
495
|
+
where: { propertyId }
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Find top-level zones for a property (no parent)
|
|
500
|
+
*
|
|
501
|
+
* @param propertyId - Property ID
|
|
502
|
+
* @returns Array of top-level zones
|
|
503
|
+
*/
|
|
504
|
+
async findTopLevel(propertyId) {
|
|
505
|
+
return await this.list({
|
|
506
|
+
where: { propertyId, parentId: null }
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Find direct children of a zone
|
|
511
|
+
*
|
|
512
|
+
* @param parentId - Parent zone ID
|
|
513
|
+
* @returns Array of child zones
|
|
514
|
+
*/
|
|
515
|
+
async findChildren(parentId) {
|
|
516
|
+
return await this.list({
|
|
517
|
+
where: { parentId }
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Find zones by path pattern
|
|
522
|
+
*
|
|
523
|
+
* @param propertyId - Property ID
|
|
524
|
+
* @param path - URL path to match
|
|
525
|
+
* @returns Array of matching zones
|
|
526
|
+
*/
|
|
527
|
+
async findByPath(propertyId, path) {
|
|
528
|
+
return await this.list({
|
|
529
|
+
where: { propertyId, path }
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Find zones by type
|
|
534
|
+
*
|
|
535
|
+
* @param propertyId - Property ID
|
|
536
|
+
* @param type - Zone type
|
|
537
|
+
* @returns Array of zones of the given type
|
|
538
|
+
*/
|
|
539
|
+
async findByType(propertyId, type) {
|
|
540
|
+
return await this.list({
|
|
541
|
+
where: { propertyId, type }
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Get the complete zone tree for a property
|
|
546
|
+
*
|
|
547
|
+
* @param propertyId - Property ID
|
|
548
|
+
* @returns ZoneTree structure
|
|
549
|
+
*/
|
|
550
|
+
async getTree(propertyId) {
|
|
551
|
+
const allZones = await this.findByProperty(propertyId);
|
|
552
|
+
const zoneMap = /* @__PURE__ */ new Map();
|
|
553
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const zone of allZones) {
|
|
555
|
+
if (zone.id) {
|
|
556
|
+
zoneMap.set(zone.id, zone);
|
|
557
|
+
}
|
|
558
|
+
if (!childrenMap.has(zone.parentId || "")) {
|
|
559
|
+
childrenMap.set(zone.parentId || "", []);
|
|
560
|
+
}
|
|
561
|
+
childrenMap.get(zone.parentId || "")?.push(zone);
|
|
562
|
+
}
|
|
563
|
+
const buildNode = (zone) => {
|
|
564
|
+
const children = (zone.id ? childrenMap.get(zone.id) : []) || [];
|
|
565
|
+
return {
|
|
566
|
+
zone,
|
|
567
|
+
children: children.map(buildNode)
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
const roots = childrenMap.get("") || [];
|
|
571
|
+
return {
|
|
572
|
+
propertyId,
|
|
573
|
+
roots: roots.map(buildNode)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Get all ancestors of a zone (path to root)
|
|
578
|
+
*
|
|
579
|
+
* @param zoneId - Zone ID
|
|
580
|
+
* @returns Array of ancestors (from root to parent)
|
|
581
|
+
*/
|
|
582
|
+
async getAncestors(zoneId) {
|
|
583
|
+
const ancestors = [];
|
|
584
|
+
const startZone = await this.get({ id: zoneId });
|
|
585
|
+
if (!startZone) return ancestors;
|
|
586
|
+
let currentId = startZone.parentId;
|
|
587
|
+
while (currentId) {
|
|
588
|
+
const parent = await this.get({ id: currentId });
|
|
589
|
+
if (!parent) break;
|
|
590
|
+
ancestors.unshift(parent);
|
|
591
|
+
currentId = parent.parentId;
|
|
592
|
+
}
|
|
593
|
+
return ancestors;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Get all descendants of a zone recursively
|
|
597
|
+
*
|
|
598
|
+
* @param zoneId - Zone ID
|
|
599
|
+
* @returns Array of all descendant zones
|
|
600
|
+
*/
|
|
601
|
+
async getDescendants(zoneId) {
|
|
602
|
+
const descendants = [];
|
|
603
|
+
const queue = [zoneId];
|
|
604
|
+
while (queue.length > 0) {
|
|
605
|
+
const currentId = queue.shift();
|
|
606
|
+
if (!currentId) continue;
|
|
607
|
+
const children = await this.findChildren(currentId);
|
|
608
|
+
for (const child of children) {
|
|
609
|
+
descendants.push(child);
|
|
610
|
+
if (child.id) {
|
|
611
|
+
queue.push(child.id);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return descendants;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get the depth of the deepest zone in a property
|
|
619
|
+
*
|
|
620
|
+
* @param propertyId - Property ID
|
|
621
|
+
* @returns Maximum depth (0 = only top-level zones)
|
|
622
|
+
*/
|
|
623
|
+
async getMaxDepth(propertyId) {
|
|
624
|
+
const allZones = await this.findByProperty(propertyId);
|
|
625
|
+
const zonesById = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const zone of allZones) {
|
|
627
|
+
if (zone.id) {
|
|
628
|
+
zonesById.set(zone.id, zone);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
632
|
+
const getDepth = (zone) => {
|
|
633
|
+
if (!zone.id) return 0;
|
|
634
|
+
const cached = depthCache.get(zone.id);
|
|
635
|
+
if (cached !== void 0) return cached;
|
|
636
|
+
let depth = 0;
|
|
637
|
+
let current = zone;
|
|
638
|
+
const visited = /* @__PURE__ */ new Set();
|
|
639
|
+
while (current?.id) {
|
|
640
|
+
const parentId = current.parentId;
|
|
641
|
+
if (!parentId) break;
|
|
642
|
+
if (visited.has(parentId)) break;
|
|
643
|
+
visited.add(parentId);
|
|
644
|
+
const parent = zonesById.get(parentId);
|
|
645
|
+
if (!parent) break;
|
|
646
|
+
depth += 1;
|
|
647
|
+
current = parent;
|
|
648
|
+
}
|
|
649
|
+
depthCache.set(zone.id, depth);
|
|
650
|
+
return depth;
|
|
651
|
+
};
|
|
652
|
+
let maxDepth = 0;
|
|
653
|
+
for (const zone of allZones) {
|
|
654
|
+
maxDepth = Math.max(maxDepth, getDepth(zone));
|
|
655
|
+
}
|
|
656
|
+
return maxDepth;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Find zones with specific dimensions
|
|
660
|
+
*
|
|
661
|
+
* @param propertyId - Property ID
|
|
662
|
+
* @param width - Required width
|
|
663
|
+
* @param height - Required height
|
|
664
|
+
* @returns Array of matching zones
|
|
665
|
+
*/
|
|
666
|
+
async findByDimensions(propertyId, width, height) {
|
|
667
|
+
return await this.list({
|
|
668
|
+
where: { propertyId, width, height }
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Find zones that allow a specific format
|
|
673
|
+
*
|
|
674
|
+
* @param propertyId - Property ID
|
|
675
|
+
* @param format - Format to check
|
|
676
|
+
* @returns Array of zones allowing the format
|
|
677
|
+
*/
|
|
678
|
+
async findByAllowedFormat(propertyId, format) {
|
|
679
|
+
const allZones = await this.findByProperty(propertyId);
|
|
680
|
+
return allZones.filter((zone) => zone.isFormatAllowed(format));
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Move a zone to a new parent
|
|
684
|
+
*
|
|
685
|
+
* @param zoneId - Zone ID to move
|
|
686
|
+
* @param newParentId - New parent ID (null for top-level)
|
|
687
|
+
* @returns Updated zone
|
|
688
|
+
*/
|
|
689
|
+
async moveZone(zoneId, newParentId) {
|
|
690
|
+
const zone = await this.get({ id: zoneId });
|
|
691
|
+
if (!zone) return null;
|
|
692
|
+
if (newParentId) {
|
|
693
|
+
const descendants = await this.getDescendants(zoneId);
|
|
694
|
+
const descendantIds = descendants.map((d) => d.id);
|
|
695
|
+
if (descendantIds.includes(newParentId)) {
|
|
696
|
+
throw new Error("Cannot move zone into one of its descendants");
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
zone.parentId = newParentId;
|
|
700
|
+
await zone.save();
|
|
701
|
+
return zone;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Delete a zone and optionally its descendants
|
|
705
|
+
*
|
|
706
|
+
* @param zoneId - Zone ID to delete
|
|
707
|
+
* @param cascade - Whether to delete descendants
|
|
708
|
+
* @returns Number of zones deleted
|
|
709
|
+
*/
|
|
710
|
+
async deleteZone(zoneId, cascade = false) {
|
|
711
|
+
const zone = await this.get({ id: zoneId });
|
|
712
|
+
if (!zone) return 0;
|
|
713
|
+
let deleted = 0;
|
|
714
|
+
if (cascade) {
|
|
715
|
+
const descendants = await this.getDescendants(zoneId);
|
|
716
|
+
for (const desc of descendants) {
|
|
717
|
+
await desc.delete();
|
|
718
|
+
deleted++;
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
const children = await this.findChildren(zoneId);
|
|
722
|
+
for (const child of children) {
|
|
723
|
+
child.parentId = zone.parentId;
|
|
724
|
+
await child.save();
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
await zone.delete();
|
|
728
|
+
deleted++;
|
|
729
|
+
return deleted;
|
|
730
|
+
}
|
|
731
|
+
// ============================================
|
|
732
|
+
// Tenant Helper Methods
|
|
733
|
+
// ============================================
|
|
734
|
+
/**
|
|
735
|
+
* Find zones belonging to a specific tenant
|
|
736
|
+
*
|
|
737
|
+
* @param tenantId - Tenant ID to filter by
|
|
738
|
+
* @returns Array of zones for the tenant
|
|
739
|
+
*/
|
|
740
|
+
async findByTenant(tenantId2) {
|
|
741
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Find global zones (no tenant association)
|
|
745
|
+
*
|
|
746
|
+
* @returns Array of global zones
|
|
747
|
+
*/
|
|
748
|
+
async findGlobal() {
|
|
749
|
+
return this.list({ where: { tenantId: null } });
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Find zones for a tenant including global zones
|
|
753
|
+
*
|
|
754
|
+
* @param tenantId - Tenant ID to filter by
|
|
755
|
+
* @returns Array of tenant-specific and global zones
|
|
756
|
+
*/
|
|
757
|
+
async findWithGlobals(tenantId2) {
|
|
758
|
+
return this.query(
|
|
759
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
760
|
+
[tenantId2]
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const Zones = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
765
|
+
__proto__: null,
|
|
766
|
+
ZoneCollection
|
|
767
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
768
|
+
export {
|
|
769
|
+
Property,
|
|
770
|
+
PropertyCollection,
|
|
771
|
+
Zone,
|
|
772
|
+
ZoneCollection,
|
|
773
|
+
promptMessageOptions,
|
|
774
|
+
smrtPropertiesSummarizePrompt
|
|
775
|
+
};
|
|
776
|
+
//# sourceMappingURL=index.js.map
|