@happyvertical/smrt-marketing 0.42.6 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,61 @@
1
1
  import { CAMPAIGN_CHANNEL_KINDS, CAMPAIGN_CHANNEL_STATUSES, CAMPAIGN_STATUSES } from "./types.js";
2
- import { ObjectRegistry, SmrtCollection, SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
3
- import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
2
+ import { ObjectRegistry, SmrtCollection, SmrtObject, ValidationError, crossPackageRef, field, foreignKey, resolveListLimit, smrt } from "@happyvertical/smrt-core";
3
+ import { TenantIsolationError, TenantScoped, getCurrentTenant, isSuperAdminBypass, isSystemContext, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
4
4
  //#region src/__smrt-register__.ts
5
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-marketing\",\"packageVersion\":\"0.42.6\",\"objects\":{\"@happyvertical/smrt-marketing:CampaignChannelCollection\":{\"name\":\"campaignchannelcollection\",\"className\":\"CampaignChannelCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannelCollection\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignChannelCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true},\"findByExecutionRef\":{\"name\":\"findByExecutionRef\",\"async\":true,\"parameters\":[{\"name\":\"channelKind\",\"type\":\"string\",\"optional\":false},{\"name\":\"channelRef\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignChannel\",\"exportName\":\"CampaignChannelCollection\",\"collectionExportName\":\"CampaignChannelCollectionCollection\",\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_channels_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaign_channels_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"b64ea87b\"}},\"@happyvertical/smrt-marketing:CampaignCollection\":{\"name\":\"campaigncollection\",\"className\":\"CampaignCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignCollection\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByCampaignKey\":{\"name\":\"findByCampaignKey\",\"async\":true,\"parameters\":[{\"name\":\"campaignKey\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<Campaign | null>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"Promise<Campaign[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Campaign\",\"exportName\":\"CampaignCollection\",\"collectionExportName\":\"CampaignCollectionCollection\",\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaigns_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaigns_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"b1cd973d\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\":{\"name\":\"campaignmetricsnapshotcollection\",\"className\":\"CampaignMetricSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignMetricSnapshotCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByDedupeKey\":{\"name\":\"findByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"dedupeKey\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot | null>\",\"isStatic\":false,\"isPublic\":true},\"getOrCreateByDedupeKey\":{\"name\":\"getOrCreateByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"CampaignMetricSnapshotOptions\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true},\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true},\"findByChannel\":{\"name\":\"findByChannel\",\"async\":true,\"parameters\":[{\"name\":\"campaignChannelId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignMetricSnapshot\",\"exportName\":\"CampaignMetricSnapshotCollection\",\"collectionExportName\":\"CampaignMetricSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaign_metric_snapshots_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"268285ee\"}},\"@happyvertical/smrt-marketing:Campaign\":{\"name\":\"campaign\",\"className\":\"Campaign\",\"qualifiedName\":\"@happyvertical/smrt-marketing:Campaign\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/Campaign.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"name\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"objective\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"draft\"},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"budgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"currency\":{\"type\":\"text\",\"required\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"transitionTo\":{\"name\":\"transitionTo\",\"async\":false,\"parameters\":[{\"name\":\"next\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"metadata\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\",\"conflictColumns\":[\"tenant_id\",\"campaign_key\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"Campaign\",\"collectionExportName\":\"CampaignCollection\",\"validationRules\":[{\"field\":\"campaignKey\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"name\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"objective\\\" TEXT DEFAULT '',\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT DEFAULT 'USD',\\n \\\"metadata\\\" TEXT DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"objective\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"draft\"},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"}},\"indexes\":[{\"name\":\"campaigns_tenant_id_campaign_key_idx\",\"columns\":[\"tenant_id\",\"campaign_key\"],\"unique\":true},{\"name\":\"campaigns_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaigns_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"1963ebd3\"}},\"@happyvertical/smrt-marketing:CampaignChannel\":{\"name\":\"campaignchannel\",\"className\":\"CampaignChannel\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannel\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/CampaignChannel.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"channelKind\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"channelRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"allocatedBudgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"planned\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\",\"conflictColumns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignChannel\",\"collectionExportName\":\"CampaignChannelCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"channelKind\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"channelRef\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"channel_kind\\\" TEXT NOT NULL DEFAULT '',\\n \\\"channel_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"allocated_budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'planned'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"channel_kind\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"channel_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"allocated_budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"planned\"}},\"indexes\":[{\"name\":\"campaign_channels_campaign_id_channel_kind_idx\",\"columns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"unique\":true},{\"name\":\"campaign_channels_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaign_channels_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"40163dfc\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\":{\"name\":\"campaignmetricsnapshot\",\"className\":\"CampaignMetricSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/CampaignMetricSnapshot.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"campaignChannelId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"CampaignChannel\",\"_meta\":{\"nullable\":true}},\"periodStart\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"periodEnd\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"spendCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"impressions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"clicks\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"conversions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"leads\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"revenueCents\":{\"type\":\"integer\",\"required\":false,\"_meta\":{\"nullable\":true}},\"source\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"dedupeKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\",\"conflictColumns\":[\"dedupe_key\"],\"api\":{\"include\":[\"list\",\"get\",\"create\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignMetricSnapshot\",\"collectionExportName\":\"CampaignMetricSnapshotCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"periodStart\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"periodEnd\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"source\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"dedupeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"campaign_channel_id\\\" UUID,\\n \\\"period_start\\\" TIMESTAMP NOT NULL,\\n \\\"period_end\\\" TIMESTAMP NOT NULL,\\n \\\"spend_cents\\\" INTEGER DEFAULT 0,\\n \\\"impressions\\\" INTEGER DEFAULT 0,\\n \\\"clicks\\\" INTEGER DEFAULT 0,\\n \\\"conversions\\\" INTEGER DEFAULT 0,\\n \\\"leads\\\" INTEGER DEFAULT 0,\\n \\\"revenue_cents\\\" INTEGER,\\n \\\"source\\\" TEXT NOT NULL DEFAULT '',\\n \\\"dedupe_key\\\" TEXT NOT NULL DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"campaign_channel_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"period_start\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"period_end\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"spend_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"impressions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"clicks\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"conversions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"leads\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"revenue_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"source\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"dedupe_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_dedupe_key_idx\",\"columns\":[\"dedupe_key\"],\"unique\":true},{\"name\":\"campaign_metric_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaign_metric_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"campaign_metric_snapshots_campaign_id_idx\",\"columns\":[\"campaign_id\"]},{\"name\":\"campaign_metric_snapshots_campaign_channel_id_idx\",\"columns\":[\"campaign_channel_id\"]}],\"version\":\"87e87389\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
5
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-marketing\",\"packageVersion\":\"0.43.0\",\"objects\":{\"@happyvertical/smrt-marketing:CampaignChannelCollection\":{\"name\":\"campaignchannelcollection\",\"className\":\"CampaignChannelCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannelCollection\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignChannelCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true},\"findByExecutionRef\":{\"name\":\"findByExecutionRef\",\"async\":true,\"parameters\":[{\"name\":\"channelKind\",\"type\":\"string\",\"optional\":false},{\"name\":\"channelRef\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignChannel\",\"exportName\":\"CampaignChannelCollection\",\"collectionExportName\":\"CampaignChannelCollectionCollection\",\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_channels_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaign_channels_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"b64ea87b\"}},\"@happyvertical/smrt-marketing:CampaignCollection\":{\"name\":\"campaigncollection\",\"className\":\"CampaignCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignCollection\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"get\":{\"name\":\"get\",\"async\":true,\"parameters\":[{\"name\":\"filter\",\"type\":\"string | SmrtWhereClause<Campaign>\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<Campaign | null>\",\"isStatic\":false,\"isPublic\":true},\"list\":{\"name\":\"list\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"SmrtListOptions<Campaign>\",\"optional\":true}],\"returnType\":\"Promise<Campaign[] | Record<string>[]>\",\"isStatic\":false,\"isPublic\":true},\"findByCampaignKey\":{\"name\":\"findByCampaignKey\",\"async\":true,\"parameters\":[{\"name\":\"campaignKey\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<Campaign | null>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"Promise<Campaign[]>\",\"isStatic\":false,\"isPublic\":true},\"listByCustomer\":{\"name\":\"listByCustomer\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"customerId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"ListCampaignsByCustomerOptions\",\"optional\":true}],\"returnType\":\"Promise<CampaignCustomerPage>\",\"isStatic\":false,\"isPublic\":true},\"summarizeByCustomers\":{\"name\":\"summarizeByCustomers\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":false},{\"name\":\"customerIds\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"Promise<CampaignCustomerSummary[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Campaign\",\"exportName\":\"CampaignCollection\",\"collectionExportName\":\"CampaignCollectionCollection\",\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaigns_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaigns_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"b1cd973d\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\":{\"name\":\"campaignmetricsnapshotcollection\",\"className\":\"CampaignMetricSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/collections/CampaignMetricSnapshotCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByDedupeKey\":{\"name\":\"findByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"dedupeKey\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot | null>\",\"isStatic\":false,\"isPublic\":true},\"getOrCreateByDedupeKey\":{\"name\":\"getOrCreateByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"CampaignMetricSnapshotOptions\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true},\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true},\"findByChannel\":{\"name\":\"findByChannel\",\"async\":true,\"parameters\":[{\"name\":\"campaignChannelId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignMetricSnapshot\",\"exportName\":\"CampaignMetricSnapshotCollection\",\"collectionExportName\":\"CampaignMetricSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"campaign_metric_snapshots_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"268285ee\"}},\"@happyvertical/smrt-marketing:Campaign\":{\"name\":\"campaign\",\"className\":\"Campaign\",\"qualifiedName\":\"@happyvertical/smrt-marketing:Campaign\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/Campaign.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"customerId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-commerce:Customer\",\"_meta\":{\"validate\":true,\"nullable\":true}},\"campaignKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"name\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"objective\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"draft\"},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"budgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"currency\":{\"type\":\"text\",\"required\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"transitionTo\":{\"name\":\"transitionTo\",\"async\":false,\"parameters\":[{\"name\":\"next\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"metadata\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\",\"conflictColumns\":[\"tenant_id\",\"campaign_key\"],\"indexes\":[{\"name\":\"campaigns_tenant_id_customer_id_start_at_id_idx\",\"columns\":[\"tenantId\",\"customerId\",\"startAt\",\"id\"]}],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"Campaign\",\"collectionExportName\":\"CampaignCollection\",\"validationRules\":[{\"field\":\"campaignKey\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"name\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"customer_id\\\" UUID,\\n \\\"campaign_key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"objective\\\" TEXT DEFAULT '',\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT DEFAULT 'USD',\\n \\\"metadata\\\" TEXT DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"customer_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"campaign_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"objective\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"draft\"},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"}},\"indexes\":[{\"name\":\"campaigns_tenant_id_campaign_key_idx\",\"columns\":[\"tenant_id\",\"campaign_key\"],\"unique\":true},{\"name\":\"campaigns_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaigns_tenant_id_customer_id_start_at_id_idx\",\"columns\":[\"tenant_id\",\"customer_id\",\"start_at\",\"id\"]},{\"name\":\"campaigns_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"campaigns_customer_id_idx\",\"columns\":[\"customer_id\"]}],\"version\":\"d0945102\"}},\"@happyvertical/smrt-marketing:CampaignChannel\":{\"name\":\"campaignchannel\",\"className\":\"CampaignChannel\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannel\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/CampaignChannel.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"channelKind\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"channelRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"allocatedBudgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"planned\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\",\"conflictColumns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignChannel\",\"collectionExportName\":\"CampaignChannelCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"channelKind\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"channelRef\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"channel_kind\\\" TEXT NOT NULL DEFAULT '',\\n \\\"channel_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"allocated_budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'planned'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"campaigns\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"channel_kind\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"channel_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"allocated_budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"planned\"}},\"indexes\":[{\"name\":\"campaign_channels_campaign_id_channel_kind_idx\",\"columns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"unique\":true},{\"name\":\"campaign_channels_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaign_channels_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cc045525\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\":{\"name\":\"campaignmetricsnapshot\",\"className\":\"CampaignMetricSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/marketing/src/models/CampaignMetricSnapshot.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"campaignChannelId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"CampaignChannel\",\"_meta\":{\"nullable\":true}},\"periodStart\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"periodEnd\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"spendCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"impressions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"clicks\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"conversions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"leads\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"revenueCents\":{\"type\":\"integer\",\"required\":false,\"_meta\":{\"nullable\":true}},\"source\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"dedupeKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\",\"conflictColumns\":[\"dedupe_key\"],\"api\":{\"include\":[\"list\",\"get\",\"create\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignMetricSnapshot\",\"collectionExportName\":\"CampaignMetricSnapshotCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"periodStart\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"periodEnd\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"source\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"dedupeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"campaign_channel_id\\\" UUID,\\n \\\"period_start\\\" TIMESTAMP NOT NULL,\\n \\\"period_end\\\" TIMESTAMP NOT NULL,\\n \\\"spend_cents\\\" INTEGER DEFAULT 0,\\n \\\"impressions\\\" INTEGER DEFAULT 0,\\n \\\"clicks\\\" INTEGER DEFAULT 0,\\n \\\"conversions\\\" INTEGER DEFAULT 0,\\n \\\"leads\\\" INTEGER DEFAULT 0,\\n \\\"revenue_cents\\\" INTEGER,\\n \\\"source\\\" TEXT NOT NULL DEFAULT '',\\n \\\"dedupe_key\\\" TEXT NOT NULL DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"campaigns\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"campaign_channel_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false,\"foreignKey\":{\"table\":\"campaign_channels\",\"column\":\"id\",\"onDelete\":\"NO ACTION\",\"onUpdate\":\"CASCADE\"}},\"period_start\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"period_end\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"spend_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"impressions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"clicks\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"conversions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"leads\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"revenue_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"source\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"dedupe_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_dedupe_key_idx\",\"columns\":[\"dedupe_key\"],\"unique\":true},{\"name\":\"campaign_metric_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"campaign_metric_snapshots_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"campaign_metric_snapshots_campaign_id_idx\",\"columns\":[\"campaign_id\"]},{\"name\":\"campaign_metric_snapshots_campaign_channel_id_idx\",\"columns\":[\"campaign_channel_id\"]}],\"version\":\"1ccd36b3\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-commerce\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
6
+ //#endregion
7
+ //#region src/errors.ts
8
+ var CampaignCustomerScopeError = class extends Error {
9
+ code = "CAMPAIGN_CUSTOMER_SCOPE";
10
+ constructor(label) {
11
+ super(`${label}: customer scope validation failed.`);
12
+ this.name = "CampaignCustomerScopeError";
13
+ }
14
+ };
15
+ //#endregion
16
+ //#region src/customer-scope.ts
17
+ var CUSTOMER_QUALIFIED_NAME = "@happyvertical/smrt-commerce:Customer";
18
+ var SAFE_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
19
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
20
+ function requireDatabase$1(options) {
21
+ const db = options.db;
22
+ if (!db || typeof db !== "object" || !("query" in db)) throw new Error("Campaign customer queries require an initialized database connection.");
23
+ return db;
24
+ }
25
+ async function resolveCustomerTable(options) {
26
+ await ObjectRegistry.ensureManifestLoaded(CUSTOMER_QUALIFIED_NAME);
27
+ const target = ObjectRegistry.getClassByQualifiedName(CUSTOMER_QUALIFIED_NAME) ?? ObjectRegistry.getClass(CUSTOMER_QUALIFIED_NAME);
28
+ if (!target) throw new Error("Campaign customer queries require @happyvertical/smrt-commerce:Customer to be registered.");
29
+ const tableName = new target.constructor(options).tableName;
30
+ if (!SAFE_SQL_IDENTIFIER.test(tableName)) throw new Error("Campaign customer table has an invalid identifier.");
31
+ return tableName;
32
+ }
33
+ async function assertCustomersBelongToTenant(options, tenantId, customerIds, label, lock = "none") {
34
+ if (customerIds.length === 0) return;
35
+ const normalizedCustomerIds = customerIds.map((customerId) => {
36
+ try {
37
+ return normalizeUuid(customerId, "customerId");
38
+ } catch {
39
+ throw new CampaignCustomerScopeError(label);
40
+ }
41
+ });
42
+ const normalizedTenantId = tenantId?.toLowerCase() ?? null;
43
+ const db = requireDatabase$1(options);
44
+ const tableName = await resolveCustomerTable(options);
45
+ const placeholders = normalizedCustomerIds.map(() => "?").join(", ");
46
+ const lockClause = postgresLockClause(db, lock);
47
+ const result = await db.query(`SELECT id, tenant_id FROM ${tableName} WHERE id IN (${placeholders})${lockClause}`, ...normalizedCustomerIds);
48
+ const tenantById = new Map(result.rows.map((row) => [String(row.id).toLowerCase(), row.tenant_id == null ? null : String(row.tenant_id).toLowerCase()]));
49
+ if (!normalizedCustomerIds.every((customerId) => tenantById.has(customerId) && tenantById.get(customerId) === normalizedTenantId)) throw new CampaignCustomerScopeError(label);
50
+ }
51
+ function postgresLockClause(db, lock) {
52
+ if (lock === "none" || !/^postgres(?:ql)?:/iu.test(db.url)) return "";
53
+ return lock === "update" ? " FOR UPDATE" : " FOR SHARE";
54
+ }
55
+ function normalizeUuid(value, label) {
56
+ if (!UUID_RE.test(value)) throw new Error(`${label} must be a canonical UUID.`);
57
+ return value.toLowerCase();
58
+ }
6
59
  //#endregion
