@happyvertical/smrt-marketing 0.39.16

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.
Files changed (67) hide show
  1. package/AGENTS.md +79 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +67 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/CampaignChannelCollection.d.ts +9 -0
  8. package/dist/collections/CampaignChannelCollection.d.ts.map +1 -0
  9. package/dist/collections/CampaignCollection.d.ts +10 -0
  10. package/dist/collections/CampaignCollection.d.ts.map +1 -0
  11. package/dist/collections/CampaignMetricSnapshotCollection.d.ts +15 -0
  12. package/dist/collections/CampaignMetricSnapshotCollection.d.ts.map +1 -0
  13. package/dist/collections/index.d.ts +4 -0
  14. package/dist/collections/index.d.ts.map +1 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +625 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/manifest.json +1173 -0
  20. package/dist/models/Campaign.d.ts +31 -0
  21. package/dist/models/Campaign.d.ts.map +1 -0
  22. package/dist/models/CampaignChannel.d.ts +22 -0
  23. package/dist/models/CampaignChannel.d.ts.map +1 -0
  24. package/dist/models/CampaignMetricSnapshot.d.ts +29 -0
  25. package/dist/models/CampaignMetricSnapshot.d.ts.map +1 -0
  26. package/dist/models/index.d.ts +4 -0
  27. package/dist/models/index.d.ts.map +1 -0
  28. package/dist/services/BudgetPacingService.d.ts +25 -0
  29. package/dist/services/BudgetPacingService.d.ts.map +1 -0
  30. package/dist/services/CampaignLifecycleService.d.ts +19 -0
  31. package/dist/services/CampaignLifecycleService.d.ts.map +1 -0
  32. package/dist/services/MetricIngestionService.d.ts +16 -0
  33. package/dist/services/MetricIngestionService.d.ts.map +1 -0
  34. package/dist/services/index.d.ts +4 -0
  35. package/dist/services/index.d.ts.map +1 -0
  36. package/dist/smrt-knowledge.json +875 -0
  37. package/dist/svelte/__tests__/components.test.js +115 -0
  38. package/dist/svelte/__tests__/helpers.test.js +94 -0
  39. package/dist/svelte/components/BudgetPacing.svelte +129 -0
  40. package/dist/svelte/components/BudgetPacing.svelte.d.ts +10 -0
  41. package/dist/svelte/components/BudgetPacing.svelte.d.ts.map +1 -0
  42. package/dist/svelte/components/CampaignDetail.svelte +208 -0
  43. package/dist/svelte/components/CampaignDetail.svelte.d.ts +14 -0
  44. package/dist/svelte/components/CampaignDetail.svelte.d.ts.map +1 -0
  45. package/dist/svelte/components/CampaignList.svelte +140 -0
  46. package/dist/svelte/components/CampaignList.svelte.d.ts +11 -0
  47. package/dist/svelte/components/CampaignList.svelte.d.ts.map +1 -0
  48. package/dist/svelte/components/ChannelMix.svelte +138 -0
  49. package/dist/svelte/components/ChannelMix.svelte.d.ts +10 -0
  50. package/dist/svelte/components/ChannelMix.svelte.d.ts.map +1 -0
  51. package/dist/svelte/components/MarketingDashboard.svelte +129 -0
  52. package/dist/svelte/components/MarketingDashboard.svelte.d.ts +13 -0
  53. package/dist/svelte/components/MarketingDashboard.svelte.d.ts.map +1 -0
  54. package/dist/svelte/format.d.ts +8 -0
  55. package/dist/svelte/format.d.ts.map +1 -0
  56. package/dist/svelte/format.js +42 -0
  57. package/dist/svelte/index.d.ts +8 -0
  58. package/dist/svelte/index.d.ts.map +1 -0
  59. package/dist/svelte/index.js +7 -0
  60. package/dist/svelte/types.d.ts +73 -0
  61. package/dist/svelte/types.d.ts.map +1 -0
  62. package/dist/svelte/types.js +80 -0
  63. package/dist/types.d.ts +61 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +29 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +79 -0
