@happyvertical/smrt-ads 0.37.2 → 0.37.4

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,1137 +1,1099 @@
1
- import { ObjectRegistry, smrt, SmrtObject, SmrtCollection, foreignKey, crossPackageRef } from "@happyvertical/smrt-core";
2
- import { tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
3
- ObjectRegistry.registerPackageManifest(
4
- new URL("./manifest.json", import.meta.url)
5
- );
1
+ import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, foreignKey, smrt } from "@happyvertical/smrt-core";
2
+ import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
3
+ //#region src/__smrt-register__.ts
4
+ ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
5
+ //#endregion
6
+ //#region src/types/index.ts
6
7
  var AdFormatType = /* @__PURE__ */ ((AdFormatType2) => {
7
- AdFormatType2["BANNER"] = "banner";
8
- AdFormatType2["NATIVE"] = "native";
9
- AdFormatType2["VIDEO"] = "video";
10
- return AdFormatType2;
8
+ AdFormatType2["BANNER"] = "banner";
9
+ AdFormatType2["NATIVE"] = "native";
10
+ AdFormatType2["VIDEO"] = "video";
11
+ return AdFormatType2;
11
12
  })(AdFormatType || {});
12
13
  var PricingModel = /* @__PURE__ */ ((PricingModel2) => {
13
- PricingModel2["FIXED"] = "fixed";
14
- PricingModel2["CPM"] = "cpm";
15
- PricingModel2["CPC"] = "cpc";
16
- PricingModel2["CPA"] = "cpa";
17
- return PricingModel2;
14
+ PricingModel2["FIXED"] = "fixed";
15
+ PricingModel2["CPM"] = "cpm";
16
+ PricingModel2["CPC"] = "cpc";
17
+ PricingModel2["CPA"] = "cpa";
18
+ return PricingModel2;
18
19
  })(PricingModel || {});
19
20
  var AdGroupStatus = /* @__PURE__ */ ((AdGroupStatus2) => {
20
- AdGroupStatus2["DRAFT"] = "draft";
21
- AdGroupStatus2["ACTIVE"] = "active";
22
- AdGroupStatus2["PAUSED"] = "paused";
23
- AdGroupStatus2["COMPLETED"] = "completed";
24
- return AdGroupStatus2;
21
+ AdGroupStatus2["DRAFT"] = "draft";
22
+ AdGroupStatus2["ACTIVE"] = "active";
23
+ AdGroupStatus2["PAUSED"] = "paused";
24
+ AdGroupStatus2["COMPLETED"] = "completed";
25
+ return AdGroupStatus2;
25
26
  })(AdGroupStatus || {});
26
27
  var AdVariationStatus = /* @__PURE__ */ ((AdVariationStatus2) => {
27
- AdVariationStatus2["DRAFT"] = "draft";
28
- AdVariationStatus2["ACTIVE"] = "active";
29
- AdVariationStatus2["PAUSED"] = "paused";
30
- return AdVariationStatus2;
28
+ AdVariationStatus2["DRAFT"] = "draft";
29
+ AdVariationStatus2["ACTIVE"] = "active";
30
+ AdVariationStatus2["PAUSED"] = "paused";
31
+ return AdVariationStatus2;
31
32
  })(AdVariationStatus || {});
32
33
  var AdEventType = /* @__PURE__ */ ((AdEventType2) => {
33
- AdEventType2["IMPRESSION"] = "impression";
34
- AdEventType2["CLICK"] = "click";
35
- AdEventType2["CONVERSION"] = "conversion";
36
- return AdEventType2;
34
+ AdEventType2["IMPRESSION"] = "impression";
35
+ AdEventType2["CLICK"] = "click";
36
+ AdEventType2["CONVERSION"] = "conversion";
37
+ return AdEventType2;
37
38
  })(AdEventType || {});
39
+ //#endregion
40
+ //#region src/models/AdDeliveryTier.ts
41
+ var __defProp$4 = Object.defineProperty;
38
42
  var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
39
43
  var __decorateClass$4 = (decorators, target, key, kind) => {
40
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
41
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
42
- if (decorator = decorators[i])
43
- result = decorator(result) || result;
44
- return result;
44
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
45
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
46
+ if (kind && result) __defProp$4(target, key, result);
47
+ return result;
45
48
  };
