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