package/dist/index.js ADDED
@@ -0,0 +1,625 @@
1
+ import { CAMPAIGN_CHANNEL_KINDS, CAMPAIGN_CHANNEL_STATUSES, CAMPAIGN_STATUSES } from "./types.js";
2
+ import { ObjectRegistry, SmrtCollection, SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
3
+ import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
4
+ //#region src/__smrt-register__.ts
5
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1784065581918,\"packageName\":\"@happyvertical/smrt-marketing\",\"packageVersion\":\"0.39.16\",\"objects\":{\"@happyvertical/smrt-marketing:CampaignChannelCollection\":{\"name\":\"campaignchannelcollection\",\"className\":\"CampaignChannelCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannelCollection\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/collections/CampaignChannelCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true},\"findByExecutionRef\":{\"name\":\"findByExecutionRef\",\"async\":true,\"parameters\":[{\"name\":\"channelKind\",\"type\":\"string\",\"optional\":false},{\"name\":\"channelRef\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignChannel[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignChannel\",\"exportName\":\"CampaignChannelCollection\",\"collectionExportName\":\"CampaignChannelCollectionCollection\",\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_channels_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaign_channels_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"b64ea87b\"}},\"@happyvertical/smrt-marketing:CampaignCollection\":{\"name\":\"campaigncollection\",\"className\":\"CampaignCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignCollection\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/collections/CampaignCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByCampaignKey\":{\"name\":\"findByCampaignKey\",\"async\":true,\"parameters\":[{\"name\":\"campaignKey\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<Campaign | null>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"Promise<Campaign[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Campaign\",\"exportName\":\"CampaignCollection\",\"collectionExportName\":\"CampaignCollectionCollection\",\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaigns_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaigns_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"b1cd973d\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\":{\"name\":\"campaignmetricsnapshotcollection\",\"className\":\"CampaignMetricSnapshotCollection\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshotCollection\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/collections/CampaignMetricSnapshotCollection.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{},\"methods\":{\"findByDedupeKey\":{\"name\":\"findByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"dedupeKey\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot | null>\",\"isStatic\":false,\"isPublic\":true},\"getOrCreateByDedupeKey\":{\"name\":\"getOrCreateByDedupeKey\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"CampaignMetricSnapshotOptions\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true},\"findByCampaign\":{\"name\":\"findByCampaign\",\"async\":true,\"parameters\":[{\"name\":\"campaignId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true},\"findByChannel\":{\"name\":\"findByChannel\",\"async\":true,\"parameters\":[{\"name\":\"campaignChannelId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<CampaignMetricSnapshot[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"CampaignMetricSnapshot\",\"exportName\":\"CampaignMetricSnapshotCollection\",\"collectionExportName\":\"CampaignMetricSnapshotCollectionCollection\",\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaign_metric_snapshots_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"268285ee\"}},\"@happyvertical/smrt-marketing:Campaign\":{\"name\":\"campaign\",\"className\":\"Campaign\",\"qualifiedName\":\"@happyvertical/smrt-marketing:Campaign\",\"collection\":\"campaigns\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/models/Campaign.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"name\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"objective\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"draft\"},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"budgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"currency\":{\"type\":\"text\",\"required\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"text\",\"required\":false,\"default\":\"{}\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"transitionTo\":{\"name\":\"transitionTo\",\"async\":false,\"parameters\":[{\"name\":\"next\",\"type\":\"CampaignStatus\",\"optional\":false}],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"Record<string>\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"metadata\",\"type\":\"Record<string>\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaigns\",\"conflictColumns\":[\"tenant_id\",\"campaign_key\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"Campaign\",\"collectionExportName\":\"CampaignCollection\",\"validationRules\":[{\"field\":\"campaignKey\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"name\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaigns\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaigns\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"objective\\\" TEXT DEFAULT '',\\n \\\"status\\\" TEXT DEFAULT 'draft',\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"currency\\\" TEXT DEFAULT 'USD',\\n \\\"metadata\\\" TEXT DEFAULT '{}'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"objective\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"draft\"},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"{}\"}},\"indexes\":[{\"name\":\"campaigns_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaigns_tenant_id_campaign_key_idx\",\"columns\":[\"tenant_id\",\"campaign_key\"],\"unique\":true}],\"version\":\"1963ebd3\"}},\"@happyvertical/smrt-marketing:CampaignChannel\":{\"name\":\"campaignchannel\",\"className\":\"CampaignChannel\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignChannel\",\"collection\":\"campaignchannels\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/models/CampaignChannel.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"channelKind\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"channelRef\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"allocatedBudgetCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"startAt\":{\"type\":\"datetime\",\"required\":false},\"endAt\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":false,\"default\":\"planned\"}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_channels\",\"conflictColumns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignChannel\",\"collectionExportName\":\"CampaignChannelCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"channelKind\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"channelRef\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_channels\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_channels\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"channel_kind\\\" TEXT NOT NULL DEFAULT '',\\n \\\"channel_ref\\\" TEXT NOT NULL DEFAULT '',\\n \\\"allocated_budget_cents\\\" INTEGER DEFAULT 0,\\n \\\"start_at\\\" TIMESTAMP,\\n \\\"end_at\\\" TIMESTAMP,\\n \\\"status\\\" TEXT DEFAULT 'planned'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"channel_kind\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"channel_ref\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"allocated_budget_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"start_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"end_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"planned\"}},\"indexes\":[{\"name\":\"campaign_channels_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaign_channels_campaign_id_channel_kind_idx\",\"columns\":[\"campaign_id\",\"channel_kind\",\"channel_ref\"],\"unique\":true}],\"version\":\"40163dfc\"}},\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\":{\"name\":\"campaignmetricsnapshot\",\"className\":\"CampaignMetricSnapshot\",\"qualifiedName\":\"@happyvertical/smrt-marketing:CampaignMetricSnapshot\",\"collection\":\"campaignmetricsnapshots\",\"filePath\":\"/home/runner/.local/share/pnpm/store/.workspaces/arc-happyvertical-node-sqm7p-runner-5xldd/smrt/smrt/packages/marketing/src/models/CampaignMetricSnapshot.ts\",\"packageName\":\"@happyvertical/smrt-marketing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"campaignId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Campaign\",\"_meta\":{\"required\":true}},\"campaignChannelId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"CampaignChannel\",\"_meta\":{\"nullable\":true}},\"periodStart\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"periodEnd\":{\"type\":\"datetime\",\"required\":true,\"_meta\":{\"required\":true}},\"spendCents\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"impressions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"clicks\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"conversions\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"leads\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"revenueCents\":{\"type\":\"integer\",\"required\":false,\"_meta\":{\"nullable\":true}},\"source\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"dedupeKey\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}}},\"methods\":{\"initialize\":{\"name\":\"initialize\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"campaign_metric_snapshots\",\"conflictColumns\":[\"dedupe_key\"],\"api\":{\"include\":[\"list\",\"get\",\"create\"]},\"mcp\":{\"include\":[\"list\",\"get\",\"create\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"CampaignMetricSnapshot\",\"collectionExportName\":\"CampaignMetricSnapshotCollection\",\"validationRules\":[{\"field\":\"campaignId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"periodStart\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"periodEnd\",\"rule\":\"required\",\"fieldType\":\"datetime\"},{\"field\":\"source\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"dedupeKey\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"campaign_metric_snapshots\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"campaign_metric_snapshots\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"campaign_id\\\" UUID NOT NULL,\\n \\\"campaign_channel_id\\\" UUID,\\n \\\"period_start\\\" TIMESTAMP NOT NULL,\\n \\\"period_end\\\" TIMESTAMP NOT NULL,\\n \\\"spend_cents\\\" INTEGER DEFAULT 0,\\n \\\"impressions\\\" INTEGER DEFAULT 0,\\n \\\"clicks\\\" INTEGER DEFAULT 0,\\n \\\"conversions\\\" INTEGER DEFAULT 0,\\n \\\"leads\\\" INTEGER DEFAULT 0,\\n \\\"revenue_cents\\\" INTEGER,\\n \\\"source\\\" TEXT NOT NULL DEFAULT '',\\n \\\"dedupe_key\\\" TEXT NOT NULL DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"campaign_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false},\"campaign_channel_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"unique\":false},\"period_start\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"period_end\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"unique\":false},\"spend_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"impressions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"clicks\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"conversions\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"leads\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"revenue_cents\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false},\"source\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"dedupe_key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"campaign_metric_snapshots_id_idx\",\"columns\":[\"id\"]},{\"name\":\"campaign_metric_snapshots_dedupe_key_idx\",\"columns\":[\"dedupe_key\"],\"unique\":true}],\"version\":\"87e87389\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
6
+ //#endregion
7
+ //#region src/models/Campaign.ts
8
+ var __defProp$2 = Object.defineProperty;
9
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
10
+ var __decorateClass$2 = (decorators, target, key, kind) => {
11
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
12
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
+ if (kind && result) __defProp$2(target, key, result);
14
+ return result;
15
+ };
16
+ var CAMPAIGN_STATUS_TRANSITIONS = {
17
+ draft: ["scheduled"],
18
+ scheduled: ["active"],
19
+ active: ["paused", "completed"],
20
+ paused: ["active", "completed"],
21
+ completed: ["archived"],
22
+ archived: []
23
+ };
24
+ var loadedCampaignStatus = /* @__PURE__ */ new WeakMap();
25
+ var Campaign = class extends SmrtObject {
26
+ tenantId = null;
27
+ campaignKey = "";
28
+ name = "";
29
+ /** Open objective key such as `awareness` or `demand_generation`. */
30
+ objective = "";
31
+ status = "draft";
32
+ startAt = null;
33
+ endAt = null;
34
+ /** Total campaign budget in integer cents. */
35
+ budgetCents = 0;
36
+ /** ISO 4217 currency for every campaign/channel amount. */
37
+ currency = "USD";
38
+ metadata = "{}";
39
+ constructor(options = {}) {
40
+ super(options);
41
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
42
+ if (options.campaignKey !== void 0) this.campaignKey = options.campaignKey;
43
+ if (options.name !== void 0) this.name = options.name;
44
+ if (options.objective !== void 0) this.objective = options.objective;
45
+ if (options.status !== void 0) this.status = options.status;
46
+ if (options.startAt !== void 0) this.startAt = Campaign.coerceDate(options.startAt);
47
+ if (options.endAt !== void 0) this.endAt = Campaign.coerceDate(options.endAt);
48
+ if (options.budgetCents !== void 0) this.budgetCents = options.budgetCents;
49
+ if (options.currency !== void 0) this.currency = options.currency;
50
+ if (options.metadata !== void 0) this.metadata = options.metadata;
51
+ }
52
+ async initialize() {
53
+ await super.initialize();
54
+ this.startAt = Campaign.coerceDate(this.startAt);
55
+ this.endAt = Campaign.coerceDate(this.endAt);
56
+ if (await this.isSaved()) loadedCampaignStatus.set(this, this.status);
57
+ return this;
58
+ }
59
+ async save() {
60
+ const prior = await this.resolvePriorStatus();
61
+ this.assertStatusTransition(prior);
62
+ this.assertSchedule();
63
+ const result = await super.save();
64
+ loadedCampaignStatus.set(this, this.status);
65
+ return result;
66
+ }
67
+ /** Apply one legal transition in memory; callers choose the save boundary. */
68
+ transitionTo(next) {
69
+ if (next === this.status) return this;
70
+ if (!(CAMPAIGN_STATUS_TRANSITIONS[this.status] ?? []).includes(next)) throw new Error(`Campaign ${this.campaignKey || this.id}: cannot transition '${this.status}' \u2192 '${next}'.`);
71
+ this.status = next;
72
+ return this;
73
+ }
74
+ getMetadata() {
75
+ if (!this.metadata) return {};
76
+ try {
77
+ const parsed = JSON.parse(this.metadata);
78
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
79
+ } catch {
80
+ return {};
81
+ }
82
+ }
83
+ setMetadata(metadata) {
84
+ this.metadata = JSON.stringify(metadata ?? {});
85
+ }
86
+ assertSchedule() {
87
+ if (this.startAt && this.endAt && this.endAt.getTime() < this.startAt.getTime()) throw new Error("Campaign endAt must be on or after startAt.");
88
+ if (!Number.isInteger(this.budgetCents) || this.budgetCents < 0) throw new Error("Campaign budgetCents must be a non-negative integer.");
89
+ }
90
+ async resolvePriorStatus() {
91
+ if (this.id) try {
92
+ const row = await this.db.get(this.tableName, { id: this.id });
93
+ if (row && row.status != null) return row.status;
94
+ } catch {}
95
+ if (this.campaignKey) try {
96
+ const row = await this.db.get(this.tableName, {
97
+ tenant_id: this.tenantId,
98
+ campaign_key: this.campaignKey
99
+ });
100
+ if (row && row.status != null) return row.status;
101
+ } catch {}
102
+ return loadedCampaignStatus.get(this);
103
+ }
104
+ assertStatusTransition(prior) {
105
+ if (!CAMPAIGN_STATUSES.includes(this.status)) throw new Error(`Campaign ${this.campaignKey || this.id}: unknown status '${this.status}'.`);
106
+ if (prior === void 0 || prior === this.status) return;
107
+ if (!(CAMPAIGN_STATUS_TRANSITIONS[prior] ?? []).includes(this.status)) throw new Error(`Campaign ${this.campaignKey || this.id}: illegal status transition '${prior}' \u2192 '${this.status}'.`);
108
+ }
109
+ static coerceDate(value) {
110
+ if (value == null) return null;
111
+ if (value instanceof Date) return value;
112
+ if (typeof value === "number" || typeof value === "string") {
113
+ const date = new Date(value);
114
+ return Number.isNaN(date.getTime()) ? null : date;
115
+ }
116
+ return null;
117
+ }
118
+ };
119
+ __decorateClass$2([tenantId({ nullable: true })], Campaign.prototype, "tenantId", 2);
120
+ __decorateClass$2([field({ required: true })], Campaign.prototype, "campaignKey", 2);
121
+ __decorateClass$2([field({ required: true })], Campaign.prototype, "name", 2);
122
+ Campaign = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
123
+ tableName: "campaigns",
124
+ conflictColumns: ["tenant_id", "campaign_key"],
125
+ api: { include: [
126
+ "list",
127
+ "get",
128
+ "create",
129
+ "update"
130
+ ] },
131
+ mcp: { include: [
132
+ "list",
133
+ "get",
134
+ "create",
135
+ "update"
136
+ ] },
137
+ cli: { include: [
138
+ "list",
139
+ "get",
140
+ "create",
141
+ "update"
142
+ ] }
143
+ })], Campaign);
144
+ //#endregion
145
+ //#region src/models/CampaignChannel.ts
146
+ var __defProp$1 = Object.defineProperty;
147
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
148
+ var __decorateClass$1 = (decorators, target, key, kind) => {
149
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
150
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
151
+ if (kind && result) __defProp$1(target, key, result);
152
+ return result;
153
+ };
154
+ var CampaignChannel = class extends SmrtObject {
155
+ tenantId = null;
156
+ campaignId = "";
157
+ channelKind = "";
158
+ channelRef = "";
159
+ allocatedBudgetCents = 0;
160
+ /** Optional execution-specific override of the Campaign start. */
161
+ startAt = null;
162
+ /** Optional execution-specific override of the Campaign end. */
163
+ endAt = null;
164
+ status = "planned";
165
+ constructor(options = {}) {
166
+ super(options);
167
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
168
+ if (options.campaignId !== void 0) this.campaignId = options.campaignId;
169
+ if (options.channelKind !== void 0) this.channelKind = options.channelKind;
170
+ if (options.channelRef !== void 0) this.channelRef = options.channelRef;
171
+ if (options.allocatedBudgetCents !== void 0) this.allocatedBudgetCents = options.allocatedBudgetCents;
172
+ if (options.startAt !== void 0) this.startAt = CampaignChannel.coerceDate(options.startAt);
173
+ if (options.endAt !== void 0) this.endAt = CampaignChannel.coerceDate(options.endAt);
174
+ if (options.status !== void 0) this.status = options.status;
175
+ }
176
+ async initialize() {
177
+ await super.initialize();
178
+ this.startAt = CampaignChannel.coerceDate(this.startAt);
179
+ this.endAt = CampaignChannel.coerceDate(this.endAt);
180
+ return this;
181
+ }
182
+ async save() {
183
+ if (!CAMPAIGN_CHANNEL_STATUSES.includes(this.status)) throw new Error(`CampaignChannel ${this.channelRef || this.id}: unknown status '${this.status}'.`);
184
+ if (!Number.isInteger(this.allocatedBudgetCents) || this.allocatedBudgetCents < 0) throw new Error("CampaignChannel allocatedBudgetCents must be a non-negative integer.");
185
+ if (this.startAt && this.endAt && this.endAt.getTime() < this.startAt.getTime()) throw new Error("CampaignChannel endAt must be on or after startAt.");
186
+ return await super.save();
187
+ }
188
+ static coerceDate(value) {
189
+ if (value == null) return null;
190
+ if (value instanceof Date) return value;
191
+ if (typeof value === "number" || typeof value === "string") {
192
+ const date = new Date(value);
193
+ return Number.isNaN(date.getTime()) ? null : date;
194
+ }
195
+ return null;
196
+ }
197
+ };
198
+ __decorateClass$1([tenantId({ nullable: true })], CampaignChannel.prototype, "tenantId", 2);
199
+ __decorateClass$1([foreignKey(Campaign, { required: true })], CampaignChannel.prototype, "campaignId", 2);
200
+ __decorateClass$1([field({ required: true })], CampaignChannel.prototype, "channelKind", 2);
201
+ __decorateClass$1([field({ required: true })], CampaignChannel.prototype, "channelRef", 2);
202
+ CampaignChannel = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
203
+ tableName: "campaign_channels",
204
+ conflictColumns: [
205
+ "campaign_id",
206
+ "channel_kind",
207
+ "channel_ref"
208
+ ],
209
+ api: { include: [
210
+ "list",
211
+ "get",
212
+ "create",
213
+ "update",
214
+ "delete"
215
+ ] },
216
+ mcp: { include: [
217
+ "list",
218
+ "get",
219
+ "create",
220
+ "update"
221
+ ] },
222
+ cli: { include: [
223
+ "list",
224
+ "get",
225
+ "create",
226
+ "update",
227
+ "delete"
228
+ ] }
229
+ })], CampaignChannel);
230
+ //#endregion
231
+ //#region src/collections/CampaignChannelCollection.ts
232
+ var CampaignChannelCollection = class extends SmrtCollection {
233
+ static _itemClass = CampaignChannel;
234
+ async findByCampaign(campaignId) {
235
+ return await this.list({
236
+ where: { campaignId },
237
+ orderBy: "start_at ASC"
238
+ });
239
+ }
240
+ async findByExecutionRef(channelKind, channelRef) {
241
+ return await this.list({ where: {
242
+ channelKind,
243
+ channelRef
244
+ } });
245
+ }
246
+ };
247
+ //#endregion
248
+ //#region src/collections/CampaignCollection.ts
249
+ var CampaignCollection = class extends SmrtCollection {
250
+ static _itemClass = Campaign;
251
+ async findByCampaignKey(campaignKey, tenantId) {
252
+ if (!campaignKey) return null;
253
+ const where = { campaignKey };
254
+ if (tenantId !== void 0) where.tenantId = tenantId;
255
+ return (await this.list({
256
+ where,
257
+ limit: 1
258
+ }))[0] ?? null;
259
+ }
260
+ async findByStatus(status) {
261
+ return await this.list({
262
+ where: { status },
263
+ orderBy: "start_at ASC"
264
+ });
265
+ }
266
+ };
267
+ //#endregion
268
+ //#region src/models/CampaignMetricSnapshot.ts
269
+ var __defProp = Object.defineProperty;
270
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
271
+ var __decorateClass = (decorators, target, key, kind) => {
272
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
273
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
274
+ if (kind && result) __defProp(target, key, result);
275
+ return result;
276
+ };
277
+ var persistedSnapshotState = /* @__PURE__ */ new WeakMap();
278
+ var CampaignMetricSnapshot = class extends SmrtObject {
279
+ tenantId = null;
280
+ campaignId = "";
281
+ campaignChannelId = null;
282
+ periodStart = /* @__PURE__ */ new Date();
283
+ periodEnd = /* @__PURE__ */ new Date();
284
+ spendCents = 0;
285
+ impressions = 0;
286
+ clicks = 0;
287
+ conversions = 0;
288
+ leads = 0;
289
+ revenueCents = null;
290
+ source = "";
291
+ dedupeKey = "";
292
+ constructor(options = {}) {
293
+ super(options);
294
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
295
+ if (options.campaignId !== void 0) this.campaignId = options.campaignId;
296
+ if (options.campaignChannelId !== void 0) this.campaignChannelId = options.campaignChannelId;
297
+ if (options.periodStart !== void 0) this.periodStart = CampaignMetricSnapshot.coerceDate(options.periodStart) ?? /* @__PURE__ */ new Date(NaN);
298
+ if (options.periodEnd !== void 0) this.periodEnd = CampaignMetricSnapshot.coerceDate(options.periodEnd) ?? /* @__PURE__ */ new Date(NaN);
299
+ if (options.spendCents !== void 0) this.spendCents = options.spendCents;
300
+ if (options.impressions !== void 0) this.impressions = options.impressions;
301
+ if (options.clicks !== void 0) this.clicks = options.clicks;
302
+ if (options.conversions !== void 0) this.conversions = options.conversions;
303
+ if (options.leads !== void 0) this.leads = options.leads;
304
+ if (options.revenueCents !== void 0) this.revenueCents = options.revenueCents;
305
+ if (options.source !== void 0) this.source = options.source;
306
+ if (options.dedupeKey !== void 0) this.dedupeKey = options.dedupeKey;
307
+ }
308
+ async initialize() {
309
+ await super.initialize();
310
+ this.periodStart = CampaignMetricSnapshot.coerceDate(this.periodStart) ?? /* @__PURE__ */ new Date(NaN);
311
+ this.periodEnd = CampaignMetricSnapshot.coerceDate(this.periodEnd) ?? /* @__PURE__ */ new Date(NaN);
312
+ if (this.isPersisted) persistedSnapshotState.set(this, this.serializeState());
313
+ return this;
314
+ }
315
+ async save() {
316
+ this.assertMetrics();
317
+ await this.assertChannelMatchesCampaign();
318
+ const captured = persistedSnapshotState.get(this);
319
+ if (captured !== void 0) {
320
+ if (captured !== this.serializeState()) throw new Error(`CampaignMetricSnapshot ${this.id ?? "<new>"}: metric snapshots are immutable evidence; ingest a correcting snapshot instead.`);
321
+ } else if (this.id && await this.isSaved()) throw new Error(`CampaignMetricSnapshot ${this.id}: refusing to overwrite an existing snapshot from a non-hydrated instance; snapshots are immutable evidence.`);
322
+ else if (this.dedupeKey) try {
323
+ const row = await this.db.get(this.tableName, { dedupe_key: this.dedupeKey });
324
+ if (row && row.id !== this.id) throw new Error(`CampaignMetricSnapshot (dedupeKey '${this.dedupeKey}'): a snapshot with this dedupe key already exists; use MetricIngestionService for idempotent ingestion.`);
325
+ } catch (error) {
326
+ if (error instanceof Error && error.message.includes("idempotent ingestion")) throw error;
327
+ }
328
+ const result = await super.save();
329
+ persistedSnapshotState.set(this, this.serializeState());
330
+ return result;
331
+ }
332
+ async delete() {
333
+ throw new Error(`CampaignMetricSnapshot ${this.id ?? "<new>"}: metric snapshots are immutable evidence and cannot be deleted.`);
334
+ }
335
+ assertMetrics() {
336
+ if (Number.isNaN(this.periodStart.getTime()) || Number.isNaN(this.periodEnd.getTime())) throw new Error("CampaignMetricSnapshot periodStart and periodEnd must be valid dates.");
337
+ if (this.periodEnd.getTime() < this.periodStart.getTime()) throw new Error("CampaignMetricSnapshot periodEnd must be on or after periodStart.");
338
+ const integerMetrics = [
339
+ ["spendCents", this.spendCents],
340
+ ["impressions", this.impressions],
341
+ ["clicks", this.clicks],
342
+ ["conversions", this.conversions],
343
+ ["leads", this.leads]
344
+ ];
345
+ for (const [name, value] of integerMetrics) if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer.`);
346
+ if (this.revenueCents !== null && (!Number.isInteger(this.revenueCents) || this.revenueCents < 0)) throw new Error("revenueCents must be null or a non-negative integer.");
347
+ }
348
+ async assertChannelMatchesCampaign() {
349
+ if (!this.campaignChannelId) return;
350
+ if ((await this.loadRelated("campaignChannelId")).campaignId !== this.campaignId) throw new Error(`CampaignMetricSnapshot campaignChannelId '${this.campaignChannelId}' does not belong to campaignId '${this.campaignId}'.`);
351
+ }
352
+ serializeState() {
353
+ return JSON.stringify({
354
+ tenantId: this.tenantId,
355
+ campaignId: this.campaignId,
356
+ campaignChannelId: this.campaignChannelId,
357
+ periodStart: this.periodStart.toISOString(),
358
+ periodEnd: this.periodEnd.toISOString(),
359
+ spendCents: this.spendCents,
360
+ impressions: this.impressions,
361
+ clicks: this.clicks,
362
+ conversions: this.conversions,
363
+ leads: this.leads,
364
+ revenueCents: this.revenueCents,
365
+ source: this.source,
366
+ dedupeKey: this.dedupeKey
367
+ });
368
+ }
369
+ static coerceDate(value) {
370
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
371
+ if (typeof value === "number" || typeof value === "string") {
372
+ const date = new Date(value);
373
+ return Number.isNaN(date.getTime()) ? null : date;
374
+ }
375
+ return null;
376
+ }
377
+ };
378
+ __decorateClass([tenantId({ nullable: true })], CampaignMetricSnapshot.prototype, "tenantId", 2);
379
+ __decorateClass([foreignKey(Campaign, { required: true })], CampaignMetricSnapshot.prototype, "campaignId", 2);
380
+ __decorateClass([foreignKey(CampaignChannel, { nullable: true })], CampaignMetricSnapshot.prototype, "campaignChannelId", 2);
381
+ __decorateClass([field({ required: true })], CampaignMetricSnapshot.prototype, "periodStart", 2);
382
+ __decorateClass([field({ required: true })], CampaignMetricSnapshot.prototype, "periodEnd", 2);
383
+ __decorateClass([field({
384
+ type: "integer",
385
+ nullable: true
386
+ })], CampaignMetricSnapshot.prototype, "revenueCents", 2);
387
+ __decorateClass([field({ required: true })], CampaignMetricSnapshot.prototype, "source", 2);
388
+ __decorateClass([field({ required: true })], CampaignMetricSnapshot.prototype, "dedupeKey", 2);
389
+ CampaignMetricSnapshot = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
390
+ tableName: "campaign_metric_snapshots",
391
+ conflictColumns: ["dedupe_key"],
392
+ api: { include: [
393
+ "list",
394
+ "get",
395
+ "create"
396
+ ] },
397
+ mcp: { include: [
398
+ "list",
399
+ "get",
400
+ "create"
401
+ ] },
402
+ cli: { include: [
403
+ "list",
404
+ "get",
405
+ "create"
406
+ ] }
407
+ })], CampaignMetricSnapshot);
408
+ //#endregion
409
+ //#region src/collections/CampaignMetricSnapshotCollection.ts
410
+ var CampaignMetricSnapshotCollection = class extends SmrtCollection {
411
+ static _itemClass = CampaignMetricSnapshot;
412
+ async findByDedupeKey(dedupeKey) {
413
+ if (!dedupeKey) return null;
414
+ return (await this.list({
415
+ where: { dedupeKey },
416
+ limit: 1
417
+ }))[0] ?? null;
418
+ }
419
+ async getOrCreateByDedupeKey(options) {
420
+ const dedupeKey = options.dedupeKey ?? "";
421
+ if (!dedupeKey) throw new Error("CampaignMetricSnapshotCollection.getOrCreateByDedupeKey requires a dedupeKey.");
422
+ const existing = await this.findByDedupeKey(dedupeKey);
423
+ if (existing) return {
424
+ snapshot: existing,
425
+ created: false
426
+ };
427
+ try {
428
+ return {
429
+ snapshot: await this.create({
430
+ ...options,
431
+ periodStart: options.periodStart,
432
+ periodEnd: options.periodEnd,
433
+ _insertOnly: true
434
+ }),
435
+ created: true
436
+ };
437
+ } catch (error) {
438
+ const concurrent = await this.findByDedupeKey(dedupeKey);
439
+ if (concurrent) return {
440
+ snapshot: concurrent,
441
+ created: false
442
+ };
443
+ throw error;
444
+ }
445
+ }
446
+ async findByCampaign(campaignId) {
447
+ return await this.list({
448
+ where: { campaignId },
449
+ orderBy: "period_start ASC"
450
+ });
451
+ }
452
+ async findByChannel(campaignChannelId) {
453
+ return await this.list({
454
+ where: { campaignChannelId },
455
+ orderBy: "period_start ASC"
456
+ });
457
+ }
458
+ };
459
+ //#endregion
460
+ //#region src/services/BudgetPacingService.ts
461
+ var BudgetPacingService = class BudgetPacingService {
462
+ constructor(campaigns, channels, snapshots) {
463
+ this.campaigns = campaigns;
464
+ this.channels = channels;
465
+ this.snapshots = snapshots;
466
+ }
467
+ campaigns;
468
+ channels;
469
+ snapshots;
470
+ static async create(classOptions = {}) {
471
+ return new BudgetPacingService(await CampaignCollection.create(classOptions), await CampaignChannelCollection.create(classOptions), await CampaignMetricSnapshotCollection.create(classOptions));
472
+ }
473
+ async getCampaignPacing(campaignId, at = /* @__PURE__ */ new Date()) {
474
+ const campaign = await this.campaigns.get({ id: campaignId });
475
+ if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);
476
+ const all = await this.snapshots.findByCampaign(campaignId);
477
+ const rollups = all.filter((snapshot) => !snapshot.campaignChannelId);
478
+ const used = this.selectCampaignEvidence(all);
479
+ return this.calculate({
480
+ campaign,
481
+ snapshots: used,
482
+ budgetCents: campaign.budgetCents,
483
+ startAt: campaign.startAt,
484
+ endAt: campaign.endAt,
485
+ at,
486
+ usedCampaignRollups: rollups.length > 0
487
+ });
488
+ }
489
+ async getChannelPacing(campaignChannelId, at = /* @__PURE__ */ new Date()) {
490
+ const channel = await this.channels.get({ id: campaignChannelId });
491
+ if (!channel) throw new Error(`CampaignChannel '${campaignChannelId}' was not found.`);
492
+ const campaign = await this.campaigns.get({ id: channel.campaignId });
493
+ if (!campaign) throw new Error(`Campaign '${channel.campaignId}' was not found.`);
494
+ const snapshots = await this.snapshots.findByChannel(campaignChannelId);
495
+ return {
496
+ ...this.calculate({
497
+ campaign,
498
+ snapshots,
499
+ budgetCents: channel.allocatedBudgetCents,
500
+ startAt: channel.startAt ?? campaign.startAt,
501
+ endAt: channel.endAt ?? campaign.endAt,
502
+ at,
503
+ usedCampaignRollups: false
504
+ }),
505
+ campaignChannelId
506
+ };
507
+ }
508
+ calculate({ campaign, snapshots, budgetCents, startAt, endAt, at, usedCampaignRollups }) {
509
+ const spendCents = snapshots.reduce((sum, snapshot) => sum + snapshot.spendCents, 0);
510
+ const elapsedFraction = this.elapsedFraction(startAt, endAt, at);
511
+ const expectedSpendCents = elapsedFraction === null ? null : Math.round(budgetCents * elapsedFraction);
512
+ const varianceCents = expectedSpendCents === null ? null : spendCents - expectedSpendCents;
513
+ const budgetFraction = budgetCents > 0 ? spendCents / budgetCents : null;
514
+ return {
515
+ campaignId: campaign.id ?? "",
516
+ currency: campaign.currency,
517
+ budgetCents,
518
+ spendCents,
519
+ remainingCents: budgetCents - spendCents,
520
+ expectedSpendCents,
521
+ varianceCents,
522
+ budgetFraction,
523
+ elapsedFraction,
524
+ status: this.pacingStatus({
525
+ campaign,
526
+ budgetCents,
527
+ spendCents,
528
+ elapsedFraction,
529
+ varianceCents,
530
+ startAt,
531
+ at
532
+ }),
533
+ snapshotCount: snapshots.length,
534
+ usedCampaignRollups
535
+ };
536
+ }
537
+ pacingStatus({ campaign, budgetCents, spendCents, elapsedFraction, varianceCents, startAt, at }) {
538
+ if (budgetCents <= 0) return "unbudgeted";
539
+ if (spendCents > budgetCents) return "over_budget";
540
+ if (startAt && at.getTime() < startAt.getTime()) return "not_started";
541
+ if (campaign.status === "completed" || campaign.status === "archived" || elapsedFraction === 1) return "complete";
542
+ if (varianceCents === null) return "on_track";
543
+ const toleranceCents = Math.max(1, Math.round(budgetCents * .05));
544
+ if (varianceCents > toleranceCents) return "ahead";
545
+ if (varianceCents < -toleranceCents) return "behind";
546
+ return "on_track";
547
+ }
548
+ /**
549
+ * Prefer a campaign rollup for each exact reporting period, while retaining
550
+ * channel evidence for periods that do not yet have a rollup. This prevents
551
+ * double counting without dropping earlier/later channel-only periods.
552
+ */
553
+ selectCampaignEvidence(snapshots) {
554
+ const rollupPeriods = new Set(snapshots.filter((snapshot) => !snapshot.campaignChannelId).map((snapshot) => this.periodKey(snapshot)));
555
+ return snapshots.filter((snapshot) => !snapshot.campaignChannelId || !rollupPeriods.has(this.periodKey(snapshot)));
556
+ }
557
+ periodKey(snapshot) {
558
+ return `${snapshot.periodStart.toISOString()}:${snapshot.periodEnd.toISOString()}`;
559
+ }
560
+ elapsedFraction(startAt, endAt, at) {
561
+ if (!startAt || !endAt) return null;
562
+ const start = startAt.getTime();
563
+ const end = endAt.getTime();
564
+ if (end <= start) return at.getTime() < start ? 0 : 1;
565
+ return Math.min(1, Math.max(0, (at.getTime() - start) / (end - start)));
566
+ }
567
+ };
568
+ //#endregion
569
+ //#region src/services/CampaignLifecycleService.ts
570
+ var CampaignLifecycleService = class CampaignLifecycleService {
571
+ constructor(campaigns) {
572
+ this.campaigns = campaigns;
573
+ }
574
+ campaigns;
575
+ static async create(classOptions = {}) {
576
+ return new CampaignLifecycleService(await CampaignCollection.create(classOptions));
577
+ }
578
+ async transition(campaignId, next) {
579
+ const campaign = await this.campaigns.get({ id: campaignId });
580
+ if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);
581
+ campaign.transitionTo(next);
582
+ await campaign.save();
583
+ return campaign;
584
+ }
585
+ async schedule(campaignId) {
586
+ return await this.transition(campaignId, "scheduled");
587
+ }
588
+ async activate(campaignId) {
589
+ return await this.transition(campaignId, "active");
590
+ }
591
+ async pause(campaignId) {
592
+ return await this.transition(campaignId, "paused");
593
+ }
594
+ async resume(campaignId) {
595
+ return await this.transition(campaignId, "active");
596
+ }
597
+ async complete(campaignId) {
598
+ return await this.transition(campaignId, "completed");
599
+ }
600
+ async archive(campaignId) {
601
+ return await this.transition(campaignId, "archived");
602
+ }
603
+ };
604
+ //#endregion
605
+ //#region src/services/MetricIngestionService.ts
606
+ var MetricIngestionService = class MetricIngestionService {
607
+ constructor(snapshots) {
608
+ this.snapshots = snapshots;
609
+ }
610
+ snapshots;
611
+ static async create(classOptions = {}) {
612
+ return new MetricIngestionService(await CampaignMetricSnapshotCollection.create(classOptions));
613
+ }
614
+ async ingest(options) {
615
+ if (!options.campaignId) throw new Error("Metric ingestion requires campaignId.");
616
+ if (!options.source?.trim()) throw new Error("Metric ingestion requires source.");
617
+ if (!options.dedupeKey?.trim()) throw new Error("Metric ingestion requires dedupeKey.");
618
+ if (options.periodStart == null || options.periodEnd == null) throw new Error("Metric ingestion requires periodStart and periodEnd.");
619
+ return await this.snapshots.getOrCreateByDedupeKey(options);
620
+ }
621
+ };
622
+ //#endregion
623
+ export { BudgetPacingService, CAMPAIGN_CHANNEL_KINDS, CAMPAIGN_CHANNEL_STATUSES, CAMPAIGN_STATUSES, Campaign, CampaignChannel, CampaignChannelCollection, CampaignCollection, CampaignLifecycleService, CampaignMetricSnapshot, CampaignMetricSnapshotCollection, MetricIngestionService };
624
+
625
+ //# sourceMappingURL=index.js.map