46
- let AdDeliveryTier = class extends SmrtObject {
47
- /**
48
- * Display name (e.g., "Sponsorship", "Standard", "House")
49
- */
50
- name = "";
51
- /**
52
- * Priority level (lower = higher priority: 1, 2, 3...)
53
- */
54
- priority = 0;
55
- /**
56
- * Pricing model for this tier
57
- */
58
- pricingModel = PricingModel.CPM;
59
- /**
60
- * Optional description
61
- */
62
- description = "";
63
- constructor(options = {}) {
64
- super(options);
65
- if (options.name !== void 0) this.name = options.name;
66
- if (options.priority !== void 0) this.priority = options.priority;
67
- if (options.pricingModel !== void 0)
68
- this.pricingModel = options.pricingModel;
69
- if (options.description !== void 0)
70
- this.description = options.description;
71
- }
72
- /**
73
- * Check if this tier is higher priority than another
74
- */
75
- isHigherPriorityThan(other) {
76
- return this.priority < other.priority;
77
- }
78
- /**
79
- * Check if this is a fixed pricing tier
80
- */
81
- isFixedPricing() {
82
- return this.pricingModel === PricingModel.FIXED;
83
- }
84
- /**
85
- * Check if this is a performance-based tier (CPC, CPA)
86
- */
87
- isPerformanceBased() {
88
- return this.pricingModel === PricingModel.CPC || this.pricingModel === PricingModel.CPA;
89
- }
49
+ var AdDeliveryTier = class extends SmrtObject {
50
+ /**
51
+ * Display name (e.g., "Sponsorship", "Standard", "House")
52
+ */
53
+ name = "";
54
+ /**
55
+ * Priority level (lower = higher priority: 1, 2, 3...)
56
+ */
57
+ priority = 0;
58
+ /**
59
+ * Pricing model for this tier
60
+ */
61
+ pricingModel = PricingModel.CPM;
62
+ /**
63
+ * Optional description
64
+ */
65
+ description = "";
66
+ constructor(options = {}) {
67
+ super(options);
68
+ if (options.name !== void 0) this.name = options.name;
69
+ if (options.priority !== void 0) this.priority = options.priority;
70
+ if (options.pricingModel !== void 0) this.pricingModel = options.pricingModel;
71
+ if (options.description !== void 0) this.description = options.description;
72
+ }
73
+ /**
74
+ * Check if this tier is higher priority than another
75
+ */
76
+ isHigherPriorityThan(other) {
77
+ return this.priority < other.priority;
78
+ }
79
+ /**
80
+ * Check if this is a fixed pricing tier
81
+ */
82
+ isFixedPricing() {
83
+ return this.pricingModel === PricingModel.FIXED;
84
+ }
85
+ /**
86
+ * Check if this is a performance-based tier (CPC, CPA)
87
+ */
88
+ isPerformanceBased() {
89
+ return this.pricingModel === PricingModel.CPC || this.pricingModel === PricingModel.CPA;
90
+ }
90
91
  };
91
- AdDeliveryTier = __decorateClass$4([
92
- smrt({
93
- api: { include: ["list", "get", "create", "update"] },
94
- mcp: { include: ["list", "get"] },
95
- cli: true
96
- })
97
- ], AdDeliveryTier);
98
- class AdDeliveryTierCollection extends SmrtCollection {
99
- static _itemClass = AdDeliveryTier;
100
- /**
101
- * Find tiers ordered by priority (ascending, lower = higher priority)
102
- *
103
- * @returns Array of tiers ordered by priority
104
- */
105
- async findByPriority() {
106
- return await this.list({
107
- orderBy: "priority ASC"
108
- });
109
- }
110
- /**
111
- * Find tiers by pricing model
112
- *
113
- * @param pricingModel - Pricing model to filter by
114
- * @returns Array of matching tiers
115
- */
116
- async findByPricingModel(pricingModel) {
117
- return await this.list({
118
- where: { pricingModel },
119
- orderBy: "priority ASC"
120
- });
121
- }
122
- /**
123
- * Get the highest priority tier
124
- *
125
- * @returns Highest priority tier or null
126
- */
127
- async getHighestPriority() {
128
- const results = await this.list({
129
- orderBy: "priority ASC",
130
- limit: 1
131
- });
132
- return results.length > 0 ? results[0] : null;
133
- }
134
- /**
135
- * Find fixed pricing tiers
136
- */
137
- async findFixedPricing() {
138
- return await this.findByPricingModel(PricingModel.FIXED);
139
- }
140
- /**
141
- * Find CPM-based tiers
142
- */
143
- async findCPM() {
144
- return await this.findByPricingModel(PricingModel.CPM);
145
- }
146
- /**
147
- * Find performance-based tiers (CPC, CPA)
148
- */
149
- async findPerformanceBased() {
150
- const cpc = await this.findByPricingModel(PricingModel.CPC);
151
- const cpa = await this.findByPricingModel(PricingModel.CPA);
152
- return [...cpc, ...cpa].sort((a, b) => a.priority - b.priority);
153
- }
154
- }
155
- var __defProp$2 = Object.defineProperty;
92
+ AdDeliveryTier = __decorateClass$4([smrt({
93
+ api: { include: [
94
+ "list",
95
+ "get",
96
+ "create",
97
+ "update"
98
+ ] },
99
+ mcp: { include: ["list", "get"] },
100
+ cli: true
101
+ })], AdDeliveryTier);
102
+ //#endregion
103
+ //#region src/collections/AdDeliveryTierCollection.ts
104
+ var AdDeliveryTierCollection = class extends SmrtCollection {
105
+ static _itemClass = AdDeliveryTier;
106
+ /**
107
+ * Find tiers ordered by priority (ascending, lower = higher priority)
108
+ *
109
+ * @returns Array of tiers ordered by priority
110
+ */
111
+ async findByPriority() {
112
+ return await this.list({ orderBy: "priority ASC" });
113
+ }
114
+ /**
115
+ * Find tiers by pricing model
116
+ *
117
+ * @param pricingModel - Pricing model to filter by
118
+ * @returns Array of matching tiers
119
+ */
120
+ async findByPricingModel(pricingModel) {
121
+ return await this.list({
122
+ where: { pricingModel },
123
+ orderBy: "priority ASC"
124
+ });
125
+ }
126
+ /**
127
+ * Get the highest priority tier
128
+ *
129
+ * @returns Highest priority tier or null
130
+ */
131
+ async getHighestPriority() {
132
+ const results = await this.list({
133
+ orderBy: "priority ASC",
134
+ limit: 1
135
+ });
136
+ return results.length > 0 ? results[0] : null;
137
+ }
138
+ /**
139
+ * Find fixed pricing tiers
140
+ */
141
+ async findFixedPricing() {
142
+ return await this.findByPricingModel(PricingModel.FIXED);
143
+ }
144
+ /**
145
+ * Find CPM-based tiers
146
+ */
147
+ async findCPM() {
148
+ return await this.findByPricingModel(PricingModel.CPM);
149
+ }
150
+ /**
151
+ * Find performance-based tiers (CPC, CPA)
152
+ */
153
+ async findPerformanceBased() {
154
+ const cpc = await this.findByPricingModel(PricingModel.CPC);
155
+ const cpa = await this.findByPricingModel(PricingModel.CPA);
156
+ return [...cpc, ...cpa].sort((a, b) => a.priority - b.priority);
157
+ }
158
+ };
159
+ //#endregion
160
+ //#region src/models/AdEvent.ts
161
+ var __defProp$3 = Object.defineProperty;
156
162
  var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
157
163
  var __decorateClass$3 = (decorators, target, key, kind) => {
158
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
159
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
160
- if (decorator = decorators[i])
161
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
162
- if (kind && result) __defProp$2(target, key, result);
163
- return result;
164
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
165
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
166
+ if (kind && result) __defProp$3(target, key, result);
167
+ return result;
168
+ };
169
+ var AdEvent = class extends SmrtObject {
170
+ tenantId = null;
171
+ variationId = "";
172
+ zoneId = "";
173
+ /**
174
+ * Site ID (denormalized from Zone for query efficiency)
175
+ */
176
+ siteId = "";
177
+ /**
178
+ * Event type (impression, click, conversion)
179
+ */
180
+ eventType = AdEventType.IMPRESSION;
181
+ /**
182
+ * Event timestamp
183
+ */
184
+ timestamp = /* @__PURE__ */ new Date();
185
+ /**
186
+ * Analytics metadata as JSON string
187
+ * (IP, user agent, referrer, etc.)
188
+ */
189
+ metadata = "";
190
+ constructor(options = {}) {
191
+ super(options);
192
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
193
+ if (options.variationId !== void 0) this.variationId = options.variationId;
194
+ if (options.zoneId !== void 0) this.zoneId = options.zoneId;
195
+ if (options.siteId !== void 0) this.siteId = options.siteId;
196
+ if (options.eventType !== void 0) this.eventType = options.eventType;
197
+ if (options.timestamp !== void 0) this.timestamp = options.timestamp;
198
+ if (options.metadata !== void 0) this.metadata = options.metadata;
199
+ }
200
+ /**
201
+ * Get metadata as object
202
+ */
203
+ getMetadata() {
204
+ if (!this.metadata) return {};
205
+ try {
206
+ return JSON.parse(this.metadata);
207
+ } catch {
208
+ return {};
209
+ }
210
+ }
211
+ /**
212
+ * Set metadata from object
213
+ */
214
+ setMetadata(data) {
215
+ this.metadata = JSON.stringify(data);
216
+ }
217
+ /**
218
+ * Check if this is an impression event
219
+ */
220
+ isImpression() {
221
+ return this.eventType === AdEventType.IMPRESSION;
222
+ }
223
+ /**
224
+ * Check if this is a click event
225
+ */
226
+ isClick() {
227
+ return this.eventType === AdEventType.CLICK;
228
+ }
229
+ /**
230
+ * Check if this is a conversion event
231
+ */
232
+ isConversion() {
233
+ return this.eventType === AdEventType.CONVERSION;
234
+ }
164
235
  };
165
- let AdEvent = class extends SmrtObject {
166
- tenantId = null;
167
- variationId = "";
168
- zoneId = "";
169
- /**
170
- * Site ID (denormalized from Zone for query efficiency)
171
- */
172
- siteId = "";
173
- /**
174
- * Event type (impression, click, conversion)
175
- */
176
- eventType = AdEventType.IMPRESSION;
177
- /**
178
- * Event timestamp
179
- */
180
- timestamp = /* @__PURE__ */ new Date();
181
- /**
182
- * Analytics metadata as JSON string
183
- * (IP, user agent, referrer, etc.)
184
- */
185
- metadata = "";
186
- constructor(options = {}) {
187
- super(options);
188
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
189
- if (options.variationId !== void 0)
190
- this.variationId = options.variationId;
191
- if (options.zoneId !== void 0) this.zoneId = options.zoneId;
192
- if (options.siteId !== void 0) this.siteId = options.siteId;
193
- if (options.eventType !== void 0) this.eventType = options.eventType;
194
- if (options.timestamp !== void 0) this.timestamp = options.timestamp;
195
- if (options.metadata !== void 0) this.metadata = options.metadata;
196
- }
197
- /**
198
- * Get metadata as object
199
- */
200
- getMetadata() {
201
- if (!this.metadata) return {};
202
- try {
203
- return JSON.parse(this.metadata);
204
- } catch {
205
- return {};
206
- }
207
- }
208
- /**
209
- * Set metadata from object
210
- */
211
- setMetadata(data) {
212
- this.metadata = JSON.stringify(data);
213
- }
214
- /**
215
- * Check if this is an impression event
216
- */
217
- isImpression() {
218
- return this.eventType === AdEventType.IMPRESSION;
219
- }
220
- /**
221
- * Check if this is a click event
222
- */
223
- isClick() {
224
- return this.eventType === AdEventType.CLICK;
225
- }
226
- /**
227
- * Check if this is a conversion event
228
- */
229
- isConversion() {
230
- return this.eventType === AdEventType.CONVERSION;
231
- }
236
+ __decorateClass$3([tenantId({ nullable: true })], AdEvent.prototype, "tenantId", 2);
237
+ __decorateClass$3([foreignKey("AdVariation")], AdEvent.prototype, "variationId", 2);
238
+ __decorateClass$3([crossPackageRef("@happyvertical/smrt-properties:Zone")], AdEvent.prototype, "zoneId", 2);
239
+ AdEvent = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
240
+ tableStrategy: "sti",
241
+ api: { include: ["create", "list"] },
242
+ mcp: { include: ["create"] },
243
+ cli: false
244
+ })], AdEvent);
245
+ //#endregion
246
+ //#region src/collections/AdEventCollection.ts
247
+ var AdEventCollection = class extends SmrtCollection {
248
+ static _itemClass = AdEvent;
249
+ /**
250
+ * Find events by variation
251
+ *
252
+ * @param variationId - Variation ID
253
+ * @returns Array of events
254
+ */
255
+ async findByVariation(variationId) {
256
+ return await this.list({
257
+ where: { variationId },
258
+ orderBy: "timestamp DESC"
259
+ });
260
+ }
261
+ /**
262
+ * Find events by zone
263
+ *
264
+ * @param zoneId - Zone ID
265
+ * @returns Array of events
266
+ */
267
+ async findByZone(zoneId) {
268
+ return await this.list({
269
+ where: { zoneId },
270
+ orderBy: "timestamp DESC"
271
+ });
272
+ }
273
+ /**
274
+ * Find events by site
275
+ *
276
+ * @param siteId - Site ID
277
+ * @returns Array of events
278
+ */
279
+ async findBySite(siteId) {
280
+ return await this.list({
281
+ where: { siteId },
282
+ orderBy: "timestamp DESC"
283
+ });
284
+ }
285
+ /**
286
+ * Find events in date range
287
+ *
288
+ * @param start - Start date
289
+ * @param end - End date
290
+ * @returns Array of events
291
+ */
292
+ async findByDateRange(start, end) {
293
+ return await this.list({
294
+ where: {
295
+ "timestamp >=": start.toISOString(),
296
+ "timestamp <=": end.toISOString()
297
+ },
298
+ orderBy: "timestamp DESC"
299
+ });
300
+ }
301
+ /**
302
+ * Find events by type
303
+ *
304
+ * @param eventType - Event type
305
+ * @returns Array of events
306
+ */
307
+ async findByType(eventType) {
308
+ return await this.list({
309
+ where: { eventType },
310
+ orderBy: "timestamp DESC"
311
+ });
312
+ }
313
+ /**
314
+ * Count events by type for a variation
315
+ *
316
+ * @param variationId - Variation ID
317
+ * @param eventType - Event type
318
+ * @returns Count of events
319
+ */
320
+ async countByType(variationId, eventType) {
321
+ return (await this.list({ where: {
322
+ variationId,
323
+ eventType
324
+ } })).length;
325
+ }
326
+ /**
327
+ * Count impressions for a variation
328
+ */
329
+ async countImpressions(variationId) {
330
+ return await this.countByType(variationId, AdEventType.IMPRESSION);
331
+ }
332
+ /**
333
+ * Count clicks for a variation
334
+ */
335
+ async countClicks(variationId) {
336
+ return await this.countByType(variationId, AdEventType.CLICK);
337
+ }
338
+ /**
339
+ * Count conversions for a variation
340
+ */
341
+ async countConversions(variationId) {
342
+ return await this.countByType(variationId, AdEventType.CONVERSION);
343
+ }
344
+ /**
345
+ * Find all impressions
346
+ */
347
+ async findImpressions() {
348
+ return await this.findByType(AdEventType.IMPRESSION);
349
+ }
350
+ /**
351
+ * Find all clicks
352
+ */
353
+ async findClicks() {
354
+ return await this.findByType(AdEventType.CLICK);
355
+ }
356
+ /**
357
+ * Find all conversions
358
+ */
359
+ async findConversions() {
360
+ return await this.findByType(AdEventType.CONVERSION);
361
+ }
362
+ /**
363
+ * Get aggregate stats for a variation
364
+ *
365
+ * @param variationId - Variation ID
366
+ * @returns Stats object with impressions, clicks, conversions, CTR
367
+ */
368
+ async getVariationStats(variationId) {
369
+ const impressions = await this.countImpressions(variationId);
370
+ const clicks = await this.countClicks(variationId);
371
+ const conversions = await this.countConversions(variationId);
372
+ return {
373
+ impressions,
374
+ clicks,
375
+ conversions,
376
+ ctr: impressions > 0 ? clicks / impressions : 0,
377
+ conversionRate: clicks > 0 ? conversions / clicks : 0
378
+ };
379
+ }
380
+ /**
381
+ * Find ad events belonging to a specific tenant
382
+ *
383
+ * @param tenantId - Tenant ID to filter by
384
+ * @returns Array of ad events for the tenant
385
+ */
386
+ async findByTenant(tenantId) {
387
+ return this.list({ where: { tenantId } });
388
+ }
389
+ /**
390
+ * Find global ad events (no tenant association).
391
+ *
392
+ * Routes through the shared tenant-global helper so it does not throw under
393
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
394
+ * flagged as an isolation violation). (#1600)
395
+ *
396
+ * @returns Array of global ad events
397
+ */
398
+ async findGlobal() {
399
+ return queryGlobal(this);
400
+ }
401
+ /**
402
+ * Find ad events for a tenant including global (shared) events.
403
+ *
404
+ * Fails closed if an active tenant context requests a different tenant's
405
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
406
+ *
407
+ * @param tenantId - Tenant ID to include
408
+ * @returns Array of tenant-specific and global ad events
409
+ */
410
+ async findWithGlobals(tenantId) {
411
+ return queryWithGlobals(this, tenantId, "AdEvent.findWithGlobals");
412
+ }
232
413
  };