7
60
  //#region src/models/Campaign.ts
8
61
  var __defProp$2 = Object.defineProperty;
@@ -24,6 +77,7 @@ var CAMPAIGN_STATUS_TRANSITIONS = {
24
77
  var loadedCampaignStatus = /* @__PURE__ */ new WeakMap();
25
78
  var Campaign = class extends SmrtObject {
26
79
  tenantId = null;
80
+ customerId = null;
27
81
  campaignKey = "";
28
82
  name = "";
29
83
  /** Open objective key such as `awareness` or `demand_generation`. */
@@ -39,6 +93,7 @@ var Campaign = class extends SmrtObject {
39
93
  constructor(options = {}) {
40
94
  super(options);
41
95
  if (options.tenantId !== void 0) this.tenantId = options.tenantId;
96
+ if (options.customerId !== void 0) this.customerId = options.customerId;
42
97
  if (options.campaignKey !== void 0) this.campaignKey = options.campaignKey;
43
98
  if (options.name !== void 0) this.name = options.name;
44
99
  if (options.objective !== void 0) this.objective = options.objective;
@@ -50,20 +105,71 @@ var Campaign = class extends SmrtObject {
50
105
  if (options.metadata !== void 0) this.metadata = options.metadata;
51
106
  }
52
107
  async initialize() {
108
+ if (this.id) try {
109
+ this.id = normalizeUuid(this.id, "id");
110
+ } catch (error) {
111
+ if (this.customerId !== null) throw new CampaignCustomerScopeError("Campaign.initialize");
112
+ throw error;
113
+ }
53
114
  await super.initialize();
54
115
  this.startAt = Campaign.coerceDate(this.startAt);
55
116
  this.endAt = Campaign.coerceDate(this.endAt);
56
- if (await this.isSaved()) loadedCampaignStatus.set(this, this.status);
117
+ if (this.isPersisted) loadedCampaignStatus.set(this, this.status);
57
118
  return this;
58
119
  }
59
120
  async save() {
121
+ if (this.customerId === null) return this.saveLifecycle();
122
+ const db = this.db;
123
+ if (typeof db.transaction !== "function") throw new Error("Campaign.save requires a database adapter with transaction support.");
124
+ return db.transaction(async (transactionDb) => this.withDatabase(transactionDb, async (bound) => bound.saveLifecycle()));
125
+ }
126
+ async saveLifecycle() {
127
+ this.prepareIdentityForSave();
128
+ if (this.customerId !== null) await assertCustomersBelongToTenant(this.options, this.tenantId, [this.customerId], "Campaign.save", "update");
60
129
  const prior = await this.resolvePriorStatus();
61
130
  this.assertStatusTransition(prior);
62
131
  this.assertSchedule();
63
- const result = await super.save();
132
+ let result;
133
+ try {
134
+ const persist = async () => await super.save();
135
+ const tenantContext = isSystemContext() || isSuperAdminBypass() ? void 0 : getCurrentTenant();
136
+ result = tenantContext ? await withTenant({
137
+ ...tenantContext,
138
+ tenantId: this.tenantId ?? tenantContext.tenantId
139
+ }, persist) : await persist();
140
+ } catch (error) {
141
+ if (error instanceof ValidationError && (error.code === "VALIDATION_CROSS_PACKAGE_REF_MISSING" || error.code === "VALIDATION_CROSS_PACKAGE_REF_UNREGISTERED")) throw new CampaignCustomerScopeError("Campaign.save");
142
+ throw error;
143
+ }
64
144
  loadedCampaignStatus.set(this, this.status);
65
145
  return result;
66
146
  }
147
+ /** Canonicalize and authorize identity before any status or scope read. */
148
+ prepareIdentityForSave() {
149
+ try {
150
+ if (this.id) this.id = normalizeUuid(this.id, "id");
151
+ if (this.customerId !== null) this.customerId = normalizeUuid(this.customerId, "customerId");
152
+ if (this.tenantId !== null) this.tenantId = normalizeUuid(this.tenantId, "tenantId");
153
+ } catch (error) {
154
+ if (this.customerId !== null) throw new CampaignCustomerScopeError("Campaign.save");
155
+ throw error;
156
+ }
157
+ if (isSystemContext() || isSuperAdminBypass()) return;
158
+ const tenantContext = getCurrentTenant();
159
+ if (!tenantContext) return;
160
+ let contextTenantId;
161
+ try {
162
+ contextTenantId = normalizeUuid(tenantContext.tenantId, "tenantId");
163
+ } catch (error) {
164
+ if (this.customerId !== null) throw new CampaignCustomerScopeError("Campaign.save");
165
+ throw error;
166
+ }
167
+ if (this.tenantId !== null && this.tenantId !== contextTenantId) throw new TenantIsolationError("Tenant isolation violation in Campaign.save.", {
168
+ tenantId: contextTenantId,
169
+ attemptedTenantId: this.tenantId
170
+ });
171
+ this.tenantId = contextTenantId;
172
+ }
67
173
  /** Apply one legal transition in memory; callers choose the save boundary. */
68
174
  transitionTo(next) {
69
175
  if (next === this.status) return this;
@@ -88,10 +194,21 @@ var Campaign = class extends SmrtObject {
88
194
  if (!Number.isInteger(this.budgetCents) || this.budgetCents < 0) throw new Error("Campaign budgetCents must be a non-negative integer.");
89
195
  }
90
196
  async resolvePriorStatus() {
91
- if (this.id) try {
92
- const row = await this.db.get(this.tableName, { id: this.id });
93
- if (row && row.status != null) return row.status;
94
- } catch {}
197
+ if (this.id) {
198
+ let scopedRow = null;
199
+ try {
200
+ scopedRow = await this.db.get(this.tableName, {
201
+ id: this.id,
202
+ tenant_id: this.tenantId
203
+ });
204
+ } catch {}
205
+ if (scopedRow?.status != null) return scopedRow.status;
206
+ let rowInAnotherLane = null;
207
+ try {
208
+ rowInAnotherLane = await this.db.get(this.tableName, { id: this.id });
209
+ } catch {}
210
+ if (rowInAnotherLane) throw new CampaignCustomerScopeError("Campaign.save");
211
+ }
95
212
  if (this.campaignKey) try {
96
213
  const row = await this.db.get(this.tableName, {
97
214
  tenant_id: this.tenantId,
@@ -117,11 +234,24 @@ var Campaign = class extends SmrtObject {
117
234
  }
118
235
  };
119
236
  __decorateClass$2([tenantId({ nullable: true })], Campaign.prototype, "tenantId", 2);
237
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-commerce:Customer", {
238
+ nullable: true,
239
+ validate: true
240
+ })], Campaign.prototype, "customerId", 2);
120
241
  __decorateClass$2([field({ required: true })], Campaign.prototype, "campaignKey", 2);
121
242
  __decorateClass$2([field({ required: true })], Campaign.prototype, "name", 2);
122
243
  Campaign = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
123
244
  tableName: "campaigns",
124
245
  conflictColumns: ["tenant_id", "campaign_key"],
246
+ indexes: [{
247
+ name: "campaigns_tenant_id_customer_id_start_at_id_idx",
248
+ columns: [
249
+ "tenantId",
250
+ "customerId",
251
+ "startAt",
252
+ "id"
253
+ ]
254
+ }],
125
255
  api: { include: [
126
256
  "list",
127
257
  "get",
@@ -246,8 +376,16 @@ var CampaignChannelCollection = class extends SmrtCollection {
246
376
  };
247
377
  //#endregion
248
378
  //#region src/collections/CampaignCollection.ts
249
- var CampaignCollection = class extends SmrtCollection {
379
+ var MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE = 100;
380
+ var MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE = 100;
381
+ var CampaignCollection = class CampaignCollection extends SmrtCollection {
250
382
  static _itemClass = Campaign;
383
+ async get(filter, options = {}) {
384
+ return withCanonicalActiveTenant(() => super.get(normalizeCampaignGetFilter(filter), options));
385
+ }
386
+ async list(options = {}) {
387
+ return withCanonicalActiveTenant(() => super.list(normalizeCampaignListOptions(options)));
388
+ }
251
389
  async findByCampaignKey(campaignKey, tenantId) {
252
390
  if (!campaignKey) return null;
253
391
  const where = { campaignKey };
@@ -263,7 +401,220 @@ var CampaignCollection = class extends SmrtCollection {
263
401
  orderBy: "start_at ASC"
264
402
  });
265
403
  }
404
+ /**
405
+ * List one tenant/customer lane newest-first using a stable UUID tiebreaker.
406
+ * Null start times follow all scheduled rows on every supported database.
407
+ */
408
+ async listByCustomer(tenantId, customerId, options = {}) {
409
+ const normalizedTenantId = normalizeTenantScope(tenantId, "CampaignCollection.listByCustomer");
410
+ const normalizedCustomerId = normalizeUuid(customerId, "customerId");
411
+ const limit = resolveListLimit(options.limit, {
412
+ defaultValue: 50,
413
+ maxValue: Number.MAX_SAFE_INTEGER,
414
+ parameterName: "campaign customer page limit"
415
+ });
416
+ if (limit > 100) throw new Error(`Campaign customer page limit must not exceed 100.`);
417
+ if (limit < 1) throw new Error("Campaign customer page limit must be at least 1.");
418
+ const cursor = normalizeCursor(options.after);
419
+ return withCanonicalTenantContext(normalizedTenantId, () => this.inCustomerReadTransaction(async (bound) => bound.listByCustomerInTransaction(normalizedTenantId, normalizedCustomerId, limit, cursor)));
420
+ }
421
+ async listByCustomerInTransaction(tenantId, customerId, limit, cursor) {
422
+ await assertCustomersBelongToTenant(this.options, tenantId, [customerId], "CampaignCollection.listByCustomer", "share");
423
+ const items = cursor?.startAt === null ? await this.listNullStartLane(tenantId, customerId, limit + 1, cursor) : await this.listScheduledLane(tenantId, customerId, limit, cursor);
424
+ const hasMore = items.length > limit;
425
+ const pageItems = hasMore ? items.slice(0, limit) : items;
426
+ return {
427
+ items: pageItems,
428
+ nextCursor: hasMore ? cursorFromCampaign(pageItems[pageItems.length - 1]) : null
429
+ };
430
+ }
431
+ /**
432
+ * Summarize a bounded customer batch with one scope check and one grouped
433
+ * aggregate query. Every requested customer receives a row, including zeros.
434
+ */
435
+ async summarizeByCustomers(tenantId, customerIds) {
436
+ const normalizedTenantId = normalizeTenantScope(tenantId, "CampaignCollection.summarizeByCustomers");
437
+ if (!Array.isArray(customerIds)) throw new Error("Campaign customer summary customerIds must be an array.");
438
+ if (customerIds.length > 100) throw new Error(`Campaign customer summary accepts at most 100 customerIds.`);
439
+ const normalizedCustomerIds = customerIds.map((customerId) => normalizeUuid(customerId, "customerId"));
440
+ const uniqueIds = [...new Set(normalizedCustomerIds)];
441
+ if (uniqueIds.length === 0) return [];
442
+ return withCanonicalTenantContext(normalizedTenantId, () => this.inCustomerReadTransaction(async (bound) => bound.summarizeByCustomersInTransaction(normalizedTenantId, normalizedCustomerIds, uniqueIds)));
443
+ }
444
+ async summarizeByCustomersInTransaction(tenantId, requestedCustomerIds, uniqueIds) {
445
+ await assertCustomersBelongToTenant(this.options, tenantId, uniqueIds, "CampaignCollection.summarizeByCustomers", "share");
446
+ const db = requireDatabase(this.options);
447
+ const placeholders = uniqueIds.map(() => "?").join(", ");
448
+ const tenantPredicate = tenantId === null ? "tenant_id IS NULL" : "tenant_id = ?";
449
+ const tenantParams = tenantId === null ? [] : [tenantId];
450
+ const result = await db.query(`SELECT customer_id,
451
+ COUNT(*) AS total_count,
452
+ SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) AS active_count,
453
+ MAX(start_at) AS latest_start_at
454
+ FROM ${this.tableName}
455
+ WHERE ${tenantPredicate} AND customer_id IN (${placeholders})
456
+ GROUP BY customer_id
457
+ ORDER BY customer_id ASC
458
+ LIMIT ?`, "active", ...tenantParams, ...uniqueIds, uniqueIds.length);
459
+ const byCustomer = new Map(result.rows.map((row) => [String(row.customer_id).toLowerCase(), row]));
460
+ return requestedCustomerIds.map((customerId) => {
461
+ const row = byCustomer.get(customerId);
462
+ return {
463
+ customerId,
464
+ totalCount: row ? toSafeCount(row.total_count, "Campaign customer total count") : 0,
465
+ activeCount: row ? toSafeCount(row.active_count, "Campaign customer active count") : 0,
466
+ latestStartAt: row ? coerceDate(row.latest_start_at) : null
467
+ };
468
+ });
469
+ }
470
+ async inCustomerReadTransaction(operation) {
471
+ const db = this.db;
472
+ if (typeof db.transaction !== "function") throw new Error("Campaign customer reads require a database adapter with transaction support.");
473
+ return db.transaction(async (transactionDb) => {
474
+ return operation(await CampaignCollection.create({
475
+ ...this.options,
476
+ db: transactionDb,
477
+ defaultListLimit: void 0,
478
+ maxListLimit: void 0
479
+ }));
480
+ });
481
+ }
482
+ async listScheduledLane(tenantId, customerId, limit, cursor) {
483
+ const base = [
484
+ { tenantId },
485
+ { customerId },
486
+ { "startAt !=": null }
487
+ ];
488
+ const where = cursor ? [[...base, { "startAt <": cursor.startAt }], [
489
+ ...base,
490
+ { startAt: cursor.startAt },
491
+ { "id <": cursor.id }
492
+ ]] : {
493
+ tenantId,
494
+ customerId,
495
+ "startAt !=": null
496
+ };
497
+ const scheduled = await this.list({
498
+ where,
499
+ orderBy: ["start_at DESC", "id DESC"],
500
+ limit: limit + 1
501
+ });
502
+ if (scheduled.length > limit) return scheduled;
503
+ const nullRows = await this.listNullStartLane(tenantId, customerId, limit - scheduled.length + 1, null);
504
+ return [...scheduled, ...nullRows];
505
+ }
506
+ async listNullStartLane(tenantId, customerId, limit, cursor) {
507
+ return this.list({
508
+ where: {
509
+ tenantId,
510
+ customerId,
511
+ startAt: null,
512
+ ...cursor ? { "id <": cursor.id } : {}
513
+ },
514
+ orderBy: "id DESC",
515
+ limit
516
+ });
517
+ }
266
518
  };
519
+ function requireDatabase(options) {
520
+ const db = options.db;
521
+ if (!db || typeof db !== "object" || !("query" in db)) throw new Error("Campaign customer summaries require an initialized database connection.");
522
+ return db;
523
+ }
524
+ function coerceDate(value) {
525
+ if (value == null) return null;
526
+ const date = value instanceof Date ? value : new Date(typeof value === "number" ? value : String(value));
527
+ return Number.isNaN(date.getTime()) ? null : date;
528
+ }
529
+ function toSafeCount(value, label) {
530
+ const count = Number(value);
531
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error(`${label} is outside the safe integer range.`);
532
+ return count;
533
+ }
534
+ function normalizeCursor(cursor) {
535
+ if (!cursor) return null;
536
+ const id = normalizeUuid(cursor.id, "campaign customer cursor id");
537
+ if (cursor.startAt === null) return {
538
+ id,
539
+ startAt: null
540
+ };
541
+ const startAt = coerceDate(cursor.startAt);
542
+ if (!startAt) throw new Error("campaign customer cursor startAt is invalid.");
543
+ return {
544
+ id,
545
+ startAt
546
+ };
547
+ }
548
+ function cursorFromCampaign(campaign) {
549
+ if (!campaign?.id) return null;
550
+ return {
551
+ id: campaign.id,
552
+ startAt: campaign.startAt
553
+ };
554
+ }
555
+ function normalizeTenantScope(tenantId, label) {
556
+ if (tenantId !== null) {
557
+ const normalized = normalizeUuid(tenantId, "tenantId");
558
+ const tenantContext2 = getCurrentTenant();
559
+ if (tenantContext2 && !isSuperAdminBypass()) {
560
+ const contextTenantId = normalizeUuid(tenantContext2.tenantId, "tenantId");
561
+ if (normalized !== contextTenantId) throw new TenantIsolationError(`Tenant isolation violation in ${label}.`, {
562
+ tenantId: contextTenantId,
563
+ attemptedTenantId: normalized
564
+ });
565
+ }
566
+ return normalized;
567
+ }
568
+ const tenantContext = getCurrentTenant();
569
+ if (tenantContext && !isSuperAdminBypass() && !isSystemContext()) throw new TenantIsolationError(`Tenant isolation violation in ${label}: tenant context cannot read global Campaign rows.`, {
570
+ tenantId: tenantContext.tenantId,
571
+ attemptedTenantId: "global"
572
+ });
573
+ return null;
574
+ }
575
+ function withCanonicalTenantContext(tenantId, operation) {
576
+ const tenantContext = getCurrentTenant();
577
+ return tenantId !== null && tenantContext && !isSuperAdminBypass() && !isSystemContext() ? withTenant({
578
+ ...tenantContext,
579
+ tenantId
580
+ }, operation) : operation();
581
+ }
582
+ function withCanonicalActiveTenant(operation) {
583
+ const tenantContext = getCurrentTenant();
584
+ if (!tenantContext || isSuperAdminBypass() || isSystemContext()) return operation();
585
+ const normalizedTenantId = normalizeUuid(tenantContext.tenantId, "tenantId");
586
+ return normalizedTenantId === tenantContext.tenantId ? operation() : withTenant({
587
+ ...tenantContext,
588
+ tenantId: normalizedTenantId
589
+ }, operation);
590
+ }
591
+ function normalizeCampaignGetFilter(filter) {
592
+ if (typeof filter !== "string") return normalizeCampaignUuidWhere(filter);
593
+ try {
594
+ return normalizeUuid(filter, "campaign id");
595
+ } catch {
596
+ return filter;
597
+ }
598
+ }
599
+ function normalizeCampaignListOptions(options) {
600
+ return options.where === void 0 ? options : {
601
+ ...options,
602
+ where: normalizeCampaignUuidWhere(options.where)
603
+ };
604
+ }
605
+ function normalizeCampaignUuidWhere(where) {
606
+ if (Array.isArray(where)) return where.map((entry) => normalizeCampaignUuidWhere(entry));
607
+ if (!where || typeof where !== "object") return where;
608
+ const normalized = {};
609
+ for (const [key, value] of Object.entries(where)) {
610
+ const field = key.trim().split(/\s+/u)[0];
611
+ const label = field === "id" ? "campaign id" : field === "customerId" ? "customerId" : field === "tenantId" ? "tenantId" : null;
612
+ if (!label) normalized[key] = value;
613
+ else if (Array.isArray(value)) normalized[key] = value.map((uuid) => typeof uuid === "string" ? normalizeUuid(uuid, label) : uuid);
614
+ else normalized[key] = typeof value === "string" ? normalizeUuid(value, label) : value;
615
+ }
616
+ return normalized;
617
+ }
267
618
  //#endregion
268
619
  //#region src/models/CampaignMetricSnapshot.ts
269
620
  var __defProp = Object.defineProperty;
@@ -620,6 +971,6 @@ var MetricIngestionService = class MetricIngestionService {
620
971
  }
621
972
  };
622
973
  //#endregion
623
- export { BudgetPacingService, CAMPAIGN_CHANNEL_KINDS, CAMPAIGN_CHANNEL_STATUSES, CAMPAIGN_STATUSES, Campaign, CampaignChannel, CampaignChannelCollection, CampaignCollection, CampaignLifecycleService, CampaignMetricSnapshot, CampaignMetricSnapshotCollection, MetricIngestionService };
974
+ export { BudgetPacingService, CAMPAIGN_CHANNEL_KINDS, CAMPAIGN_CHANNEL_STATUSES, CAMPAIGN_STATUSES, Campaign, CampaignChannel, CampaignChannelCollection, CampaignCollection, CampaignCustomerScopeError, CampaignLifecycleService, CampaignMetricSnapshot, CampaignMetricSnapshotCollection, MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE, MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE, MetricIngestionService };
624
975
 
625
976
  //# sourceMappingURL=index.js.map