@happyvertical/smrt-marketing 0.57.43 → 0.57.44
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 +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +3 -3
- package/package.json +6 -6
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["elapsedFraction","tenantContext"],"sources":["../src/__smrt-register__.ts","../src/errors.ts","../src/customer-scope.ts","../src/models/Campaign.ts","../src/models/CampaignChannel.ts","../src/collections/CampaignChannelCollection.ts","../src/services/pacing-calculation.ts","../src/models/CampaignMetricSnapshot.ts","../src/collections/CampaignMetricSnapshotCollection.ts","../src/collections/CampaignCollection.ts","../src/recipes.ts","../src/services/BudgetPacingService.ts","../src/services/CampaignLifecycleService.ts","../src/services/MetricIngestionService.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-marketing\\\",\\\"packageVersion\\\":\\\"0.57.43\\\",\\\"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\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\",\\\"delete\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\",\\\"delete\\\"]}},\\\"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 \\\\\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"SmrtWhereClause<Campaign>\\\"}]},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"object\\\",\\\"optional\\\":true,\\\"memberTypes\\\":[\\\"CollectionCacheConfig | false\\\"]}],\\\"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, unknown>[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByCampaignKey\\\":{\\\"name\\\":\\\"findByCampaignKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"campaignKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":true,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]}],\\\"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,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"ListCampaignsByCustomerOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<CampaignCustomerPage>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"listReportingByCustomer\\\":{\\\"name\\\":\\\"listReportingByCustomer\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"ListCampaignReportingByCustomerOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<CampaignReportingPage>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"summarizeByCustomers\\\":{\\\"name\\\":\\\"summarizeByCustomers\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerIds\\\",\\\"type\\\":\\\"string[]\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<CampaignCustomerSummary[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"campaigns\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]}},\\\"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 \\\\\\\"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,\\\"nullsNotDistinct\\\":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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]}},\\\"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 \\\\\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The customer this campaign is run for, if any.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The customer this campaign is run for, if any.\\\",\\\"validate\\\":true,\\\"nullable\\\":true}},\\\"campaignKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"A short reference of your own for this campaign, kept the same once it is in use.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"A short reference of your own for this campaign, kept the same once it is in use.\\\"}},\\\"name\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What the campaign is called.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"What the campaign is called.\\\"}},\\\"objective\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What the campaign is meant to achieve, such as awareness or new leads.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"What the campaign is meant to achieve, such as awareness or new leads.\\\"}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"draft\\\",\\\"description\\\":\\\"Where the campaign stands, from draft through active to completed and archived.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"Where the campaign stands, from draft through active to completed and archived.\\\"}},\\\"startAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When the campaign starts.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When the campaign starts.\\\"}},\\\"endAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When the campaign ends.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When the campaign ends.\\\"}},\\\"budgetCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"The total you plan to spend on the campaign, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The total you plan to spend on the campaign, in cents.\\\"}},\\\"currency\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"USD\\\",\\\"description\\\":\\\"The currency every amount on this campaign is in.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The currency every amount on this campaign is in.\\\"}},\\\"metadata\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"{}\\\"}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"transitionTo\\\":{\\\"name\\\":\\\"transitionTo\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"next\\\",\\\"type\\\":\\\"CampaignStatus\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"this\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getMetadata\\\":{\\\"name\\\":\\\"getMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Record<string, unknown>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setMetadata\\\":{\\\"name\\\":\\\"setMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"metadata\\\",\\\"type\\\":\\\"Record<string, unknown>\\\",\\\"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\\\",\\\"displayLabelField\\\":\\\"name\\\",\\\"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,\\\"nullsNotDistinct\\\":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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The campaign this channel runs for.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The campaign this channel runs for.\\\",\\\"required\\\":true}},\\\"channelKind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What sort of channel this is, such as an ad group, a social post, a message, content or an event.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"What sort of channel this is, such as an ad group, a social post, a message, content or an event.\\\"}},\\\"channelRef\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"The name or reference the channel goes by, so its results can be matched to it.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The name or reference the channel goes by, so its results can be matched to it.\\\"}},\\\"allocatedBudgetCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"The share of the campaign budget set aside for this channel, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The share of the campaign budget set aside for this channel, in cents.\\\"}},\\\"startAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When this channel starts, if different from the campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When this channel starts, if different from the campaign.\\\"}},\\\"endAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When this channel ends, if different from the campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When this channel ends, if different from the campaign.\\\"}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"planned\\\",\\\"description\\\":\\\"Where this channel stands, from planned through active to completed or cancelled.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"Where this channel stands, from planned through active to completed or cancelled.\\\"}}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The campaign these results belong to.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The campaign these results belong to.\\\",\\\"required\\\":true}},\\\"campaignChannelId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"CampaignChannel\\\",\\\"description\\\":\\\"The channel these results came from. Empty when they cover the whole campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The channel these results came from. Empty when they cover the whole campaign.\\\",\\\"nullable\\\":true}},\\\"periodStart\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":true,\\\"description\\\":\\\"The first day of the period these results cover.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The first day of the period these results cover.\\\"}},\\\"periodEnd\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":true,\\\"description\\\":\\\"The last day of the period these results cover.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The last day of the period these results cover.\\\"}},\\\"spendCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"What was spent in the period, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"What was spent in the period, in cents.\\\"}},\\\"impressions\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many times the campaign was shown.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many times the campaign was shown.\\\"}},\\\"clicks\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many times someone clicked through.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many times someone clicked through.\\\"}},\\\"conversions\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many people did what the campaign asked, such as buying.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many people did what the campaign asked, such as buying.\\\"}},\\\"leads\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many new leads came in.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many new leads came in.\\\"}},\\\"revenueCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"description\\\":\\\"The sales brought in during the period, in cents, if known.\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"description\\\":\\\"The sales brought in during the period, in cents, if known.\\\"}},\\\"source\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"Where these results were reported from.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"Where these results were reported from.\\\"}},\\\"dedupeKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"The reference this result is filed under, so the same report is never counted twice.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The reference this result is filed under, so the same report is never counted twice.\\\"}}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"recipes\\\":[{\\\"id\\\":\\\"marketing.campaigns\\\",\\\"className\\\":\\\"MarketingCampaignsRecipe\\\",\\\"label\\\":\\\"Campaigns\\\",\\\"summary\\\":\\\"Plan a marketing campaign, set its budget and dates, choose where it runs and see what it brought in.\\\",\\\"synonyms\\\":[\\\"marketing\\\",\\\"marketing campaigns\\\",\\\"promotions\\\",\\\"advertising\\\",\\\"ad campaigns\\\",\\\"campaign results\\\"],\\\"models\\\":[\\\"@happyvertical/smrt-marketing:Campaign\\\",\\\"@happyvertical/smrt-marketing:CampaignChannel\\\",\\\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\\\"],\\\"nav\\\":[{\\\"label\\\":\\\"Campaigns\\\",\\\"model\\\":\\\"@happyvertical/smrt-marketing:Campaign\\\",\\\"icon\\\":\\\"tag\\\",\\\"description\\\":\\\"Every campaign, with its budget, dates and where it stands.\\\",\\\"noun\\\":\\\"campaign\\\"},{\\\"label\\\":\\\"Channels\\\",\\\"model\\\":\\\"@happyvertical/smrt-marketing:CampaignChannel\\\",\\\"icon\\\":\\\"layers\\\",\\\"description\\\":\\\"Where each campaign runs, such as an ad group or a social post, and its share of the budget.\\\",\\\"noun\\\":\\\"channel\\\"}],\\\"requires\\\":[],\\\"section\\\":{\\\"id\\\":\\\"grow\\\",\\\"label\\\":\\\"Grow\\\",\\\"icon\\\":\\\"tag\\\",\\\"description\\\":\\\"Bring in new customers and reward the people who help.\\\"},\\\"options\\\":{\\\"@happyvertical/smrt-marketing:Campaign\\\":{\\\"fields\\\":{\\\"name\\\":{\\\"order\\\":1},\\\"objective\\\":{\\\"order\\\":2},\\\"status\\\":{\\\"order\\\":3},\\\"startAt\\\":{\\\"label\\\":\\\"Starts\\\",\\\"order\\\":4},\\\"endAt\\\":{\\\"label\\\":\\\"Ends\\\",\\\"order\\\":5},\\\"budgetCents\\\":{\\\"label\\\":\\\"Budget (cents)\\\",\\\"order\\\":6},\\\"currency\\\":{\\\"order\\\":7},\\\"customerId\\\":{\\\"label\\\":\\\"Customer\\\",\\\"visibility\\\":\\\"advanced\\\"},\\\"campaignKey\\\":{\\\"label\\\":\\\"Reference\\\",\\\"visibility\\\":\\\"advanced\\\"},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"},\\\"metadata\\\":{\\\"visibility\\\":\\\"hidden\\\"}}},\\\"@happyvertical/smrt-marketing:CampaignChannel\\\":{\\\"fields\\\":{\\\"campaignId\\\":{\\\"label\\\":\\\"Campaign\\\",\\\"order\\\":1},\\\"channelKind\\\":{\\\"label\\\":\\\"Kind\\\",\\\"order\\\":2},\\\"channelRef\\\":{\\\"label\\\":\\\"Name\\\",\\\"order\\\":3},\\\"allocatedBudgetCents\\\":{\\\"label\\\":\\\"Budget share (cents)\\\",\\\"order\\\":4},\\\"status\\\":{\\\"order\\\":5},\\\"startAt\\\":{\\\"label\\\":\\\"Starts\\\",\\\"order\\\":6},\\\"endAt\\\":{\\\"label\\\":\\\"Ends\\\",\\\"order\\\":7},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"}}},\\\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\\\":{\\\"fields\\\":{\\\"campaignId\\\":{\\\"label\\\":\\\"Campaign\\\",\\\"order\\\":1},\\\"campaignChannelId\\\":{\\\"label\\\":\\\"Channel\\\",\\\"order\\\":2},\\\"periodStart\\\":{\\\"label\\\":\\\"From\\\",\\\"order\\\":3},\\\"periodEnd\\\":{\\\"label\\\":\\\"To\\\",\\\"order\\\":4},\\\"spendCents\\\":{\\\"label\\\":\\\"Spend (cents)\\\",\\\"order\\\":5},\\\"impressions\\\":{\\\"order\\\":6},\\\"clicks\\\":{\\\"order\\\":7},\\\"conversions\\\":{\\\"order\\\":8},\\\"leads\\\":{\\\"order\\\":9},\\\"revenueCents\\\":{\\\"label\\\":\\\"Revenue (cents)\\\",\\\"order\\\":10},\\\"source\\\":{\\\"order\\\":11},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"},\\\"dedupeKey\\\":{\\\"visibility\\\":\\\"hidden\\\"}}}},\\\"help\\\":{\\\"markdown\\\":\\\"## Overview\\\\n\\\\nCampaigns is where you plan a push to bring in customers: a sale, a launch, a\\\\nseasonal promotion. Each campaign has a budget, start and end dates and a\\\\nstatus that shows where it stands, from draft through active to completed. A\\\\ncampaign runs on one or more channels, such as an ad group, a social post, an\\\\nemail or an event, each with its share of the budget. Results come back from\\\\nthe channels' reporting, period by period, so you can see what each campaign\\\\nand channel brought in for what it cost.\\\\n\\\\n## Tasks\\\\n\\\\n### Plan a campaign\\\\n\\\\n1. Open the list of campaigns and choose **New**.\\\\n2. Type the **{field:Campaign.name}**, for example \\\\\\\"Spring widget sale\\\\\\\".\\\\n3. Say what it is for in **{field:Campaign.objective}**, such as awareness or new leads.\\\\n4. Set when it **{field:Campaign.startAt}** and when it **{field:Campaign.endAt}**. The end cannot be before the start.\\\\n5. Enter the **{field:Campaign.budgetCents}** as a whole number of cents, so $500 is 50000, and check the **{field:Campaign.currency}**.\\\\n6. Save. A new campaign starts as a draft.\\\\n\\\\n### See where a campaign stands\\\\n\\\\n1. Open the list of campaigns.\\\\n2. The **{field:Campaign.status}** shows whether each one is still a draft, scheduled, active, paused, completed or archived. It moves forward as the campaign is scheduled, run and wrapped up; you do not set it by hand.\\\\n3. Open a campaign to change its name, dates or budget while it is still being planned.\\\\n\\\\n### Add a channel to a campaign\\\\n\\\\n1. Open the list of channels and choose **New**.\\\\n2. Pick the **{field:CampaignChannel.campaignId}** it belongs to.\\\\n3. Choose the **{field:CampaignChannel.channelKind}**, such as an ad group, a social post, a message, content or an event.\\\\n4. Type the **{field:CampaignChannel.channelRef}** the channel goes by, so its results can be matched to it later.\\\\n5. Enter its **{field:CampaignChannel.allocatedBudgetCents}** as a whole number of cents.\\\\n6. If the channel runs on different dates from the campaign, set when it **{field:CampaignChannel.startAt}** and **{field:CampaignChannel.endAt}**.\\\\n7. Save. You can edit a channel later, or remove one that will not run.\\\\n\\\\n### Read the results\\\\n\\\\nResults arrive from the channels' reporting; nobody types them in. Each one\\\\ncovers a period for one campaign, or for one channel within it. A result is\\\\nnever changed once it is in: a correction arrives as a new result for the same\\\\nperiod.\\\\n\\\\n1. Open a campaign and look at its results, listed under the campaign.\\\\n2. Each result names the **{field:CampaignMetricSnapshot.campaignId}** it belongs to. A result with no **{field:CampaignMetricSnapshot.campaignChannelId}** covers the whole campaign.\\\\n3. Check the period, **{field:CampaignMetricSnapshot.periodStart}** to **{field:CampaignMetricSnapshot.periodEnd}**.\\\\n4. Compare the **{field:CampaignMetricSnapshot.spendCents}** with the **{field:CampaignMetricSnapshot.clicks}**, **{field:CampaignMetricSnapshot.leads}** and **{field:CampaignMetricSnapshot.conversions}** it brought in, and the **{field:CampaignMetricSnapshot.revenueCents}** where the channel reports it.\\\\n5. The **{field:CampaignMetricSnapshot.source}** says which channel's reporting sent the result.\\\\n\\\",\\\"fieldRefs\\\":[\\\"Campaign.budgetCents\\\",\\\"Campaign.currency\\\",\\\"Campaign.endAt\\\",\\\"Campaign.name\\\",\\\"Campaign.objective\\\",\\\"Campaign.startAt\\\",\\\"Campaign.status\\\",\\\"CampaignChannel.allocatedBudgetCents\\\",\\\"CampaignChannel.campaignId\\\",\\\"CampaignChannel.channelKind\\\",\\\"CampaignChannel.channelRef\\\",\\\"CampaignChannel.endAt\\\",\\\"CampaignChannel.startAt\\\",\\\"CampaignMetricSnapshot.campaignChannelId\\\",\\\"CampaignMetricSnapshot.campaignId\\\",\\\"CampaignMetricSnapshot.clicks\\\",\\\"CampaignMetricSnapshot.conversions\\\",\\\"CampaignMetricSnapshot.leads\\\",\\\"CampaignMetricSnapshot.periodEnd\\\",\\\"CampaignMetricSnapshot.periodStart\\\",\\\"CampaignMetricSnapshot.revenueCents\\\",\\\"CampaignMetricSnapshot.source\\\",\\\"CampaignMetricSnapshot.spendCents\\\"]},\\\"demo\\\":{\\\"mode\\\":\\\"server\\\",\\\"reasons\\\":[\\\"Needs a server because its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624).\\\"]}}],\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-commerce\\\",\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3624\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","/** Fail-closed customer/tenant mismatch without revealing row existence. */\nexport class CampaignCustomerScopeError extends Error {\n readonly code = 'CAMPAIGN_CUSTOMER_SCOPE';\n\n constructor(label: string) {\n super(`${label}: customer scope validation failed.`);\n this.name = 'CampaignCustomerScopeError';\n }\n}\n","import {\n ObjectRegistry,\n type SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { CampaignCustomerScopeError } from './errors.js';\n\nconst CUSTOMER_QUALIFIED_NAME = '@happyvertical/smrt-commerce:Customer';\nconst SAFE_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;\n\ntype QueryDatabase = Pick<DatabaseInterface, 'query' | 'url'>;\n\ninterface DatabaseOptions {\n db?: SmrtObjectOptions['db'];\n}\n\nfunction requireDatabase(options: DatabaseOptions): QueryDatabase {\n const db = options.db;\n if (!db || typeof db !== 'object' || !('query' in db)) {\n throw new Error(\n 'Campaign customer queries require an initialized database connection.',\n );\n }\n return db as QueryDatabase;\n}\n\nasync function resolveCustomerTable(options: DatabaseOptions): Promise<string> {\n await ObjectRegistry.ensureManifestLoaded(CUSTOMER_QUALIFIED_NAME);\n const target =\n ObjectRegistry.getClassByQualifiedName(CUSTOMER_QUALIFIED_NAME) ??\n ObjectRegistry.getClass(CUSTOMER_QUALIFIED_NAME);\n if (!target) {\n throw new Error(\n 'Campaign customer queries require @happyvertical/smrt-commerce:Customer to be registered.',\n );\n }\n\n const probe = new target.constructor(options) as SmrtObject;\n const tableName = probe.tableName;\n if (!SAFE_SQL_IDENTIFIER.test(tableName)) {\n throw new Error('Campaign customer table has an invalid identifier.');\n }\n return tableName;\n}\n\n/**\n * Verify a bounded set of canonical commerce Customers in one database read.\n * The generic failure deliberately does not reveal whether an id is missing or\n * belongs to another tenant.\n */\nexport async function assertCustomersBelongToTenant(\n options: DatabaseOptions,\n tenantId: string | null,\n customerIds: readonly string[],\n label: string,\n lock: 'none' | 'share' | 'update' = 'none',\n): Promise<void> {\n if (customerIds.length === 0) return;\n const normalizedCustomerIds = customerIds.map((customerId) => {\n try {\n return normalizeUuid(customerId, 'customerId');\n } catch {\n throw new CampaignCustomerScopeError(label);\n }\n });\n const normalizedTenantId = tenantId?.toLowerCase() ?? null;\n const db = requireDatabase(options);\n const tableName = await resolveCustomerTable(options);\n const placeholders = normalizedCustomerIds.map(() => '?').join(', ');\n const lockClause = postgresLockClause(db, lock);\n const result = await db.query(\n `SELECT id, tenant_id FROM ${tableName} WHERE id IN (${placeholders})${lockClause}`,\n ...normalizedCustomerIds,\n );\n const tenantById = new Map(\n result.rows.map((row) => [\n String(row.id).toLowerCase(),\n row.tenant_id == null ? null : String(row.tenant_id).toLowerCase(),\n ]),\n );\n const valid = normalizedCustomerIds.every(\n (customerId) =>\n tenantById.has(customerId) &&\n tenantById.get(customerId) === normalizedTenantId,\n );\n if (!valid) {\n throw new CampaignCustomerScopeError(label);\n }\n}\n\nfunction postgresLockClause(\n db: QueryDatabase,\n lock: 'none' | 'share' | 'update',\n): string {\n if (lock === 'none' || !/^postgres(?:ql)?:/iu.test(db.url)) return '';\n return lock === 'update' ? ' FOR UPDATE' : ' FOR SHARE';\n}\n\n/** Parse and canonicalize one UUID without echoing the rejected value. */\nexport function normalizeUuid(value: string, label: string): string {\n if (!UUID_RE.test(value)) {\n throw new Error(`${label} must be a canonical UUID.`);\n }\n return value.toLowerCase();\n}\n","/** Campaign identity, budget, schedule, and guarded lifecycle. */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n ValidationError,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertCustomersBelongToTenant,\n normalizeUuid,\n} from '../customer-scope.js';\nimport { CampaignCustomerScopeError } from '../errors.js';\nimport {\n CAMPAIGN_STATUSES,\n type CampaignOptions,\n type CampaignStatus,\n} from '../types.js';\n\nconst CAMPAIGN_STATUS_TRANSITIONS: Record<CampaignStatus, CampaignStatus[]> = {\n draft: ['scheduled'],\n scheduled: ['active'],\n active: ['paused', 'completed'],\n paused: ['active', 'completed'],\n completed: ['archived'],\n archived: [],\n};\n\nconst loadedCampaignStatus = new WeakMap<Campaign, CampaignStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaigns',\n conflictColumns: ['tenant_id', 'campaign_key'],\n indexes: [\n {\n name: 'campaigns_tenant_id_customer_id_start_at_id_idx',\n columns: ['tenantId', 'customerId', 'startAt', 'id'],\n },\n ],\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update'] },\n})\nexport class Campaign extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Canonical commerce Customer that owns this campaign, when assigned. */\n @field({ description: 'The customer this campaign is run for, if any.' })\n @crossPackageRef('@happyvertical/smrt-commerce:Customer', {\n nullable: true,\n validate: true,\n })\n customerId: string | null = null;\n\n /** Stable caller-defined key used by CRM/referral systems. */\n @field({\n required: true,\n description:\n 'A short reference of your own for this campaign, kept the same once it is in use.',\n })\n campaignKey: string = '';\n\n @field({ required: true, description: 'What the campaign is called.' })\n name: string = '';\n\n /** Open objective key such as `awareness` or `demand_generation`. */\n @field({\n description:\n 'What the campaign is meant to achieve, such as awareness or new leads.',\n })\n objective: string = '';\n\n @field({\n description:\n 'Where the campaign stands, from draft through active to completed and archived.',\n })\n status: CampaignStatus = 'draft';\n\n @field({ description: 'When the campaign starts.' })\n startAt: Date | null = null;\n\n @field({ description: 'When the campaign ends.' })\n endAt: Date | null = null;\n\n /** Total campaign budget in integer cents. */\n @field({\n description: 'The total you plan to spend on the campaign, in cents.',\n })\n budgetCents: number = 0;\n\n /** ISO 4217 currency for every campaign/channel amount. */\n @field({\n description: 'The currency every amount on this campaign is in.',\n })\n currency: string = 'USD';\n\n metadata: string = '{}';\n\n constructor(options: CampaignOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.campaignKey !== undefined)\n this.campaignKey = options.campaignKey;\n if (options.name !== undefined) this.name = options.name;\n if (options.objective !== undefined) this.objective = options.objective;\n if (options.status !== undefined) this.status = options.status;\n if (options.startAt !== undefined)\n this.startAt = Campaign.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = Campaign.coerceDate(options.endAt);\n if (options.budgetCents !== undefined)\n this.budgetCents = options.budgetCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n override async initialize(): Promise<this> {\n if (this.id) {\n try {\n this.id = normalizeUuid(this.id, 'id');\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.initialize');\n }\n throw error;\n }\n }\n await super.initialize();\n this.startAt = Campaign.coerceDate(this.startAt);\n this.endAt = Campaign.coerceDate(this.endAt);\n if (this.isPersisted) loadedCampaignStatus.set(this, this.status);\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n if (this.customerId === null) return this.saveLifecycle(options);\n\n const db = this.db;\n if (typeof db.transaction !== 'function') {\n throw new Error(\n 'Campaign.save requires a database adapter with transaction support.',\n );\n }\n return db.transaction(async (transactionDb) =>\n this.withDatabase(transactionDb, async (bound) =>\n bound.saveLifecycle(options),\n ),\n );\n }\n\n private async saveLifecycle(options?: SmrtSaveOptions): Promise<this> {\n this.prepareIdentityForSave();\n if (this.customerId !== null) {\n await assertCustomersBelongToTenant(\n this.options,\n this.tenantId,\n [this.customerId],\n 'Campaign.save',\n 'update',\n );\n }\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n this.assertSchedule();\n let result: this;\n try {\n const persist = async () => (await super.save(options)) as this;\n const tenantContext =\n isSystemContext() || isSuperAdminBypass()\n ? undefined\n : getCurrentTenant();\n result = tenantContext\n ? await withTenant(\n {\n ...tenantContext,\n tenantId: this.tenantId ?? tenantContext.tenantId,\n },\n persist,\n )\n : await persist();\n } catch (error) {\n if (\n error instanceof ValidationError &&\n (error.code === 'VALIDATION_CROSS_PACKAGE_REF_MISSING' ||\n error.code === 'VALIDATION_CROSS_PACKAGE_REF_UNREGISTERED')\n ) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n loadedCampaignStatus.set(this, this.status);\n return result;\n }\n\n /** Canonicalize and authorize identity before any status or scope read. */\n private prepareIdentityForSave(): void {\n try {\n if (this.id) this.id = normalizeUuid(this.id, 'id');\n if (this.customerId !== null) {\n this.customerId = normalizeUuid(this.customerId, 'customerId');\n }\n if (this.tenantId !== null) {\n this.tenantId = normalizeUuid(this.tenantId, 'tenantId');\n }\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n\n if (isSystemContext() || isSuperAdminBypass()) return;\n const tenantContext = getCurrentTenant();\n if (!tenantContext) return;\n\n let contextTenantId: string;\n try {\n contextTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n if (this.tenantId !== null && this.tenantId !== contextTenantId) {\n throw new TenantIsolationError(\n 'Tenant isolation violation in Campaign.save.',\n { tenantId: contextTenantId, attemptedTenantId: this.tenantId },\n );\n }\n this.tenantId = contextTenantId;\n }\n\n /** Apply one legal transition in memory; callers choose the save boundary. */\n transitionTo(next: CampaignStatus): this {\n if (next === this.status) return this;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[this.status] ?? [];\n if (!allowed.includes(next)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: cannot transition ` +\n `'${this.status}' → '${next}'.`,\n );\n }\n this.status = next;\n return this;\n }\n\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this.metadata = JSON.stringify(metadata ?? {});\n }\n\n private assertSchedule(): void {\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('Campaign endAt must be on or after startAt.');\n }\n if (!Number.isInteger(this.budgetCents) || this.budgetCents < 0) {\n throw new Error('Campaign budgetCents must be a non-negative integer.');\n }\n }\n\n private async resolvePriorStatus(): Promise<CampaignStatus | undefined> {\n if (this.id) {\n let scopedRow: Record<string, unknown> | null = null;\n try {\n scopedRow = await this.db.get(this.tableName, {\n id: this.id,\n tenant_id: this.tenantId,\n });\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n if (scopedRow?.status != null) {\n return scopedRow.status as CampaignStatus;\n }\n\n // A caller-supplied ID already owned by another lane must not expose its\n // lifecycle state or be repurposed by an upsert.\n let rowInAnotherLane: Record<string, unknown> | null = null;\n try {\n rowInAnotherLane = await this.db.get(this.tableName, { id: this.id });\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n if (rowInAnotherLane) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n }\n if (this.campaignKey) {\n try {\n const row = await this.db.get(this.tableName, {\n tenant_id: this.tenantId,\n campaign_key: this.campaignKey,\n });\n if (row && row.status != null) return row.status as CampaignStatus;\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n }\n return loadedCampaignStatus.get(this);\n }\n\n private assertStatusTransition(prior: CampaignStatus | undefined): void {\n if (!CAMPAIGN_STATUSES.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (prior === undefined || prior === this.status) return;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'.`,\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default Campaign;\n","/** Generic links from a campaign to cross-package channel execution rows. */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CampaignChannelOptions,\n CampaignChannelStatus,\n} from '../types.js';\nimport { CAMPAIGN_CHANNEL_STATUSES } from '../types.js';\nimport { Campaign } from './Campaign.js';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_channels',\n conflictColumns: ['campaign_id', 'channel_kind', 'channel_ref'],\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update', 'delete'] },\n})\nexport class CampaignChannel extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ description: 'The campaign this channel runs for.' })\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Open kind vocabulary; recommended values live in CAMPAIGN_CHANNEL_KINDS. */\n @field({\n required: true,\n description:\n 'What sort of channel this is, such as an ad group, a social post, a message, content or an event.',\n })\n channelKind: string = '';\n\n /** Opaque identifier in the namespace named by channelKind. */\n @field({\n required: true,\n description:\n 'The name or reference the channel goes by, so its results can be matched to it.',\n })\n channelRef: string = '';\n\n @field({\n description:\n 'The share of the campaign budget set aside for this channel, in cents.',\n })\n allocatedBudgetCents: number = 0;\n\n /** Optional execution-specific override of the Campaign start. */\n @field({\n description: 'When this channel starts, if different from the campaign.',\n })\n startAt: Date | null = null;\n\n /** Optional execution-specific override of the Campaign end. */\n @field({\n description: 'When this channel ends, if different from the campaign.',\n })\n endAt: Date | null = null;\n\n @field({\n description:\n 'Where this channel stands, from planned through active to completed or cancelled.',\n })\n status: CampaignChannelStatus = 'planned';\n\n constructor(options: CampaignChannelOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.channelKind !== undefined)\n this.channelKind = options.channelKind;\n if (options.channelRef !== undefined) this.channelRef = options.channelRef;\n if (options.allocatedBudgetCents !== undefined)\n this.allocatedBudgetCents = options.allocatedBudgetCents;\n if (options.startAt !== undefined)\n this.startAt = CampaignChannel.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = CampaignChannel.coerceDate(options.endAt);\n if (options.status !== undefined) this.status = options.status;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.startAt = CampaignChannel.coerceDate(this.startAt);\n this.endAt = CampaignChannel.coerceDate(this.endAt);\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n if (!CAMPAIGN_CHANNEL_STATUSES.includes(this.status)) {\n throw new Error(\n `CampaignChannel ${this.channelRef || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (\n !Number.isInteger(this.allocatedBudgetCents) ||\n this.allocatedBudgetCents < 0\n ) {\n throw new Error(\n 'CampaignChannel allocatedBudgetCents must be a non-negative integer.',\n );\n }\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('CampaignChannel endAt must be on or after startAt.');\n }\n return (await super.save(options)) as this;\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignChannel;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignChannel } from '../models/CampaignChannel.js';\n\nexport class CampaignChannelCollection extends SmrtCollection<CampaignChannel> {\n static readonly _itemClass = CampaignChannel;\n\n async findByCampaign(campaignId: string): Promise<CampaignChannel[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'start_at ASC',\n });\n }\n\n async findByExecutionRef(\n channelKind: string,\n channelRef: string,\n ): Promise<CampaignChannel[]> {\n return await this.list({ where: { channelKind, channelRef } });\n }\n}\n\nexport default CampaignChannelCollection;\n","import type { Campaign } from '../models/Campaign.js';\nimport type { BudgetPacingResult, BudgetPacingStatus } from '../types.js';\n\nexport interface BudgetPacingEvidence {\n spendCents: number;\n snapshotCount: number;\n usedCampaignRollups: boolean;\n}\n\n/** Canonical campaign pacing calculation for grouped and row-level evidence. */\nexport function calculateCampaignPacing(\n campaign: Campaign,\n evidence: BudgetPacingEvidence,\n at = new Date(),\n): BudgetPacingResult {\n return calculatePacing({\n campaign,\n evidence,\n budgetCents: campaign.budgetCents,\n startAt: campaign.startAt,\n endAt: campaign.endAt,\n at,\n });\n}\n\nexport function calculatePacing({\n campaign,\n evidence,\n budgetCents,\n startAt,\n endAt,\n at,\n}: {\n campaign: Campaign;\n evidence: BudgetPacingEvidence;\n budgetCents: number;\n startAt: Date | null;\n endAt: Date | null;\n at: Date;\n}): BudgetPacingResult {\n const elapsed = elapsedFraction(startAt, endAt, at);\n const expectedSpendCents =\n elapsed === null ? null : Math.round(budgetCents * elapsed);\n const varianceCents =\n expectedSpendCents === null\n ? null\n : evidence.spendCents - expectedSpendCents;\n return {\n campaignId: campaign.id ?? '',\n currency: campaign.currency,\n budgetCents,\n spendCents: evidence.spendCents,\n remainingCents: budgetCents - evidence.spendCents,\n expectedSpendCents,\n varianceCents,\n budgetFraction: budgetCents > 0 ? evidence.spendCents / budgetCents : null,\n elapsedFraction: elapsed,\n status: pacingStatus({\n campaign,\n budgetCents,\n spendCents: evidence.spendCents,\n elapsedFraction: elapsed,\n varianceCents,\n startAt,\n at,\n }),\n snapshotCount: evidence.snapshotCount,\n usedCampaignRollups: evidence.usedCampaignRollups,\n };\n}\n\nfunction pacingStatus({\n campaign,\n budgetCents,\n spendCents,\n elapsedFraction,\n varianceCents,\n startAt,\n at,\n}: {\n campaign: Campaign;\n budgetCents: number;\n spendCents: number;\n elapsedFraction: number | null;\n varianceCents: number | null;\n startAt: Date | null;\n at: Date;\n}): BudgetPacingStatus {\n if (budgetCents <= 0) return 'unbudgeted';\n if (spendCents > budgetCents) return 'over_budget';\n if (startAt && at.getTime() < startAt.getTime()) return 'not_started';\n if (\n campaign.status === 'completed' ||\n campaign.status === 'archived' ||\n elapsedFraction === 1\n ) {\n return 'complete';\n }\n if (varianceCents === null) return 'on_track';\n\n // A five-percent budget band avoids noisy status flips from minor timing\n // differences between spend collection and scheduled pacing.\n const toleranceCents = Math.max(1, Math.round(budgetCents * 0.05));\n if (varianceCents > toleranceCents) return 'ahead';\n if (varianceCents < -toleranceCents) return 'behind';\n return 'on_track';\n}\n\nfunction elapsedFraction(\n startAt: Date | null,\n endAt: Date | null,\n at: Date,\n): number | null {\n if (!startAt || !endAt) return null;\n const start = startAt.getTime();\n const end = endAt.getTime();\n if (end <= start) return at.getTime() < start ? 0 : 1;\n return Math.min(1, Math.max(0, (at.getTime() - start) / (end - start)));\n}\n","/** Immutable period performance evidence for a campaign or campaign channel. */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\nimport { Campaign } from './Campaign.js';\nimport { CampaignChannel } from './CampaignChannel.js';\n\nconst persistedSnapshotState = new WeakMap<CampaignMetricSnapshot, string>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_metric_snapshots',\n conflictColumns: ['dedupe_key'],\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: { include: ['list', 'get', 'create'] },\n})\nexport class CampaignMetricSnapshot extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ description: 'The campaign these results belong to.' })\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Null for campaign rollups; set for channel-scoped evidence. */\n @field({\n description:\n 'The channel these results came from. Empty when they cover the whole campaign.',\n })\n @foreignKey(CampaignChannel, { nullable: true })\n campaignChannelId: string | null = null;\n\n @field({\n required: true,\n description: 'The first day of the period these results cover.',\n })\n periodStart: Date = new Date();\n\n @field({\n required: true,\n description: 'The last day of the period these results cover.',\n })\n periodEnd: Date = new Date();\n\n @field({ description: 'What was spent in the period, in cents.' })\n spendCents: number = 0;\n\n @field({ description: 'How many times the campaign was shown.' })\n impressions: number = 0;\n\n @field({ description: 'How many times someone clicked through.' })\n clicks: number = 0;\n\n @field({\n description: 'How many people did what the campaign asked, such as buying.',\n })\n conversions: number = 0;\n\n @field({ description: 'How many new leads came in.' })\n leads: number = 0;\n\n @field({\n type: 'integer',\n nullable: true,\n description: 'The sales brought in during the period, in cents, if known.',\n })\n revenueCents: number | null = null;\n\n /** Ingestion source such as `google_ads`, `meta`, or `rollup_job`. */\n @field({\n required: true,\n description: 'Where these results were reported from.',\n })\n source: string = '';\n\n @field({\n required: true,\n description:\n 'The reference this result is filed under, so the same report is never counted twice.',\n })\n dedupeKey: string = '';\n\n constructor(options: CampaignMetricSnapshotOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.campaignChannelId !== undefined)\n this.campaignChannelId = options.campaignChannelId;\n if (options.periodStart !== undefined)\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(options.periodStart) ??\n new Date(Number.NaN);\n if (options.periodEnd !== undefined)\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(options.periodEnd) ??\n new Date(Number.NaN);\n if (options.spendCents !== undefined) this.spendCents = options.spendCents;\n if (options.impressions !== undefined)\n this.impressions = options.impressions;\n if (options.clicks !== undefined) this.clicks = options.clicks;\n if (options.conversions !== undefined)\n this.conversions = options.conversions;\n if (options.leads !== undefined) this.leads = options.leads;\n if (options.revenueCents !== undefined)\n this.revenueCents = options.revenueCents;\n if (options.source !== undefined) this.source = options.source;\n if (options.dedupeKey !== undefined) this.dedupeKey = options.dedupeKey;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(this.periodStart) ??\n new Date(Number.NaN);\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(this.periodEnd) ?? new Date(Number.NaN);\n if (this.isPersisted) {\n persistedSnapshotState.set(this, this.serializeState());\n }\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n this.assertMetrics();\n await this.assertChannelMatchesCampaign();\n const captured = persistedSnapshotState.get(this);\n if (captured !== undefined) {\n if (captured !== this.serializeState()) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots ` +\n 'are immutable evidence; ingest a correcting snapshot instead.',\n );\n }\n } else if (this.id && (await this.isSaved())) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id}: refusing to overwrite an existing ` +\n 'snapshot from a non-hydrated instance; snapshots are immutable evidence.',\n );\n } else if (this.dedupeKey) {\n try {\n const row = await this.db.get(this.tableName, {\n dedupe_key: this.dedupeKey,\n });\n if (row && row.id !== this.id) {\n throw new Error(\n `CampaignMetricSnapshot (dedupeKey '${this.dedupeKey}'): a ` +\n 'snapshot with this dedupe key already exists; use ' +\n 'MetricIngestionService for idempotent ingestion.',\n );\n }\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('idempotent ingestion')\n ) {\n throw error;\n }\n // DB not ready / table absent; there is no persisted row to protect.\n }\n }\n\n const result = (await super.save(options)) as this;\n persistedSnapshotState.set(this, this.serializeState());\n return result;\n }\n\n override async delete(): Promise<void> {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots are ` +\n 'immutable evidence and cannot be deleted.',\n );\n }\n\n private assertMetrics(): void {\n if (\n Number.isNaN(this.periodStart.getTime()) ||\n Number.isNaN(this.periodEnd.getTime())\n ) {\n throw new Error(\n 'CampaignMetricSnapshot periodStart and periodEnd must be valid dates.',\n );\n }\n if (this.periodEnd.getTime() < this.periodStart.getTime()) {\n throw new Error(\n 'CampaignMetricSnapshot periodEnd must be on or after periodStart.',\n );\n }\n const integerMetrics: Array<[string, number]> = [\n ['spendCents', this.spendCents],\n ['impressions', this.impressions],\n ['clicks', this.clicks],\n ['conversions', this.conversions],\n ['leads', this.leads],\n ];\n for (const [name, value] of integerMetrics) {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer.`);\n }\n }\n if (\n this.revenueCents !== null &&\n (!Number.isInteger(this.revenueCents) || this.revenueCents < 0)\n ) {\n throw new Error('revenueCents must be null or a non-negative integer.');\n }\n }\n\n private async assertChannelMatchesCampaign(): Promise<void> {\n if (!this.campaignChannelId) return;\n const channel = (await this.loadRelated(\n 'campaignChannelId',\n )) as CampaignChannel;\n if (channel.campaignId !== this.campaignId) {\n throw new Error(\n `CampaignMetricSnapshot campaignChannelId '${this.campaignChannelId}' ` +\n `does not belong to campaignId '${this.campaignId}'.`,\n );\n }\n }\n\n private serializeState(): string {\n return JSON.stringify({\n tenantId: this.tenantId,\n campaignId: this.campaignId,\n campaignChannelId: this.campaignChannelId,\n periodStart: this.periodStart.toISOString(),\n periodEnd: this.periodEnd.toISOString(),\n spendCents: this.spendCents,\n impressions: this.impressions,\n clicks: this.clicks,\n conversions: this.conversions,\n leads: this.leads,\n revenueCents: this.revenueCents,\n source: this.source,\n dedupeKey: this.dedupeKey,\n });\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignMetricSnapshot;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\nexport class CampaignMetricSnapshotCollection extends SmrtCollection<CampaignMetricSnapshot> {\n static readonly _itemClass = CampaignMetricSnapshot;\n\n async findByDedupeKey(\n dedupeKey: string,\n ): Promise<CampaignMetricSnapshot | null> {\n if (!dedupeKey) return null;\n const rows = await this.list({ where: { dedupeKey }, limit: 1 });\n return rows[0] ?? null;\n }\n\n async getOrCreateByDedupeKey(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n const dedupeKey = options.dedupeKey ?? '';\n if (!dedupeKey) {\n throw new Error(\n 'CampaignMetricSnapshotCollection.getOrCreateByDedupeKey requires a dedupeKey.',\n );\n }\n const existing = await this.findByDedupeKey(dedupeKey);\n if (existing) return { snapshot: existing, created: false };\n\n try {\n const snapshot = await this.create({\n ...options,\n // Keep runtime values uncoerced so the model rejects null/invalid\n // periods instead of allowing Date(null) to become the Unix epoch.\n periodStart: options.periodStart as Date | undefined,\n periodEnd: options.periodEnd as Date | undefined,\n _insertOnly: true,\n });\n return { snapshot, created: true };\n } catch (error) {\n // A concurrent ingest may have won the unique dedupe-key insert after\n // our initial read. Re-read the immutable winner instead of allowing a\n // natural-key upsert to replace its evidence in place.\n const concurrent = await this.findByDedupeKey(dedupeKey);\n if (concurrent) return { snapshot: concurrent, created: false };\n throw error;\n }\n }\n\n async findByCampaign(campaignId: string): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'period_start ASC',\n });\n }\n\n async findByChannel(\n campaignChannelId: string,\n ): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignChannelId },\n orderBy: 'period_start ASC',\n });\n }\n}\n\nexport default CampaignMetricSnapshotCollection;\n","import {\n type CollectionCacheConfig,\n resolveListLimit,\n SmrtCollection,\n type SmrtListOptions,\n type SmrtSelectedRow,\n type SmrtSelectField,\n type SmrtWhereClause,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n assertCustomersBelongToTenant,\n normalizeUuid,\n} from '../customer-scope.js';\nimport { Campaign } from '../models/Campaign.js';\nimport { calculateCampaignPacing } from '../services/pacing-calculation.js';\nimport type {\n CampaignChannelMixEntry,\n CampaignCustomerCursor,\n CampaignCustomerCursorInput,\n CampaignCustomerPage,\n CampaignCustomerSummary,\n CampaignMetricTotals,\n CampaignReportingPage,\n CampaignStatus,\n ListCampaignReportingByCustomerOptions,\n ListCampaignsByCustomerOptions,\n} from '../types.js';\nimport { CampaignChannelCollection } from './CampaignChannelCollection.js';\nimport { CampaignMetricSnapshotCollection } from './CampaignMetricSnapshotCollection.js';\n\nexport const MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE = 100;\nexport const MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE = 100;\n\nexport class CampaignCollection extends SmrtCollection<Campaign> {\n static readonly _itemClass = Campaign;\n\n override async get(\n filter: string | SmrtWhereClause<Campaign>,\n options: { cache?: CollectionCacheConfig | false } = {},\n ): Promise<Campaign | null> {\n return withCanonicalActiveTenant(() =>\n super.get(normalizeCampaignGetFilter(filter), options),\n );\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<Campaign>[],\n >(\n options: SmrtListOptions<Campaign> & { select: Select; include?: never },\n ): Promise<SmrtSelectedRow<Campaign, Select>[]>;\n override async list(\n options?: Omit<SmrtListOptions<Campaign>, 'select'> & {\n select?: undefined;\n },\n ): Promise<Campaign[]>;\n override async list(\n options: SmrtListOptions<Campaign> = {},\n ): Promise<Campaign[] | Record<string, unknown>[]> {\n return withCanonicalActiveTenant(\n () =>\n super.list(normalizeCampaignListOptions(options) as never) as Promise<\n Campaign[] | Record<string, unknown>[]\n >,\n );\n }\n\n async findByCampaignKey(\n campaignKey: string,\n tenantId?: string | null,\n ): Promise<Campaign | null> {\n if (!campaignKey) return null;\n const where: Record<string, unknown> = { campaignKey };\n if (tenantId !== undefined) where.tenantId = tenantId;\n const rows = await this.list({ where, limit: 1 });\n return rows[0] ?? null;\n }\n\n async findByStatus(status: CampaignStatus): Promise<Campaign[]> {\n return await this.list({\n where: { status },\n orderBy: 'start_at ASC',\n });\n }\n\n /**\n * List one tenant/customer lane newest-first using a stable UUID tiebreaker.\n * Null start times follow all scheduled rows on every supported database.\n */\n async listByCustomer(\n tenantId: string | null,\n customerId: string,\n options: ListCampaignsByCustomerOptions = {},\n ): Promise<CampaignCustomerPage> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.listByCustomer',\n );\n const normalizedCustomerId = normalizeUuid(customerId, 'customerId');\n const limit = resolveListLimit(options.limit, {\n defaultValue: 50,\n maxValue: Number.MAX_SAFE_INTEGER,\n parameterName: 'campaign customer page limit',\n });\n if (limit > MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE) {\n throw new Error(\n `Campaign customer page limit must not exceed ${MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE}.`,\n );\n }\n if (limit < 1) {\n throw new Error('Campaign customer page limit must be at least 1.');\n }\n const cursor = normalizeCursor(options.after);\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) =>\n bound.listByCustomerInTransaction(\n normalizedTenantId,\n normalizedCustomerId,\n limit,\n cursor,\n 'CampaignCollection.listByCustomer',\n ),\n ),\n );\n }\n\n /**\n * Return one bounded customer page with its complete read-only reporting\n * projection. Channels and immutable evidence are aggregated in two grouped\n * reads, independent of page size; no campaign callback or lazy load runs.\n */\n async listReportingByCustomer(\n tenantId: string | null,\n customerId: string,\n options: ListCampaignReportingByCustomerOptions = {},\n ): Promise<CampaignReportingPage> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.listReportingByCustomer',\n );\n const normalizedCustomerId = normalizeUuid(customerId, 'customerId');\n const limit = resolveListLimit(options.limit, {\n defaultValue: 50,\n maxValue: Number.MAX_SAFE_INTEGER,\n parameterName: 'campaign reporting page limit',\n });\n if (limit > MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE) {\n throw new Error(\n `Campaign reporting page limit must not exceed ${MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE}.`,\n );\n }\n if (limit < 1) {\n throw new Error('Campaign reporting page limit must be at least 1.');\n }\n const cursor = normalizeCursor(options.after);\n const at = normalizeReportingAt(options.at);\n\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) => {\n const page = await bound.listByCustomerInTransaction(\n normalizedTenantId,\n normalizedCustomerId,\n limit,\n cursor,\n 'CampaignCollection.listReportingByCustomer',\n );\n return bound.projectReportingPage(normalizedTenantId, page, at);\n }),\n );\n }\n\n private async projectReportingPage(\n tenantId: string | null,\n page: CampaignCustomerPage,\n at: Date,\n ): Promise<CampaignReportingPage> {\n const campaignIds = page.items.map((campaign) => {\n if (!campaign.id) {\n throw new Error('Campaign reporting requires persisted Campaign rows.');\n }\n return normalizeUuid(campaign.id, 'campaign id');\n });\n if (campaignIds.length === 0) {\n return { items: [], nextCursor: page.nextCursor };\n }\n\n const channelRows = await this.loadChannelMixRows(tenantId, campaignIds);\n const metricRows = await this.loadMetricRows(tenantId, campaignIds);\n const channelsByCampaign = groupChannelMix(channelRows);\n const metricsByCampaign = groupMetricTotals(metricRows);\n\n return {\n items: page.items.map((campaign) => {\n const campaignId = campaign.id ?? '';\n const channelMix = channelsByCampaign.get(campaignId) ?? [];\n const metrics =\n metricsByCampaign.get(campaignId) ?? emptyMetricTotals();\n return {\n campaign,\n channelCount: channelMix.reduce(\n (sum, entry) =>\n checkedAdd(sum, entry.count, 'Campaign channel count'),\n 0,\n ),\n channelMix,\n metricTotals: metrics.totals,\n pacing: calculateCampaignPacing(\n campaign,\n {\n spendCents: metrics.totals.spendCents,\n snapshotCount: metrics.snapshotCount,\n usedCampaignRollups: metrics.usedCampaignRollups,\n },\n at,\n ),\n };\n }),\n nextCursor: page.nextCursor,\n };\n }\n\n private async loadChannelMixRows(\n tenantId: string | null,\n campaignIds: string[],\n ): Promise<Array<Record<string, unknown>>> {\n const channels = await CampaignChannelCollection.create({\n ...this.options,\n db: this.db,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n const db = requireDatabase(this.options);\n const placeholders = campaignIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `SELECT campaign_id, channel_kind, COUNT(*) AS channel_count\n FROM ${channels.tableName}\n WHERE ${tenantPredicate} AND campaign_id IN (${placeholders})\n GROUP BY campaign_id, channel_kind\n ORDER BY campaign_id ASC, channel_kind ASC`,\n ...tenantParams,\n ...campaignIds,\n );\n return result.rows;\n }\n\n private async loadMetricRows(\n tenantId: string | null,\n campaignIds: string[],\n ): Promise<Array<Record<string, unknown>>> {\n const snapshots = await CampaignMetricSnapshotCollection.create({\n ...this.options,\n db: this.db,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n const db = requireDatabase(this.options);\n const placeholders = campaignIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `WITH scoped_snapshots AS (\n SELECT campaign_id,\n campaign_channel_id,\n period_start,\n period_end,\n spend_cents,\n impressions,\n clicks,\n conversions,\n leads,\n revenue_cents\n FROM ${snapshots.tableName}\n WHERE ${tenantPredicate} AND campaign_id IN (${placeholders})\n ), rollup_periods AS (\n SELECT DISTINCT campaign_id, period_start, period_end\n FROM scoped_snapshots\n WHERE campaign_channel_id IS NULL\n ), selected_snapshots AS (\n SELECT s.*\n FROM scoped_snapshots s\n LEFT JOIN rollup_periods r\n ON r.campaign_id = s.campaign_id\n AND r.period_start = s.period_start\n AND r.period_end = s.period_end\n WHERE s.campaign_channel_id IS NULL OR r.campaign_id IS NULL\n )\n SELECT campaign_id,\n COUNT(*) AS snapshot_count,\n MAX(CASE WHEN campaign_channel_id IS NULL THEN 1 ELSE 0 END)\n AS used_campaign_rollups,\n SUM(spend_cents) AS spend_cents,\n SUM(impressions) AS impressions,\n SUM(clicks) AS clicks,\n SUM(conversions) AS conversions,\n SUM(leads) AS leads,\n SUM(COALESCE(revenue_cents, 0)) AS revenue_cents\n FROM selected_snapshots\n GROUP BY campaign_id\n ORDER BY campaign_id ASC`,\n ...tenantParams,\n ...campaignIds,\n );\n return result.rows;\n }\n\n private async listByCustomerInTransaction(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n operation: string,\n ): Promise<CampaignCustomerPage> {\n await assertCustomersBelongToTenant(\n this.options,\n tenantId,\n [customerId],\n operation,\n 'share',\n );\n const items =\n cursor?.startAt === null\n ? await this.listNullStartLane(tenantId, customerId, limit + 1, cursor)\n : await this.listScheduledLane(tenantId, customerId, limit, cursor);\n const hasMore = items.length > limit;\n const pageItems = hasMore ? items.slice(0, limit) : items;\n return {\n items: pageItems,\n nextCursor: hasMore\n ? cursorFromCampaign(pageItems[pageItems.length - 1])\n : null,\n };\n }\n\n /**\n * Summarize a bounded customer batch with one scope check and one grouped\n * aggregate query. Every requested customer receives a row, including zeros.\n */\n async summarizeByCustomers(\n tenantId: string | null,\n customerIds: string[],\n ): Promise<CampaignCustomerSummary[]> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.summarizeByCustomers',\n );\n if (!Array.isArray(customerIds)) {\n throw new Error(\n 'Campaign customer summary customerIds must be an array.',\n );\n }\n if (customerIds.length > MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE) {\n throw new Error(\n `Campaign customer summary accepts at most ${MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE} customerIds.`,\n );\n }\n const normalizedCustomerIds = customerIds.map((customerId) =>\n normalizeUuid(customerId, 'customerId'),\n );\n const uniqueIds = [...new Set(normalizedCustomerIds)];\n if (uniqueIds.length === 0) return [];\n\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) =>\n bound.summarizeByCustomersInTransaction(\n normalizedTenantId,\n normalizedCustomerIds,\n uniqueIds,\n ),\n ),\n );\n }\n\n private async summarizeByCustomersInTransaction(\n tenantId: string | null,\n requestedCustomerIds: string[],\n uniqueIds: string[],\n ): Promise<CampaignCustomerSummary[]> {\n await assertCustomersBelongToTenant(\n this.options,\n tenantId,\n uniqueIds,\n 'CampaignCollection.summarizeByCustomers',\n 'share',\n );\n const db = requireDatabase(this.options);\n const placeholders = uniqueIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `SELECT customer_id,\n COUNT(*) AS total_count,\n SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) AS active_count,\n MAX(start_at) AS latest_start_at\n FROM ${this.tableName}\n WHERE ${tenantPredicate} AND customer_id IN (${placeholders})\n GROUP BY customer_id\n ORDER BY customer_id ASC\n LIMIT ?`,\n 'active',\n ...tenantParams,\n ...uniqueIds,\n uniqueIds.length,\n );\n const byCustomer = new Map(\n result.rows.map((row) => [String(row.customer_id).toLowerCase(), row]),\n );\n return requestedCustomerIds.map((customerId) => {\n const row = byCustomer.get(customerId);\n return {\n customerId,\n totalCount: row\n ? toSafeCount(row.total_count, 'Campaign customer total count')\n : 0,\n activeCount: row\n ? toSafeCount(row.active_count, 'Campaign customer active count')\n : 0,\n latestStartAt: row ? coerceDate(row.latest_start_at) : null,\n };\n });\n }\n\n private async inCustomerReadTransaction<T>(\n operation: (bound: CampaignCollection) => Promise<T>,\n ): Promise<T> {\n const db = this.db as DatabaseInterface;\n if (typeof db.transaction !== 'function') {\n throw new Error(\n 'Campaign customer reads require a database adapter with transaction support.',\n );\n }\n return db.transaction(async (transactionDb) => {\n const bound = await CampaignCollection.create({\n ...this.options,\n db: transactionDb,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n return operation(bound);\n });\n }\n\n private async listScheduledLane(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n ): Promise<Campaign[]> {\n const base = [{ tenantId }, { customerId }, { 'startAt !=': null }];\n const where = cursor\n ? [\n [...base, { 'startAt <': cursor.startAt }],\n [...base, { startAt: cursor.startAt }, { 'id <': cursor.id }],\n ]\n : { tenantId, customerId, 'startAt !=': null };\n const scheduled = await this.list({\n where,\n orderBy: ['start_at DESC', 'id DESC'],\n limit: limit + 1,\n });\n if (scheduled.length > limit) return scheduled;\n const nullRows = await this.listNullStartLane(\n tenantId,\n customerId,\n limit - scheduled.length + 1,\n null,\n );\n return [...scheduled, ...nullRows];\n }\n\n private async listNullStartLane(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n ): Promise<Campaign[]> {\n return this.list({\n where: {\n tenantId,\n customerId,\n startAt: null,\n ...(cursor ? { 'id <': cursor.id } : {}),\n },\n orderBy: 'id DESC',\n limit,\n });\n }\n}\n\ninterface QueryDatabase {\n query(\n sql: string,\n ...params: unknown[]\n ): Promise<{ rows: Array<Record<string, unknown>> }>;\n}\n\ninterface GroupedMetricTotals {\n totals: CampaignMetricTotals;\n snapshotCount: number;\n usedCampaignRollups: boolean;\n}\n\nfunction groupChannelMix(\n rows: Array<Record<string, unknown>>,\n): Map<string, CampaignChannelMixEntry[]> {\n const grouped = new Map<string, CampaignChannelMixEntry[]>();\n for (const row of rows) {\n const campaignId = normalizeUuid(String(row.campaign_id), 'campaign id');\n const channelKind = String(row.channel_kind);\n if (!channelKind) throw new Error('Campaign channel kind is empty.');\n const entry = {\n channelKind,\n count: toSafeCount(row.channel_count, 'Campaign channel count'),\n };\n const entries = grouped.get(campaignId) ?? [];\n entries.push(entry);\n grouped.set(campaignId, entries);\n }\n return grouped;\n}\n\nfunction groupMetricTotals(\n rows: Array<Record<string, unknown>>,\n): Map<string, GroupedMetricTotals> {\n return new Map(\n rows.map((row) => {\n const campaignId = normalizeUuid(String(row.campaign_id), 'campaign id');\n return [\n campaignId,\n {\n totals: {\n spendCents: toSafeCount(row.spend_cents, 'Campaign spend total'),\n impressions: toSafeCount(\n row.impressions,\n 'Campaign impression total',\n ),\n clicks: toSafeCount(row.clicks, 'Campaign click total'),\n conversions: toSafeCount(\n row.conversions,\n 'Campaign conversion total',\n ),\n leads: toSafeCount(row.leads, 'Campaign lead total'),\n revenueCents: toSafeCount(\n row.revenue_cents,\n 'Campaign revenue total',\n ),\n },\n snapshotCount: toSafeCount(\n row.snapshot_count,\n 'Campaign snapshot count',\n ),\n usedCampaignRollups: toBooleanFlag(row.used_campaign_rollups),\n },\n ];\n }),\n );\n}\n\nfunction emptyMetricTotals(): GroupedMetricTotals {\n return {\n totals: {\n spendCents: 0,\n impressions: 0,\n clicks: 0,\n conversions: 0,\n leads: 0,\n revenueCents: 0,\n },\n snapshotCount: 0,\n usedCampaignRollups: false,\n };\n}\n\nfunction toBooleanFlag(value: unknown): boolean {\n return value === true || value === 1 || value === '1';\n}\n\nfunction checkedAdd(left: number, right: number, label: string): number {\n const sum = left + right;\n if (!Number.isSafeInteger(sum) || sum < 0) {\n throw new Error(`${label} is outside the safe integer range.`);\n }\n return sum;\n}\n\nfunction requireDatabase(options: { db?: unknown }): QueryDatabase {\n const db = options.db;\n if (!db || typeof db !== 'object' || !('query' in db)) {\n throw new Error(\n 'Campaign reporting and summaries require an initialized database connection.',\n );\n }\n return db as QueryDatabase;\n}\n\nfunction coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n const date =\n value instanceof Date\n ? value\n : new Date(typeof value === 'number' ? value : String(value));\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\nfunction toSafeCount(value: unknown, label: string): number {\n const count = Number(value);\n if (!Number.isSafeInteger(count) || count < 0) {\n throw new Error(`${label} is outside the safe integer range.`);\n }\n return count;\n}\n\nfunction normalizeCursor(\n cursor: CampaignCustomerCursorInput | undefined,\n): CampaignCustomerCursor | null {\n if (!cursor) return null;\n const id = normalizeUuid(cursor.id, 'campaign customer cursor id');\n if (cursor.startAt === null) return { id, startAt: null };\n const startAt = coerceDate(cursor.startAt);\n if (!startAt) throw new Error('campaign customer cursor startAt is invalid.');\n return { id, startAt };\n}\n\nfunction normalizeReportingAt(value: Date | string | number | undefined): Date {\n if (value === undefined) return new Date();\n const at = coerceDate(value);\n if (!at) throw new Error('campaign reporting at is invalid.');\n return at;\n}\n\nfunction cursorFromCampaign(\n campaign: Campaign | undefined,\n): CampaignCustomerCursor | null {\n if (!campaign?.id) return null;\n return { id: campaign.id, startAt: campaign.startAt };\n}\n\nfunction normalizeTenantScope(\n tenantId: string | null,\n label: string,\n): string | null {\n if (tenantId !== null) {\n const normalized = normalizeUuid(tenantId, 'tenantId');\n const tenantContext = getCurrentTenant();\n if (tenantContext && !isSuperAdminBypass()) {\n const contextTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n if (normalized !== contextTenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${label}.`,\n { tenantId: contextTenantId, attemptedTenantId: normalized },\n );\n }\n }\n return normalized;\n }\n const tenantContext = getCurrentTenant();\n if (tenantContext && !isSuperAdminBypass() && !isSystemContext()) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${label}: tenant context cannot read global Campaign rows.`,\n { tenantId: tenantContext.tenantId, attemptedTenantId: 'global' },\n );\n }\n return null;\n}\n\nfunction withCanonicalTenantContext<T>(\n tenantId: string | null,\n operation: () => Promise<T>,\n): Promise<T> {\n const tenantContext = getCurrentTenant();\n return tenantId !== null &&\n tenantContext &&\n !isSuperAdminBypass() &&\n !isSystemContext()\n ? withTenant({ ...tenantContext, tenantId }, operation)\n : operation();\n}\n\nfunction withCanonicalActiveTenant<T>(operation: () => Promise<T>): Promise<T> {\n const tenantContext = getCurrentTenant();\n if (!tenantContext || isSuperAdminBypass() || isSystemContext()) {\n return operation();\n }\n const normalizedTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n return normalizedTenantId === tenantContext.tenantId\n ? operation()\n : withTenant({ ...tenantContext, tenantId: normalizedTenantId }, operation);\n}\n\nfunction normalizeCampaignGetFilter(\n filter: string | SmrtWhereClause<Campaign>,\n): string | SmrtWhereClause<Campaign> {\n if (typeof filter !== 'string') return normalizeCampaignUuidWhere(filter);\n try {\n return normalizeUuid(filter, 'campaign id');\n } catch {\n return filter;\n }\n}\n\nfunction normalizeCampaignListOptions(\n options: SmrtListOptions<Campaign>,\n): SmrtListOptions<Campaign> {\n return options.where === undefined\n ? options\n : { ...options, where: normalizeCampaignUuidWhere(options.where) };\n}\n\nfunction normalizeCampaignUuidWhere<T>(where: T): T {\n if (Array.isArray(where)) {\n return where.map((entry) => normalizeCampaignUuidWhere(entry)) as T;\n }\n if (!where || typeof where !== 'object') return where;\n\n const normalized: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(where)) {\n const field = key.trim().split(/\\s+/u)[0];\n const label =\n field === 'id'\n ? 'campaign id'\n : field === 'customerId'\n ? 'customerId'\n : field === 'tenantId'\n ? 'tenantId'\n : null;\n if (!label) {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.map((uuid) =>\n typeof uuid === 'string' ? normalizeUuid(uuid, label) : uuid,\n );\n } else {\n normalized[key] =\n typeof value === 'string' ? normalizeUuid(value, label) : value;\n }\n }\n return normalized as T;\n}\n\nexport default CampaignCollection;\n","/**\n * Declared recipes for smrt-marketing (#4409, #4437): user-facing units of\n * functionality an app or agent can pick instead of the whole package.\n * The scanner reads these statics into the `recipes` array of `manifest.json`\n * and `smrt-knowledge.json`; nothing here runs at that point.\n *\n * @packageDocumentation\n */\n\nimport { SmrtRecipe } from '@happyvertical/smrt-core';\nimport { Campaign } from './models/Campaign.js';\nimport { CampaignChannel } from './models/CampaignChannel.js';\nimport { CampaignMetricSnapshot } from './models/CampaignMetricSnapshot.js';\n\n/** Campaigns: Plan a campaign, choose where it runs and see what it brought in. */\nexport class MarketingCampaignsRecipe extends SmrtRecipe {\n static id = 'marketing.campaigns';\n static help = './campaigns.recipe.md';\n static label = 'Campaigns';\n static summary =\n 'Plan a marketing campaign, set its budget and dates, choose where it runs and see what it brought in.';\n static synonyms = [\n 'marketing',\n 'marketing campaigns',\n 'promotions',\n 'advertising',\n 'ad campaigns',\n 'campaign results',\n ];\n static section = {\n id: 'grow',\n label: 'Grow',\n icon: 'tag',\n description: 'Bring in new customers and reward the people who help.',\n };\n static models = [Campaign, CampaignChannel, CampaignMetricSnapshot];\n static nav = [\n {\n label: 'Campaigns',\n model: Campaign,\n icon: 'tag',\n description:\n 'Every campaign, with its budget, dates and where it stands.',\n noun: 'campaign',\n },\n {\n label: 'Channels',\n model: CampaignChannel,\n icon: 'layers',\n description:\n 'Where each campaign runs, such as an ad group or a social post, and its share of the budget.',\n noun: 'channel',\n },\n ];\n static options = {\n Campaign: {\n fields: {\n name: { order: 1 },\n objective: { order: 2 },\n status: { order: 3 },\n startAt: { label: 'Starts', order: 4 },\n endAt: { label: 'Ends', order: 5 },\n budgetCents: { label: 'Budget (cents)', order: 6 },\n currency: { order: 7 },\n customerId: { label: 'Customer', visibility: 'advanced' },\n campaignKey: { label: 'Reference', visibility: 'advanced' },\n tenantId: { visibility: 'hidden' },\n metadata: { visibility: 'hidden' },\n },\n },\n CampaignChannel: {\n fields: {\n campaignId: { label: 'Campaign', order: 1 },\n channelKind: { label: 'Kind', order: 2 },\n channelRef: { label: 'Name', order: 3 },\n allocatedBudgetCents: { label: 'Budget share (cents)', order: 4 },\n status: { order: 5 },\n startAt: { label: 'Starts', order: 6 },\n endAt: { label: 'Ends', order: 7 },\n tenantId: { visibility: 'hidden' },\n },\n },\n CampaignMetricSnapshot: {\n fields: {\n campaignId: { label: 'Campaign', order: 1 },\n campaignChannelId: { label: 'Channel', order: 2 },\n periodStart: { label: 'From', order: 3 },\n periodEnd: { label: 'To', order: 4 },\n spendCents: { label: 'Spend (cents)', order: 5 },\n impressions: { order: 6 },\n clicks: { order: 7 },\n conversions: { order: 8 },\n leads: { order: 9 },\n revenueCents: { label: 'Revenue (cents)', order: 10 },\n source: { order: 11 },\n tenantId: { visibility: 'hidden' },\n dedupeKey: { visibility: 'hidden' },\n },\n },\n } as const;\n}\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n CampaignChannelCollection,\n CampaignCollection,\n CampaignMetricSnapshotCollection,\n} from '../collections/index.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { BudgetPacingResult } from '../types.js';\nimport {\n calculateCampaignPacing,\n calculatePacing,\n} from './pacing-calculation.js';\n\n/** Computes spend-to-budget pacing from immutable snapshots; stores nothing. */\nexport class BudgetPacingService {\n constructor(\n private readonly campaigns: CampaignCollection,\n private readonly channels: CampaignChannelCollection,\n private readonly snapshots: CampaignMetricSnapshotCollection,\n ) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<BudgetPacingService> {\n return new BudgetPacingService(\n await CampaignCollection.create(classOptions),\n await CampaignChannelCollection.create(classOptions),\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async getCampaignPacing(\n campaignId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n\n const all = await this.snapshots.findByCampaign(campaignId);\n const rollups = all.filter((snapshot) => !snapshot.campaignChannelId);\n const used = this.selectCampaignEvidence(all);\n return calculateCampaignPacing(\n campaign,\n {\n spendCents: used.reduce(\n (sum, snapshot) => sum + snapshot.spendCents,\n 0,\n ),\n snapshotCount: used.length,\n usedCampaignRollups: rollups.length > 0,\n },\n at,\n );\n }\n\n async getChannelPacing(\n campaignChannelId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const channel = await this.channels.get({ id: campaignChannelId });\n if (!channel) {\n throw new Error(`CampaignChannel '${campaignChannelId}' was not found.`);\n }\n const campaign = await this.campaigns.get({ id: channel.campaignId });\n if (!campaign) {\n throw new Error(`Campaign '${channel.campaignId}' was not found.`);\n }\n const snapshots = await this.snapshots.findByChannel(campaignChannelId);\n return {\n ...calculatePacing({\n campaign,\n evidence: {\n spendCents: snapshots.reduce(\n (sum, snapshot) => sum + snapshot.spendCents,\n 0,\n ),\n snapshotCount: snapshots.length,\n usedCampaignRollups: false,\n },\n budgetCents: channel.allocatedBudgetCents,\n startAt: channel.startAt ?? campaign.startAt,\n endAt: channel.endAt ?? campaign.endAt,\n at,\n }),\n campaignChannelId,\n };\n }\n\n /**\n * Prefer a campaign rollup for each exact reporting period, while retaining\n * channel evidence for periods that do not yet have a rollup. This prevents\n * double counting without dropping earlier/later channel-only periods.\n */\n private selectCampaignEvidence(\n snapshots: CampaignMetricSnapshot[],\n ): CampaignMetricSnapshot[] {\n const rollupPeriods = new Set(\n snapshots\n .filter((snapshot) => !snapshot.campaignChannelId)\n .map((snapshot) => this.periodKey(snapshot)),\n );\n return snapshots.filter(\n (snapshot) =>\n !snapshot.campaignChannelId ||\n !rollupPeriods.has(this.periodKey(snapshot)),\n );\n }\n\n private periodKey(snapshot: CampaignMetricSnapshot): string {\n return `${snapshot.periodStart.toISOString()}:${snapshot.periodEnd.toISOString()}`;\n }\n}\n\nexport default BudgetPacingService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignCollection } from '../collections/CampaignCollection.js';\nimport type { Campaign } from '../models/Campaign.js';\nimport type { CampaignStatus } from '../types.js';\n\n/** Single authoritative write path for Campaign lifecycle changes. */\nexport class CampaignLifecycleService {\n constructor(private readonly campaigns: CampaignCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CampaignLifecycleService> {\n return new CampaignLifecycleService(\n await CampaignCollection.create(classOptions),\n );\n }\n\n async transition(\n campaignId: string,\n next: CampaignStatus,\n ): Promise<Campaign> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n campaign.transitionTo(next);\n await campaign.save();\n return campaign;\n }\n\n async schedule(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'scheduled');\n }\n\n async activate(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async pause(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'paused');\n }\n\n async resume(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async complete(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'completed');\n }\n\n async archive(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'archived');\n }\n}\n\nexport default CampaignLifecycleService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshotCollection } from '../collections/CampaignMetricSnapshotCollection.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\n/** Idempotent, validation-first ingestion for immutable metric evidence. */\nexport class MetricIngestionService {\n constructor(private readonly snapshots: CampaignMetricSnapshotCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<MetricIngestionService> {\n return new MetricIngestionService(\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async ingest(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n if (!options.campaignId) {\n throw new Error('Metric ingestion requires campaignId.');\n }\n if (!options.source?.trim()) {\n throw new Error('Metric ingestion requires source.');\n }\n if (!options.dedupeKey?.trim()) {\n throw new Error('Metric ingestion requires dedupeKey.');\n }\n if (options.periodStart == null || options.periodEnd == null) {\n throw new Error('Metric ingestion requires periodStart and periodEnd.');\n }\n return await this.snapshots.getOrCreateByDedupeKey(options);\n }\n}\n\nexport default MetricIngestionService;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["elapsedFraction","tenantContext"],"sources":["../src/__smrt-register__.ts","../src/errors.ts","../src/customer-scope.ts","../src/models/Campaign.ts","../src/models/CampaignChannel.ts","../src/collections/CampaignChannelCollection.ts","../src/services/pacing-calculation.ts","../src/models/CampaignMetricSnapshot.ts","../src/collections/CampaignMetricSnapshotCollection.ts","../src/collections/CampaignCollection.ts","../src/recipes.ts","../src/services/BudgetPacingService.ts","../src/services/CampaignLifecycleService.ts","../src/services/MetricIngestionService.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-marketing\\\",\\\"packageVersion\\\":\\\"0.57.44\\\",\\\"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\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\",\\\"delete\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\",\\\"delete\\\"]}},\\\"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 \\\\\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"SmrtWhereClause<Campaign>\\\"}]},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"object\\\",\\\"optional\\\":true,\\\"memberTypes\\\":[\\\"CollectionCacheConfig | false\\\"]}],\\\"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, unknown>[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByCampaignKey\\\":{\\\"name\\\":\\\"findByCampaignKey\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"campaignKey\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":true,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]}],\\\"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,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"ListCampaignsByCustomerOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<CampaignCustomerPage>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"listReportingByCustomer\\\":{\\\"name\\\":\\\"listReportingByCustomer\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"ListCampaignReportingByCustomerOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<CampaignReportingPage>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"summarizeByCustomers\\\":{\\\"name\\\":\\\"summarizeByCustomers\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"tenantId\\\",\\\"type\\\":\\\"string | null\\\",\\\"optional\\\":false,\\\"unionBranches\\\":[{\\\"type\\\":\\\"string\\\"},{\\\"type\\\":\\\"null\\\"}]},{\\\"name\\\":\\\"customerIds\\\",\\\"type\\\":\\\"string[]\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<CampaignCustomerSummary[]>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"campaigns\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\",\\\"update\\\"]}},\\\"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 \\\\\\\"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,\\\"nullsNotDistinct\\\":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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"api\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]},\\\"mcp\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]},\\\"cli\\\":{\\\"include\\\":[\\\"list\\\",\\\"get\\\",\\\"create\\\"]}},\\\"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 \\\\\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The customer this campaign is run for, if any.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The customer this campaign is run for, if any.\\\",\\\"validate\\\":true,\\\"nullable\\\":true}},\\\"campaignKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"A short reference of your own for this campaign, kept the same once it is in use.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"A short reference of your own for this campaign, kept the same once it is in use.\\\"}},\\\"name\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What the campaign is called.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"What the campaign is called.\\\"}},\\\"objective\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What the campaign is meant to achieve, such as awareness or new leads.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"What the campaign is meant to achieve, such as awareness or new leads.\\\"}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"draft\\\",\\\"description\\\":\\\"Where the campaign stands, from draft through active to completed and archived.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"Where the campaign stands, from draft through active to completed and archived.\\\"}},\\\"startAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When the campaign starts.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When the campaign starts.\\\"}},\\\"endAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When the campaign ends.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When the campaign ends.\\\"}},\\\"budgetCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"The total you plan to spend on the campaign, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The total you plan to spend on the campaign, in cents.\\\"}},\\\"currency\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"USD\\\",\\\"description\\\":\\\"The currency every amount on this campaign is in.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The currency every amount on this campaign is in.\\\"}},\\\"metadata\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"{}\\\"}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"transitionTo\\\":{\\\"name\\\":\\\"transitionTo\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"next\\\",\\\"type\\\":\\\"CampaignStatus\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"this\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getMetadata\\\":{\\\"name\\\":\\\"getMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Record<string, unknown>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setMetadata\\\":{\\\"name\\\":\\\"setMetadata\\\",\\\"async\\\":false,\\\"parameters\\\":[{\\\"name\\\":\\\"metadata\\\",\\\"type\\\":\\\"Record<string, unknown>\\\",\\\"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\\\",\\\"displayLabelField\\\":\\\"name\\\",\\\"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,\\\"nullsNotDistinct\\\":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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The campaign this channel runs for.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The campaign this channel runs for.\\\",\\\"required\\\":true}},\\\"channelKind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"What sort of channel this is, such as an ad group, a social post, a message, content or an event.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"What sort of channel this is, such as an ad group, a social post, a message, content or an event.\\\"}},\\\"channelRef\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"The name or reference the channel goes by, so its results can be matched to it.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The name or reference the channel goes by, so its results can be matched to it.\\\"}},\\\"allocatedBudgetCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"The share of the campaign budget set aside for this channel, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The share of the campaign budget set aside for this channel, in cents.\\\"}},\\\"startAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When this channel starts, if different from the campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When this channel starts, if different from the campaign.\\\"}},\\\"endAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false,\\\"description\\\":\\\"When this channel ends, if different from the campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"When this channel ends, if different from the campaign.\\\"}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"default\\\":\\\"planned\\\",\\\"description\\\":\\\"Where this channel stands, from planned through active to completed or cancelled.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"Where this channel stands, from planned through active to completed or cancelled.\\\"}}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"},\\\"@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\\\",\\\"description\\\":\\\"The campaign these results belong to.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The campaign these results belong to.\\\",\\\"required\\\":true}},\\\"campaignChannelId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"CampaignChannel\\\",\\\"description\\\":\\\"The channel these results came from. Empty when they cover the whole campaign.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"The channel these results came from. Empty when they cover the whole campaign.\\\",\\\"nullable\\\":true}},\\\"periodStart\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":true,\\\"description\\\":\\\"The first day of the period these results cover.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The first day of the period these results cover.\\\"}},\\\"periodEnd\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":true,\\\"description\\\":\\\"The last day of the period these results cover.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The last day of the period these results cover.\\\"}},\\\"spendCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"What was spent in the period, in cents.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"What was spent in the period, in cents.\\\"}},\\\"impressions\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many times the campaign was shown.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many times the campaign was shown.\\\"}},\\\"clicks\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many times someone clicked through.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many times someone clicked through.\\\"}},\\\"conversions\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many people did what the campaign asked, such as buying.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many people did what the campaign asked, such as buying.\\\"}},\\\"leads\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"default\\\":0,\\\"description\\\":\\\"How many new leads came in.\\\",\\\"_meta\\\":{\\\"description\\\":\\\"How many new leads came in.\\\"}},\\\"revenueCents\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"description\\\":\\\"The sales brought in during the period, in cents, if known.\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"description\\\":\\\"The sales brought in during the period, in cents, if known.\\\"}},\\\"source\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"Where these results were reported from.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"Where these results were reported from.\\\"}},\\\"dedupeKey\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"description\\\":\\\"The reference this result is filed under, so the same report is never counted twice.\\\",\\\"_meta\\\":{\\\"required\\\":true,\\\"description\\\":\\\"The reference this result is filed under, so the same report is never counted twice.\\\"}}},\\\"methods\\\":{\\\"initialize\\\":{\\\"name\\\":\\\"initialize\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"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\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-marketing\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"recipes\\\":[{\\\"id\\\":\\\"marketing.campaigns\\\",\\\"className\\\":\\\"MarketingCampaignsRecipe\\\",\\\"label\\\":\\\"Campaigns\\\",\\\"summary\\\":\\\"Plan a marketing campaign, set its budget and dates, choose where it runs and see what it brought in.\\\",\\\"synonyms\\\":[\\\"marketing\\\",\\\"marketing campaigns\\\",\\\"promotions\\\",\\\"advertising\\\",\\\"ad campaigns\\\",\\\"campaign results\\\"],\\\"models\\\":[\\\"@happyvertical/smrt-marketing:Campaign\\\",\\\"@happyvertical/smrt-marketing:CampaignChannel\\\",\\\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\\\"],\\\"nav\\\":[{\\\"label\\\":\\\"Campaigns\\\",\\\"model\\\":\\\"@happyvertical/smrt-marketing:Campaign\\\",\\\"icon\\\":\\\"tag\\\",\\\"description\\\":\\\"Every campaign, with its budget, dates and where it stands.\\\",\\\"noun\\\":\\\"campaign\\\"},{\\\"label\\\":\\\"Channels\\\",\\\"model\\\":\\\"@happyvertical/smrt-marketing:CampaignChannel\\\",\\\"icon\\\":\\\"layers\\\",\\\"description\\\":\\\"Where each campaign runs, such as an ad group or a social post, and its share of the budget.\\\",\\\"noun\\\":\\\"channel\\\"}],\\\"requires\\\":[],\\\"section\\\":{\\\"id\\\":\\\"grow\\\",\\\"label\\\":\\\"Grow\\\",\\\"icon\\\":\\\"tag\\\",\\\"description\\\":\\\"Bring in new customers and reward the people who help.\\\"},\\\"options\\\":{\\\"@happyvertical/smrt-marketing:Campaign\\\":{\\\"fields\\\":{\\\"name\\\":{\\\"order\\\":1},\\\"objective\\\":{\\\"order\\\":2},\\\"status\\\":{\\\"order\\\":3},\\\"startAt\\\":{\\\"label\\\":\\\"Starts\\\",\\\"order\\\":4},\\\"endAt\\\":{\\\"label\\\":\\\"Ends\\\",\\\"order\\\":5},\\\"budgetCents\\\":{\\\"label\\\":\\\"Budget (cents)\\\",\\\"order\\\":6},\\\"currency\\\":{\\\"order\\\":7},\\\"customerId\\\":{\\\"label\\\":\\\"Customer\\\",\\\"visibility\\\":\\\"advanced\\\"},\\\"campaignKey\\\":{\\\"label\\\":\\\"Reference\\\",\\\"visibility\\\":\\\"advanced\\\"},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"},\\\"metadata\\\":{\\\"visibility\\\":\\\"hidden\\\"}}},\\\"@happyvertical/smrt-marketing:CampaignChannel\\\":{\\\"fields\\\":{\\\"campaignId\\\":{\\\"label\\\":\\\"Campaign\\\",\\\"order\\\":1},\\\"channelKind\\\":{\\\"label\\\":\\\"Kind\\\",\\\"order\\\":2},\\\"channelRef\\\":{\\\"label\\\":\\\"Name\\\",\\\"order\\\":3},\\\"allocatedBudgetCents\\\":{\\\"label\\\":\\\"Budget share (cents)\\\",\\\"order\\\":4},\\\"status\\\":{\\\"order\\\":5},\\\"startAt\\\":{\\\"label\\\":\\\"Starts\\\",\\\"order\\\":6},\\\"endAt\\\":{\\\"label\\\":\\\"Ends\\\",\\\"order\\\":7},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"}}},\\\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\\\":{\\\"fields\\\":{\\\"campaignId\\\":{\\\"label\\\":\\\"Campaign\\\",\\\"order\\\":1},\\\"campaignChannelId\\\":{\\\"label\\\":\\\"Channel\\\",\\\"order\\\":2},\\\"periodStart\\\":{\\\"label\\\":\\\"From\\\",\\\"order\\\":3},\\\"periodEnd\\\":{\\\"label\\\":\\\"To\\\",\\\"order\\\":4},\\\"spendCents\\\":{\\\"label\\\":\\\"Spend (cents)\\\",\\\"order\\\":5},\\\"impressions\\\":{\\\"order\\\":6},\\\"clicks\\\":{\\\"order\\\":7},\\\"conversions\\\":{\\\"order\\\":8},\\\"leads\\\":{\\\"order\\\":9},\\\"revenueCents\\\":{\\\"label\\\":\\\"Revenue (cents)\\\",\\\"order\\\":10},\\\"source\\\":{\\\"order\\\":11},\\\"tenantId\\\":{\\\"visibility\\\":\\\"hidden\\\"},\\\"dedupeKey\\\":{\\\"visibility\\\":\\\"hidden\\\"}}}},\\\"help\\\":{\\\"markdown\\\":\\\"## Overview\\\\n\\\\nCampaigns is where you plan a push to bring in customers: a sale, a launch, a\\\\nseasonal promotion. Each campaign has a budget, start and end dates and a\\\\nstatus that shows where it stands, from draft through active to completed. A\\\\ncampaign runs on one or more channels, such as an ad group, a social post, an\\\\nemail or an event, each with its share of the budget. Results come back from\\\\nthe channels' reporting, period by period, so you can see what each campaign\\\\nand channel brought in for what it cost.\\\\n\\\\n## Tasks\\\\n\\\\n### Plan a campaign\\\\n\\\\n1. Open the list of campaigns and choose **New**.\\\\n2. Type the **{field:Campaign.name}**, for example \\\\\\\"Spring widget sale\\\\\\\".\\\\n3. Say what it is for in **{field:Campaign.objective}**, such as awareness or new leads.\\\\n4. Set when it **{field:Campaign.startAt}** and when it **{field:Campaign.endAt}**. The end cannot be before the start.\\\\n5. Enter the **{field:Campaign.budgetCents}** as a whole number of cents, so $500 is 50000, and check the **{field:Campaign.currency}**.\\\\n6. Save. A new campaign starts as a draft.\\\\n\\\\n### See where a campaign stands\\\\n\\\\n1. Open the list of campaigns.\\\\n2. The **{field:Campaign.status}** shows whether each one is still a draft, scheduled, active, paused, completed or archived. It moves forward as the campaign is scheduled, run and wrapped up; you do not set it by hand.\\\\n3. Open a campaign to change its name, dates or budget while it is still being planned.\\\\n\\\\n### Add a channel to a campaign\\\\n\\\\n1. Open the list of channels and choose **New**.\\\\n2. Pick the **{field:CampaignChannel.campaignId}** it belongs to.\\\\n3. Choose the **{field:CampaignChannel.channelKind}**, such as an ad group, a social post, a message, content or an event.\\\\n4. Type the **{field:CampaignChannel.channelRef}** the channel goes by, so its results can be matched to it later.\\\\n5. Enter its **{field:CampaignChannel.allocatedBudgetCents}** as a whole number of cents.\\\\n6. If the channel runs on different dates from the campaign, set when it **{field:CampaignChannel.startAt}** and **{field:CampaignChannel.endAt}**.\\\\n7. Save. You can edit a channel later, or remove one that will not run.\\\\n\\\\n### Read the results\\\\n\\\\nResults arrive from the channels' reporting; nobody types them in. Each one\\\\ncovers a period for one campaign, or for one channel within it. A result is\\\\nnever changed once it is in: a correction arrives as a new result for the same\\\\nperiod.\\\\n\\\\n1. Open a campaign and look at its results, listed under the campaign.\\\\n2. Each result names the **{field:CampaignMetricSnapshot.campaignId}** it belongs to. A result with no **{field:CampaignMetricSnapshot.campaignChannelId}** covers the whole campaign.\\\\n3. Check the period, **{field:CampaignMetricSnapshot.periodStart}** to **{field:CampaignMetricSnapshot.periodEnd}**.\\\\n4. Compare the **{field:CampaignMetricSnapshot.spendCents}** with the **{field:CampaignMetricSnapshot.clicks}**, **{field:CampaignMetricSnapshot.leads}** and **{field:CampaignMetricSnapshot.conversions}** it brought in, and the **{field:CampaignMetricSnapshot.revenueCents}** where the channel reports it.\\\\n5. The **{field:CampaignMetricSnapshot.source}** says which channel's reporting sent the result.\\\\n\\\",\\\"fieldRefs\\\":[\\\"Campaign.budgetCents\\\",\\\"Campaign.currency\\\",\\\"Campaign.endAt\\\",\\\"Campaign.name\\\",\\\"Campaign.objective\\\",\\\"Campaign.startAt\\\",\\\"Campaign.status\\\",\\\"CampaignChannel.allocatedBudgetCents\\\",\\\"CampaignChannel.campaignId\\\",\\\"CampaignChannel.channelKind\\\",\\\"CampaignChannel.channelRef\\\",\\\"CampaignChannel.endAt\\\",\\\"CampaignChannel.startAt\\\",\\\"CampaignMetricSnapshot.campaignChannelId\\\",\\\"CampaignMetricSnapshot.campaignId\\\",\\\"CampaignMetricSnapshot.clicks\\\",\\\"CampaignMetricSnapshot.conversions\\\",\\\"CampaignMetricSnapshot.leads\\\",\\\"CampaignMetricSnapshot.periodEnd\\\",\\\"CampaignMetricSnapshot.periodStart\\\",\\\"CampaignMetricSnapshot.revenueCents\\\",\\\"CampaignMetricSnapshot.source\\\",\\\"CampaignMetricSnapshot.spendCents\\\"]},\\\"demo\\\":{\\\"mode\\\":\\\"server\\\",\\\"reasons\\\":[\\\"Needs a server because its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624).\\\"]}}],\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-commerce\\\",\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3624\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","/** Fail-closed customer/tenant mismatch without revealing row existence. */\nexport class CampaignCustomerScopeError extends Error {\n readonly code = 'CAMPAIGN_CUSTOMER_SCOPE';\n\n constructor(label: string) {\n super(`${label}: customer scope validation failed.`);\n this.name = 'CampaignCustomerScopeError';\n }\n}\n","import {\n ObjectRegistry,\n type SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { CampaignCustomerScopeError } from './errors.js';\n\nconst CUSTOMER_QUALIFIED_NAME = '@happyvertical/smrt-commerce:Customer';\nconst SAFE_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;\n\ntype QueryDatabase = Pick<DatabaseInterface, 'query' | 'url'>;\n\ninterface DatabaseOptions {\n db?: SmrtObjectOptions['db'];\n}\n\nfunction requireDatabase(options: DatabaseOptions): QueryDatabase {\n const db = options.db;\n if (!db || typeof db !== 'object' || !('query' in db)) {\n throw new Error(\n 'Campaign customer queries require an initialized database connection.',\n );\n }\n return db as QueryDatabase;\n}\n\nasync function resolveCustomerTable(options: DatabaseOptions): Promise<string> {\n await ObjectRegistry.ensureManifestLoaded(CUSTOMER_QUALIFIED_NAME);\n const target =\n ObjectRegistry.getClassByQualifiedName(CUSTOMER_QUALIFIED_NAME) ??\n ObjectRegistry.getClass(CUSTOMER_QUALIFIED_NAME);\n if (!target) {\n throw new Error(\n 'Campaign customer queries require @happyvertical/smrt-commerce:Customer to be registered.',\n );\n }\n\n const probe = new target.constructor(options) as SmrtObject;\n const tableName = probe.tableName;\n if (!SAFE_SQL_IDENTIFIER.test(tableName)) {\n throw new Error('Campaign customer table has an invalid identifier.');\n }\n return tableName;\n}\n\n/**\n * Verify a bounded set of canonical commerce Customers in one database read.\n * The generic failure deliberately does not reveal whether an id is missing or\n * belongs to another tenant.\n */\nexport async function assertCustomersBelongToTenant(\n options: DatabaseOptions,\n tenantId: string | null,\n customerIds: readonly string[],\n label: string,\n lock: 'none' | 'share' | 'update' = 'none',\n): Promise<void> {\n if (customerIds.length === 0) return;\n const normalizedCustomerIds = customerIds.map((customerId) => {\n try {\n return normalizeUuid(customerId, 'customerId');\n } catch {\n throw new CampaignCustomerScopeError(label);\n }\n });\n const normalizedTenantId = tenantId?.toLowerCase() ?? null;\n const db = requireDatabase(options);\n const tableName = await resolveCustomerTable(options);\n const placeholders = normalizedCustomerIds.map(() => '?').join(', ');\n const lockClause = postgresLockClause(db, lock);\n const result = await db.query(\n `SELECT id, tenant_id FROM ${tableName} WHERE id IN (${placeholders})${lockClause}`,\n ...normalizedCustomerIds,\n );\n const tenantById = new Map(\n result.rows.map((row) => [\n String(row.id).toLowerCase(),\n row.tenant_id == null ? null : String(row.tenant_id).toLowerCase(),\n ]),\n );\n const valid = normalizedCustomerIds.every(\n (customerId) =>\n tenantById.has(customerId) &&\n tenantById.get(customerId) === normalizedTenantId,\n );\n if (!valid) {\n throw new CampaignCustomerScopeError(label);\n }\n}\n\nfunction postgresLockClause(\n db: QueryDatabase,\n lock: 'none' | 'share' | 'update',\n): string {\n if (lock === 'none' || !/^postgres(?:ql)?:/iu.test(db.url)) return '';\n return lock === 'update' ? ' FOR UPDATE' : ' FOR SHARE';\n}\n\n/** Parse and canonicalize one UUID without echoing the rejected value. */\nexport function normalizeUuid(value: string, label: string): string {\n if (!UUID_RE.test(value)) {\n throw new Error(`${label} must be a canonical UUID.`);\n }\n return value.toLowerCase();\n}\n","/** Campaign identity, budget, schedule, and guarded lifecycle. */\n\nimport {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n ValidationError,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertCustomersBelongToTenant,\n normalizeUuid,\n} from '../customer-scope.js';\nimport { CampaignCustomerScopeError } from '../errors.js';\nimport {\n CAMPAIGN_STATUSES,\n type CampaignOptions,\n type CampaignStatus,\n} from '../types.js';\n\nconst CAMPAIGN_STATUS_TRANSITIONS: Record<CampaignStatus, CampaignStatus[]> = {\n draft: ['scheduled'],\n scheduled: ['active'],\n active: ['paused', 'completed'],\n paused: ['active', 'completed'],\n completed: ['archived'],\n archived: [],\n};\n\nconst loadedCampaignStatus = new WeakMap<Campaign, CampaignStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaigns',\n conflictColumns: ['tenant_id', 'campaign_key'],\n indexes: [\n {\n name: 'campaigns_tenant_id_customer_id_start_at_id_idx',\n columns: ['tenantId', 'customerId', 'startAt', 'id'],\n },\n ],\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update'] },\n})\nexport class Campaign extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Canonical commerce Customer that owns this campaign, when assigned. */\n @field({ description: 'The customer this campaign is run for, if any.' })\n @crossPackageRef('@happyvertical/smrt-commerce:Customer', {\n nullable: true,\n validate: true,\n })\n customerId: string | null = null;\n\n /** Stable caller-defined key used by CRM/referral systems. */\n @field({\n required: true,\n description:\n 'A short reference of your own for this campaign, kept the same once it is in use.',\n })\n campaignKey: string = '';\n\n @field({ required: true, description: 'What the campaign is called.' })\n name: string = '';\n\n /** Open objective key such as `awareness` or `demand_generation`. */\n @field({\n description:\n 'What the campaign is meant to achieve, such as awareness or new leads.',\n })\n objective: string = '';\n\n @field({\n description:\n 'Where the campaign stands, from draft through active to completed and archived.',\n })\n status: CampaignStatus = 'draft';\n\n @field({ description: 'When the campaign starts.' })\n startAt: Date | null = null;\n\n @field({ description: 'When the campaign ends.' })\n endAt: Date | null = null;\n\n /** Total campaign budget in integer cents. */\n @field({\n description: 'The total you plan to spend on the campaign, in cents.',\n })\n budgetCents: number = 0;\n\n /** ISO 4217 currency for every campaign/channel amount. */\n @field({\n description: 'The currency every amount on this campaign is in.',\n })\n currency: string = 'USD';\n\n metadata: string = '{}';\n\n constructor(options: CampaignOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.campaignKey !== undefined)\n this.campaignKey = options.campaignKey;\n if (options.name !== undefined) this.name = options.name;\n if (options.objective !== undefined) this.objective = options.objective;\n if (options.status !== undefined) this.status = options.status;\n if (options.startAt !== undefined)\n this.startAt = Campaign.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = Campaign.coerceDate(options.endAt);\n if (options.budgetCents !== undefined)\n this.budgetCents = options.budgetCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n override async initialize(): Promise<this> {\n if (this.id) {\n try {\n this.id = normalizeUuid(this.id, 'id');\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.initialize');\n }\n throw error;\n }\n }\n await super.initialize();\n this.startAt = Campaign.coerceDate(this.startAt);\n this.endAt = Campaign.coerceDate(this.endAt);\n if (this.isPersisted) loadedCampaignStatus.set(this, this.status);\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n if (this.customerId === null) return this.saveLifecycle(options);\n\n const db = this.db;\n if (typeof db.transaction !== 'function') {\n throw new Error(\n 'Campaign.save requires a database adapter with transaction support.',\n );\n }\n return db.transaction(async (transactionDb) =>\n this.withDatabase(transactionDb, async (bound) =>\n bound.saveLifecycle(options),\n ),\n );\n }\n\n private async saveLifecycle(options?: SmrtSaveOptions): Promise<this> {\n this.prepareIdentityForSave();\n if (this.customerId !== null) {\n await assertCustomersBelongToTenant(\n this.options,\n this.tenantId,\n [this.customerId],\n 'Campaign.save',\n 'update',\n );\n }\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n this.assertSchedule();\n let result: this;\n try {\n const persist = async () => (await super.save(options)) as this;\n const tenantContext =\n isSystemContext() || isSuperAdminBypass()\n ? undefined\n : getCurrentTenant();\n result = tenantContext\n ? await withTenant(\n {\n ...tenantContext,\n tenantId: this.tenantId ?? tenantContext.tenantId,\n },\n persist,\n )\n : await persist();\n } catch (error) {\n if (\n error instanceof ValidationError &&\n (error.code === 'VALIDATION_CROSS_PACKAGE_REF_MISSING' ||\n error.code === 'VALIDATION_CROSS_PACKAGE_REF_UNREGISTERED')\n ) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n loadedCampaignStatus.set(this, this.status);\n return result;\n }\n\n /** Canonicalize and authorize identity before any status or scope read. */\n private prepareIdentityForSave(): void {\n try {\n if (this.id) this.id = normalizeUuid(this.id, 'id');\n if (this.customerId !== null) {\n this.customerId = normalizeUuid(this.customerId, 'customerId');\n }\n if (this.tenantId !== null) {\n this.tenantId = normalizeUuid(this.tenantId, 'tenantId');\n }\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n\n if (isSystemContext() || isSuperAdminBypass()) return;\n const tenantContext = getCurrentTenant();\n if (!tenantContext) return;\n\n let contextTenantId: string;\n try {\n contextTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n } catch (error) {\n if (this.customerId !== null) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n throw error;\n }\n if (this.tenantId !== null && this.tenantId !== contextTenantId) {\n throw new TenantIsolationError(\n 'Tenant isolation violation in Campaign.save.',\n { tenantId: contextTenantId, attemptedTenantId: this.tenantId },\n );\n }\n this.tenantId = contextTenantId;\n }\n\n /** Apply one legal transition in memory; callers choose the save boundary. */\n transitionTo(next: CampaignStatus): this {\n if (next === this.status) return this;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[this.status] ?? [];\n if (!allowed.includes(next)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: cannot transition ` +\n `'${this.status}' → '${next}'.`,\n );\n }\n this.status = next;\n return this;\n }\n\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this.metadata = JSON.stringify(metadata ?? {});\n }\n\n private assertSchedule(): void {\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('Campaign endAt must be on or after startAt.');\n }\n if (!Number.isInteger(this.budgetCents) || this.budgetCents < 0) {\n throw new Error('Campaign budgetCents must be a non-negative integer.');\n }\n }\n\n private async resolvePriorStatus(): Promise<CampaignStatus | undefined> {\n if (this.id) {\n let scopedRow: Record<string, unknown> | null = null;\n try {\n scopedRow = await this.db.get(this.tableName, {\n id: this.id,\n tenant_id: this.tenantId,\n });\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n if (scopedRow?.status != null) {\n return scopedRow.status as CampaignStatus;\n }\n\n // A caller-supplied ID already owned by another lane must not expose its\n // lifecycle state or be repurposed by an upsert.\n let rowInAnotherLane: Record<string, unknown> | null = null;\n try {\n rowInAnotherLane = await this.db.get(this.tableName, { id: this.id });\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n if (rowInAnotherLane) {\n throw new CampaignCustomerScopeError('Campaign.save');\n }\n }\n if (this.campaignKey) {\n try {\n const row = await this.db.get(this.tableName, {\n tenant_id: this.tenantId,\n campaign_key: this.campaignKey,\n });\n if (row && row.status != null) return row.status as CampaignStatus;\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n }\n return loadedCampaignStatus.get(this);\n }\n\n private assertStatusTransition(prior: CampaignStatus | undefined): void {\n if (!CAMPAIGN_STATUSES.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (prior === undefined || prior === this.status) return;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'.`,\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default Campaign;\n","/** Generic links from a campaign to cross-package channel execution rows. */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CampaignChannelOptions,\n CampaignChannelStatus,\n} from '../types.js';\nimport { CAMPAIGN_CHANNEL_STATUSES } from '../types.js';\nimport { Campaign } from './Campaign.js';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_channels',\n conflictColumns: ['campaign_id', 'channel_kind', 'channel_ref'],\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update', 'delete'] },\n})\nexport class CampaignChannel extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ description: 'The campaign this channel runs for.' })\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Open kind vocabulary; recommended values live in CAMPAIGN_CHANNEL_KINDS. */\n @field({\n required: true,\n description:\n 'What sort of channel this is, such as an ad group, a social post, a message, content or an event.',\n })\n channelKind: string = '';\n\n /** Opaque identifier in the namespace named by channelKind. */\n @field({\n required: true,\n description:\n 'The name or reference the channel goes by, so its results can be matched to it.',\n })\n channelRef: string = '';\n\n @field({\n description:\n 'The share of the campaign budget set aside for this channel, in cents.',\n })\n allocatedBudgetCents: number = 0;\n\n /** Optional execution-specific override of the Campaign start. */\n @field({\n description: 'When this channel starts, if different from the campaign.',\n })\n startAt: Date | null = null;\n\n /** Optional execution-specific override of the Campaign end. */\n @field({\n description: 'When this channel ends, if different from the campaign.',\n })\n endAt: Date | null = null;\n\n @field({\n description:\n 'Where this channel stands, from planned through active to completed or cancelled.',\n })\n status: CampaignChannelStatus = 'planned';\n\n constructor(options: CampaignChannelOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.channelKind !== undefined)\n this.channelKind = options.channelKind;\n if (options.channelRef !== undefined) this.channelRef = options.channelRef;\n if (options.allocatedBudgetCents !== undefined)\n this.allocatedBudgetCents = options.allocatedBudgetCents;\n if (options.startAt !== undefined)\n this.startAt = CampaignChannel.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = CampaignChannel.coerceDate(options.endAt);\n if (options.status !== undefined) this.status = options.status;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.startAt = CampaignChannel.coerceDate(this.startAt);\n this.endAt = CampaignChannel.coerceDate(this.endAt);\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n if (!CAMPAIGN_CHANNEL_STATUSES.includes(this.status)) {\n throw new Error(\n `CampaignChannel ${this.channelRef || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (\n !Number.isInteger(this.allocatedBudgetCents) ||\n this.allocatedBudgetCents < 0\n ) {\n throw new Error(\n 'CampaignChannel allocatedBudgetCents must be a non-negative integer.',\n );\n }\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('CampaignChannel endAt must be on or after startAt.');\n }\n return (await super.save(options)) as this;\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignChannel;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignChannel } from '../models/CampaignChannel.js';\n\nexport class CampaignChannelCollection extends SmrtCollection<CampaignChannel> {\n static readonly _itemClass = CampaignChannel;\n\n async findByCampaign(campaignId: string): Promise<CampaignChannel[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'start_at ASC',\n });\n }\n\n async findByExecutionRef(\n channelKind: string,\n channelRef: string,\n ): Promise<CampaignChannel[]> {\n return await this.list({ where: { channelKind, channelRef } });\n }\n}\n\nexport default CampaignChannelCollection;\n","import type { Campaign } from '../models/Campaign.js';\nimport type { BudgetPacingResult, BudgetPacingStatus } from '../types.js';\n\nexport interface BudgetPacingEvidence {\n spendCents: number;\n snapshotCount: number;\n usedCampaignRollups: boolean;\n}\n\n/** Canonical campaign pacing calculation for grouped and row-level evidence. */\nexport function calculateCampaignPacing(\n campaign: Campaign,\n evidence: BudgetPacingEvidence,\n at = new Date(),\n): BudgetPacingResult {\n return calculatePacing({\n campaign,\n evidence,\n budgetCents: campaign.budgetCents,\n startAt: campaign.startAt,\n endAt: campaign.endAt,\n at,\n });\n}\n\nexport function calculatePacing({\n campaign,\n evidence,\n budgetCents,\n startAt,\n endAt,\n at,\n}: {\n campaign: Campaign;\n evidence: BudgetPacingEvidence;\n budgetCents: number;\n startAt: Date | null;\n endAt: Date | null;\n at: Date;\n}): BudgetPacingResult {\n const elapsed = elapsedFraction(startAt, endAt, at);\n const expectedSpendCents =\n elapsed === null ? null : Math.round(budgetCents * elapsed);\n const varianceCents =\n expectedSpendCents === null\n ? null\n : evidence.spendCents - expectedSpendCents;\n return {\n campaignId: campaign.id ?? '',\n currency: campaign.currency,\n budgetCents,\n spendCents: evidence.spendCents,\n remainingCents: budgetCents - evidence.spendCents,\n expectedSpendCents,\n varianceCents,\n budgetFraction: budgetCents > 0 ? evidence.spendCents / budgetCents : null,\n elapsedFraction: elapsed,\n status: pacingStatus({\n campaign,\n budgetCents,\n spendCents: evidence.spendCents,\n elapsedFraction: elapsed,\n varianceCents,\n startAt,\n at,\n }),\n snapshotCount: evidence.snapshotCount,\n usedCampaignRollups: evidence.usedCampaignRollups,\n };\n}\n\nfunction pacingStatus({\n campaign,\n budgetCents,\n spendCents,\n elapsedFraction,\n varianceCents,\n startAt,\n at,\n}: {\n campaign: Campaign;\n budgetCents: number;\n spendCents: number;\n elapsedFraction: number | null;\n varianceCents: number | null;\n startAt: Date | null;\n at: Date;\n}): BudgetPacingStatus {\n if (budgetCents <= 0) return 'unbudgeted';\n if (spendCents > budgetCents) return 'over_budget';\n if (startAt && at.getTime() < startAt.getTime()) return 'not_started';\n if (\n campaign.status === 'completed' ||\n campaign.status === 'archived' ||\n elapsedFraction === 1\n ) {\n return 'complete';\n }\n if (varianceCents === null) return 'on_track';\n\n // A five-percent budget band avoids noisy status flips from minor timing\n // differences between spend collection and scheduled pacing.\n const toleranceCents = Math.max(1, Math.round(budgetCents * 0.05));\n if (varianceCents > toleranceCents) return 'ahead';\n if (varianceCents < -toleranceCents) return 'behind';\n return 'on_track';\n}\n\nfunction elapsedFraction(\n startAt: Date | null,\n endAt: Date | null,\n at: Date,\n): number | null {\n if (!startAt || !endAt) return null;\n const start = startAt.getTime();\n const end = endAt.getTime();\n if (end <= start) return at.getTime() < start ? 0 : 1;\n return Math.min(1, Math.max(0, (at.getTime() - start) / (end - start)));\n}\n","/** Immutable period performance evidence for a campaign or campaign channel. */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\nimport { Campaign } from './Campaign.js';\nimport { CampaignChannel } from './CampaignChannel.js';\n\nconst persistedSnapshotState = new WeakMap<CampaignMetricSnapshot, string>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_metric_snapshots',\n conflictColumns: ['dedupe_key'],\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: { include: ['list', 'get', 'create'] },\n})\nexport class CampaignMetricSnapshot extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-marketing\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ description: 'The campaign these results belong to.' })\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Null for campaign rollups; set for channel-scoped evidence. */\n @field({\n description:\n 'The channel these results came from. Empty when they cover the whole campaign.',\n })\n @foreignKey(CampaignChannel, { nullable: true })\n campaignChannelId: string | null = null;\n\n @field({\n required: true,\n description: 'The first day of the period these results cover.',\n })\n periodStart: Date = new Date();\n\n @field({\n required: true,\n description: 'The last day of the period these results cover.',\n })\n periodEnd: Date = new Date();\n\n @field({ description: 'What was spent in the period, in cents.' })\n spendCents: number = 0;\n\n @field({ description: 'How many times the campaign was shown.' })\n impressions: number = 0;\n\n @field({ description: 'How many times someone clicked through.' })\n clicks: number = 0;\n\n @field({\n description: 'How many people did what the campaign asked, such as buying.',\n })\n conversions: number = 0;\n\n @field({ description: 'How many new leads came in.' })\n leads: number = 0;\n\n @field({\n type: 'integer',\n nullable: true,\n description: 'The sales brought in during the period, in cents, if known.',\n })\n revenueCents: number | null = null;\n\n /** Ingestion source such as `google_ads`, `meta`, or `rollup_job`. */\n @field({\n required: true,\n description: 'Where these results were reported from.',\n })\n source: string = '';\n\n @field({\n required: true,\n description:\n 'The reference this result is filed under, so the same report is never counted twice.',\n })\n dedupeKey: string = '';\n\n constructor(options: CampaignMetricSnapshotOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.campaignChannelId !== undefined)\n this.campaignChannelId = options.campaignChannelId;\n if (options.periodStart !== undefined)\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(options.periodStart) ??\n new Date(Number.NaN);\n if (options.periodEnd !== undefined)\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(options.periodEnd) ??\n new Date(Number.NaN);\n if (options.spendCents !== undefined) this.spendCents = options.spendCents;\n if (options.impressions !== undefined)\n this.impressions = options.impressions;\n if (options.clicks !== undefined) this.clicks = options.clicks;\n if (options.conversions !== undefined)\n this.conversions = options.conversions;\n if (options.leads !== undefined) this.leads = options.leads;\n if (options.revenueCents !== undefined)\n this.revenueCents = options.revenueCents;\n if (options.source !== undefined) this.source = options.source;\n if (options.dedupeKey !== undefined) this.dedupeKey = options.dedupeKey;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(this.periodStart) ??\n new Date(Number.NaN);\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(this.periodEnd) ?? new Date(Number.NaN);\n if (this.isPersisted) {\n persistedSnapshotState.set(this, this.serializeState());\n }\n return this;\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n this.assertMetrics();\n await this.assertChannelMatchesCampaign();\n const captured = persistedSnapshotState.get(this);\n if (captured !== undefined) {\n if (captured !== this.serializeState()) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots ` +\n 'are immutable evidence; ingest a correcting snapshot instead.',\n );\n }\n } else if (this.id && (await this.isSaved())) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id}: refusing to overwrite an existing ` +\n 'snapshot from a non-hydrated instance; snapshots are immutable evidence.',\n );\n } else if (this.dedupeKey) {\n try {\n const row = await this.db.get(this.tableName, {\n dedupe_key: this.dedupeKey,\n });\n if (row && row.id !== this.id) {\n throw new Error(\n `CampaignMetricSnapshot (dedupeKey '${this.dedupeKey}'): a ` +\n 'snapshot with this dedupe key already exists; use ' +\n 'MetricIngestionService for idempotent ingestion.',\n );\n }\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('idempotent ingestion')\n ) {\n throw error;\n }\n // DB not ready / table absent; there is no persisted row to protect.\n }\n }\n\n const result = (await super.save(options)) as this;\n persistedSnapshotState.set(this, this.serializeState());\n return result;\n }\n\n override async delete(): Promise<void> {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots are ` +\n 'immutable evidence and cannot be deleted.',\n );\n }\n\n private assertMetrics(): void {\n if (\n Number.isNaN(this.periodStart.getTime()) ||\n Number.isNaN(this.periodEnd.getTime())\n ) {\n throw new Error(\n 'CampaignMetricSnapshot periodStart and periodEnd must be valid dates.',\n );\n }\n if (this.periodEnd.getTime() < this.periodStart.getTime()) {\n throw new Error(\n 'CampaignMetricSnapshot periodEnd must be on or after periodStart.',\n );\n }\n const integerMetrics: Array<[string, number]> = [\n ['spendCents', this.spendCents],\n ['impressions', this.impressions],\n ['clicks', this.clicks],\n ['conversions', this.conversions],\n ['leads', this.leads],\n ];\n for (const [name, value] of integerMetrics) {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer.`);\n }\n }\n if (\n this.revenueCents !== null &&\n (!Number.isInteger(this.revenueCents) || this.revenueCents < 0)\n ) {\n throw new Error('revenueCents must be null or a non-negative integer.');\n }\n }\n\n private async assertChannelMatchesCampaign(): Promise<void> {\n if (!this.campaignChannelId) return;\n const channel = (await this.loadRelated(\n 'campaignChannelId',\n )) as CampaignChannel;\n if (channel.campaignId !== this.campaignId) {\n throw new Error(\n `CampaignMetricSnapshot campaignChannelId '${this.campaignChannelId}' ` +\n `does not belong to campaignId '${this.campaignId}'.`,\n );\n }\n }\n\n private serializeState(): string {\n return JSON.stringify({\n tenantId: this.tenantId,\n campaignId: this.campaignId,\n campaignChannelId: this.campaignChannelId,\n periodStart: this.periodStart.toISOString(),\n periodEnd: this.periodEnd.toISOString(),\n spendCents: this.spendCents,\n impressions: this.impressions,\n clicks: this.clicks,\n conversions: this.conversions,\n leads: this.leads,\n revenueCents: this.revenueCents,\n source: this.source,\n dedupeKey: this.dedupeKey,\n });\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignMetricSnapshot;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\nexport class CampaignMetricSnapshotCollection extends SmrtCollection<CampaignMetricSnapshot> {\n static readonly _itemClass = CampaignMetricSnapshot;\n\n async findByDedupeKey(\n dedupeKey: string,\n ): Promise<CampaignMetricSnapshot | null> {\n if (!dedupeKey) return null;\n const rows = await this.list({ where: { dedupeKey }, limit: 1 });\n return rows[0] ?? null;\n }\n\n async getOrCreateByDedupeKey(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n const dedupeKey = options.dedupeKey ?? '';\n if (!dedupeKey) {\n throw new Error(\n 'CampaignMetricSnapshotCollection.getOrCreateByDedupeKey requires a dedupeKey.',\n );\n }\n const existing = await this.findByDedupeKey(dedupeKey);\n if (existing) return { snapshot: existing, created: false };\n\n try {\n const snapshot = await this.create({\n ...options,\n // Keep runtime values uncoerced so the model rejects null/invalid\n // periods instead of allowing Date(null) to become the Unix epoch.\n periodStart: options.periodStart as Date | undefined,\n periodEnd: options.periodEnd as Date | undefined,\n _insertOnly: true,\n });\n return { snapshot, created: true };\n } catch (error) {\n // A concurrent ingest may have won the unique dedupe-key insert after\n // our initial read. Re-read the immutable winner instead of allowing a\n // natural-key upsert to replace its evidence in place.\n const concurrent = await this.findByDedupeKey(dedupeKey);\n if (concurrent) return { snapshot: concurrent, created: false };\n throw error;\n }\n }\n\n async findByCampaign(campaignId: string): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'period_start ASC',\n });\n }\n\n async findByChannel(\n campaignChannelId: string,\n ): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignChannelId },\n orderBy: 'period_start ASC',\n });\n }\n}\n\nexport default CampaignMetricSnapshotCollection;\n","import {\n type CollectionCacheConfig,\n resolveListLimit,\n SmrtCollection,\n type SmrtListOptions,\n type SmrtSelectedRow,\n type SmrtSelectField,\n type SmrtWhereClause,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n isSystemContext,\n TenantIsolationError,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n assertCustomersBelongToTenant,\n normalizeUuid,\n} from '../customer-scope.js';\nimport { Campaign } from '../models/Campaign.js';\nimport { calculateCampaignPacing } from '../services/pacing-calculation.js';\nimport type {\n CampaignChannelMixEntry,\n CampaignCustomerCursor,\n CampaignCustomerCursorInput,\n CampaignCustomerPage,\n CampaignCustomerSummary,\n CampaignMetricTotals,\n CampaignReportingPage,\n CampaignStatus,\n ListCampaignReportingByCustomerOptions,\n ListCampaignsByCustomerOptions,\n} from '../types.js';\nimport { CampaignChannelCollection } from './CampaignChannelCollection.js';\nimport { CampaignMetricSnapshotCollection } from './CampaignMetricSnapshotCollection.js';\n\nexport const MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE = 100;\nexport const MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE = 100;\n\nexport class CampaignCollection extends SmrtCollection<Campaign> {\n static readonly _itemClass = Campaign;\n\n override async get(\n filter: string | SmrtWhereClause<Campaign>,\n options: { cache?: CollectionCacheConfig | false } = {},\n ): Promise<Campaign | null> {\n return withCanonicalActiveTenant(() =>\n super.get(normalizeCampaignGetFilter(filter), options),\n );\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<Campaign>[],\n >(\n options: SmrtListOptions<Campaign> & { select: Select; include?: never },\n ): Promise<SmrtSelectedRow<Campaign, Select>[]>;\n override async list(\n options?: Omit<SmrtListOptions<Campaign>, 'select'> & {\n select?: undefined;\n },\n ): Promise<Campaign[]>;\n override async list(\n options: SmrtListOptions<Campaign> = {},\n ): Promise<Campaign[] | Record<string, unknown>[]> {\n return withCanonicalActiveTenant(\n () =>\n super.list(normalizeCampaignListOptions(options) as never) as Promise<\n Campaign[] | Record<string, unknown>[]\n >,\n );\n }\n\n async findByCampaignKey(\n campaignKey: string,\n tenantId?: string | null,\n ): Promise<Campaign | null> {\n if (!campaignKey) return null;\n const where: Record<string, unknown> = { campaignKey };\n if (tenantId !== undefined) where.tenantId = tenantId;\n const rows = await this.list({ where, limit: 1 });\n return rows[0] ?? null;\n }\n\n async findByStatus(status: CampaignStatus): Promise<Campaign[]> {\n return await this.list({\n where: { status },\n orderBy: 'start_at ASC',\n });\n }\n\n /**\n * List one tenant/customer lane newest-first using a stable UUID tiebreaker.\n * Null start times follow all scheduled rows on every supported database.\n */\n async listByCustomer(\n tenantId: string | null,\n customerId: string,\n options: ListCampaignsByCustomerOptions = {},\n ): Promise<CampaignCustomerPage> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.listByCustomer',\n );\n const normalizedCustomerId = normalizeUuid(customerId, 'customerId');\n const limit = resolveListLimit(options.limit, {\n defaultValue: 50,\n maxValue: Number.MAX_SAFE_INTEGER,\n parameterName: 'campaign customer page limit',\n });\n if (limit > MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE) {\n throw new Error(\n `Campaign customer page limit must not exceed ${MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE}.`,\n );\n }\n if (limit < 1) {\n throw new Error('Campaign customer page limit must be at least 1.');\n }\n const cursor = normalizeCursor(options.after);\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) =>\n bound.listByCustomerInTransaction(\n normalizedTenantId,\n normalizedCustomerId,\n limit,\n cursor,\n 'CampaignCollection.listByCustomer',\n ),\n ),\n );\n }\n\n /**\n * Return one bounded customer page with its complete read-only reporting\n * projection. Channels and immutable evidence are aggregated in two grouped\n * reads, independent of page size; no campaign callback or lazy load runs.\n */\n async listReportingByCustomer(\n tenantId: string | null,\n customerId: string,\n options: ListCampaignReportingByCustomerOptions = {},\n ): Promise<CampaignReportingPage> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.listReportingByCustomer',\n );\n const normalizedCustomerId = normalizeUuid(customerId, 'customerId');\n const limit = resolveListLimit(options.limit, {\n defaultValue: 50,\n maxValue: Number.MAX_SAFE_INTEGER,\n parameterName: 'campaign reporting page limit',\n });\n if (limit > MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE) {\n throw new Error(\n `Campaign reporting page limit must not exceed ${MAX_CAMPAIGN_CUSTOMER_PAGE_SIZE}.`,\n );\n }\n if (limit < 1) {\n throw new Error('Campaign reporting page limit must be at least 1.');\n }\n const cursor = normalizeCursor(options.after);\n const at = normalizeReportingAt(options.at);\n\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) => {\n const page = await bound.listByCustomerInTransaction(\n normalizedTenantId,\n normalizedCustomerId,\n limit,\n cursor,\n 'CampaignCollection.listReportingByCustomer',\n );\n return bound.projectReportingPage(normalizedTenantId, page, at);\n }),\n );\n }\n\n private async projectReportingPage(\n tenantId: string | null,\n page: CampaignCustomerPage,\n at: Date,\n ): Promise<CampaignReportingPage> {\n const campaignIds = page.items.map((campaign) => {\n if (!campaign.id) {\n throw new Error('Campaign reporting requires persisted Campaign rows.');\n }\n return normalizeUuid(campaign.id, 'campaign id');\n });\n if (campaignIds.length === 0) {\n return { items: [], nextCursor: page.nextCursor };\n }\n\n const channelRows = await this.loadChannelMixRows(tenantId, campaignIds);\n const metricRows = await this.loadMetricRows(tenantId, campaignIds);\n const channelsByCampaign = groupChannelMix(channelRows);\n const metricsByCampaign = groupMetricTotals(metricRows);\n\n return {\n items: page.items.map((campaign) => {\n const campaignId = campaign.id ?? '';\n const channelMix = channelsByCampaign.get(campaignId) ?? [];\n const metrics =\n metricsByCampaign.get(campaignId) ?? emptyMetricTotals();\n return {\n campaign,\n channelCount: channelMix.reduce(\n (sum, entry) =>\n checkedAdd(sum, entry.count, 'Campaign channel count'),\n 0,\n ),\n channelMix,\n metricTotals: metrics.totals,\n pacing: calculateCampaignPacing(\n campaign,\n {\n spendCents: metrics.totals.spendCents,\n snapshotCount: metrics.snapshotCount,\n usedCampaignRollups: metrics.usedCampaignRollups,\n },\n at,\n ),\n };\n }),\n nextCursor: page.nextCursor,\n };\n }\n\n private async loadChannelMixRows(\n tenantId: string | null,\n campaignIds: string[],\n ): Promise<Array<Record<string, unknown>>> {\n const channels = await CampaignChannelCollection.create({\n ...this.options,\n db: this.db,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n const db = requireDatabase(this.options);\n const placeholders = campaignIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `SELECT campaign_id, channel_kind, COUNT(*) AS channel_count\n FROM ${channels.tableName}\n WHERE ${tenantPredicate} AND campaign_id IN (${placeholders})\n GROUP BY campaign_id, channel_kind\n ORDER BY campaign_id ASC, channel_kind ASC`,\n ...tenantParams,\n ...campaignIds,\n );\n return result.rows;\n }\n\n private async loadMetricRows(\n tenantId: string | null,\n campaignIds: string[],\n ): Promise<Array<Record<string, unknown>>> {\n const snapshots = await CampaignMetricSnapshotCollection.create({\n ...this.options,\n db: this.db,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n const db = requireDatabase(this.options);\n const placeholders = campaignIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `WITH scoped_snapshots AS (\n SELECT campaign_id,\n campaign_channel_id,\n period_start,\n period_end,\n spend_cents,\n impressions,\n clicks,\n conversions,\n leads,\n revenue_cents\n FROM ${snapshots.tableName}\n WHERE ${tenantPredicate} AND campaign_id IN (${placeholders})\n ), rollup_periods AS (\n SELECT DISTINCT campaign_id, period_start, period_end\n FROM scoped_snapshots\n WHERE campaign_channel_id IS NULL\n ), selected_snapshots AS (\n SELECT s.*\n FROM scoped_snapshots s\n LEFT JOIN rollup_periods r\n ON r.campaign_id = s.campaign_id\n AND r.period_start = s.period_start\n AND r.period_end = s.period_end\n WHERE s.campaign_channel_id IS NULL OR r.campaign_id IS NULL\n )\n SELECT campaign_id,\n COUNT(*) AS snapshot_count,\n MAX(CASE WHEN campaign_channel_id IS NULL THEN 1 ELSE 0 END)\n AS used_campaign_rollups,\n SUM(spend_cents) AS spend_cents,\n SUM(impressions) AS impressions,\n SUM(clicks) AS clicks,\n SUM(conversions) AS conversions,\n SUM(leads) AS leads,\n SUM(COALESCE(revenue_cents, 0)) AS revenue_cents\n FROM selected_snapshots\n GROUP BY campaign_id\n ORDER BY campaign_id ASC`,\n ...tenantParams,\n ...campaignIds,\n );\n return result.rows;\n }\n\n private async listByCustomerInTransaction(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n operation: string,\n ): Promise<CampaignCustomerPage> {\n await assertCustomersBelongToTenant(\n this.options,\n tenantId,\n [customerId],\n operation,\n 'share',\n );\n const items =\n cursor?.startAt === null\n ? await this.listNullStartLane(tenantId, customerId, limit + 1, cursor)\n : await this.listScheduledLane(tenantId, customerId, limit, cursor);\n const hasMore = items.length > limit;\n const pageItems = hasMore ? items.slice(0, limit) : items;\n return {\n items: pageItems,\n nextCursor: hasMore\n ? cursorFromCampaign(pageItems[pageItems.length - 1])\n : null,\n };\n }\n\n /**\n * Summarize a bounded customer batch with one scope check and one grouped\n * aggregate query. Every requested customer receives a row, including zeros.\n */\n async summarizeByCustomers(\n tenantId: string | null,\n customerIds: string[],\n ): Promise<CampaignCustomerSummary[]> {\n const normalizedTenantId = normalizeTenantScope(\n tenantId,\n 'CampaignCollection.summarizeByCustomers',\n );\n if (!Array.isArray(customerIds)) {\n throw new Error(\n 'Campaign customer summary customerIds must be an array.',\n );\n }\n if (customerIds.length > MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE) {\n throw new Error(\n `Campaign customer summary accepts at most ${MAX_CAMPAIGN_CUSTOMER_BATCH_SIZE} customerIds.`,\n );\n }\n const normalizedCustomerIds = customerIds.map((customerId) =>\n normalizeUuid(customerId, 'customerId'),\n );\n const uniqueIds = [...new Set(normalizedCustomerIds)];\n if (uniqueIds.length === 0) return [];\n\n return withCanonicalTenantContext(normalizedTenantId, () =>\n this.inCustomerReadTransaction(async (bound) =>\n bound.summarizeByCustomersInTransaction(\n normalizedTenantId,\n normalizedCustomerIds,\n uniqueIds,\n ),\n ),\n );\n }\n\n private async summarizeByCustomersInTransaction(\n tenantId: string | null,\n requestedCustomerIds: string[],\n uniqueIds: string[],\n ): Promise<CampaignCustomerSummary[]> {\n await assertCustomersBelongToTenant(\n this.options,\n tenantId,\n uniqueIds,\n 'CampaignCollection.summarizeByCustomers',\n 'share',\n );\n const db = requireDatabase(this.options);\n const placeholders = uniqueIds.map(() => '?').join(', ');\n const tenantPredicate =\n tenantId === null ? 'tenant_id IS NULL' : 'tenant_id = ?';\n const tenantParams = tenantId === null ? [] : [tenantId];\n const result = await db.query(\n `SELECT customer_id,\n COUNT(*) AS total_count,\n SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) AS active_count,\n MAX(start_at) AS latest_start_at\n FROM ${this.tableName}\n WHERE ${tenantPredicate} AND customer_id IN (${placeholders})\n GROUP BY customer_id\n ORDER BY customer_id ASC\n LIMIT ?`,\n 'active',\n ...tenantParams,\n ...uniqueIds,\n uniqueIds.length,\n );\n const byCustomer = new Map(\n result.rows.map((row) => [String(row.customer_id).toLowerCase(), row]),\n );\n return requestedCustomerIds.map((customerId) => {\n const row = byCustomer.get(customerId);\n return {\n customerId,\n totalCount: row\n ? toSafeCount(row.total_count, 'Campaign customer total count')\n : 0,\n activeCount: row\n ? toSafeCount(row.active_count, 'Campaign customer active count')\n : 0,\n latestStartAt: row ? coerceDate(row.latest_start_at) : null,\n };\n });\n }\n\n private async inCustomerReadTransaction<T>(\n operation: (bound: CampaignCollection) => Promise<T>,\n ): Promise<T> {\n const db = this.db as DatabaseInterface;\n if (typeof db.transaction !== 'function') {\n throw new Error(\n 'Campaign customer reads require a database adapter with transaction support.',\n );\n }\n return db.transaction(async (transactionDb) => {\n const bound = await CampaignCollection.create({\n ...this.options,\n db: transactionDb,\n defaultListLimit: undefined,\n maxListLimit: undefined,\n });\n return operation(bound);\n });\n }\n\n private async listScheduledLane(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n ): Promise<Campaign[]> {\n const base = [{ tenantId }, { customerId }, { 'startAt !=': null }];\n const where = cursor\n ? [\n [...base, { 'startAt <': cursor.startAt }],\n [...base, { startAt: cursor.startAt }, { 'id <': cursor.id }],\n ]\n : { tenantId, customerId, 'startAt !=': null };\n const scheduled = await this.list({\n where,\n orderBy: ['start_at DESC', 'id DESC'],\n limit: limit + 1,\n });\n if (scheduled.length > limit) return scheduled;\n const nullRows = await this.listNullStartLane(\n tenantId,\n customerId,\n limit - scheduled.length + 1,\n null,\n );\n return [...scheduled, ...nullRows];\n }\n\n private async listNullStartLane(\n tenantId: string | null,\n customerId: string,\n limit: number,\n cursor: CampaignCustomerCursor | null,\n ): Promise<Campaign[]> {\n return this.list({\n where: {\n tenantId,\n customerId,\n startAt: null,\n ...(cursor ? { 'id <': cursor.id } : {}),\n },\n orderBy: 'id DESC',\n limit,\n });\n }\n}\n\ninterface QueryDatabase {\n query(\n sql: string,\n ...params: unknown[]\n ): Promise<{ rows: Array<Record<string, unknown>> }>;\n}\n\ninterface GroupedMetricTotals {\n totals: CampaignMetricTotals;\n snapshotCount: number;\n usedCampaignRollups: boolean;\n}\n\nfunction groupChannelMix(\n rows: Array<Record<string, unknown>>,\n): Map<string, CampaignChannelMixEntry[]> {\n const grouped = new Map<string, CampaignChannelMixEntry[]>();\n for (const row of rows) {\n const campaignId = normalizeUuid(String(row.campaign_id), 'campaign id');\n const channelKind = String(row.channel_kind);\n if (!channelKind) throw new Error('Campaign channel kind is empty.');\n const entry = {\n channelKind,\n count: toSafeCount(row.channel_count, 'Campaign channel count'),\n };\n const entries = grouped.get(campaignId) ?? [];\n entries.push(entry);\n grouped.set(campaignId, entries);\n }\n return grouped;\n}\n\nfunction groupMetricTotals(\n rows: Array<Record<string, unknown>>,\n): Map<string, GroupedMetricTotals> {\n return new Map(\n rows.map((row) => {\n const campaignId = normalizeUuid(String(row.campaign_id), 'campaign id');\n return [\n campaignId,\n {\n totals: {\n spendCents: toSafeCount(row.spend_cents, 'Campaign spend total'),\n impressions: toSafeCount(\n row.impressions,\n 'Campaign impression total',\n ),\n clicks: toSafeCount(row.clicks, 'Campaign click total'),\n conversions: toSafeCount(\n row.conversions,\n 'Campaign conversion total',\n ),\n leads: toSafeCount(row.leads, 'Campaign lead total'),\n revenueCents: toSafeCount(\n row.revenue_cents,\n 'Campaign revenue total',\n ),\n },\n snapshotCount: toSafeCount(\n row.snapshot_count,\n 'Campaign snapshot count',\n ),\n usedCampaignRollups: toBooleanFlag(row.used_campaign_rollups),\n },\n ];\n }),\n );\n}\n\nfunction emptyMetricTotals(): GroupedMetricTotals {\n return {\n totals: {\n spendCents: 0,\n impressions: 0,\n clicks: 0,\n conversions: 0,\n leads: 0,\n revenueCents: 0,\n },\n snapshotCount: 0,\n usedCampaignRollups: false,\n };\n}\n\nfunction toBooleanFlag(value: unknown): boolean {\n return value === true || value === 1 || value === '1';\n}\n\nfunction checkedAdd(left: number, right: number, label: string): number {\n const sum = left + right;\n if (!Number.isSafeInteger(sum) || sum < 0) {\n throw new Error(`${label} is outside the safe integer range.`);\n }\n return sum;\n}\n\nfunction requireDatabase(options: { db?: unknown }): QueryDatabase {\n const db = options.db;\n if (!db || typeof db !== 'object' || !('query' in db)) {\n throw new Error(\n 'Campaign reporting and summaries require an initialized database connection.',\n );\n }\n return db as QueryDatabase;\n}\n\nfunction coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n const date =\n value instanceof Date\n ? value\n : new Date(typeof value === 'number' ? value : String(value));\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\nfunction toSafeCount(value: unknown, label: string): number {\n const count = Number(value);\n if (!Number.isSafeInteger(count) || count < 0) {\n throw new Error(`${label} is outside the safe integer range.`);\n }\n return count;\n}\n\nfunction normalizeCursor(\n cursor: CampaignCustomerCursorInput | undefined,\n): CampaignCustomerCursor | null {\n if (!cursor) return null;\n const id = normalizeUuid(cursor.id, 'campaign customer cursor id');\n if (cursor.startAt === null) return { id, startAt: null };\n const startAt = coerceDate(cursor.startAt);\n if (!startAt) throw new Error('campaign customer cursor startAt is invalid.');\n return { id, startAt };\n}\n\nfunction normalizeReportingAt(value: Date | string | number | undefined): Date {\n if (value === undefined) return new Date();\n const at = coerceDate(value);\n if (!at) throw new Error('campaign reporting at is invalid.');\n return at;\n}\n\nfunction cursorFromCampaign(\n campaign: Campaign | undefined,\n): CampaignCustomerCursor | null {\n if (!campaign?.id) return null;\n return { id: campaign.id, startAt: campaign.startAt };\n}\n\nfunction normalizeTenantScope(\n tenantId: string | null,\n label: string,\n): string | null {\n if (tenantId !== null) {\n const normalized = normalizeUuid(tenantId, 'tenantId');\n const tenantContext = getCurrentTenant();\n if (tenantContext && !isSuperAdminBypass()) {\n const contextTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n if (normalized !== contextTenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${label}.`,\n { tenantId: contextTenantId, attemptedTenantId: normalized },\n );\n }\n }\n return normalized;\n }\n const tenantContext = getCurrentTenant();\n if (tenantContext && !isSuperAdminBypass() && !isSystemContext()) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${label}: tenant context cannot read global Campaign rows.`,\n { tenantId: tenantContext.tenantId, attemptedTenantId: 'global' },\n );\n }\n return null;\n}\n\nfunction withCanonicalTenantContext<T>(\n tenantId: string | null,\n operation: () => Promise<T>,\n): Promise<T> {\n const tenantContext = getCurrentTenant();\n return tenantId !== null &&\n tenantContext &&\n !isSuperAdminBypass() &&\n !isSystemContext()\n ? withTenant({ ...tenantContext, tenantId }, operation)\n : operation();\n}\n\nfunction withCanonicalActiveTenant<T>(operation: () => Promise<T>): Promise<T> {\n const tenantContext = getCurrentTenant();\n if (!tenantContext || isSuperAdminBypass() || isSystemContext()) {\n return operation();\n }\n const normalizedTenantId = normalizeUuid(tenantContext.tenantId, 'tenantId');\n return normalizedTenantId === tenantContext.tenantId\n ? operation()\n : withTenant({ ...tenantContext, tenantId: normalizedTenantId }, operation);\n}\n\nfunction normalizeCampaignGetFilter(\n filter: string | SmrtWhereClause<Campaign>,\n): string | SmrtWhereClause<Campaign> {\n if (typeof filter !== 'string') return normalizeCampaignUuidWhere(filter);\n try {\n return normalizeUuid(filter, 'campaign id');\n } catch {\n return filter;\n }\n}\n\nfunction normalizeCampaignListOptions(\n options: SmrtListOptions<Campaign>,\n): SmrtListOptions<Campaign> {\n return options.where === undefined\n ? options\n : { ...options, where: normalizeCampaignUuidWhere(options.where) };\n}\n\nfunction normalizeCampaignUuidWhere<T>(where: T): T {\n if (Array.isArray(where)) {\n return where.map((entry) => normalizeCampaignUuidWhere(entry)) as T;\n }\n if (!where || typeof where !== 'object') return where;\n\n const normalized: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(where)) {\n const field = key.trim().split(/\\s+/u)[0];\n const label =\n field === 'id'\n ? 'campaign id'\n : field === 'customerId'\n ? 'customerId'\n : field === 'tenantId'\n ? 'tenantId'\n : null;\n if (!label) {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.map((uuid) =>\n typeof uuid === 'string' ? normalizeUuid(uuid, label) : uuid,\n );\n } else {\n normalized[key] =\n typeof value === 'string' ? normalizeUuid(value, label) : value;\n }\n }\n return normalized as T;\n}\n\nexport default CampaignCollection;\n","/**\n * Declared recipes for smrt-marketing (#4409, #4437): user-facing units of\n * functionality an app or agent can pick instead of the whole package.\n * The scanner reads these statics into the `recipes` array of `manifest.json`\n * and `smrt-knowledge.json`; nothing here runs at that point.\n *\n * @packageDocumentation\n */\n\nimport { SmrtRecipe } from '@happyvertical/smrt-core';\nimport { Campaign } from './models/Campaign.js';\nimport { CampaignChannel } from './models/CampaignChannel.js';\nimport { CampaignMetricSnapshot } from './models/CampaignMetricSnapshot.js';\n\n/** Campaigns: Plan a campaign, choose where it runs and see what it brought in. */\nexport class MarketingCampaignsRecipe extends SmrtRecipe {\n static id = 'marketing.campaigns';\n static help = './campaigns.recipe.md';\n static label = 'Campaigns';\n static summary =\n 'Plan a marketing campaign, set its budget and dates, choose where it runs and see what it brought in.';\n static synonyms = [\n 'marketing',\n 'marketing campaigns',\n 'promotions',\n 'advertising',\n 'ad campaigns',\n 'campaign results',\n ];\n static section = {\n id: 'grow',\n label: 'Grow',\n icon: 'tag',\n description: 'Bring in new customers and reward the people who help.',\n };\n static models = [Campaign, CampaignChannel, CampaignMetricSnapshot];\n static nav = [\n {\n label: 'Campaigns',\n model: Campaign,\n icon: 'tag',\n description:\n 'Every campaign, with its budget, dates and where it stands.',\n noun: 'campaign',\n },\n {\n label: 'Channels',\n model: CampaignChannel,\n icon: 'layers',\n description:\n 'Where each campaign runs, such as an ad group or a social post, and its share of the budget.',\n noun: 'channel',\n },\n ];\n static options = {\n Campaign: {\n fields: {\n name: { order: 1 },\n objective: { order: 2 },\n status: { order: 3 },\n startAt: { label: 'Starts', order: 4 },\n endAt: { label: 'Ends', order: 5 },\n budgetCents: { label: 'Budget (cents)', order: 6 },\n currency: { order: 7 },\n customerId: { label: 'Customer', visibility: 'advanced' },\n campaignKey: { label: 'Reference', visibility: 'advanced' },\n tenantId: { visibility: 'hidden' },\n metadata: { visibility: 'hidden' },\n },\n },\n CampaignChannel: {\n fields: {\n campaignId: { label: 'Campaign', order: 1 },\n channelKind: { label: 'Kind', order: 2 },\n channelRef: { label: 'Name', order: 3 },\n allocatedBudgetCents: { label: 'Budget share (cents)', order: 4 },\n status: { order: 5 },\n startAt: { label: 'Starts', order: 6 },\n endAt: { label: 'Ends', order: 7 },\n tenantId: { visibility: 'hidden' },\n },\n },\n CampaignMetricSnapshot: {\n fields: {\n campaignId: { label: 'Campaign', order: 1 },\n campaignChannelId: { label: 'Channel', order: 2 },\n periodStart: { label: 'From', order: 3 },\n periodEnd: { label: 'To', order: 4 },\n spendCents: { label: 'Spend (cents)', order: 5 },\n impressions: { order: 6 },\n clicks: { order: 7 },\n conversions: { order: 8 },\n leads: { order: 9 },\n revenueCents: { label: 'Revenue (cents)', order: 10 },\n source: { order: 11 },\n tenantId: { visibility: 'hidden' },\n dedupeKey: { visibility: 'hidden' },\n },\n },\n } as const;\n}\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n CampaignChannelCollection,\n CampaignCollection,\n CampaignMetricSnapshotCollection,\n} from '../collections/index.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { BudgetPacingResult } from '../types.js';\nimport {\n calculateCampaignPacing,\n calculatePacing,\n} from './pacing-calculation.js';\n\n/** Computes spend-to-budget pacing from immutable snapshots; stores nothing. */\nexport class BudgetPacingService {\n constructor(\n private readonly campaigns: CampaignCollection,\n private readonly channels: CampaignChannelCollection,\n private readonly snapshots: CampaignMetricSnapshotCollection,\n ) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<BudgetPacingService> {\n return new BudgetPacingService(\n await CampaignCollection.create(classOptions),\n await CampaignChannelCollection.create(classOptions),\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async getCampaignPacing(\n campaignId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n\n const all = await this.snapshots.findByCampaign(campaignId);\n const rollups = all.filter((snapshot) => !snapshot.campaignChannelId);\n const used = this.selectCampaignEvidence(all);\n return calculateCampaignPacing(\n campaign,\n {\n spendCents: used.reduce(\n (sum, snapshot) => sum + snapshot.spendCents,\n 0,\n ),\n snapshotCount: used.length,\n usedCampaignRollups: rollups.length > 0,\n },\n at,\n );\n }\n\n async getChannelPacing(\n campaignChannelId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const channel = await this.channels.get({ id: campaignChannelId });\n if (!channel) {\n throw new Error(`CampaignChannel '${campaignChannelId}' was not found.`);\n }\n const campaign = await this.campaigns.get({ id: channel.campaignId });\n if (!campaign) {\n throw new Error(`Campaign '${channel.campaignId}' was not found.`);\n }\n const snapshots = await this.snapshots.findByChannel(campaignChannelId);\n return {\n ...calculatePacing({\n campaign,\n evidence: {\n spendCents: snapshots.reduce(\n (sum, snapshot) => sum + snapshot.spendCents,\n 0,\n ),\n snapshotCount: snapshots.length,\n usedCampaignRollups: false,\n },\n budgetCents: channel.allocatedBudgetCents,\n startAt: channel.startAt ?? campaign.startAt,\n endAt: channel.endAt ?? campaign.endAt,\n at,\n }),\n campaignChannelId,\n };\n }\n\n /**\n * Prefer a campaign rollup for each exact reporting period, while retaining\n * channel evidence for periods that do not yet have a rollup. This prevents\n * double counting without dropping earlier/later channel-only periods.\n */\n private selectCampaignEvidence(\n snapshots: CampaignMetricSnapshot[],\n ): CampaignMetricSnapshot[] {\n const rollupPeriods = new Set(\n snapshots\n .filter((snapshot) => !snapshot.campaignChannelId)\n .map((snapshot) => this.periodKey(snapshot)),\n );\n return snapshots.filter(\n (snapshot) =>\n !snapshot.campaignChannelId ||\n !rollupPeriods.has(this.periodKey(snapshot)),\n );\n }\n\n private periodKey(snapshot: CampaignMetricSnapshot): string {\n return `${snapshot.periodStart.toISOString()}:${snapshot.periodEnd.toISOString()}`;\n }\n}\n\nexport default BudgetPacingService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignCollection } from '../collections/CampaignCollection.js';\nimport type { Campaign } from '../models/Campaign.js';\nimport type { CampaignStatus } from '../types.js';\n\n/** Single authoritative write path for Campaign lifecycle changes. */\nexport class CampaignLifecycleService {\n constructor(private readonly campaigns: CampaignCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CampaignLifecycleService> {\n return new CampaignLifecycleService(\n await CampaignCollection.create(classOptions),\n );\n }\n\n async transition(\n campaignId: string,\n next: CampaignStatus,\n ): Promise<Campaign> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n campaign.transitionTo(next);\n await campaign.save();\n return campaign;\n }\n\n async schedule(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'scheduled');\n }\n\n async activate(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async pause(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'paused');\n }\n\n async resume(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async complete(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'completed');\n }\n\n async archive(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'archived');\n }\n}\n\nexport default CampaignLifecycleService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshotCollection } from '../collections/CampaignMetricSnapshotCollection.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\n/** Idempotent, validation-first ingestion for immutable metric evidence. */\nexport class MetricIngestionService {\n constructor(private readonly snapshots: CampaignMetricSnapshotCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<MetricIngestionService> {\n return new MetricIngestionService(\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async ingest(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n if (!options.campaignId) {\n throw new Error('Metric ingestion requires campaignId.');\n }\n if (!options.source?.trim()) {\n throw new Error('Metric ingestion requires source.');\n }\n if (!options.dedupeKey?.trim()) {\n throw new Error('Metric ingestion requires dedupeKey.');\n }\n if (options.periodStart == null || options.periodEnd == null) {\n throw new Error('Metric ingestion requires periodStart and periodEnd.');\n }\n return await this.snapshots.getOrCreateByDedupeKey(options);\n }\n}\n\nexport default MetricIngestionService;\n"],"mappings":"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"}
|