233
- __decorateClass$3([
234
- tenantId({ nullable: true })
235
- ], AdEvent.prototype, "tenantId", 2);
236
- __decorateClass$3([
237
- foreignKey("AdVariation")
238
- ], AdEvent.prototype, "variationId", 2);
239
- __decorateClass$3([
240
- crossPackageRef("@happyvertical/smrt-properties:Zone")
241
- ], AdEvent.prototype, "zoneId", 2);
242
- AdEvent = __decorateClass$3([
243
- TenantScoped({ mode: "optional" }),
244
- smrt({
245
- tableStrategy: "sti",
246
- api: { include: ["create", "list"] },
247
- // No update/delete (immutable)
248
- mcp: { include: ["create"] },
249
- cli: false
250
- // High volume, not useful in CLI
251
- })
252
- ], AdEvent);
253
- class AdEventCollection extends SmrtCollection {
254
- static _itemClass = AdEvent;
255
- /**
256
- * Find events by variation
257
- *
258
- * @param variationId - Variation ID
259
- * @returns Array of events
260
- */
261
- async findByVariation(variationId) {
262
- return await this.list({
263
- where: { variationId },
264
- orderBy: "timestamp DESC"
265
- });
266
- }
267
- /**
268
- * Find events by zone
269
- *
270
- * @param zoneId - Zone ID
271
- * @returns Array of events
272
- */
273
- async findByZone(zoneId) {
274
- return await this.list({
275
- where: { zoneId },
276
- orderBy: "timestamp DESC"
277
- });
278
- }
279
- /**
280
- * Find events by site
281
- *
282
- * @param siteId - Site ID
283
- * @returns Array of events
284
- */
285
- async findBySite(siteId) {
286
- return await this.list({
287
- where: { siteId },
288
- orderBy: "timestamp DESC"
289
- });
290
- }
291
- /**
292
- * Find events in date range
293
- *
294
- * @param start - Start date
295
- * @param end - End date
296
- * @returns Array of events
297
- */
298
- async findByDateRange(start, end) {
299
- return await this.list({
300
- where: {
301
- "timestamp >=": start.toISOString(),
302
- "timestamp <=": end.toISOString()
303
- },
304
- orderBy: "timestamp DESC"
305
- });
306
- }
307
- /**
308
- * Find events by type
309
- *
310
- * @param eventType - Event type
311
- * @returns Array of events
312
- */
313
- async findByType(eventType) {
314
- return await this.list({
315
- where: { eventType },
316
- orderBy: "timestamp DESC"
317
- });
318
- }
319
- /**
320
- * Count events by type for a variation
321
- *
322
- * @param variationId - Variation ID
323
- * @param eventType - Event type
324
- * @returns Count of events
325
- */
326
- async countByType(variationId, eventType) {
327
- const events = await this.list({
328
- where: { variationId, eventType }
329
- });
330
- return events.length;
331
- }
332
- /**
333
- * Count impressions for a variation
334
- */
335
- async countImpressions(variationId) {
336
- return await this.countByType(variationId, AdEventType.IMPRESSION);
337
- }
338
- /**
339
- * Count clicks for a variation
340
- */
341
- async countClicks(variationId) {
342
- return await this.countByType(variationId, AdEventType.CLICK);
343
- }
344
- /**
345
- * Count conversions for a variation
346
- */
347
- async countConversions(variationId) {
348
- return await this.countByType(variationId, AdEventType.CONVERSION);
349
- }
350
- /**
351
- * Find all impressions
352
- */
353
- async findImpressions() {
354
- return await this.findByType(AdEventType.IMPRESSION);
355
- }
356
- /**
357
- * Find all clicks
358
- */
359
- async findClicks() {
360
- return await this.findByType(AdEventType.CLICK);
361
- }
362
- /**
363
- * Find all conversions
364
- */
365
- async findConversions() {
366
- return await this.findByType(AdEventType.CONVERSION);
367
- }
368
- /**
369
- * Get aggregate stats for a variation
370
- *
371
- * @param variationId - Variation ID
372
- * @returns Stats object with impressions, clicks, conversions, CTR
373
- */
374
- async getVariationStats(variationId) {
375
- const impressions = await this.countImpressions(variationId);
376
- const clicks = await this.countClicks(variationId);
377
- const conversions = await this.countConversions(variationId);
378
- return {
379
- impressions,
380
- clicks,
381
- conversions,
382
- ctr: impressions > 0 ? clicks / impressions : 0,
383
- conversionRate: clicks > 0 ? conversions / clicks : 0
384
- };
385
- }
386
- // ─────────────────────────────────────────────────────────────────────────────
387
- // Tenancy Helper Methods
388
- // ─────────────────────────────────────────────────────────────────────────────
389
- /**
390
- * Find ad events belonging to a specific tenant
391
- *
392
- * @param tenantId - Tenant ID to filter by
393
- * @returns Array of ad events for the tenant
394
- */
395
- async findByTenant(tenantId2) {
396
- return this.list({ where: { tenantId: tenantId2 } });
397
- }
398
- /**
399
- * Find global ad events (no tenant association).
400
- *
401
- * Routes through the shared tenant-global helper so it does not throw under
402
- * an active tenant context (an explicit `tenant_id IS NULL` filter would be
403
- * flagged as an isolation violation). (#1600)
404
- *
405
- * @returns Array of global ad events
406
- */
407
- async findGlobal() {
408
- return queryGlobal(this);
409
- }
410
- /**
411
- * Find ad events for a tenant including global (shared) events.
412
- *
413
- * Fails closed if an active tenant context requests a different tenant's
414
- * rows; the admin/system path keeps the cross-tenant capability. (#1600)
415
- *
416
- * @param tenantId - Tenant ID to include
417
- * @returns Array of tenant-specific and global ad events
418
- */
419
- async findWithGlobals(tenantId2) {
420
- return queryWithGlobals(this, tenantId2, "AdEvent.findWithGlobals");
421
- }
422
- }
414
+ //#endregion
415
+ //#region src/models/AdFormat.ts
416
+ var __defProp$2 = Object.defineProperty;
423
417
  var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
424
418
  var __decorateClass$2 = (decorators, target, key, kind) => {
425
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
426
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
427
- if (decorator = decorators[i])
428
- result = decorator(result) || result;
429
- return result;
419
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
420
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
421
+ if (kind && result) __defProp$2(target, key, result);
422
+ return result;
423
+ };
424
+ var AdFormat = class extends SmrtObject {
425
+ /**
426
+ * Display name (e.g., "Leaderboard", "Medium Rectangle")
427
+ */
428
+ name = "";
429
+ /**
430
+ * Width in pixels
431
+ */
432
+ width = 0;
433
+ /**
434
+ * Height in pixels
435
+ */
436
+ height = 0;
437
+ /**
438
+ * Format type (banner, native, video)
439
+ */
440
+ formatType = AdFormatType.BANNER;
441
+ /**
442
+ * Optional description
443
+ */
444
+ description = "";
445
+ constructor(options = {}) {
446
+ super(options);
447
+ if (options.name !== void 0) this.name = options.name;
448
+ if (options.width !== void 0) this.width = options.width;
449
+ if (options.height !== void 0) this.height = options.height;
450
+ if (options.formatType !== void 0) this.formatType = options.formatType;
451
+ if (options.description !== void 0) this.description = options.description;
452
+ }
453
+ /**
454
+ * Get dimensions as string (e.g., "728x90")
455
+ */
456
+ getDimensions() {
457
+ return `${this.width}x${this.height}`;
458
+ }
459
+ /**
460
+ * Check if format matches specific dimensions
461
+ */
462
+ matchesDimensions(width, height) {
463
+ return this.width === width && this.height === height;
464
+ }
430
465
  };
431
- let AdFormat = class extends SmrtObject {
432
- /**
433
- * Display name (e.g., "Leaderboard", "Medium Rectangle")
434
- */
435
- name = "";
436
- /**
437
- * Width in pixels
438
- */
439
- width = 0;
440
- /**
441
- * Height in pixels
442
- */
443
- height = 0;
444
- /**
445
- * Format type (banner, native, video)
446
- */
447
- formatType = AdFormatType.BANNER;
448
- /**
449
- * Optional description
450
- */
451
- description = "";
452
- constructor(options = {}) {
453
- super(options);
454
- if (options.name !== void 0) this.name = options.name;
455
- if (options.width !== void 0) this.width = options.width;
456
- if (options.height !== void 0) this.height = options.height;
457
- if (options.formatType !== void 0) this.formatType = options.formatType;
458
- if (options.description !== void 0)
459
- this.description = options.description;
460
- }
461
- /**
462
- * Get dimensions as string (e.g., "728x90")
463
- */
464
- getDimensions() {
465
- return `${this.width}x${this.height}`;
466
- }
467
- /**
468
- * Check if format matches specific dimensions
469
- */
470
- matchesDimensions(width, height) {
471
- return this.width === width && this.height === height;
472
- }
466
+ AdFormat = __decorateClass$2([smrt({
467
+ api: { include: [
468
+ "list",
469
+ "get",
470
+ "create",
471
+ "update"
472
+ ] },
473
+ mcp: { include: ["list", "get"] },
474
+ cli: true
475
+ })], AdFormat);
476
+ //#endregion
477
+ //#region src/collections/AdFormatCollection.ts
478
+ var AdFormatCollection = class extends SmrtCollection {
479
+ static _itemClass = AdFormat;
480
+ /**
481
+ * Find format by dimensions
482
+ *
483
+ * @param width - Width in pixels
484
+ * @param height - Height in pixels
485
+ * @returns Matching format or null
486
+ */
487
+ async findByDimensions(width, height) {
488
+ const results = await this.list({
489
+ where: {
490
+ width,
491
+ height
492
+ },
493
+ limit: 1
494
+ });
495
+ return results.length > 0 ? results[0] : null;
496
+ }
497
+ /**
498
+ * Find formats by type
499
+ *
500
+ * @param formatType - Format type (banner, native, video)
501
+ * @returns Array of matching formats
502
+ */
503
+ async findByType(formatType) {
504
+ return await this.list({
505
+ where: { formatType },
506
+ orderBy: "name ASC"
507
+ });
508
+ }
509
+ /**
510
+ * Find all banner formats
511
+ */
512
+ async findBanners() {
513
+ return await this.findByType(AdFormatType.BANNER);
514
+ }
515
+ /**
516
+ * Find all native formats
517
+ */
518
+ async findNative() {
519
+ return await this.findByType(AdFormatType.NATIVE);
520
+ }
521
+ /**
522
+ * Find all video formats
523
+ */
524
+ async findVideo() {
525
+ return await this.findByType(AdFormatType.VIDEO);
526
+ }
473
527
  };
474
- AdFormat = __decorateClass$2([
475
- smrt({
476
- api: { include: ["list", "get", "create", "update"] },
477
- mcp: { include: ["list", "get"] },
478
- cli: true
479
- })
480
- ], AdFormat);
481
- class AdFormatCollection extends SmrtCollection {
482
- static _itemClass = AdFormat;
483
- /**
484
- * Find format by dimensions
485
- *
486
- * @param width - Width in pixels
487
- * @param height - Height in pixels
488
- * @returns Matching format or null
489
- */
490
- async findByDimensions(width, height) {
491
- const results = await this.list({
492
- where: { width, height },
493
- limit: 1
494
- });
495
- return results.length > 0 ? results[0] : null;
496
- }
497
- /**
498
- * Find formats by type
499
- *
500
- * @param formatType - Format type (banner, native, video)
501
- * @returns Array of matching formats
502
- */
503
- async findByType(formatType) {
504
- return await this.list({
505
- where: { formatType },
506
- orderBy: "name ASC"
507
- });
508
- }
509
- /**
510
- * Find all banner formats
511
- */
512
- async findBanners() {
513
- return await this.findByType(AdFormatType.BANNER);
514
- }
515
- /**
516
- * Find all native formats
517
- */
518
- async findNative() {
519
- return await this.findByType(AdFormatType.NATIVE);
520
- }
521
- /**
522
- * Find all video formats
523
- */
524
- async findVideo() {
525
- return await this.findByType(AdFormatType.VIDEO);
526
- }
527
- }
528
+ //#endregion
529
+ //#region src/models/AdGroup.ts
528
530
  var __defProp$1 = Object.defineProperty;
529
531
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
530
532
  var __decorateClass$1 = (decorators, target, key, kind) => {
531
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
532
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
533
- if (decorator = decorators[i])
534
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
535
- if (kind && result) __defProp$1(target, key, result);
536
- return result;
533
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
534
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
535
+ if (kind && result) __defProp$1(target, key, result);
536
+ return result;
537
+ };
538
+ var AdGroup = class extends SmrtObject {
539
+ tenantId = null;
540
+ contractId = "";
541
+ tierId = "";
542
+ /**
543
+ * Display name (e.g., "Summer Sale - Desktop")
544
+ */
545
+ name = "";
546
+ /**
547
+ * Vertical/category tag slug from smrt-tags (context="advertising")
548
+ */
549
+ verticalSlug = "";
550
+ /**
551
+ * Targeting rules as JSON string
552
+ */
553
+ targeting = "";
554
+ /**
555
+ * Allowed Zone IDs as JSON array string (FK to smrt-properties Zone)
556
+ */
557
+ zoneIds = "";
558
+ /**
559
+ * Campaign start date
560
+ */
561
+ startDate = null;
562
+ /**
563
+ * Campaign end date
564
+ */
565
+ endDate = null;
566
+ /**
567
+ * Daily budget limit
568
+ */
569
+ dailyBudget = 0;
570
+ /**
571
+ * Total campaign budget
572
+ */
573
+ totalBudget = 0;
574
+ /**
575
+ * Current status
576
+ */
577
+ status = AdGroupStatus.DRAFT;
578
+ constructor(options = {}) {
579
+ super(options);
580
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
581
+ if (options.contractId !== void 0) this.contractId = options.contractId;
582
+ if (options.tierId !== void 0) this.tierId = options.tierId;
583
+ if (options.name !== void 0) this.name = options.name;
584
+ if (options.verticalSlug !== void 0) this.verticalSlug = options.verticalSlug;
585
+ if (options.targeting !== void 0) this.targeting = options.targeting;
586
+ if (options.zoneIds !== void 0) this.zoneIds = options.zoneIds;
587
+ if (options.startDate !== void 0) this.startDate = options.startDate;
588
+ if (options.endDate !== void 0) this.endDate = options.endDate;
589
+ if (options.dailyBudget !== void 0) this.dailyBudget = options.dailyBudget;
590
+ if (options.totalBudget !== void 0) this.totalBudget = options.totalBudget;
591
+ if (options.status !== void 0) this.status = options.status;
592
+ }
593
+ /**
594
+ * Get zone IDs as array
595
+ */
596
+ getZoneIds() {
597
+ if (!this.zoneIds) return [];
598
+ try {
599
+ return JSON.parse(this.zoneIds);
600
+ } catch {
601
+ return [];
602
+ }
603
+ }
604
+ /**
605
+ * Set zone IDs from array
606
+ */
607
+ setZoneIds(ids) {
608
+ this.zoneIds = JSON.stringify(ids);
609
+ }
610
+ /**
611
+ * Add a zone ID
612
+ */
613
+ addZoneId(zoneId) {
614
+ const ids = this.getZoneIds();
615
+ if (!ids.includes(zoneId)) {
616
+ ids.push(zoneId);
617
+ this.setZoneIds(ids);
618
+ }
619
+ }
620
+ /**
621
+ * Remove a zone ID
622
+ */
623
+ removeZoneId(zoneId) {
624
+ const ids = this.getZoneIds().filter((id) => id !== zoneId);
625
+ this.setZoneIds(ids);
626
+ }
627
+ /**
628
+ * Check if zone ID is allowed
629
+ */
630
+ hasZoneId(zoneId) {
631
+ return this.getZoneIds().includes(zoneId);
632
+ }
633
+ /**
634
+ * Get targeting rules as object
635
+ */
636
+ getTargeting() {
637
+ if (!this.targeting) return {};
638
+ try {
639
+ return JSON.parse(this.targeting);
640
+ } catch {
641
+ return {};
642
+ }
643
+ }
644
+ /**
645
+ * Set targeting rules from object
646
+ */
647
+ setTargeting(rules) {
648
+ this.targeting = JSON.stringify(rules);
649
+ }
650
+ /**
651
+ * Check if ad group is currently active
652
+ */
653
+ isActive() {
654
+ if (this.status !== AdGroupStatus.ACTIVE) return false;
655
+ const now = /* @__PURE__ */ new Date();
656
+ if (this.startDate && now < this.startDate) return false;
657
+ if (this.endDate && now > this.endDate) return false;
658
+ return true;
659
+ }
660
+ /**
661
+ * Check if ad group is in draft state
662
+ */
663
+ isDraft() {
664
+ return this.status === AdGroupStatus.DRAFT;
665
+ }
666
+ /**
667
+ * Check if ad group is paused
668
+ */
669
+ isPaused() {
670
+ return this.status === AdGroupStatus.PAUSED;
671
+ }
672
+ /**
673
+ * Check if ad group is completed
674
+ */
675
+ isCompleted() {
676
+ return this.status === AdGroupStatus.COMPLETED;
677
+ }
678
+ /**
679
+ * Check if ad group has ended (past end date)
680
+ */
681
+ hasEnded() {
682
+ if (!this.endDate) return false;
683
+ return /* @__PURE__ */ new Date() > this.endDate;
684
+ }
685
+ /**
686
+ * Check if ad group has started
687
+ */
688
+ hasStarted() {
689
+ if (!this.startDate) return true;
690
+ return /* @__PURE__ */ new Date() >= this.startDate;
691
+ }
537
692
  };
538
- let AdGroup = class extends SmrtObject {
539
- tenantId = null;
540
- contractId = "";
541
- tierId = "";
542
- /**
543
- * Display name (e.g., "Summer Sale - Desktop")
544
- */
545
- name = "";
546
- /**
547
- * Vertical/category tag slug from smrt-tags (context="advertising")
548
- */
549
- verticalSlug = "";
550
- /**
551
- * Targeting rules as JSON string
552
- */
553
- targeting = "";
554
- /**
555
- * Allowed Zone IDs as JSON array string (FK to smrt-properties Zone)
556
- */
557
- zoneIds = "";
558
- /**
559
- * Campaign start date
560
- */
561
- startDate = null;
562
- /**
563
- * Campaign end date
564
- */
565
- endDate = null;
566
- /**
567
- * Daily budget limit
568
- */
569
- dailyBudget = 0;
570
- /**
571
- * Total campaign budget
572
- */
573
- totalBudget = 0;
574
- /**
575
- * Current status
576
- */
577
- status = AdGroupStatus.DRAFT;
578
- constructor(options = {}) {
579
- super(options);
580
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
581
- if (options.contractId !== void 0) this.contractId = options.contractId;
582
- if (options.tierId !== void 0) this.tierId = options.tierId;
583
- if (options.name !== void 0) this.name = options.name;
584
- if (options.verticalSlug !== void 0)
585
- this.verticalSlug = options.verticalSlug;
586
- if (options.targeting !== void 0) this.targeting = options.targeting;
587
- if (options.zoneIds !== void 0) this.zoneIds = options.zoneIds;
588
- if (options.startDate !== void 0) this.startDate = options.startDate;
589
- if (options.endDate !== void 0) this.endDate = options.endDate;
590
- if (options.dailyBudget !== void 0)
591
- this.dailyBudget = options.dailyBudget;
592
- if (options.totalBudget !== void 0)
593
- this.totalBudget = options.totalBudget;
594
- if (options.status !== void 0) this.status = options.status;
595
- }
596
- /**
597
- * Get zone IDs as array
598
- */
599
- getZoneIds() {
600
- if (!this.zoneIds) return [];
601
- try {
602
- return JSON.parse(this.zoneIds);
603
- } catch {
604
- return [];
605
- }
606
- }
607
- /**
608
- * Set zone IDs from array
609
- */
610
- setZoneIds(ids) {
611
- this.zoneIds = JSON.stringify(ids);
612
- }
613
- /**
614
- * Add a zone ID
615
- */
616
- addZoneId(zoneId) {
617
- const ids = this.getZoneIds();
618
- if (!ids.includes(zoneId)) {
619
- ids.push(zoneId);
620
- this.setZoneIds(ids);
621
- }
622
- }
623
- /**
624
- * Remove a zone ID
625
- */
626
- removeZoneId(zoneId) {
627
- const ids = this.getZoneIds().filter((id) => id !== zoneId);
628
- this.setZoneIds(ids);
629
- }
630
- /**
631
- * Check if zone ID is allowed
632
- */
633
- hasZoneId(zoneId) {
634
- return this.getZoneIds().includes(zoneId);
635
- }
636
- /**
637
- * Get targeting rules as object
638
- */
639
- getTargeting() {
640
- if (!this.targeting) return {};
641
- try {
642
- return JSON.parse(this.targeting);
643
- } catch {
644
- return {};
645
- }
646
- }
647
- /**
648
- * Set targeting rules from object
649
- */
650
- setTargeting(rules) {
651
- this.targeting = JSON.stringify(rules);
652
- }
653
- /**
654
- * Check if ad group is currently active
655
- */
656
- isActive() {
657
- if (this.status !== AdGroupStatus.ACTIVE) return false;
658
- const now = /* @__PURE__ */ new Date();
659
- if (this.startDate && now < this.startDate) return false;
660
- if (this.endDate && now > this.endDate) return false;
661
- return true;
662
- }
663
- /**
664
- * Check if ad group is in draft state
665
- */
666
- isDraft() {
667
- return this.status === AdGroupStatus.DRAFT;
668
- }
669
- /**
670
- * Check if ad group is paused
671
- */
672
- isPaused() {
673
- return this.status === AdGroupStatus.PAUSED;
674
- }
675
- /**
676
- * Check if ad group is completed
677
- */
678
- isCompleted() {
679
- return this.status === AdGroupStatus.COMPLETED;
680
- }
681
- /**
682
- * Check if ad group has ended (past end date)
683
- */
684
- hasEnded() {
685
- if (!this.endDate) return false;
686
- return /* @__PURE__ */ new Date() > this.endDate;
687
- }
688
- /**
689
- * Check if ad group has started
690
- */
691
- hasStarted() {
692
- if (!this.startDate) return true;
693
- return /* @__PURE__ */ new Date() >= this.startDate;
694
- }
693
+ __decorateClass$1([tenantId({ nullable: true })], AdGroup.prototype, "tenantId", 2);
694
+ __decorateClass$1([crossPackageRef("@happyvertical/smrt-commerce:Contract")], AdGroup.prototype, "contractId", 2);
695
+ __decorateClass$1([foreignKey("AdDeliveryTier")], AdGroup.prototype, "tierId", 2);
696
+ AdGroup = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
697
+ tableStrategy: "sti",
698
+ api: { include: [
699
+ "list",
700
+ "get",
701
+ "create",
702
+ "update"
703
+ ] },
704
+ mcp: { include: [
705
+ "list",
706
+ "get",
707
+ "create"
708
+ ] },
709
+ cli: true
710
+ })], AdGroup);
711
+ //#endregion
712
+ //#region src/collections/AdGroupCollection.ts
713
+ var AdGroupCollection = class extends SmrtCollection {
714
+ static _itemClass = AdGroup;
715
+ /**
716
+ * Find ad groups by contract
717
+ *
718
+ * @param contractId - Contract ID
719
+ * @returns Array of ad groups
720
+ */
721
+ async findByContract(contractId) {
722
+ return await this.list({
723
+ where: { contractId },
724
+ orderBy: "created_at DESC"
725
+ });
726
+ }
727
+ /**
728
+ * Find ad groups by tier
729
+ *
730
+ * @param tierId - Delivery tier ID
731
+ * @returns Array of ad groups
732
+ */
733
+ async findByTier(tierId) {
734
+ return await this.list({
735
+ where: { tierId },
736
+ orderBy: "name ASC"
737
+ });
738
+ }
739
+ /**
740
+ * Find ad groups by status
741
+ *
742
+ * @param status - Ad group status
743
+ * @returns Array of ad groups
744
+ */
745
+ async findByStatus(status) {
746
+ return await this.list({
747
+ where: { status },
748
+ orderBy: "created_at DESC"
749
+ });
750
+ }
751
+ /**
752
+ * Find currently active ad groups
753
+ * (status=active, started, not ended)
754
+ *
755
+ * @returns Array of active ad groups
756
+ */
757
+ async findActive() {
758
+ return (await this.list({
759
+ where: { status: AdGroupStatus.ACTIVE },
760
+ orderBy: "created_at DESC"
761
+ })).filter((group) => group.isActive());
762
+ }
763
+ /**
764
+ * Find ad groups by vertical slug
765
+ *
766
+ * @param verticalSlug - Tag slug from smrt-tags
767
+ * @returns Array of ad groups
768
+ */
769
+ async findByVertical(verticalSlug) {
770
+ return await this.list({
771
+ where: { verticalSlug },
772
+ orderBy: "name ASC"
773
+ });
774
+ }
775
+ /**
776
+ * Find ad groups that can serve to a specific zone
777
+ *
778
+ * @param zoneId - Zone ID to check
779
+ * @returns Array of ad groups containing zone
780
+ */
781
+ async findByZone(zoneId) {
782
+ return (await this.list({ where: { status: AdGroupStatus.ACTIVE } })).filter((group) => group.hasZoneId(zoneId));
783
+ }
784
+ /**
785
+ * Find ad groups eligible to serve for a zone
786
+ * (active, has zone, within date range)
787
+ *
788
+ * @param zoneId - Zone ID to check
789
+ * @returns Array of eligible ad groups
790
+ */
791
+ async findEligibleForZone(zoneId) {
792
+ return (await this.findByZone(zoneId)).filter((group) => group.isActive());
793
+ }
794
+ /**
795
+ * Find all draft ad groups
796
+ */
797
+ async findDrafts() {
798
+ return await this.findByStatus(AdGroupStatus.DRAFT);
799
+ }
800
+ /**
801
+ * Find all paused ad groups
802
+ */
803
+ async findPaused() {
804
+ return await this.findByStatus(AdGroupStatus.PAUSED);
805
+ }
806
+ /**
807
+ * Find all completed ad groups
808
+ */
809
+ async findCompleted() {
810
+ return await this.findByStatus(AdGroupStatus.COMPLETED);
811
+ }
812
+ /**
813
+ * Find ad groups belonging to a specific tenant
814
+ *
815
+ * @param tenantId - Tenant ID to filter by
816
+ * @returns Array of ad groups for the tenant
817
+ */
818
+ async findByTenant(tenantId) {
819
+ return this.list({ where: { tenantId } });
820
+ }
821
+ /**
822
+ * Find global ad groups (no tenant association).
823
+ *
824
+ * Routes through the shared tenant-global helper so it does not throw under
825
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
826
+ * flagged as an isolation violation). (#1600)
827
+ *
828
+ * @returns Array of global ad groups
829
+ */
830
+ async findGlobal() {
831
+ return queryGlobal(this);
832
+ }
833
+ /**
834
+ * Find ad groups for a tenant including global (shared) ad groups.
835
+ *
836
+ * Fails closed if an active tenant context requests a different tenant's
837
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
838
+ *
839
+ * @param tenantId - Tenant ID to include
840
+ * @returns Array of tenant-specific and global ad groups
841
+ */
842
+ async findWithGlobals(tenantId) {
843
+ return queryWithGlobals(this, tenantId, "AdGroup.findWithGlobals");
844
+ }
695
845
  };
696
- __decorateClass$1([
697
- tenantId({ nullable: true })
698
- ], AdGroup.prototype, "tenantId", 2);
699
- __decorateClass$1([
700
- crossPackageRef("@happyvertical/smrt-commerce:Contract")
701
- ], AdGroup.prototype, "contractId", 2);
702
- __decorateClass$1([
703
- foreignKey("AdDeliveryTier")
704
- ], AdGroup.prototype, "tierId", 2);
705
- AdGroup = __decorateClass$1([
706
- TenantScoped({ mode: "optional" }),
707
- smrt({
708
- tableStrategy: "sti",
709
- api: { include: ["list", "get", "create", "update"] },
710
- mcp: { include: ["list", "get", "create"] },
711
- cli: true
712
- })
713
- ], AdGroup);
714
- class AdGroupCollection extends SmrtCollection {
715
- static _itemClass = AdGroup;
716
- /**
717
- * Find ad groups by contract
718
- *
719
- * @param contractId - Contract ID
720
- * @returns Array of ad groups
721
- */
722
- async findByContract(contractId) {
723
- return await this.list({
724
- where: { contractId },
725
- orderBy: "created_at DESC"
726
- });
727
- }
728
- /**
729
- * Find ad groups by tier
730
- *
731
- * @param tierId - Delivery tier ID
732
- * @returns Array of ad groups
733
- */
734
- async findByTier(tierId) {
735
- return await this.list({
736
- where: { tierId },
737
- orderBy: "name ASC"
738
- });
739
- }
740
- /**
741
- * Find ad groups by status
742
- *
743
- * @param status - Ad group status
744
- * @returns Array of ad groups
745
- */
746
- async findByStatus(status) {
747
- return await this.list({
748
- where: { status },
749
- orderBy: "created_at DESC"
750
- });
751
- }
752
- /**
753
- * Find currently active ad groups
754
- * (status=active, started, not ended)
755
- *
756
- * @returns Array of active ad groups
757
- */
758
- async findActive() {
759
- const results = await this.list({
760
- where: {
761
- status: AdGroupStatus.ACTIVE
762
- },
763
- orderBy: "created_at DESC"
764
- });
765
- return results.filter((group) => group.isActive());
766
- }
767
- /**
768
- * Find ad groups by vertical slug
769
- *
770
- * @param verticalSlug - Tag slug from smrt-tags
771
- * @returns Array of ad groups
772
- */
773
- async findByVertical(verticalSlug) {
774
- return await this.list({
775
- where: { verticalSlug },
776
- orderBy: "name ASC"
777
- });
778
- }
779
- /**
780
- * Find ad groups that can serve to a specific zone
781
- *
782
- * @param zoneId - Zone ID to check
783
- * @returns Array of ad groups containing zone
784
- */
785
- async findByZone(zoneId) {
786
- const all = await this.list({
787
- where: { status: AdGroupStatus.ACTIVE }
788
- });
789
- return all.filter((group) => group.hasZoneId(zoneId));
790
- }
791
- /**
792
- * Find ad groups eligible to serve for a zone
793
- * (active, has zone, within date range)
794
- *
795
- * @param zoneId - Zone ID to check
796
- * @returns Array of eligible ad groups
797
- */
798
- async findEligibleForZone(zoneId) {
799
- const groups = await this.findByZone(zoneId);
800
- return groups.filter((group) => group.isActive());
801
- }
802
- /**
803
- * Find all draft ad groups
804
- */
805
- async findDrafts() {
806
- return await this.findByStatus(AdGroupStatus.DRAFT);
807
- }
808
- /**
809
- * Find all paused ad groups
810
- */
811
- async findPaused() {
812
- return await this.findByStatus(AdGroupStatus.PAUSED);
813
- }
814
- /**
815
- * Find all completed ad groups
816
- */
817
- async findCompleted() {
818
- return await this.findByStatus(AdGroupStatus.COMPLETED);
819
- }
820
- // ─────────────────────────────────────────────────────────────────────────────
821
- // Tenancy Helper Methods
822
- // ─────────────────────────────────────────────────────────────────────────────
823
- /**
824
- * Find ad groups belonging to a specific tenant
825
- *
826
- * @param tenantId - Tenant ID to filter by
827
- * @returns Array of ad groups for the tenant
828
- */
829
- async findByTenant(tenantId2) {
830
- return this.list({ where: { tenantId: tenantId2 } });
831
- }
832
- /**
833
- * Find global ad groups (no tenant association).
834
- *
835
- * Routes through the shared tenant-global helper so it does not throw under
836
- * an active tenant context (an explicit `tenant_id IS NULL` filter would be
837
- * flagged as an isolation violation). (#1600)
838
- *
839
- * @returns Array of global ad groups
840
- */
841
- async findGlobal() {
842
- return queryGlobal(this);
843
- }
844
- /**
845
- * Find ad groups for a tenant including global (shared) ad groups.
846
- *
847
- * Fails closed if an active tenant context requests a different tenant's
848
- * rows; the admin/system path keeps the cross-tenant capability. (#1600)
849
- *
850
- * @param tenantId - Tenant ID to include
851
- * @returns Array of tenant-specific and global ad groups
852
- */
853
- async findWithGlobals(tenantId2) {
854
- return queryWithGlobals(this, tenantId2, "AdGroup.findWithGlobals");
855
- }
856
- }
846
+ //#endregion
847
+ //#region src/models/AdVariation.ts
857
848
  var __defProp = Object.defineProperty;
858
849
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
859
850
  var __decorateClass = (decorators, target, key, kind) => {
860
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
861
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
862
- if (decorator = decorators[i])
863
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
864
- if (kind && result) __defProp(target, key, result);
865
- return result;
851
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
852
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
853
+ if (kind && result) __defProp(target, key, result);
854
+ return result;
866
855
  };
867
- let AdVariation = class extends SmrtObject {
868
- tenantId = null;
869
- groupId = "";
870
- formatId = "";
871
- assetId = "";
872
- /**
873
- * Display name (e.g., "Version A - Blue CTA")
874
- */
875
- name = "";
876
- /**
877
- * Click destination URL
878
- */
879
- clickUrl = "";
880
- /**
881
- * Accessibility alt text
882
- */
883
- altText = "";
884
- /**
885
- * A/B testing weight (higher = more likely to be selected)
886
- */
887
- weight = 1;
888
- /**
889
- * Current status
890
- */
891
- status = AdVariationStatus.DRAFT;
892
- /**
893
- * Denormalized impression count (updated async)
894
- */
895
- impressions = 0;
896
- /**
897
- * Denormalized click count (updated async)
898
- */
899
- clicks = 0;
900
- constructor(options = {}) {
901
- super(options);
902
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
903
- if (options.groupId !== void 0) this.groupId = options.groupId;
904
- if (options.formatId !== void 0) this.formatId = options.formatId;
905
- if (options.assetId !== void 0) this.assetId = options.assetId;
906
- if (options.name !== void 0) this.name = options.name;
907
- if (options.clickUrl !== void 0) this.clickUrl = options.clickUrl;
908
- if (options.altText !== void 0) this.altText = options.altText;
909
- if (options.weight !== void 0) this.weight = options.weight;
910
- if (options.status !== void 0) this.status = options.status;
911
- if (options.impressions !== void 0)
912
- this.impressions = options.impressions;
913
- if (options.clicks !== void 0) this.clicks = options.clicks;
914
- }
915
- /**
916
- * Check if variation is active
917
- */
918
- isActive() {
919
- return this.status === AdVariationStatus.ACTIVE;
920
- }
921
- /**
922
- * Check if variation is in draft state
923
- */
924
- isDraft() {
925
- return this.status === AdVariationStatus.DRAFT;
926
- }
927
- /**
928
- * Check if variation is paused
929
- */
930
- isPaused() {
931
- return this.status === AdVariationStatus.PAUSED;
932
- }
933
- /**
934
- * Calculate click-through rate (CTR)
935
- */
936
- getCTR() {
937
- if (this.impressions === 0) return 0;
938
- return this.clicks / this.impressions;
939
- }
940
- /**
941
- * Increment impression count
942
- */
943
- recordImpression() {
944
- this.impressions += 1;
945
- }
946
- /**
947
- * Increment click count
948
- */
949
- recordClick() {
950
- this.clicks += 1;
951
- }
856
+ var AdVariation = class extends SmrtObject {
857
+ tenantId = null;
858
+ groupId = "";
859
+ formatId = "";
860
+ assetId = "";
861
+ /**
862
+ * Display name (e.g., "Version A - Blue CTA")
863
+ */
864
+ name = "";
865
+ /**
866
+ * Click destination URL
867
+ */
868
+ clickUrl = "";
869
+ /**
870
+ * Accessibility alt text
871
+ */
872
+ altText = "";
873
+ /**
874
+ * A/B testing weight (higher = more likely to be selected)
875
+ */
876
+ weight = 1;
877
+ /**
878
+ * Current status
879
+ */
880
+ status = AdVariationStatus.DRAFT;
881
+ /**
882
+ * Denormalized impression count (updated async)
883
+ */
884
+ impressions = 0;
885
+ /**
886
+ * Denormalized click count (updated async)
887
+ */
888
+ clicks = 0;
889
+ constructor(options = {}) {
890
+ super(options);
891
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
892
+ if (options.groupId !== void 0) this.groupId = options.groupId;
893
+ if (options.formatId !== void 0) this.formatId = options.formatId;
894
+ if (options.assetId !== void 0) this.assetId = options.assetId;
895
+ if (options.name !== void 0) this.name = options.name;
896
+ if (options.clickUrl !== void 0) this.clickUrl = options.clickUrl;
897
+ if (options.altText !== void 0) this.altText = options.altText;
898
+ if (options.weight !== void 0) this.weight = options.weight;
899
+ if (options.status !== void 0) this.status = options.status;
900
+ if (options.impressions !== void 0) this.impressions = options.impressions;
901
+ if (options.clicks !== void 0) this.clicks = options.clicks;
902
+ }
903
+ /**
904
+ * Check if variation is active
905
+ */
906
+ isActive() {
907
+ return this.status === AdVariationStatus.ACTIVE;
908
+ }
909
+ /**
910
+ * Check if variation is in draft state
911
+ */
912
+ isDraft() {
913
+ return this.status === AdVariationStatus.DRAFT;
914
+ }
915
+ /**
916
+ * Check if variation is paused
917
+ */
918
+ isPaused() {
919
+ return this.status === AdVariationStatus.PAUSED;
920
+ }
921
+ /**
922
+ * Calculate click-through rate (CTR)
923
+ */
924
+ getCTR() {
925
+ if (this.impressions === 0) return 0;
926
+ return this.clicks / this.impressions;
927
+ }
928
+ /**
929
+ * Increment impression count
930
+ */
931
+ recordImpression() {
932
+ this.impressions += 1;
933
+ }
934
+ /**
935
+ * Increment click count
936
+ */
937
+ recordClick() {
938
+ this.clicks += 1;
939
+ }
952
940
  };
953
- __decorateClass([
954
- tenantId({ nullable: true })
955
- ], AdVariation.prototype, "tenantId", 2);
956
- __decorateClass([
957
- foreignKey("AdGroup")
958
- ], AdVariation.prototype, "groupId", 2);
959
- __decorateClass([
960
- foreignKey("AdFormat")
961
- ], AdVariation.prototype, "formatId", 2);
962
- __decorateClass([
963
- crossPackageRef("@happyvertical/smrt-assets:Asset")
964
- ], AdVariation.prototype, "assetId", 2);
965
- AdVariation = __decorateClass([
966
- TenantScoped({ mode: "optional" }),
967
- smrt({
968
- tableStrategy: "sti",
969
- api: { include: ["list", "get", "create", "update"] },
970
- mcp: { include: ["list", "get", "create"] },
971
- cli: true
972
- })
973
- ], AdVariation);
974
- class AdVariationCollection extends SmrtCollection {
975
- static _itemClass = AdVariation;
976
- /**
977
- * Find variations by ad group
978
- *
979
- * @param groupId - Ad group ID
980
- * @returns Array of variations
981
- */
982
- async findByGroup(groupId) {
983
- return await this.list({
984
- where: { groupId },
985
- orderBy: "name ASC"
986
- });
987
- }
988
- /**
989
- * Find variations by format
990
- *
991
- * @param formatId - Ad format ID
992
- * @returns Array of variations
993
- */
994
- async findByFormat(formatId) {
995
- return await this.list({
996
- where: { formatId },
997
- orderBy: "name ASC"
998
- });
999
- }
1000
- /**
1001
- * Find active variations for a group
1002
- *
1003
- * @param groupId - Ad group ID
1004
- * @returns Array of active variations
1005
- */
1006
- async findActiveByGroup(groupId) {
1007
- return await this.list({
1008
- where: {
1009
- groupId,
1010
- status: AdVariationStatus.ACTIVE
1011
- },
1012
- orderBy: "name ASC"
1013
- });
1014
- }
1015
- /**
1016
- * Select a variation using weighted random selection
1017
- * Higher weight = more likely to be selected
1018
- *
1019
- * @param groupId - Ad group ID
1020
- * @returns Selected variation or null if none available
1021
- */
1022
- async selectByWeight(groupId) {
1023
- const active = await this.findActiveByGroup(groupId);
1024
- if (active.length === 0) return null;
1025
- if (active.length === 1) return active[0];
1026
- const totalWeight = active.reduce((sum, v) => sum + v.weight, 0);
1027
- if (totalWeight <= 0) return active[0];
1028
- let random = Math.random() * totalWeight;
1029
- for (const variation of active) {
1030
- random -= variation.weight;
1031
- if (random <= 0) {
1032
- return variation;
1033
- }
1034
- }
1035
- return active[active.length - 1];
1036
- }
1037
- /**
1038
- * Find variations by status
1039
- *
1040
- * @param status - Variation status
1041
- * @returns Array of variations
1042
- */
1043
- async findByStatus(status) {
1044
- return await this.list({
1045
- where: { status },
1046
- orderBy: "created_at DESC"
1047
- });
1048
- }
1049
- /**
1050
- * Find all active variations
1051
- */
1052
- async findActive() {
1053
- return await this.findByStatus(AdVariationStatus.ACTIVE);
1054
- }
1055
- /**
1056
- * Find all draft variations
1057
- */
1058
- async findDrafts() {
1059
- return await this.findByStatus(AdVariationStatus.DRAFT);
1060
- }
1061
- /**
1062
- * Find all paused variations
1063
- */
1064
- async findPaused() {
1065
- return await this.findByStatus(AdVariationStatus.PAUSED);
1066
- }
1067
- /**
1068
- * Find top performing variations by CTR
1069
- *
1070
- * @param limit - Maximum number to return
1071
- * @returns Array of variations sorted by CTR descending
1072
- */
1073
- async findTopPerformers(limit = 10) {
1074
- const all = await this.list({
1075
- where: { status: AdVariationStatus.ACTIVE }
1076
- });
1077
- return all.filter((v) => v.impressions > 0).sort((a, b) => b.getCTR() - a.getCTR()).slice(0, limit);
1078
- }
1079
- // ─────────────────────────────────────────────────────────────────────────────
1080
- // Tenancy Helper Methods
1081
- // ─────────────────────────────────────────────────────────────────────────────
1082
- /**
1083
- * Find ad variations belonging to a specific tenant
1084
- *
1085
- * @param tenantId - Tenant ID to filter by
1086
- * @returns Array of ad variations for the tenant
1087
- */
1088
- async findByTenant(tenantId2) {
1089
- return this.list({ where: { tenantId: tenantId2 } });
1090
- }
1091
- /**
1092
- * Find global ad variations (no tenant association).
1093
- *
1094
- * Routes through the shared tenant-global helper so it does not throw under
1095
- * an active tenant context (an explicit `tenant_id IS NULL` filter would be
1096
- * flagged as an isolation violation). (#1600)
1097
- *
1098
- * @returns Array of global ad variations
1099
- */
1100
- async findGlobal() {
1101
- return queryGlobal(this);
1102
- }
1103
- /**
1104
- * Find ad variations for a tenant including global (shared) variations.
1105
- *
1106
- * Fails closed if an active tenant context requests a different tenant's
1107
- * rows; the admin/system path keeps the cross-tenant capability. (#1600)
1108
- *
1109
- * @param tenantId - Tenant ID to include
1110
- * @returns Array of tenant-specific and global ad variations
1111
- */
1112
- async findWithGlobals(tenantId2) {
1113
- return queryWithGlobals(
1114
- this,
1115
- tenantId2,
1116
- "AdVariation.findWithGlobals"
1117
- );
1118
- }
1119
- }
1120
- export {
1121
- AdDeliveryTier,
1122
- AdDeliveryTierCollection,
1123
- AdEvent,
1124
- AdEventCollection,
1125
- AdEventType,
1126
- AdFormat,
1127
- AdFormatCollection,
1128
- AdFormatType,
1129
- AdGroup,
1130
- AdGroupCollection,
1131
- AdGroupStatus,
1132
- AdVariation,
1133
- AdVariationCollection,
1134
- AdVariationStatus,
1135
- PricingModel
941
+ __decorateClass([tenantId({ nullable: true })], AdVariation.prototype, "tenantId", 2);
942
+ __decorateClass([foreignKey("AdGroup")], AdVariation.prototype, "groupId", 2);
943
+ __decorateClass([foreignKey("AdFormat")], AdVariation.prototype, "formatId", 2);
944
+ __decorateClass([crossPackageRef("@happyvertical/smrt-assets:Asset")], AdVariation.prototype, "assetId", 2);
945
+ AdVariation = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
946
+ tableStrategy: "sti",
947
+ api: { include: [
948
+ "list",
949
+ "get",
950
+ "create",
951
+ "update"
952
+ ] },
953
+ mcp: { include: [
954
+ "list",
955
+ "get",
956
+ "create"
957
+ ] },
958
+ cli: true
959
+ })], AdVariation);
960
+ //#endregion
961
+ //#region src/collections/AdVariationCollection.ts
962
+ var AdVariationCollection = class extends SmrtCollection {
963
+ static _itemClass = AdVariation;
964
+ /**
965
+ * Find variations by ad group
966
+ *
967
+ * @param groupId - Ad group ID
968
+ * @returns Array of variations
969
+ */
970
+ async findByGroup(groupId) {
971
+ return await this.list({
972
+ where: { groupId },
973
+ orderBy: "name ASC"
974
+ });
975
+ }
976
+ /**
977
+ * Find variations by format
978
+ *
979
+ * @param formatId - Ad format ID
980
+ * @returns Array of variations
981
+ */
982
+ async findByFormat(formatId) {
983
+ return await this.list({
984
+ where: { formatId },
985
+ orderBy: "name ASC"
986
+ });
987
+ }
988
+ /**
989
+ * Find active variations for a group
990
+ *
991
+ * @param groupId - Ad group ID
992
+ * @returns Array of active variations
993
+ */
994
+ async findActiveByGroup(groupId) {
995
+ return await this.list({
996
+ where: {
997
+ groupId,
998
+ status: AdVariationStatus.ACTIVE
999
+ },
1000
+ orderBy: "name ASC"
1001
+ });
1002
+ }
1003
+ /**
1004
+ * Select a variation using weighted random selection
1005
+ * Higher weight = more likely to be selected
1006
+ *
1007
+ * @param groupId - Ad group ID
1008
+ * @returns Selected variation or null if none available
1009
+ */
1010
+ async selectByWeight(groupId) {
1011
+ const active = await this.findActiveByGroup(groupId);
1012
+ if (active.length === 0) return null;
1013
+ if (active.length === 1) return active[0];
1014
+ const totalWeight = active.reduce((sum, v) => sum + v.weight, 0);
1015
+ if (totalWeight <= 0) return active[0];
1016
+ let random = Math.random() * totalWeight;
1017
+ for (const variation of active) {
1018
+ random -= variation.weight;
1019
+ if (random <= 0) return variation;
1020
+ }
1021
+ return active[active.length - 1];
1022
+ }
1023
+ /**
1024
+ * Find variations by status
1025
+ *
1026
+ * @param status - Variation status
1027
+ * @returns Array of variations
1028
+ */
1029
+ async findByStatus(status) {
1030
+ return await this.list({
1031
+ where: { status },
1032
+ orderBy: "created_at DESC"
1033
+ });
1034
+ }
1035
+ /**
1036
+ * Find all active variations
1037
+ */
1038
+ async findActive() {
1039
+ return await this.findByStatus(AdVariationStatus.ACTIVE);
1040
+ }
1041
+ /**
1042
+ * Find all draft variations
1043
+ */
1044
+ async findDrafts() {
1045
+ return await this.findByStatus(AdVariationStatus.DRAFT);
1046
+ }
1047
+ /**
1048
+ * Find all paused variations
1049
+ */
1050
+ async findPaused() {
1051
+ return await this.findByStatus(AdVariationStatus.PAUSED);
1052
+ }
1053
+ /**
1054
+ * Find top performing variations by CTR
1055
+ *
1056
+ * @param limit - Maximum number to return
1057
+ * @returns Array of variations sorted by CTR descending
1058
+ */
1059
+ async findTopPerformers(limit = 10) {
1060
+ return (await this.list({ where: { status: AdVariationStatus.ACTIVE } })).filter((v) => v.impressions > 0).sort((a, b) => b.getCTR() - a.getCTR()).slice(0, limit);
1061
+ }
1062
+ /**
1063
+ * Find ad variations belonging to a specific tenant
1064
+ *
1065
+ * @param tenantId - Tenant ID to filter by
1066
+ * @returns Array of ad variations for the tenant
1067
+ */
1068
+ async findByTenant(tenantId) {
1069
+ return this.list({ where: { tenantId } });
1070
+ }
1071
+ /**
1072
+ * Find global ad variations (no tenant association).
1073
+ *
1074
+ * Routes through the shared tenant-global helper so it does not throw under
1075
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
1076
+ * flagged as an isolation violation). (#1600)
1077
+ *
1078
+ * @returns Array of global ad variations
1079
+ */
1080
+ async findGlobal() {
1081
+ return queryGlobal(this);
1082
+ }
1083
+ /**
1084
+ * Find ad variations for a tenant including global (shared) variations.
1085
+ *
1086
+ * Fails closed if an active tenant context requests a different tenant's
1087
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
1088
+ *
1089
+ * @param tenantId - Tenant ID to include
1090
+ * @returns Array of tenant-specific and global ad variations
1091
+ */
1092
+ async findWithGlobals(tenantId) {
1093
+ return queryWithGlobals(this, tenantId, "AdVariation.findWithGlobals");
1094
+ }
1136
1095
  };
1137
- //# sourceMappingURL=index.js.map
1096
+ //#endregion
1097
+ export { AdDeliveryTier, AdDeliveryTierCollection, AdEvent, AdEventCollection, AdEventType, AdFormat, AdFormatCollection, AdFormatType, AdGroup, AdGroupCollection, AdGroupStatus, AdVariation, AdVariationCollection, AdVariationStatus, PricingModel };
1098
+
1099
+ //# sourceMappingURL=index.js.map