@happyvertical/smrt-analytics 0.34.9 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +3 -3
- package/dist/models/AnalyticsDataStream.d.ts +19 -2
- package/dist/models/AnalyticsDataStream.d.ts.map +1 -1
- package/dist/models/AnalyticsEvent.d.ts +24 -2
- package/dist/models/AnalyticsEvent.d.ts.map +1 -1
- package/dist/models/AnalyticsProperty.d.ts +22 -2
- package/dist/models/AnalyticsProperty.d.ts.map +1 -1
- package/dist/models/AnalyticsReport.d.ts +31 -3
- package/dist/models/AnalyticsReport.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/package.json +7 -7
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/prompts.ts","../src/types/index.ts","../src/models/AnalyticsDataStream.ts","../src/collections/AnalyticsDataStreamCollection.ts","../src/models/AnalyticsEvent.ts","../src/collections/AnalyticsEventCollection.ts","../src/models/AnalyticsProperty.ts","../src/collections/AnalyticsPropertyCollection.ts","../src/models/AnalyticsReport.ts","../src/collections/AnalyticsReportCollection.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Prompt registrations for the @happyvertical/smrt-analytics package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-properties` (see\n * `prompts.ts`) and `@happyvertical/smrt-content` (see `content-prompts.ts`).\n *\n * PII / internal-field exclusion policy:\n *\n * Variables that this package never forwards to the AI provider:\n * - `id`, `tenantId`, `propertyId` and other foreign-key/UUID fields —\n * they identify internal records and provide no analytic value.\n * - `apiSecret`, `measurementId`, `externalId`, `siteDomain` — these are\n * provider credentials or platform-specific identifiers (e.g. GA4 API\n * secrets, Matomo `idSite`, custom `G-XXXX` measurement IDs) that may\n * be tenant-private configuration.\n * - `providerMetadata` — extensible JSON blob that may contain\n * credentials, account IDs, or other configuration secrets.\n * - `lastError`, raw `dimensionFilter` / `metricFilter` JSON — internal\n * error strings (which may contain auth tokens) and filter expressions\n * that may reference cookie IDs or user-pseudo-IDs are kept out of the\n * prompt variable set.\n *\n * Variables this package DOES forward (potentially carrying PII the caller\n * persisted):\n * - `reportData` (a JSON.stringify of the persisted `resultData`) — this\n * is forwarded VERBATIM to the AI in `analyzeResults` and\n * `hasPositiveTrends`. The package cannot strip PII because the row\n * schema is determined by which dimensions/metrics the caller asked\n * the analytics provider to return. If the persisted rows contain\n * `userPseudoId`, `clientId`, IP-derived geolocation, or any other\n * identifier, those fields WILL reach the AI provider. The forwarding\n * is pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`. Callers must either\n * exclude PII-bearing dimensions before persisting, apply a column\n * allowlist at the call site, or override the prompt template via\n * `PromptOverride` to redact rows.\n *\n * Acceptable variables (always safe): human-readable names, time periods,\n * computed aggregates (row counts, totals), and high-level dimension/metric\n * labels (which are public GA4/Plausible/Matomo schema names).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n/**\n * `AnalyticsProperty.analyzePerformance()` prompt. Only the human-readable\n * display name, provider label, and the requested period reach the model;\n * provider IDs, API secrets, and the providerMetadata blob are excluded.\n */\nexport const smrtAnalyticsAnalyzePerformancePrompt = definePrompt({\n key: 'smrtAnalytics.property.analyzePerformance',\n template: `Analyze the analytics performance for this property over the last {period}.\nConsider: traffic trends, user engagement, conversion patterns.\nProperty: {propertyDisplayName} ({propertyProvider})`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * `AnalyticsReport.analyzeResults()` prompt. Surfaces report name, the\n * dimension/metric labels (which are public GA4/Plausible/Matomo schema\n * names — not user data), the date-range window, and the row count plus\n * a JSON-stringified `resultData` blob.\n *\n * **`resultData` is forwarded verbatim.** This package does not strip PII\n * from result rows — it cannot, because the row schema is determined by\n * the dimensions/metrics the caller asked the analytics provider to\n * return. If the caller persists rows containing a `userPseudoId`,\n * `clientId`, IP-derived geolocation, or any other identifier, those\n * fields WILL reach the AI provider. Either:\n *\n * - exclude PII-bearing dimensions before persisting `resultData`\n * (e.g. don't request `userPseudoId` as a GA4 dimension), or\n * - apply a column allowlist at the call site before invoking\n * `analyzeResults()`, or\n * - override the prompt template via `PromptOverride` to redact rows.\n *\n * The forwarding contract is pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`.\n */\nexport const smrtAnalyticsAnalyzeResultsPrompt = definePrompt({\n key: 'smrtAnalytics.report.analyzeResults',\n template: `Analyze these analytics report results and provide insights:\n\nReport: {reportName}\nDimensions: {reportDimensions}\nMetrics: {reportMetrics}\nDate Range: {dateRangeStart} to {dateRangeEnd}\nRow Count: {rowCount}\n\nData: {reportData}\n\nProvide:\n1. Key findings\n2. Trends or patterns\n3. Actionable recommendations`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * `AnalyticsReport.hasPositiveTrends()` boolean classifier prompt. Same\n * variables as `analyzeResults` minus the descriptive name — only the\n * provider-schema metric labels and the aggregate `resultData` payload are\n * needed to decide whether the trend is positive.\n *\n * The template explicitly instructs the model to begin its response with\n * either \"yes\" or \"no\" so the boolean coercion in `hasPositiveTrends()`\n * (`/^\\s*(yes|true)\\b/i`) is reliable. Without the leading-word\n * instruction the model commonly answers with prose that happens to\n * describe positive trends but starts with a sentence like \"The\n * conversion rate is up...\" — the parser would then treat that as\n * `false` despite a positive analysis. Tenants overriding this template\n * MUST preserve the leading yes/no convention.\n */\nexport const smrtAnalyticsHasPositiveTrendsPrompt = definePrompt({\n key: 'smrtAnalytics.report.hasPositiveTrends',\n template: `Based on these report results, are the metrics showing positive trends?\n\nMetrics: {reportMetrics}\nData: {reportData}\n\nConsider: user growth, engagement, conversions as positive indicators.\n\nBegin your response with the single word \"yes\" or \"no\" (lower case), then\noptionally provide a one-sentence explanation. Tooling parses the leading\nword — \"yes\" or \"true\" means positive, anything else means negative.`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * Build the message-options object passed to `aiClient.message()` from a\n * `ResolvedPromptAI` shape. Mirrors the helper in `smrt-properties` and\n * `smrt-content` so behavior across packages is identical.\n */\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * Types and enums for smrt-analytics package\n */\n\n/**\n * Analytics provider types\n */\nexport enum AnalyticsProvider {\n GA4 = 'ga4',\n PLAUSIBLE = 'plausible',\n MATOMO = 'matomo',\n}\n\n/**\n * Status for analytics properties\n */\nexport enum AnalyticsPropertyStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n PENDING = 'pending',\n}\n\n/**\n * Data stream types (matching SDK types)\n */\nexport enum DataStreamType {\n WEB = 'WEB_DATA_STREAM',\n ANDROID = 'ANDROID_APP_DATA_STREAM',\n IOS = 'IOS_APP_DATA_STREAM',\n}\n\n/**\n * Status for data streams\n */\nexport enum DataStreamStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n}\n\n/**\n * Report status\n */\nexport enum ReportStatus {\n DRAFT = 'draft',\n SCHEDULED = 'scheduled',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Report frequency for scheduled reports\n */\nexport enum ReportFrequency {\n ONCE = 'once',\n DAILY = 'daily',\n WEEKLY = 'weekly',\n MONTHLY = 'monthly',\n}\n\n/**\n * Custom dimension scope (matching SDK types)\n */\nexport enum CustomDimensionScope {\n EVENT = 'EVENT',\n USER = 'USER',\n ITEM = 'ITEM',\n}\n\n/**\n * Event tracking status\n */\nexport enum TrackingEventStatus {\n PENDING = 'pending',\n SENT = 'sent',\n FAILED = 'failed',\n}\n\n/**\n * Key event counting method (matching SDK types)\n */\nexport enum CountingMethod {\n ONCE_PER_EVENT = 'ONCE_PER_EVENT',\n ONCE_PER_SESSION = 'ONCE_PER_SESSION',\n}\n\n// ============================================================================\n// Analytics aggregation types\n// ============================================================================\n\n/**\n * Day-over-day property stats with trend calculation\n */\nexport interface PropertyStatsWithTrend {\n todayPageviews: number;\n todayUsers: number;\n yesterdayPageviews: number;\n yesterdayUsers: number;\n trend: 'up' | 'down' | 'flat';\n /**\n * Day-over-day percentage change. `null` when today grew from a zero\n * yesterday-baseline (a \"new\" surge with no finite percentage); the UI\n * renders this as \"new\" rather than 0%.\n */\n trendPercent: number | null;\n}\n\n// ============================================================================\n// SDK-compatible type definitions\n// These mirror the types from @happyvertical/analytics for when SDK is not installed\n// ============================================================================\n\n/**\n * Analytics property/site representation (SDK-compatible)\n */\nexport interface SDKProperty {\n id: string;\n name: string;\n displayName: string;\n createTime: string;\n updateTime?: string;\n timeZone?: string;\n currencyCode?: string;\n industryCategory?: string;\n serviceLevel?: 'STANDARD' | 'PREMIUM';\n}\n\n/**\n * Data stream representation (SDK-compatible)\n */\nexport interface SDKDataStream {\n id: string;\n type: 'WEB_DATA_STREAM' | 'ANDROID_APP_DATA_STREAM' | 'IOS_APP_DATA_STREAM';\n displayName: string;\n measurementId?: string;\n firebaseAppId?: string;\n defaultUri?: string;\n createTime: string;\n updateTime?: string;\n}\n\n/**\n * Custom dimension representation (SDK-compatible)\n */\nexport interface SDKCustomDimension {\n id: string;\n name: string;\n parameterName: string;\n displayName: string;\n description?: string;\n scope: 'EVENT' | 'USER' | 'ITEM';\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Custom metric representation (SDK-compatible)\n */\nexport interface SDKCustomMetric {\n id: string;\n name: string;\n parameterName: string;\n displayName: string;\n description?: string;\n scope: 'EVENT';\n measurementUnit: string;\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n/**\n * Key event (conversion) representation (SDK-compatible)\n */\nexport interface SDKKeyEvent {\n id: string;\n name: string;\n eventName: string;\n createTime: string;\n countingMethod?: 'ONCE_PER_EVENT' | 'ONCE_PER_SESSION';\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n/**\n * Report options (SDK-compatible)\n */\nexport interface SDKReportOptions {\n dateRanges: Array<{\n startDate: string;\n endDate: string;\n name?: string;\n }>;\n dimensions?: Array<{ name: string }>;\n metrics: Array<{ name: string }>;\n offset?: number;\n limit?: number;\n orderBys?: Array<{\n metric?: { metricName: string };\n dimension?: { dimensionName: string };\n desc?: boolean;\n }>;\n keepEmptyRows?: boolean;\n returnPropertyQuota?: boolean;\n}\n\n/**\n * Report result (SDK-compatible)\n */\nexport interface SDKReportResult {\n dimensionHeaders: Array<{ name: string }>;\n metricHeaders: Array<{ name: string; type: string }>;\n rows: Array<{\n dimensionValues: Array<{ value: string }>;\n metricValues: Array<{ value: string }>;\n }>;\n rowCount?: number;\n metadata?: {\n currencyCode?: string;\n timeZone?: string;\n dataLossFromOtherRow?: boolean;\n emptyReason?: string;\n };\n}\n\n/**\n * Track event payload (SDK-compatible)\n */\nexport interface SDKTrackEvent {\n name: string;\n params?: Record<string, string | number | boolean>;\n clientId?: string;\n userId?: string;\n timestamp?: number;\n nonPersonalizedAds?: boolean;\n}\n\n/**\n * Pageview event payload (SDK-compatible)\n */\nexport interface SDKPageviewEvent {\n pagePath: string;\n pageTitle?: string;\n pageLocation?: string;\n clientId?: string;\n userId?: string;\n params?: Record<string, string | number | boolean>;\n}\n\n/**\n * Analytics provider capabilities (SDK-compatible)\n */\nexport interface AnalyticsCapabilities {\n propertyManagement: boolean;\n dataStreams: boolean;\n customDimensions: boolean;\n customMetrics: boolean;\n keyEvents: boolean;\n reporting: boolean;\n realtimeReporting: boolean;\n serverSideTracking: boolean;\n clientSideSnippet: boolean;\n userIdentification: boolean;\n batchTracking: boolean;\n}\n\n/**\n * Analytics interface (SDK-compatible)\n * This is a subset of the full SDK interface for type compatibility\n */\nexport interface AnalyticsInterface {\n createProperty(options: {\n displayName: string;\n timeZone?: string;\n currencyCode?: string;\n }): Promise<SDKProperty>;\n listProperties(): Promise<SDKProperty[]>;\n getProperty(propertyId: string): Promise<SDKProperty>;\n getDataStreams(propertyId: string): Promise<SDKDataStream[]>;\n runReport(\n propertyId: string,\n options: SDKReportOptions,\n ): Promise<SDKReportResult>;\n track(event: SDKTrackEvent): Promise<void>;\n trackPageview(pageview: SDKPageviewEvent): Promise<void>;\n trackBatch(events: SDKTrackEvent[]): Promise<void>;\n getCapabilities(): Promise<AnalyticsCapabilities>;\n}\n","/**\n * AnalyticsDataStream model - Represents a data stream for a property\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { DataStreamStatus, DataStreamType } from '../types/index.js';\n\n/**\n * AnalyticsDataStream represents a data stream (web, iOS, Android) for an analytics property.\n *\n * @example\n * ```typescript\n * const stream = await streams.create({\n * propertyId: property.id,\n * displayName: 'Web Stream',\n * streamType: DataStreamType.WEB,\n * measurementId: 'G-XXXXXXXXXX',\n * defaultUri: 'https://example.com'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class AnalyticsDataStream extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Data streams expose tenant-private platform configuration (measurement\n * IDs, Firebase app IDs, bundle/package names, default URIs). Without tenant\n * scoping the generated `list`/`get` API returns every tenant's streams.\n * `@TenantScoped` auto-filters reads and binds writes to the active tenant.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Human-readable display name\n */\n displayName: string = '';\n\n /**\n * Stream type (WEB, ANDROID, IOS)\n */\n streamType: DataStreamType = DataStreamType.WEB;\n\n /**\n * External stream ID from provider\n */\n externalId: string = '';\n\n /**\n * Measurement ID for web streams (G-XXXXXXX)\n */\n measurementId: string = '';\n\n /**\n * Firebase App ID for app streams\n */\n firebaseAppId: string = '';\n\n /**\n * Default URI for web streams\n */\n defaultUri: string = '';\n\n /**\n * Bundle ID for iOS apps\n */\n bundleId: string = '';\n\n /**\n * Package name for Android apps\n */\n packageName: string = '';\n\n /**\n * Stream status\n */\n status: DataStreamStatus = DataStreamStatus.ACTIVE;\n\n /**\n * Enhanced measurement enabled\n */\n enhancedMeasurement: boolean = true;\n\n constructor(options: any = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.displayName !== undefined)\n this.displayName = options.displayName;\n if (options.streamType !== undefined) this.streamType = options.streamType;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.measurementId !== undefined)\n this.measurementId = options.measurementId;\n if (options.firebaseAppId !== undefined)\n this.firebaseAppId = options.firebaseAppId;\n if (options.defaultUri !== undefined) this.defaultUri = options.defaultUri;\n if (options.bundleId !== undefined) this.bundleId = options.bundleId;\n if (options.packageName !== undefined)\n this.packageName = options.packageName;\n if (options.status !== undefined) this.status = options.status;\n if (options.enhancedMeasurement !== undefined)\n this.enhancedMeasurement = options.enhancedMeasurement;\n }\n\n /**\n * Check if this is a web stream\n */\n isWeb(): boolean {\n return this.streamType === DataStreamType.WEB;\n }\n\n /**\n * Check if this is an iOS app stream\n */\n isIOS(): boolean {\n return this.streamType === DataStreamType.IOS;\n }\n\n /**\n * Check if this is an Android app stream\n */\n isAndroid(): boolean {\n return this.streamType === DataStreamType.ANDROID;\n }\n\n /**\n * Check if this is a mobile app stream (iOS or Android)\n */\n isMobileApp(): boolean {\n return this.isIOS() || this.isAndroid();\n }\n\n /**\n * Get the platform identifier (measurementId for web, firebaseAppId for apps)\n */\n getPlatformId(): string {\n if (this.isWeb()) {\n return this.measurementId;\n }\n return this.firebaseAppId;\n }\n}\n\nexport default AnalyticsDataStream;\n","/**\n * AnalyticsDataStreamCollection - Collection manager for AnalyticsDataStream objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsDataStream } from '../models/AnalyticsDataStream.js';\nimport { DataStreamStatus, DataStreamType } from '../types/index.js';\n\nexport class AnalyticsDataStreamCollection extends SmrtCollection<AnalyticsDataStream> {\n static readonly _itemClass = AnalyticsDataStream;\n\n /**\n * Find streams by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of data streams\n */\n async findByProperty(propertyId: string): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find stream by external ID\n *\n * @param externalId - External stream ID from provider\n * @returns Matching stream or null\n */\n async findByExternalId(\n externalId: string,\n ): Promise<AnalyticsDataStream | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find stream by measurement ID (web streams)\n *\n * @param measurementId - GA4 measurement ID (G-XXXXXXX)\n * @returns Matching stream or null\n */\n async findByMeasurementId(\n measurementId: string,\n ): Promise<AnalyticsDataStream | null> {\n const results = await this.list({\n where: { measurementId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find streams by type\n *\n * @param streamType - Stream type (WEB, ANDROID, IOS)\n * @returns Array of matching streams\n */\n async findByType(streamType: DataStreamType): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { streamType },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all web streams\n */\n async findWebStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.WEB);\n }\n\n /**\n * Find all iOS app streams\n */\n async findIOSStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.IOS);\n }\n\n /**\n * Find all Android app streams\n */\n async findAndroidStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.ANDROID);\n }\n\n /**\n * Find all mobile app streams (iOS + Android)\n */\n async findMobileStreams(): Promise<AnalyticsDataStream[]> {\n const ios = await this.findIOSStreams();\n const android = await this.findAndroidStreams();\n return [...ios, ...android].sort((a, b) =>\n a.displayName.localeCompare(b.displayName),\n );\n }\n\n /**\n * Find streams by status\n *\n * @param status - Stream status\n * @returns Array of matching streams\n */\n async findByStatus(status: DataStreamStatus): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { status },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all active streams\n */\n async findActive(): Promise<AnalyticsDataStream[]> {\n return await this.findByStatus(DataStreamStatus.ACTIVE);\n }\n\n /**\n * Find active streams for a property\n *\n * @param propertyId - Parent property ID\n * @returns Array of active streams\n */\n async findActiveByProperty(\n propertyId: string,\n ): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: {\n propertyId,\n status: DataStreamStatus.ACTIVE,\n },\n orderBy: 'displayName ASC',\n });\n }\n}\n","/**\n * AnalyticsEvent model - Tracked events log\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { TrackingEventStatus } from '../types/index.js';\n\n/**\n * AnalyticsEvent represents a tracked analytics event (server-side tracking log).\n *\n * @example\n * ```typescript\n * const event = await events.create({\n * propertyId: property.id,\n * eventName: 'purchase',\n * clientId: 'user-123',\n * params: JSON.stringify({ value: 99.99, currency: 'USD' })\n * });\n * await event.send();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get', 'track'] },\n cli: true,\n})\nexport class AnalyticsEvent extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Analytics events carry end-user PII (`userId`, `clientId`, `ipAddress`,\n * `userAgent`, `sessionId`). Without tenant scoping the generated\n * `list`/`get` API leaks every tenant's visitor data, and `create` lets a\n * caller write events bound to another tenant's `propertyId`. `@TenantScoped`\n * auto-filters reads and binds writes to the active tenant context.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Event name (e.g., 'purchase', 'page_view', 'sign_up')\n */\n eventName: string = '';\n\n /**\n * Client ID for anonymous tracking\n */\n clientId: string = '';\n\n /**\n * User ID for identified tracking\n */\n userId: string = '';\n\n /**\n * Event parameters (JSON)\n */\n params: string = '{}';\n\n /**\n * Event timestamp\n */\n eventTimestamp: Date = new Date();\n\n /**\n * Tracking status\n */\n status: TrackingEventStatus = TrackingEventStatus.PENDING;\n\n /**\n * Timestamp when sent to provider\n */\n sentAt: Date | null = null;\n\n /**\n * Error message if sending failed\n */\n errorMessage: string = '';\n\n /**\n * Retry count\n */\n retryCount: number = 0;\n\n /**\n * Disable personalized ads\n */\n nonPersonalizedAds: boolean = false;\n\n /**\n * Session ID\n */\n sessionId: string = '';\n\n /**\n * Page path (for pageview events)\n */\n pagePath: string = '';\n\n /**\n * Page title (for pageview events)\n */\n pageTitle: string = '';\n\n /**\n * User agent\n */\n userAgent: string = '';\n\n /**\n * IP address (anonymized)\n */\n ipAddress: string = '';\n\n constructor(options: any = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.eventName !== undefined) this.eventName = options.eventName;\n if (options.clientId !== undefined) this.clientId = options.clientId;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.params !== undefined) this.params = options.params;\n if (options.eventTimestamp !== undefined)\n this.eventTimestamp = options.eventTimestamp;\n if (options.status !== undefined) this.status = options.status;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.retryCount !== undefined) this.retryCount = options.retryCount;\n if (options.nonPersonalizedAds !== undefined)\n this.nonPersonalizedAds = options.nonPersonalizedAds;\n if (options.sessionId !== undefined) this.sessionId = options.sessionId;\n if (options.pagePath !== undefined) this.pagePath = options.pagePath;\n if (options.pageTitle !== undefined) this.pageTitle = options.pageTitle;\n if (options.userAgent !== undefined) this.userAgent = options.userAgent;\n if (options.ipAddress !== undefined) this.ipAddress = options.ipAddress;\n }\n\n /**\n * Get parsed event parameters\n */\n getParams(): Record<string, string | number | boolean> {\n try {\n return JSON.parse(this.params);\n } catch {\n return {};\n }\n }\n\n /**\n * Set event parameters\n */\n setParams(params: Record<string, string | number | boolean>): void {\n this.params = JSON.stringify(params);\n }\n\n /**\n * Add a parameter to the event\n */\n addParam(key: string, value: string | number | boolean): void {\n const params = this.getParams();\n params[key] = value;\n this.setParams(params);\n }\n\n /**\n * Check if this is a pageview event\n */\n isPageview(): boolean {\n return this.eventName === 'page_view' || this.eventName === 'pageview';\n }\n\n /**\n * Check if this is a conversion event\n */\n isConversion(): boolean {\n const conversionEvents = [\n 'purchase',\n 'sign_up',\n 'generate_lead',\n 'begin_checkout',\n ];\n return conversionEvents.includes(this.eventName);\n }\n\n /**\n * Mark as sent successfully\n */\n markSent(): void {\n this.status = TrackingEventStatus.SENT;\n this.sentAt = new Date();\n this.errorMessage = '';\n }\n\n /**\n * Mark as failed\n */\n markFailed(error: string): void {\n this.status = TrackingEventStatus.FAILED;\n this.errorMessage = error;\n this.retryCount++;\n }\n\n /**\n * Reset for retry\n */\n resetForRetry(): void {\n this.status = TrackingEventStatus.PENDING;\n this.sentAt = null;\n }\n\n /**\n * Check if event should be retried\n */\n shouldRetry(maxRetries: number = 3): boolean {\n return (\n this.status === TrackingEventStatus.FAILED && this.retryCount < maxRetries\n );\n }\n\n /**\n * Build SDK TrackEvent object\n */\n toTrackEvent(): {\n name: string;\n params?: Record<string, string | number | boolean>;\n clientId?: string;\n userId?: string;\n timestamp?: number;\n nonPersonalizedAds?: boolean;\n } {\n return {\n name: this.eventName,\n params: this.getParams(),\n clientId: this.clientId || undefined,\n userId: this.userId || undefined,\n timestamp: this.eventTimestamp.getTime() * 1000, // Convert to microseconds\n nonPersonalizedAds: this.nonPersonalizedAds,\n };\n }\n}\n\nexport default AnalyticsEvent;\n","/**\n * AnalyticsEventCollection - Collection manager for AnalyticsEvent objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsEvent } from '../models/AnalyticsEvent.js';\nimport {\n type PropertyStatsWithTrend,\n TrackingEventStatus,\n} from '../types/index.js';\n\nexport class AnalyticsEventCollection extends SmrtCollection<AnalyticsEvent> {\n static readonly _itemClass = AnalyticsEvent;\n\n /**\n * Offset of `timeZone` at `instant`, in milliseconds (wall-clock minus UTC).\n *\n * Reads the zone's wall-clock Y/M/D h:m:s for `instant` via\n * `Intl.DateTimeFormat` parts and subtracts the real UTC instant. Positive\n * east of UTC, negative west (e.g. `America/Los_Angeles` returns roughly\n * `-7h`/`-8h` depending on DST).\n *\n * @throws RangeError if `timeZone` is not a valid IANA identifier.\n */\n private zoneOffsetMs(instant: Date, timeZone: string): number {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n }).formatToParts(instant);\n const lookup = (type: Intl.DateTimeFormatPartTypes): number =>\n Number.parseInt(parts.find((p) => p.type === type)?.value ?? '0', 10);\n let hour = lookup('hour');\n // Intl can emit hour \"24\" for midnight under hour12:false; normalize to 0.\n if (hour === 24) hour = 0;\n const asUtc = Date.UTC(\n lookup('year'),\n lookup('month') - 1,\n lookup('day'),\n hour,\n lookup('minute'),\n lookup('second'),\n );\n return asUtc - instant.getTime();\n }\n\n /**\n * Resolve the UTC instant marking the start of the calendar day (00:00) that\n * `instant` falls on **within the given IANA time zone**.\n *\n * Day-over-day buckets (\"today vs yesterday\") must respect the property's\n * configured `timeZone` (defaults to `America/Los_Angeles`), otherwise a\n * pageview at 11:30pm local time — already the next UTC day — is bucketed\n * into the wrong day. We read the wall-clock civil date for the zone, then\n * map that date's local midnight back to a UTC instant, correcting for the\n * zone offset (and re-correcting once across a DST boundary).\n *\n * Invalid/unknown zone identifiers fall back to UTC day boundaries (matching\n * the previous behaviour) rather than throwing.\n *\n * @param instant - Reference instant.\n * @param timeZone - IANA time zone (e.g. `America/Los_Angeles`).\n * @returns UTC `Date` for local midnight of the day `instant` is in.\n */\n protected startOfDayInZone(instant: Date, timeZone: string): Date {\n let civil: { year: number; month: number; day: number };\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(instant);\n const lookup = (type: Intl.DateTimeFormatPartTypes): number =>\n Number.parseInt(parts.find((p) => p.type === type)?.value ?? '0', 10);\n civil = {\n year: lookup('year'),\n month: lookup('month'),\n day: lookup('day'),\n };\n } catch {\n // Unknown/invalid time zone -> UTC day boundary.\n return new Date(\n Date.UTC(\n instant.getUTCFullYear(),\n instant.getUTCMonth(),\n instant.getUTCDate(),\n ),\n );\n }\n\n // Local midnight of `civil`, as a UTC instant: treat civil-midnight as if\n // UTC, then subtract the zone offset at that guess. Re-derive the offset at\n // the corrected instant and re-correct once if it changed (DST edge).\n const guess = Date.UTC(civil.year, civil.month - 1, civil.day);\n const offset = this.zoneOffsetMs(new Date(guess), timeZone);\n let utc = guess - offset;\n const offset2 = this.zoneOffsetMs(new Date(utc), timeZone);\n if (offset2 !== offset) utc = guess - offset2;\n return new Date(utc);\n }\n\n /**\n * Resolve the UTC instant for the start of the day *before* `todayStart`'s\n * local day, in `timeZone`.\n *\n * Steps back 12h from local midnight (landing safely inside the previous\n * civil day regardless of DST — a naive `- 24h` skips a day across\n * spring-forward), then re-resolves start-of-day.\n *\n * @param todayStart - Local-midnight UTC instant from {@link startOfDayInZone}.\n * @param timeZone - IANA time zone.\n * @returns UTC `Date` for local midnight of the prior calendar day.\n */\n protected startOfYesterdayInZone(todayStart: Date, timeZone: string): Date {\n return this.startOfDayInZone(\n new Date(todayStart.getTime() - 12 * 3_600_000),\n timeZone,\n );\n }\n\n /**\n * Classify a day-over-day change into a trend direction + percent.\n *\n * - `yesterday > 0`: percent = rounded delta; >5% up, <-5% down, else flat.\n * - `yesterday === 0 && today > 0`: a brand-new surge from a zero baseline —\n * classified `up` with a `null` percent (no finite percentage exists), so\n * the UI renders \"new\" rather than a misleading flat 0%.\n * - `yesterday === 0 && today === 0`: flat, 0%.\n *\n * @param today - Today's count.\n * @param yesterday - Yesterday's count.\n * @returns Trend direction and percent (null when growing from zero).\n */\n protected classifyTrend(\n today: number,\n yesterday: number,\n ): { trend: 'up' | 'down' | 'flat'; trendPercent: number | null } {\n if (yesterday > 0) {\n const change = ((today - yesterday) / yesterday) * 100;\n const trendPercent = Math.round(change);\n let trend: 'up' | 'down' | 'flat' = 'flat';\n if (change > 5) trend = 'up';\n else if (change < -5) trend = 'down';\n return { trend, trendPercent };\n }\n if (today > 0) {\n // Growth from a zero baseline: a real surge, no finite percentage.\n return { trend: 'up', trendPercent: null };\n }\n return { trend: 'flat', trendPercent: 0 };\n }\n\n /**\n * Find events by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of events\n */\n async findByProperty(propertyId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by event name\n *\n * @param eventName - Event name to filter by\n * @returns Array of matching events\n */\n async findByEventName(eventName: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { eventName },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by client ID\n *\n * @param clientId - Client ID\n * @returns Array of events for this client\n */\n async findByClientId(clientId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { clientId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by user ID\n *\n * @param userId - User ID\n * @returns Array of events for this user\n */\n async findByUserId(userId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { userId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by status\n *\n * @param status - Tracking event status\n * @returns Array of matching events\n */\n async findByStatus(status: TrackingEventStatus): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { status },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find all pending events\n */\n async findPending(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.PENDING);\n }\n\n /**\n * Find all sent events\n */\n async findSent(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.SENT);\n }\n\n /**\n * Find all failed events\n */\n async findFailed(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.FAILED);\n }\n\n /**\n * Find events that should be retried\n *\n * @param maxRetries - Maximum retry count\n * @returns Array of events eligible for retry\n */\n async findForRetry(maxRetries: number = 3): Promise<AnalyticsEvent[]> {\n const failed = await this.findFailed();\n return failed.filter((e) => e.shouldRetry(maxRetries));\n }\n\n /**\n * Find pending events for a property\n *\n * @param propertyId - Parent property ID\n * @returns Array of pending events\n */\n async findPendingByProperty(propertyId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: {\n propertyId,\n status: TrackingEventStatus.PENDING,\n },\n orderBy: 'eventTimestamp ASC', // Process oldest first\n });\n }\n\n /**\n * Find events by date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of events in date range\n */\n async findByDateRange(\n startDate: Date,\n endDate: Date,\n ): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: {\n 'eventTimestamp >=': startDate.toISOString(),\n 'eventTimestamp <=': endDate.toISOString(),\n },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find conversion events\n *\n * @param propertyId - Optional property ID filter\n * @returns Array of conversion events\n */\n async findConversions(propertyId?: string): Promise<AnalyticsEvent[]> {\n const conversionEvents = [\n 'purchase',\n 'sign_up',\n 'generate_lead',\n 'begin_checkout',\n ];\n const all = propertyId\n ? await this.findByProperty(propertyId)\n : await this.list({ orderBy: 'eventTimestamp DESC' });\n\n return all.filter((e) => conversionEvents.includes(e.eventName));\n }\n\n /**\n * Find pageview events\n *\n * @param propertyId - Optional property ID filter\n * @returns Array of pageview events\n */\n async findPageviews(propertyId?: string): Promise<AnalyticsEvent[]> {\n const where: Record<string, unknown> = { eventName: 'page_view' };\n if (propertyId) {\n where.propertyId = propertyId;\n }\n return await this.list({\n where,\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Count events by event name for a property\n *\n * @param propertyId - Property ID\n * @returns Map of event name to count\n */\n async countByEventName(propertyId: string): Promise<Map<string, number>> {\n const events = await this.findByProperty(propertyId);\n const counts = new Map<string, number>();\n\n for (const event of events) {\n const current = counts.get(event.eventName) || 0;\n counts.set(event.eventName, current + 1);\n }\n\n return counts;\n }\n\n /**\n * Get event stats for a property\n *\n * @param propertyId - Property ID\n * @returns Event statistics\n */\n async getPropertyStats(propertyId: string): Promise<{\n total: number;\n pending: number;\n sent: number;\n failed: number;\n conversions: number;\n pageviews: number;\n }> {\n const events = await this.findByProperty(propertyId);\n\n return {\n total: events.length,\n pending: events.filter((e) => e.status === TrackingEventStatus.PENDING)\n .length,\n sent: events.filter((e) => e.status === TrackingEventStatus.SENT).length,\n failed: events.filter((e) => e.status === TrackingEventStatus.FAILED)\n .length,\n conversions: events.filter((e) => e.isConversion()).length,\n pageviews: events.filter((e) => e.isPageview()).length,\n };\n }\n\n /**\n * Get day-over-day pageview stats with trend for a property.\n *\n * Compares today's pageview count against yesterday's to produce a\n * trend direction and percentage change. A threshold of 5% is used\n * to classify 'up' vs 'down' vs 'flat'; growth from a zero baseline is\n * classified `up` with a `null` percent (see {@link classifyTrend}).\n *\n * Day boundaries are computed in `timeZone` (an IANA identifier such as the\n * property's `AnalyticsProperty.timeZone`, which defaults to\n * `America/Los_Angeles`) so an event near local midnight buckets into the\n * correct calendar day. Defaults to `'UTC'` when omitted.\n *\n * @param propertyId - Property ID\n * @param now - Optional current date (for testing)\n * @param timeZone - IANA time zone for day boundaries (default `'UTC'`)\n * @returns Stats with trend\n */\n async getPropertyStatsWithTrend(\n propertyId: string,\n now?: Date,\n timeZone: string = 'UTC',\n ): Promise<PropertyStatsWithTrend> {\n const currentTime = now || new Date();\n const todayStart = this.startOfDayInZone(currentTime, timeZone);\n const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);\n\n // Single query for both days to avoid concurrent DuckDB prepared statements\n const allPageviewEvents = await this.list({\n where: {\n propertyId,\n eventName: 'page_view',\n 'eventTimestamp >=': yesterdayStart.toISOString(),\n 'eventTimestamp <=': currentTime.toISOString(),\n },\n });\n\n const todayPageviewEvents = allPageviewEvents.filter(\n (e) => new Date(e.eventTimestamp) >= todayStart,\n );\n const yesterdayPageviewEvents = allPageviewEvents.filter(\n (e) => new Date(e.eventTimestamp) < todayStart,\n );\n\n // Count unique clients (users)\n const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));\n const yesterdayClients = new Set(\n yesterdayPageviewEvents.map((e) => e.clientId),\n );\n\n const todayPageviews = todayPageviewEvents.length;\n const yesterdayPageviews = yesterdayPageviewEvents.length;\n\n const { trend, trendPercent } = this.classifyTrend(\n todayPageviews,\n yesterdayPageviews,\n );\n\n return {\n todayPageviews,\n todayUsers: todayClients.size,\n yesterdayPageviews,\n yesterdayUsers: yesterdayClients.size,\n trend,\n trendPercent,\n };\n }\n\n /**\n * Get day-over-day stats for multiple properties in batch.\n *\n * Day boundaries are computed in `timeZone` (default `'UTC'`); see\n * {@link getPropertyStatsWithTrend}. A single zone applies to the whole\n * batch, so callers mixing properties with different `timeZone` values\n * should batch per zone (or fall back to per-property calls).\n *\n * @param propertyIds - Array of property IDs\n * @param now - Optional current date (for testing)\n * @param timeZone - IANA time zone for day boundaries (default `'UTC'`)\n * @returns Map of propertyId to stats\n */\n async getBatchPropertyStats(\n propertyIds: string[],\n now?: Date,\n timeZone: string = 'UTC',\n ): Promise<Map<string, PropertyStatsWithTrend>> {\n const results = new Map<string, PropertyStatsWithTrend>();\n\n // Fetch all date-ranged events once to avoid N+1 queries\n const currentTime = now || new Date();\n const todayStart = this.startOfDayInZone(currentTime, timeZone);\n const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);\n\n // Single query for both days to avoid concurrent DuckDB prepared statements\n const allEvents = await this.list({\n where: {\n eventName: 'page_view',\n 'eventTimestamp >=': yesterdayStart.toISOString(),\n 'eventTimestamp <=': currentTime.toISOString(),\n },\n });\n\n // Pre-group events by propertyId and day in a single pass\n const todayByProperty = new Map<string, AnalyticsEvent[]>();\n const yesterdayByProperty = new Map<string, AnalyticsEvent[]>();\n for (const e of allEvents) {\n const isToday = new Date(e.eventTimestamp) >= todayStart;\n const map = isToday ? todayByProperty : yesterdayByProperty;\n const list = map.get(e.propertyId);\n if (list) list.push(e);\n else map.set(e.propertyId, [e]);\n }\n\n for (const propertyId of propertyIds) {\n const todayPageviewEvents = todayByProperty.get(propertyId) ?? [];\n const yesterdayPageviewEvents = yesterdayByProperty.get(propertyId) ?? [];\n\n const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));\n const yesterdayClients = new Set(\n yesterdayPageviewEvents.map((e) => e.clientId),\n );\n\n const todayPageviews = todayPageviewEvents.length;\n const yesterdayPageviews = yesterdayPageviewEvents.length;\n\n const { trend, trendPercent } = this.classifyTrend(\n todayPageviews,\n yesterdayPageviews,\n );\n\n results.set(propertyId, {\n todayPageviews,\n todayUsers: todayClients.size,\n yesterdayPageviews,\n yesterdayUsers: yesterdayClients.size,\n trend,\n trendPercent,\n });\n }\n\n return results;\n }\n}\n","/**\n * AnalyticsProperty model - Represents an analytics property/site\n * @packageDocumentation\n */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtAnalyticsAnalyzePerformancePrompt,\n} from '../prompts.js';\nimport { AnalyticsPropertyStatus, AnalyticsProvider } from '../types/index.js';\n\n/**\n * AnalyticsProperty represents an analytics property (GA4 property,\n * Plausible site, or Matomo site).\n *\n * @example\n * ```typescript\n * const property = await properties.create({\n * name: 'My Website',\n * displayName: 'My Website Analytics',\n * provider: AnalyticsProvider.GA4,\n * externalId: 'properties/123456789',\n * measurementId: 'G-XXXXXXXXXX'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'sync', 'runReport'] },\n cli: true,\n})\nexport class AnalyticsProperty extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Without tenant scoping the generated `list`/`get` API and any raw query\n * return every tenant's properties — including the `@field({ sensitive })`\n * `apiSecret` / `providerMetadata` credentials. `@TenantScoped` registers\n * this class with the tenant interceptor so reads are auto-filtered and\n * writes are bound to the active tenant context.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Internal name/identifier\n */\n name: string = '';\n\n /**\n * Human-readable display name\n */\n displayName: string = '';\n\n /**\n * Analytics provider (ga4, plausible, matomo)\n */\n provider: AnalyticsProvider = AnalyticsProvider.GA4;\n\n /**\n * External ID from the provider (e.g., \"properties/123456789\" for GA4,\n * idSite for Matomo)\n */\n externalId: string = '';\n\n /**\n * Measurement ID for GA4 (G-XXXXXXXXXX)\n */\n measurementId: string = '';\n\n /**\n * Provider API secret/token (GA4 API secret, Matomo token_auth)\n *\n * Sensitive (#1540): excluded from generated API/MCP responses and rejected\n * as a `where` filter key so it can't be probed.\n */\n @field({ sensitive: true })\n apiSecret: string = '';\n\n /**\n * Site domain for Plausible/Matomo\n */\n siteDomain: string = '';\n\n /**\n * Property timezone\n */\n timeZone: string = 'America/Los_Angeles';\n\n /**\n * Currency code (e.g., 'USD', 'EUR')\n */\n currencyCode: string = 'USD';\n\n /**\n * Industry category\n */\n industryCategory: string = '';\n\n /**\n * Service level (STANDARD, PREMIUM)\n */\n serviceLevel: string = 'STANDARD';\n\n /**\n * Property status\n */\n status: AnalyticsPropertyStatus = AnalyticsPropertyStatus.ACTIVE;\n\n /**\n * Last sync timestamp with provider\n */\n lastSyncAt: Date | null = null;\n\n /**\n * Metadata from provider (JSON)\n *\n * Sensitive (#1540): may carry provider credentials/tokens, so it is excluded\n * from generated API/MCP responses and rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n providerMetadata: string = '{}';\n\n constructor(options: any = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.displayName !== undefined)\n this.displayName = options.displayName;\n if (options.provider !== undefined) this.provider = options.provider;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.measurementId !== undefined)\n this.measurementId = options.measurementId;\n if (options.apiSecret !== undefined) this.apiSecret = options.apiSecret;\n if (options.siteDomain !== undefined) this.siteDomain = options.siteDomain;\n if (options.timeZone !== undefined) this.timeZone = options.timeZone;\n if (options.currencyCode !== undefined)\n this.currencyCode = options.currencyCode;\n if (options.industryCategory !== undefined)\n this.industryCategory = options.industryCategory;\n if (options.serviceLevel !== undefined)\n this.serviceLevel = options.serviceLevel;\n if (options.status !== undefined) this.status = options.status;\n if (options.lastSyncAt !== undefined) this.lastSyncAt = options.lastSyncAt;\n if (options.providerMetadata !== undefined)\n this.providerMetadata = options.providerMetadata;\n }\n\n /**\n * Check if this is a GA4 property\n */\n isGA4(): boolean {\n return this.provider === AnalyticsProvider.GA4;\n }\n\n /**\n * Check if this is a Plausible site\n */\n isPlausible(): boolean {\n return this.provider === AnalyticsProvider.PLAUSIBLE;\n }\n\n /**\n * Check if this is a Matomo site\n */\n isMatomo(): boolean {\n return this.provider === AnalyticsProvider.MATOMO;\n }\n\n /**\n * Get parsed provider metadata\n */\n getProviderMetadata(): Record<string, unknown> {\n try {\n return JSON.parse(this.providerMetadata);\n } catch {\n return {};\n }\n }\n\n /**\n * Set provider metadata\n */\n setProviderMetadata(metadata: Record<string, unknown>): void {\n this.providerMetadata = JSON.stringify(metadata);\n }\n\n /**\n * Mark as synced with provider\n */\n markSynced(): void {\n this.lastSyncAt = new Date();\n }\n\n /**\n * AI-powered: Analyze property performance.\n *\n * Uses the `smrtAnalytics.property.analyzePerformance` prompt registered\n * via `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Only non-PII fields (display name, provider label, requested period)\n * are sent to the AI provider. Internal identifiers (`id`, `externalId`,\n * `measurementId`, `apiSecret`, `providerMetadata`) are intentionally\n * excluded — see `../prompts.ts` for the full exclusion rationale.\n */\n async analyzePerformance(options: { period?: string } = {}): Promise<{\n action: string;\n period: string;\n analysis: string;\n }> {\n const period = options.period || '30 days';\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias so stored prompt overrides are honored on first call before\n // `getAiClient()` triggers full initialization. SmrtObject already types\n // both options on `SmrtClassOptions` so no `any` cast is needed — that\n // keeps a misspelt option name surfacing as a TypeScript error rather\n // than silently falling through.\n const db = this.options.db ?? this.options.persistence;\n\n // This model is `@TenantScoped` and declares a `tenantId` field, but we\n // still deliberately OMIT `tenantId` from the resolvePrompt options so the\n // resolver falls back to the AsyncLocalStorage tenancy context via\n // `getTenantId()`. Passing `this.tenantId` (which may be null for\n // tenant-agnostic rows) explicitly would short-circuit that fallback and\n // silently ignore tenant-specific prompt overrides active in the\n // surrounding `withTenant(...)` block.\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsAnalyzePerformancePrompt.key,\n {\n db,\n variables: {\n period,\n propertyDisplayName: this.displayName || '',\n propertyProvider: this.provider || '',\n },\n },\n );\n\n const ai = await this.getAiClient();\n const analysis = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return {\n action: 'analyzePerformance',\n period,\n analysis: analysis.trim(),\n };\n }\n\n /**\n * AI-powered: Check if property is performing well\n */\n async isPerformingWell(): Promise<boolean> {\n return await this.is(\n `\n Based on the property configuration and metadata:\n - Property: ${this.displayName}\n - Provider: ${this.provider}\n - Status: ${this.status}\n - Last sync: ${this.lastSyncAt}\n\n Is this property properly configured and likely performing well?\n `,\n // Property fields hand-rolled above; skip is()'s object-data injection.\n { includeData: false },\n );\n }\n}\n\nexport default AnalyticsProperty;\n","/**\n * AnalyticsPropertyCollection - Collection manager for AnalyticsProperty objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsProperty } from '../models/AnalyticsProperty.js';\nimport { AnalyticsPropertyStatus, AnalyticsProvider } from '../types/index.js';\n\nexport class AnalyticsPropertyCollection extends SmrtCollection<AnalyticsProperty> {\n static readonly _itemClass = AnalyticsProperty;\n\n /**\n * Find property by external ID\n *\n * @param externalId - External ID from provider\n * @returns Matching property or null\n */\n async findByExternalId(\n externalId: string,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find property by measurement ID (GA4)\n *\n * @param measurementId - GA4 measurement ID (G-XXXXXXXXXX)\n * @returns Matching property or null\n */\n async findByMeasurementId(\n measurementId: string,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: { measurementId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find property by site domain and optional provider.\n *\n * @param siteDomain - Provider site domain\n * @param provider - Optional provider discriminator for migration/coexistence\n * @returns Matching property or null\n */\n async findBySiteDomain(\n siteDomain: string,\n provider?: AnalyticsProvider,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: provider ? { siteDomain, provider } : { siteDomain },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find properties by provider\n *\n * @param provider - Analytics provider\n * @returns Array of matching properties\n */\n async findByProvider(\n provider: AnalyticsProvider,\n ): Promise<AnalyticsProperty[]> {\n return await this.list({\n where: { provider },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all GA4 properties\n */\n async findGA4Properties(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.GA4);\n }\n\n /**\n * Find all Plausible sites\n */\n async findPlausibleSites(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.PLAUSIBLE);\n }\n\n /**\n * Find all Matomo sites\n */\n async findMatomoSites(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.MATOMO);\n }\n\n /**\n * Find properties by status\n *\n * @param status - Property status\n * @returns Array of matching properties\n */\n async findByStatus(\n status: AnalyticsPropertyStatus,\n ): Promise<AnalyticsProperty[]> {\n return await this.list({\n where: { status },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all active properties\n */\n async findActive(): Promise<AnalyticsProperty[]> {\n return await this.findByStatus(AnalyticsPropertyStatus.ACTIVE);\n }\n\n /**\n * Find properties that need syncing (not synced in last N hours)\n *\n * @param hoursAgo - Hours since last sync\n * @returns Array of properties needing sync\n */\n async findNeedingSync(hoursAgo: number = 24): Promise<AnalyticsProperty[]> {\n const cutoff = new Date();\n cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1000);\n\n // Get properties where lastSyncAt is null or older than cutoff\n const all = await this.findActive();\n return all.filter((p) => !p.lastSyncAt || p.lastSyncAt < cutoff);\n }\n}\n","/**\n * AnalyticsReport model - Saved report configurations and results\n * @packageDocumentation\n */\n\nimport { foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtAnalyticsAnalyzeResultsPrompt,\n smrtAnalyticsHasPositiveTrendsPrompt,\n} from '../prompts.js';\nimport { ReportFrequency, ReportStatus } from '../types/index.js';\n\n/**\n * AnalyticsReport represents a saved report configuration with optional scheduling.\n *\n * @example\n * ```typescript\n * const report = await reports.create({\n * propertyId: property.id,\n * name: 'Weekly Traffic Report',\n * dimensions: JSON.stringify([{ name: 'country' }, { name: 'deviceCategory' }]),\n * metrics: JSON.stringify([{ name: 'activeUsers' }, { name: 'sessions' }]),\n * frequency: ReportFrequency.WEEKLY\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'run'] },\n mcp: { include: ['list', 'get', 'run', 'analyze'] },\n cli: true,\n})\nexport class AnalyticsReport extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Reports persist `resultData` rows that may contain tenant-private metrics\n * and PII-bearing dimensions. Without tenant scoping the generated\n * `list`/`get` API returns every tenant's cached report data, and the\n * AI-powered `analyze`/`run` operations could run over another tenant's\n * rows. `@TenantScoped` auto-filters reads and binds writes to the tenant.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Report name\n */\n name: string = '';\n\n /**\n * Report description\n */\n description: string = '';\n\n /**\n * Dimensions to group by (JSON array)\n */\n dimensions: string = '[]';\n\n /**\n * Metrics to retrieve (JSON array)\n */\n metrics: string = '[]';\n\n /**\n * Date range start (relative or absolute)\n */\n dateRangeStart: string = '7daysAgo';\n\n /**\n * Date range end (relative or absolute)\n */\n dateRangeEnd: string = 'today';\n\n /**\n * Dimension filter expression (JSON)\n */\n dimensionFilter: string = '';\n\n /**\n * Metric filter expression (JSON)\n */\n metricFilter: string = '';\n\n /**\n * Sort order (JSON array)\n */\n orderBy: string = '[]';\n\n /**\n * Maximum results to return\n */\n maxResults: number = 0;\n\n /**\n * Report status\n */\n status: ReportStatus = ReportStatus.DRAFT;\n\n /**\n * Scheduling frequency\n */\n frequency: ReportFrequency = ReportFrequency.ONCE;\n\n /**\n * Last run timestamp\n */\n lastRunAt: Date | null = null;\n\n /**\n * Next scheduled run\n */\n nextRunAt: Date | null = null;\n\n /**\n * Cached result data (JSON)\n */\n resultData: string = '';\n\n /**\n * Row count from last run\n */\n rowCount: number = 0;\n\n /**\n * Error message from last failed run\n */\n lastError: string = '';\n\n constructor(options: any = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.dimensions !== undefined) this.dimensions = options.dimensions;\n if (options.metrics !== undefined) this.metrics = options.metrics;\n if (options.dateRangeStart !== undefined)\n this.dateRangeStart = options.dateRangeStart;\n if (options.dateRangeEnd !== undefined)\n this.dateRangeEnd = options.dateRangeEnd;\n if (options.dimensionFilter !== undefined)\n this.dimensionFilter = options.dimensionFilter;\n if (options.metricFilter !== undefined)\n this.metricFilter = options.metricFilter;\n if (options.orderBy !== undefined) this.orderBy = options.orderBy;\n if (options.maxResults !== undefined) this.maxResults = options.maxResults;\n if (options.status !== undefined) this.status = options.status;\n if (options.frequency !== undefined) this.frequency = options.frequency;\n if (options.lastRunAt !== undefined) this.lastRunAt = options.lastRunAt;\n if (options.nextRunAt !== undefined) this.nextRunAt = options.nextRunAt;\n if (options.resultData !== undefined) this.resultData = options.resultData;\n if (options.rowCount !== undefined) this.rowCount = options.rowCount;\n if (options.lastError !== undefined) this.lastError = options.lastError;\n }\n\n /**\n * Get parsed dimensions\n */\n getDimensions(): Array<{ name: string }> {\n try {\n return JSON.parse(this.dimensions);\n } catch {\n return [];\n }\n }\n\n /**\n * Set dimensions\n */\n setDimensions(dimensions: Array<{ name: string }>): void {\n this.dimensions = JSON.stringify(dimensions);\n }\n\n /**\n * Get parsed metrics\n */\n getMetrics(): Array<{ name: string }> {\n try {\n return JSON.parse(this.metrics);\n } catch {\n return [];\n }\n }\n\n /**\n * Set metrics\n */\n setMetrics(metrics: Array<{ name: string }>): void {\n this.metrics = JSON.stringify(metrics);\n }\n\n /**\n * Get parsed result data\n */\n getResultData(): Record<string, unknown> | null {\n if (!this.resultData) return null;\n try {\n return JSON.parse(this.resultData);\n } catch {\n return null;\n }\n }\n\n /**\n * Set result data\n */\n setResultData(data: Record<string, unknown>): void {\n this.resultData = JSON.stringify(data);\n }\n\n /**\n * Mark report as running\n */\n markRunning(): void {\n this.status = ReportStatus.RUNNING;\n this.lastError = '';\n }\n\n /**\n * Mark report as completed with results\n */\n markCompleted(rowCount: number): void {\n this.status = ReportStatus.COMPLETED;\n this.lastRunAt = new Date();\n this.rowCount = rowCount;\n this.lastError = '';\n this.calculateNextRun();\n }\n\n /**\n * Mark report as failed\n */\n markFailed(error: string): void {\n this.status = ReportStatus.FAILED;\n this.lastRunAt = new Date();\n this.lastError = error;\n this.calculateNextRun();\n }\n\n /**\n * Calculate next scheduled run based on frequency\n */\n calculateNextRun(): void {\n if (this.frequency === ReportFrequency.ONCE) {\n this.nextRunAt = null;\n return;\n }\n\n const now = new Date();\n const next = new Date(now);\n\n switch (this.frequency) {\n case ReportFrequency.DAILY:\n next.setDate(next.getDate() + 1);\n break;\n case ReportFrequency.WEEKLY:\n next.setDate(next.getDate() + 7);\n break;\n case ReportFrequency.MONTHLY:\n next.setMonth(next.getMonth() + 1);\n break;\n }\n\n this.nextRunAt = next;\n }\n\n /**\n * Check if report is due to run\n */\n isDue(): boolean {\n if (this.frequency === ReportFrequency.ONCE) {\n return this.status === ReportStatus.SCHEDULED && !this.lastRunAt;\n }\n if (!this.nextRunAt) return false;\n return new Date() >= this.nextRunAt;\n }\n\n /**\n * AI-powered: Analyze report results.\n *\n * Uses the `smrtAnalytics.report.analyzeResults` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Internal identifiers (`id`, `propertyId`, `tenantId`, `lastError`, raw\n * `dimensionFilter` / `metricFilter` JSON) are excluded from the prompt\n * variables — see `../prompts.ts` for the exclusion rationale.\n *\n * **`resultData` is FORWARDED VERBATIM.** The persisted result rows are\n * JSON-stringified into the `reportData` variable; this package cannot\n * strip PII because the row schema is determined by which dimensions /\n * metrics the caller asked the analytics provider to return. If the\n * persisted rows contain `userPseudoId`, `clientId`, IP-derived\n * geolocation, or any other identifier, those fields WILL reach the AI\n * provider. Callers are responsible for excluding PII-bearing dimensions\n * before persisting, applying a column allowlist at the call site, or\n * overriding the prompt template via `PromptOverride`. The forwarding is\n * pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`.\n *\n * The previous implementation issued a second freeform `this.do()` call\n * to re-summarize \"top 3 insights\"; that behaviour is now folded into\n * the single registered template (which already asks for findings,\n * trends, and recommendations) — `insights` mirrors `analysis` so the\n * return shape is preserved without a redundant AI round-trip.\n */\n async analyzeResults(_options: any = {}): Promise<{\n action: string;\n analysis: string;\n insights: string;\n }> {\n const resultData = this.getResultData();\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias so stored prompt overrides are honored on first call before\n // `getAiClient()` triggers full initialization. SmrtObject already types\n // both options on `SmrtClassOptions` so no `any` cast is needed — that\n // keeps a misspelt option name surfacing as a TypeScript error rather\n // than silently falling through.\n const db = this.options.db ?? this.options.persistence;\n\n // This model is `@TenantScoped` and declares a `tenantId` field, but we\n // still deliberately OMIT `tenantId` from the resolvePrompt options so the\n // resolver falls back to the AsyncLocalStorage tenancy context via\n // `getTenantId()`. Passing `this.tenantId` (which may be null for\n // tenant-agnostic rows) explicitly would short-circuit that fallback and\n // silently ignore tenant-specific prompt overrides active in the\n // surrounding `withTenant(...)` block.\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsAnalyzeResultsPrompt.key,\n {\n db,\n variables: {\n reportName: this.name || '',\n reportDimensions: this.dimensions || '[]',\n reportMetrics: this.metrics || '[]',\n dateRangeStart: this.dateRangeStart || '',\n dateRangeEnd: this.dateRangeEnd || '',\n rowCount: String(this.rowCount),\n reportData: JSON.stringify(resultData, null, 2),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const analysis = (\n await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n )\n ).trim();\n\n return {\n action: 'analyzeResults',\n analysis,\n insights: analysis,\n };\n }\n\n /**\n * AI-powered: Check if results show positive trends.\n *\n * Uses the `smrtAnalytics.report.hasPositiveTrends` prompt registered\n * via `@happyvertical/smrt-prompts`. Only the metric labels and the\n * aggregate `resultData` JSON are sent to the AI provider — though as\n * with `analyzeResults`, `resultData` is forwarded verbatim and may\n * carry PII the caller persisted; see `analyzeResults` docstring and\n * `../prompts.ts`.\n *\n * Boolean coercion uses `/^\\s*(yes|true)\\b/i` against the trimmed\n * response. The registered prompt template explicitly instructs the\n * model to begin its answer with the literal word \"yes\" or \"no\" so\n * this regex is reliable; tenant overrides MUST preserve that leading-\n * word convention or the boolean will silently fall to `false`.\n */\n async hasPositiveTrends(): Promise<boolean> {\n const resultData = this.getResultData();\n\n // See `analyzeResults` above for the typed-options + tenancy-fallback rationale.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsHasPositiveTrendsPrompt.key,\n {\n db,\n variables: {\n reportMetrics: this.metrics || '[]',\n reportData: JSON.stringify(resultData),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = (\n await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n )\n ).trim();\n\n return /^\\s*(yes|true)\\b/i.test(response);\n }\n}\n\nexport default AnalyticsReport;\n","/**\n * AnalyticsReportCollection - Collection manager for AnalyticsReport objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsReport } from '../models/AnalyticsReport.js';\nimport { ReportFrequency, ReportStatus } from '../types/index.js';\n\nexport class AnalyticsReportCollection extends SmrtCollection<AnalyticsReport> {\n static readonly _itemClass = AnalyticsReport;\n\n /**\n * Find reports by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of reports\n */\n async findByProperty(propertyId: string): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find reports by status\n *\n * @param status - Report status\n * @returns Array of matching reports\n */\n async findByStatus(status: ReportStatus): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { status },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find all draft reports\n */\n async findDrafts(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.DRAFT);\n }\n\n /**\n * Find all scheduled reports\n */\n async findScheduled(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.SCHEDULED);\n }\n\n /**\n * Find all completed reports\n */\n async findCompleted(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.COMPLETED);\n }\n\n /**\n * Find all failed reports\n */\n async findFailed(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.FAILED);\n }\n\n /**\n * Find reports by frequency\n *\n * @param frequency - Report frequency\n * @returns Array of matching reports\n */\n async findByFrequency(\n frequency: ReportFrequency,\n ): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { frequency },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find all recurring reports (not one-time)\n */\n async findRecurring(): Promise<AnalyticsReport[]> {\n const all = await this.list({ orderBy: 'name ASC' });\n return all.filter((r) => r.frequency !== ReportFrequency.ONCE);\n }\n\n /**\n * Find reports due to run\n *\n * @returns Array of reports that are due\n */\n async findDue(): Promise<AnalyticsReport[]> {\n const all = await this.list({\n where: { status: ReportStatus.SCHEDULED },\n });\n return all.filter((r) => r.isDue());\n }\n\n /**\n * Find reports for a property by status\n *\n * @param propertyId - Parent property ID\n * @param status - Report status\n * @returns Array of matching reports\n */\n async findByPropertyAndStatus(\n propertyId: string,\n status: ReportStatus,\n ): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { propertyId, status },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find recently run reports\n *\n * @param hoursAgo - Hours since last run\n * @returns Array of recently run reports\n */\n async findRecentlyRun(hoursAgo: number = 24): Promise<AnalyticsReport[]> {\n const cutoff = new Date();\n cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1000);\n\n const all = await this.list({ orderBy: 'lastRunAt DESC' });\n return all.filter((r) => r.lastRunAt && r.lastRunAt >= cutoff);\n }\n}\n"],"names":["AnalyticsProvider","AnalyticsPropertyStatus","DataStreamType","DataStreamStatus","ReportStatus","ReportFrequency","CustomDimensionScope","TrackingEventStatus","CountingMethod","__decorateClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/prompts.ts","../src/types/index.ts","../src/models/AnalyticsDataStream.ts","../src/collections/AnalyticsDataStreamCollection.ts","../src/models/AnalyticsEvent.ts","../src/collections/AnalyticsEventCollection.ts","../src/models/AnalyticsProperty.ts","../src/collections/AnalyticsPropertyCollection.ts","../src/models/AnalyticsReport.ts","../src/collections/AnalyticsReportCollection.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Prompt registrations for the @happyvertical/smrt-analytics package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-properties` (see\n * `prompts.ts`) and `@happyvertical/smrt-content` (see `content-prompts.ts`).\n *\n * PII / internal-field exclusion policy:\n *\n * Variables that this package never forwards to the AI provider:\n * - `id`, `tenantId`, `propertyId` and other foreign-key/UUID fields —\n * they identify internal records and provide no analytic value.\n * - `apiSecret`, `measurementId`, `externalId`, `siteDomain` — these are\n * provider credentials or platform-specific identifiers (e.g. GA4 API\n * secrets, Matomo `idSite`, custom `G-XXXX` measurement IDs) that may\n * be tenant-private configuration.\n * - `providerMetadata` — extensible JSON blob that may contain\n * credentials, account IDs, or other configuration secrets.\n * - `lastError`, raw `dimensionFilter` / `metricFilter` JSON — internal\n * error strings (which may contain auth tokens) and filter expressions\n * that may reference cookie IDs or user-pseudo-IDs are kept out of the\n * prompt variable set.\n *\n * Variables this package DOES forward (potentially carrying PII the caller\n * persisted):\n * - `reportData` (a JSON.stringify of the persisted `resultData`) — this\n * is forwarded VERBATIM to the AI in `analyzeResults` and\n * `hasPositiveTrends`. The package cannot strip PII because the row\n * schema is determined by which dimensions/metrics the caller asked\n * the analytics provider to return. If the persisted rows contain\n * `userPseudoId`, `clientId`, IP-derived geolocation, or any other\n * identifier, those fields WILL reach the AI provider. The forwarding\n * is pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`. Callers must either\n * exclude PII-bearing dimensions before persisting, apply a column\n * allowlist at the call site, or override the prompt template via\n * `PromptOverride` to redact rows.\n *\n * Acceptable variables (always safe): human-readable names, time periods,\n * computed aggregates (row counts, totals), and high-level dimension/metric\n * labels (which are public GA4/Plausible/Matomo schema names).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n/**\n * `AnalyticsProperty.analyzePerformance()` prompt. Only the human-readable\n * display name, provider label, and the requested period reach the model;\n * provider IDs, API secrets, and the providerMetadata blob are excluded.\n */\nexport const smrtAnalyticsAnalyzePerformancePrompt = definePrompt({\n key: 'smrtAnalytics.property.analyzePerformance',\n template: `Analyze the analytics performance for this property over the last {period}.\nConsider: traffic trends, user engagement, conversion patterns.\nProperty: {propertyDisplayName} ({propertyProvider})`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * `AnalyticsReport.analyzeResults()` prompt. Surfaces report name, the\n * dimension/metric labels (which are public GA4/Plausible/Matomo schema\n * names — not user data), the date-range window, and the row count plus\n * a JSON-stringified `resultData` blob.\n *\n * **`resultData` is forwarded verbatim.** This package does not strip PII\n * from result rows — it cannot, because the row schema is determined by\n * the dimensions/metrics the caller asked the analytics provider to\n * return. If the caller persists rows containing a `userPseudoId`,\n * `clientId`, IP-derived geolocation, or any other identifier, those\n * fields WILL reach the AI provider. Either:\n *\n * - exclude PII-bearing dimensions before persisting `resultData`\n * (e.g. don't request `userPseudoId` as a GA4 dimension), or\n * - apply a column allowlist at the call site before invoking\n * `analyzeResults()`, or\n * - override the prompt template via `PromptOverride` to redact rows.\n *\n * The forwarding contract is pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`.\n */\nexport const smrtAnalyticsAnalyzeResultsPrompt = definePrompt({\n key: 'smrtAnalytics.report.analyzeResults',\n template: `Analyze these analytics report results and provide insights:\n\nReport: {reportName}\nDimensions: {reportDimensions}\nMetrics: {reportMetrics}\nDate Range: {dateRangeStart} to {dateRangeEnd}\nRow Count: {rowCount}\n\nData: {reportData}\n\nProvide:\n1. Key findings\n2. Trends or patterns\n3. Actionable recommendations`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * `AnalyticsReport.hasPositiveTrends()` boolean classifier prompt. Same\n * variables as `analyzeResults` minus the descriptive name — only the\n * provider-schema metric labels and the aggregate `resultData` payload are\n * needed to decide whether the trend is positive.\n *\n * The template explicitly instructs the model to begin its response with\n * either \"yes\" or \"no\" so the boolean coercion in `hasPositiveTrends()`\n * (`/^\\s*(yes|true)\\b/i`) is reliable. Without the leading-word\n * instruction the model commonly answers with prose that happens to\n * describe positive trends but starts with a sentence like \"The\n * conversion rate is up...\" — the parser would then treat that as\n * `false` despite a positive analysis. Tenants overriding this template\n * MUST preserve the leading yes/no convention.\n */\nexport const smrtAnalyticsHasPositiveTrendsPrompt = definePrompt({\n key: 'smrtAnalytics.report.hasPositiveTrends',\n template: `Based on these report results, are the metrics showing positive trends?\n\nMetrics: {reportMetrics}\nData: {reportData}\n\nConsider: user growth, engagement, conversions as positive indicators.\n\nBegin your response with the single word \"yes\" or \"no\" (lower case), then\noptionally provide a one-sentence explanation. Tooling parses the leading\nword — \"yes\" or \"true\" means positive, anything else means negative.`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\n/**\n * Build the message-options object passed to `aiClient.message()` from a\n * `ResolvedPromptAI` shape. Mirrors the helper in `smrt-properties` and\n * `smrt-content` so behavior across packages is identical.\n */\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * Types and enums for smrt-analytics package\n */\n\n/**\n * Analytics provider types\n */\nexport enum AnalyticsProvider {\n GA4 = 'ga4',\n PLAUSIBLE = 'plausible',\n MATOMO = 'matomo',\n}\n\n/**\n * Status for analytics properties\n */\nexport enum AnalyticsPropertyStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n PENDING = 'pending',\n}\n\n/**\n * Data stream types (matching SDK types)\n */\nexport enum DataStreamType {\n WEB = 'WEB_DATA_STREAM',\n ANDROID = 'ANDROID_APP_DATA_STREAM',\n IOS = 'IOS_APP_DATA_STREAM',\n}\n\n/**\n * Status for data streams\n */\nexport enum DataStreamStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n}\n\n/**\n * Report status\n */\nexport enum ReportStatus {\n DRAFT = 'draft',\n SCHEDULED = 'scheduled',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Report frequency for scheduled reports\n */\nexport enum ReportFrequency {\n ONCE = 'once',\n DAILY = 'daily',\n WEEKLY = 'weekly',\n MONTHLY = 'monthly',\n}\n\n/**\n * Custom dimension scope (matching SDK types)\n */\nexport enum CustomDimensionScope {\n EVENT = 'EVENT',\n USER = 'USER',\n ITEM = 'ITEM',\n}\n\n/**\n * Event tracking status\n */\nexport enum TrackingEventStatus {\n PENDING = 'pending',\n SENT = 'sent',\n FAILED = 'failed',\n}\n\n/**\n * Key event counting method (matching SDK types)\n */\nexport enum CountingMethod {\n ONCE_PER_EVENT = 'ONCE_PER_EVENT',\n ONCE_PER_SESSION = 'ONCE_PER_SESSION',\n}\n\n// ============================================================================\n// Analytics aggregation types\n// ============================================================================\n\n/**\n * Day-over-day property stats with trend calculation\n */\nexport interface PropertyStatsWithTrend {\n todayPageviews: number;\n todayUsers: number;\n yesterdayPageviews: number;\n yesterdayUsers: number;\n trend: 'up' | 'down' | 'flat';\n /**\n * Day-over-day percentage change. `null` when today grew from a zero\n * yesterday-baseline (a \"new\" surge with no finite percentage); the UI\n * renders this as \"new\" rather than 0%.\n */\n trendPercent: number | null;\n}\n\n// ============================================================================\n// SDK-compatible type definitions\n// These mirror the types from @happyvertical/analytics for when SDK is not installed\n// ============================================================================\n\n/**\n * Analytics property/site representation (SDK-compatible)\n */\nexport interface SDKProperty {\n id: string;\n name: string;\n displayName: string;\n createTime: string;\n updateTime?: string;\n timeZone?: string;\n currencyCode?: string;\n industryCategory?: string;\n serviceLevel?: 'STANDARD' | 'PREMIUM';\n}\n\n/**\n * Data stream representation (SDK-compatible)\n */\nexport interface SDKDataStream {\n id: string;\n type: 'WEB_DATA_STREAM' | 'ANDROID_APP_DATA_STREAM' | 'IOS_APP_DATA_STREAM';\n displayName: string;\n measurementId?: string;\n firebaseAppId?: string;\n defaultUri?: string;\n createTime: string;\n updateTime?: string;\n}\n\n/**\n * Custom dimension representation (SDK-compatible)\n */\nexport interface SDKCustomDimension {\n id: string;\n name: string;\n parameterName: string;\n displayName: string;\n description?: string;\n scope: 'EVENT' | 'USER' | 'ITEM';\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Custom metric representation (SDK-compatible)\n */\nexport interface SDKCustomMetric {\n id: string;\n name: string;\n parameterName: string;\n displayName: string;\n description?: string;\n scope: 'EVENT';\n measurementUnit: string;\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n/**\n * Key event (conversion) representation (SDK-compatible)\n */\nexport interface SDKKeyEvent {\n id: string;\n name: string;\n eventName: string;\n createTime: string;\n countingMethod?: 'ONCE_PER_EVENT' | 'ONCE_PER_SESSION';\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n/**\n * Report options (SDK-compatible)\n */\nexport interface SDKReportOptions {\n dateRanges: Array<{\n startDate: string;\n endDate: string;\n name?: string;\n }>;\n dimensions?: Array<{ name: string }>;\n metrics: Array<{ name: string }>;\n offset?: number;\n limit?: number;\n orderBys?: Array<{\n metric?: { metricName: string };\n dimension?: { dimensionName: string };\n desc?: boolean;\n }>;\n keepEmptyRows?: boolean;\n returnPropertyQuota?: boolean;\n}\n\n/**\n * Report result (SDK-compatible)\n */\nexport interface SDKReportResult {\n dimensionHeaders: Array<{ name: string }>;\n metricHeaders: Array<{ name: string; type: string }>;\n rows: Array<{\n dimensionValues: Array<{ value: string }>;\n metricValues: Array<{ value: string }>;\n }>;\n rowCount?: number;\n metadata?: {\n currencyCode?: string;\n timeZone?: string;\n dataLossFromOtherRow?: boolean;\n emptyReason?: string;\n };\n}\n\n/**\n * Track event payload (SDK-compatible)\n */\nexport interface SDKTrackEvent {\n name: string;\n params?: Record<string, string | number | boolean>;\n clientId?: string;\n userId?: string;\n timestamp?: number;\n nonPersonalizedAds?: boolean;\n}\n\n/**\n * Pageview event payload (SDK-compatible)\n */\nexport interface SDKPageviewEvent {\n pagePath: string;\n pageTitle?: string;\n pageLocation?: string;\n clientId?: string;\n userId?: string;\n params?: Record<string, string | number | boolean>;\n}\n\n/**\n * Analytics provider capabilities (SDK-compatible)\n */\nexport interface AnalyticsCapabilities {\n propertyManagement: boolean;\n dataStreams: boolean;\n customDimensions: boolean;\n customMetrics: boolean;\n keyEvents: boolean;\n reporting: boolean;\n realtimeReporting: boolean;\n serverSideTracking: boolean;\n clientSideSnippet: boolean;\n userIdentification: boolean;\n batchTracking: boolean;\n}\n\n/**\n * Analytics interface (SDK-compatible)\n * This is a subset of the full SDK interface for type compatibility\n */\nexport interface AnalyticsInterface {\n createProperty(options: {\n displayName: string;\n timeZone?: string;\n currencyCode?: string;\n }): Promise<SDKProperty>;\n listProperties(): Promise<SDKProperty[]>;\n getProperty(propertyId: string): Promise<SDKProperty>;\n getDataStreams(propertyId: string): Promise<SDKDataStream[]>;\n runReport(\n propertyId: string,\n options: SDKReportOptions,\n ): Promise<SDKReportResult>;\n track(event: SDKTrackEvent): Promise<void>;\n trackPageview(pageview: SDKPageviewEvent): Promise<void>;\n trackBatch(events: SDKTrackEvent[]): Promise<void>;\n getCapabilities(): Promise<AnalyticsCapabilities>;\n}\n","/**\n * AnalyticsDataStream model - Represents a data stream for a property\n * @packageDocumentation\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { DataStreamStatus, DataStreamType } from '../types/index.js';\n\n/**\n * Options for constructing an {@link AnalyticsDataStream}.\n */\nexport interface AnalyticsDataStreamOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n propertyId?: string;\n displayName?: string;\n streamType?: DataStreamType;\n externalId?: string;\n measurementId?: string;\n firebaseAppId?: string;\n defaultUri?: string;\n bundleId?: string;\n packageName?: string;\n status?: DataStreamStatus;\n enhancedMeasurement?: boolean;\n}\n\n/**\n * AnalyticsDataStream represents a data stream (web, iOS, Android) for an analytics property.\n *\n * @example\n * ```typescript\n * const stream = await streams.create({\n * propertyId: property.id,\n * displayName: 'Web Stream',\n * streamType: DataStreamType.WEB,\n * measurementId: 'G-XXXXXXXXXX',\n * defaultUri: 'https://example.com'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class AnalyticsDataStream extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Data streams expose tenant-private platform configuration (measurement\n * IDs, Firebase app IDs, bundle/package names, default URIs). Without tenant\n * scoping the generated `list`/`get` API returns every tenant's streams.\n * `@TenantScoped` auto-filters reads and binds writes to the active tenant.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Human-readable display name\n */\n displayName: string = '';\n\n /**\n * Stream type (WEB, ANDROID, IOS)\n */\n streamType: DataStreamType = DataStreamType.WEB;\n\n /**\n * External stream ID from provider\n */\n externalId: string = '';\n\n /**\n * Measurement ID for web streams (G-XXXXXXX)\n */\n measurementId: string = '';\n\n /**\n * Firebase App ID for app streams\n */\n firebaseAppId: string = '';\n\n /**\n * Default URI for web streams\n */\n defaultUri: string = '';\n\n /**\n * Bundle ID for iOS apps\n */\n bundleId: string = '';\n\n /**\n * Package name for Android apps\n */\n packageName: string = '';\n\n /**\n * Stream status\n */\n status: DataStreamStatus = DataStreamStatus.ACTIVE;\n\n /**\n * Enhanced measurement enabled\n */\n enhancedMeasurement: boolean = true;\n\n constructor(options: AnalyticsDataStreamOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.displayName !== undefined)\n this.displayName = options.displayName;\n if (options.streamType !== undefined) this.streamType = options.streamType;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.measurementId !== undefined)\n this.measurementId = options.measurementId;\n if (options.firebaseAppId !== undefined)\n this.firebaseAppId = options.firebaseAppId;\n if (options.defaultUri !== undefined) this.defaultUri = options.defaultUri;\n if (options.bundleId !== undefined) this.bundleId = options.bundleId;\n if (options.packageName !== undefined)\n this.packageName = options.packageName;\n if (options.status !== undefined) this.status = options.status;\n if (options.enhancedMeasurement !== undefined)\n this.enhancedMeasurement = options.enhancedMeasurement;\n }\n\n /**\n * Check if this is a web stream\n */\n isWeb(): boolean {\n return this.streamType === DataStreamType.WEB;\n }\n\n /**\n * Check if this is an iOS app stream\n */\n isIOS(): boolean {\n return this.streamType === DataStreamType.IOS;\n }\n\n /**\n * Check if this is an Android app stream\n */\n isAndroid(): boolean {\n return this.streamType === DataStreamType.ANDROID;\n }\n\n /**\n * Check if this is a mobile app stream (iOS or Android)\n */\n isMobileApp(): boolean {\n return this.isIOS() || this.isAndroid();\n }\n\n /**\n * Get the platform identifier (measurementId for web, firebaseAppId for apps)\n */\n getPlatformId(): string {\n if (this.isWeb()) {\n return this.measurementId;\n }\n return this.firebaseAppId;\n }\n}\n\nexport default AnalyticsDataStream;\n","/**\n * AnalyticsDataStreamCollection - Collection manager for AnalyticsDataStream objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsDataStream } from '../models/AnalyticsDataStream.js';\nimport { DataStreamStatus, DataStreamType } from '../types/index.js';\n\nexport class AnalyticsDataStreamCollection extends SmrtCollection<AnalyticsDataStream> {\n static readonly _itemClass = AnalyticsDataStream;\n\n /**\n * Find streams by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of data streams\n */\n async findByProperty(propertyId: string): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find stream by external ID\n *\n * @param externalId - External stream ID from provider\n * @returns Matching stream or null\n */\n async findByExternalId(\n externalId: string,\n ): Promise<AnalyticsDataStream | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find stream by measurement ID (web streams)\n *\n * @param measurementId - GA4 measurement ID (G-XXXXXXX)\n * @returns Matching stream or null\n */\n async findByMeasurementId(\n measurementId: string,\n ): Promise<AnalyticsDataStream | null> {\n const results = await this.list({\n where: { measurementId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find streams by type\n *\n * @param streamType - Stream type (WEB, ANDROID, IOS)\n * @returns Array of matching streams\n */\n async findByType(streamType: DataStreamType): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { streamType },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all web streams\n */\n async findWebStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.WEB);\n }\n\n /**\n * Find all iOS app streams\n */\n async findIOSStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.IOS);\n }\n\n /**\n * Find all Android app streams\n */\n async findAndroidStreams(): Promise<AnalyticsDataStream[]> {\n return await this.findByType(DataStreamType.ANDROID);\n }\n\n /**\n * Find all mobile app streams (iOS + Android)\n */\n async findMobileStreams(): Promise<AnalyticsDataStream[]> {\n const ios = await this.findIOSStreams();\n const android = await this.findAndroidStreams();\n return [...ios, ...android].sort((a, b) =>\n a.displayName.localeCompare(b.displayName),\n );\n }\n\n /**\n * Find streams by status\n *\n * @param status - Stream status\n * @returns Array of matching streams\n */\n async findByStatus(status: DataStreamStatus): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: { status },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all active streams\n */\n async findActive(): Promise<AnalyticsDataStream[]> {\n return await this.findByStatus(DataStreamStatus.ACTIVE);\n }\n\n /**\n * Find active streams for a property\n *\n * @param propertyId - Parent property ID\n * @returns Array of active streams\n */\n async findActiveByProperty(\n propertyId: string,\n ): Promise<AnalyticsDataStream[]> {\n return await this.list({\n where: {\n propertyId,\n status: DataStreamStatus.ACTIVE,\n },\n orderBy: 'displayName ASC',\n });\n }\n}\n","/**\n * AnalyticsEvent model - Tracked events log\n * @packageDocumentation\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { TrackingEventStatus } from '../types/index.js';\n\n/**\n * Options for constructing an {@link AnalyticsEvent}.\n */\nexport interface AnalyticsEventOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n propertyId?: string;\n eventName?: string;\n clientId?: string;\n userId?: string;\n params?: string;\n eventTimestamp?: Date;\n status?: TrackingEventStatus;\n sentAt?: Date | null;\n errorMessage?: string;\n retryCount?: number;\n nonPersonalizedAds?: boolean;\n sessionId?: string;\n pagePath?: string;\n pageTitle?: string;\n userAgent?: string;\n ipAddress?: string;\n}\n\n/**\n * AnalyticsEvent represents a tracked analytics event (server-side tracking log).\n *\n * @example\n * ```typescript\n * const event = await events.create({\n * propertyId: property.id,\n * eventName: 'purchase',\n * clientId: 'user-123',\n * params: JSON.stringify({ value: 99.99, currency: 'USD' })\n * });\n * await event.send();\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get', 'track'] },\n cli: true,\n})\nexport class AnalyticsEvent extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Analytics events carry end-user PII (`userId`, `clientId`, `ipAddress`,\n * `userAgent`, `sessionId`). Without tenant scoping the generated\n * `list`/`get` API leaks every tenant's visitor data, and `create` lets a\n * caller write events bound to another tenant's `propertyId`. `@TenantScoped`\n * auto-filters reads and binds writes to the active tenant context.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Event name (e.g., 'purchase', 'page_view', 'sign_up')\n */\n eventName: string = '';\n\n /**\n * Client ID for anonymous tracking\n */\n clientId: string = '';\n\n /**\n * User ID for identified tracking\n */\n userId: string = '';\n\n /**\n * Event parameters (JSON)\n */\n params: string = '{}';\n\n /**\n * Event timestamp\n */\n eventTimestamp: Date = new Date();\n\n /**\n * Tracking status\n */\n status: TrackingEventStatus = TrackingEventStatus.PENDING;\n\n /**\n * Timestamp when sent to provider\n */\n sentAt: Date | null = null;\n\n /**\n * Error message if sending failed\n */\n errorMessage: string = '';\n\n /**\n * Retry count\n */\n retryCount: number = 0;\n\n /**\n * Disable personalized ads\n */\n nonPersonalizedAds: boolean = false;\n\n /**\n * Session ID\n */\n sessionId: string = '';\n\n /**\n * Page path (for pageview events)\n */\n pagePath: string = '';\n\n /**\n * Page title (for pageview events)\n */\n pageTitle: string = '';\n\n /**\n * User agent\n */\n userAgent: string = '';\n\n /**\n * IP address (anonymized)\n */\n ipAddress: string = '';\n\n constructor(options: AnalyticsEventOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.eventName !== undefined) this.eventName = options.eventName;\n if (options.clientId !== undefined) this.clientId = options.clientId;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.params !== undefined) this.params = options.params;\n if (options.eventTimestamp !== undefined)\n this.eventTimestamp = options.eventTimestamp;\n if (options.status !== undefined) this.status = options.status;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt;\n if (options.errorMessage !== undefined)\n this.errorMessage = options.errorMessage;\n if (options.retryCount !== undefined) this.retryCount = options.retryCount;\n if (options.nonPersonalizedAds !== undefined)\n this.nonPersonalizedAds = options.nonPersonalizedAds;\n if (options.sessionId !== undefined) this.sessionId = options.sessionId;\n if (options.pagePath !== undefined) this.pagePath = options.pagePath;\n if (options.pageTitle !== undefined) this.pageTitle = options.pageTitle;\n if (options.userAgent !== undefined) this.userAgent = options.userAgent;\n if (options.ipAddress !== undefined) this.ipAddress = options.ipAddress;\n }\n\n /**\n * Get parsed event parameters\n */\n getParams(): Record<string, string | number | boolean> {\n try {\n return JSON.parse(this.params);\n } catch {\n return {};\n }\n }\n\n /**\n * Set event parameters\n */\n setParams(params: Record<string, string | number | boolean>): void {\n this.params = JSON.stringify(params);\n }\n\n /**\n * Add a parameter to the event\n */\n addParam(key: string, value: string | number | boolean): void {\n const params = this.getParams();\n params[key] = value;\n this.setParams(params);\n }\n\n /**\n * Check if this is a pageview event\n */\n isPageview(): boolean {\n return this.eventName === 'page_view' || this.eventName === 'pageview';\n }\n\n /**\n * Check if this is a conversion event\n */\n isConversion(): boolean {\n const conversionEvents = [\n 'purchase',\n 'sign_up',\n 'generate_lead',\n 'begin_checkout',\n ];\n return conversionEvents.includes(this.eventName);\n }\n\n /**\n * Mark as sent successfully\n */\n markSent(): void {\n this.status = TrackingEventStatus.SENT;\n this.sentAt = new Date();\n this.errorMessage = '';\n }\n\n /**\n * Mark as failed\n */\n markFailed(error: string): void {\n this.status = TrackingEventStatus.FAILED;\n this.errorMessage = error;\n this.retryCount++;\n }\n\n /**\n * Reset for retry\n */\n resetForRetry(): void {\n this.status = TrackingEventStatus.PENDING;\n this.sentAt = null;\n }\n\n /**\n * Check if event should be retried\n */\n shouldRetry(maxRetries: number = 3): boolean {\n return (\n this.status === TrackingEventStatus.FAILED && this.retryCount < maxRetries\n );\n }\n\n /**\n * Build SDK TrackEvent object\n */\n toTrackEvent(): {\n name: string;\n params?: Record<string, string | number | boolean>;\n clientId?: string;\n userId?: string;\n timestamp?: number;\n nonPersonalizedAds?: boolean;\n } {\n return {\n name: this.eventName,\n params: this.getParams(),\n clientId: this.clientId || undefined,\n userId: this.userId || undefined,\n timestamp: this.eventTimestamp.getTime() * 1000, // Convert to microseconds\n nonPersonalizedAds: this.nonPersonalizedAds,\n };\n }\n}\n\nexport default AnalyticsEvent;\n","/**\n * AnalyticsEventCollection - Collection manager for AnalyticsEvent objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsEvent } from '../models/AnalyticsEvent.js';\nimport {\n type PropertyStatsWithTrend,\n TrackingEventStatus,\n} from '../types/index.js';\n\nexport class AnalyticsEventCollection extends SmrtCollection<AnalyticsEvent> {\n static readonly _itemClass = AnalyticsEvent;\n\n /**\n * Offset of `timeZone` at `instant`, in milliseconds (wall-clock minus UTC).\n *\n * Reads the zone's wall-clock Y/M/D h:m:s for `instant` via\n * `Intl.DateTimeFormat` parts and subtracts the real UTC instant. Positive\n * east of UTC, negative west (e.g. `America/Los_Angeles` returns roughly\n * `-7h`/`-8h` depending on DST).\n *\n * @throws RangeError if `timeZone` is not a valid IANA identifier.\n */\n private zoneOffsetMs(instant: Date, timeZone: string): number {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n }).formatToParts(instant);\n const lookup = (type: Intl.DateTimeFormatPartTypes): number =>\n Number.parseInt(parts.find((p) => p.type === type)?.value ?? '0', 10);\n let hour = lookup('hour');\n // Intl can emit hour \"24\" for midnight under hour12:false; normalize to 0.\n if (hour === 24) hour = 0;\n const asUtc = Date.UTC(\n lookup('year'),\n lookup('month') - 1,\n lookup('day'),\n hour,\n lookup('minute'),\n lookup('second'),\n );\n return asUtc - instant.getTime();\n }\n\n /**\n * Resolve the UTC instant marking the start of the calendar day (00:00) that\n * `instant` falls on **within the given IANA time zone**.\n *\n * Day-over-day buckets (\"today vs yesterday\") must respect the property's\n * configured `timeZone` (defaults to `America/Los_Angeles`), otherwise a\n * pageview at 11:30pm local time — already the next UTC day — is bucketed\n * into the wrong day. We read the wall-clock civil date for the zone, then\n * map that date's local midnight back to a UTC instant, correcting for the\n * zone offset (and re-correcting once across a DST boundary).\n *\n * Invalid/unknown zone identifiers fall back to UTC day boundaries (matching\n * the previous behaviour) rather than throwing.\n *\n * @param instant - Reference instant.\n * @param timeZone - IANA time zone (e.g. `America/Los_Angeles`).\n * @returns UTC `Date` for local midnight of the day `instant` is in.\n */\n protected startOfDayInZone(instant: Date, timeZone: string): Date {\n let civil: { year: number; month: number; day: number };\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(instant);\n const lookup = (type: Intl.DateTimeFormatPartTypes): number =>\n Number.parseInt(parts.find((p) => p.type === type)?.value ?? '0', 10);\n civil = {\n year: lookup('year'),\n month: lookup('month'),\n day: lookup('day'),\n };\n } catch {\n // Unknown/invalid time zone -> UTC day boundary.\n return new Date(\n Date.UTC(\n instant.getUTCFullYear(),\n instant.getUTCMonth(),\n instant.getUTCDate(),\n ),\n );\n }\n\n // Local midnight of `civil`, as a UTC instant: treat civil-midnight as if\n // UTC, then subtract the zone offset at that guess. Re-derive the offset at\n // the corrected instant and re-correct once if it changed (DST edge).\n const guess = Date.UTC(civil.year, civil.month - 1, civil.day);\n const offset = this.zoneOffsetMs(new Date(guess), timeZone);\n let utc = guess - offset;\n const offset2 = this.zoneOffsetMs(new Date(utc), timeZone);\n if (offset2 !== offset) utc = guess - offset2;\n return new Date(utc);\n }\n\n /**\n * Resolve the UTC instant for the start of the day *before* `todayStart`'s\n * local day, in `timeZone`.\n *\n * Steps back 12h from local midnight (landing safely inside the previous\n * civil day regardless of DST — a naive `- 24h` skips a day across\n * spring-forward), then re-resolves start-of-day.\n *\n * @param todayStart - Local-midnight UTC instant from {@link startOfDayInZone}.\n * @param timeZone - IANA time zone.\n * @returns UTC `Date` for local midnight of the prior calendar day.\n */\n protected startOfYesterdayInZone(todayStart: Date, timeZone: string): Date {\n return this.startOfDayInZone(\n new Date(todayStart.getTime() - 12 * 3_600_000),\n timeZone,\n );\n }\n\n /**\n * Classify a day-over-day change into a trend direction + percent.\n *\n * - `yesterday > 0`: percent = rounded delta; >5% up, <-5% down, else flat.\n * - `yesterday === 0 && today > 0`: a brand-new surge from a zero baseline —\n * classified `up` with a `null` percent (no finite percentage exists), so\n * the UI renders \"new\" rather than a misleading flat 0%.\n * - `yesterday === 0 && today === 0`: flat, 0%.\n *\n * @param today - Today's count.\n * @param yesterday - Yesterday's count.\n * @returns Trend direction and percent (null when growing from zero).\n */\n protected classifyTrend(\n today: number,\n yesterday: number,\n ): { trend: 'up' | 'down' | 'flat'; trendPercent: number | null } {\n if (yesterday > 0) {\n const change = ((today - yesterday) / yesterday) * 100;\n const trendPercent = Math.round(change);\n let trend: 'up' | 'down' | 'flat' = 'flat';\n if (change > 5) trend = 'up';\n else if (change < -5) trend = 'down';\n return { trend, trendPercent };\n }\n if (today > 0) {\n // Growth from a zero baseline: a real surge, no finite percentage.\n return { trend: 'up', trendPercent: null };\n }\n return { trend: 'flat', trendPercent: 0 };\n }\n\n /**\n * Find events by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of events\n */\n async findByProperty(propertyId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by event name\n *\n * @param eventName - Event name to filter by\n * @returns Array of matching events\n */\n async findByEventName(eventName: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { eventName },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by client ID\n *\n * @param clientId - Client ID\n * @returns Array of events for this client\n */\n async findByClientId(clientId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { clientId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by user ID\n *\n * @param userId - User ID\n * @returns Array of events for this user\n */\n async findByUserId(userId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { userId },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find events by status\n *\n * @param status - Tracking event status\n * @returns Array of matching events\n */\n async findByStatus(status: TrackingEventStatus): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: { status },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find all pending events\n */\n async findPending(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.PENDING);\n }\n\n /**\n * Find all sent events\n */\n async findSent(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.SENT);\n }\n\n /**\n * Find all failed events\n */\n async findFailed(): Promise<AnalyticsEvent[]> {\n return await this.findByStatus(TrackingEventStatus.FAILED);\n }\n\n /**\n * Find events that should be retried\n *\n * @param maxRetries - Maximum retry count\n * @returns Array of events eligible for retry\n */\n async findForRetry(maxRetries: number = 3): Promise<AnalyticsEvent[]> {\n const failed = await this.findFailed();\n return failed.filter((e) => e.shouldRetry(maxRetries));\n }\n\n /**\n * Find pending events for a property\n *\n * @param propertyId - Parent property ID\n * @returns Array of pending events\n */\n async findPendingByProperty(propertyId: string): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: {\n propertyId,\n status: TrackingEventStatus.PENDING,\n },\n orderBy: 'eventTimestamp ASC', // Process oldest first\n });\n }\n\n /**\n * Find events by date range\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of events in date range\n */\n async findByDateRange(\n startDate: Date,\n endDate: Date,\n ): Promise<AnalyticsEvent[]> {\n return await this.list({\n where: {\n 'eventTimestamp >=': startDate.toISOString(),\n 'eventTimestamp <=': endDate.toISOString(),\n },\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Find conversion events\n *\n * @param propertyId - Optional property ID filter\n * @returns Array of conversion events\n */\n async findConversions(propertyId?: string): Promise<AnalyticsEvent[]> {\n const conversionEvents = [\n 'purchase',\n 'sign_up',\n 'generate_lead',\n 'begin_checkout',\n ];\n const all = propertyId\n ? await this.findByProperty(propertyId)\n : await this.list({ orderBy: 'eventTimestamp DESC' });\n\n return all.filter((e) => conversionEvents.includes(e.eventName));\n }\n\n /**\n * Find pageview events\n *\n * @param propertyId - Optional property ID filter\n * @returns Array of pageview events\n */\n async findPageviews(propertyId?: string): Promise<AnalyticsEvent[]> {\n const where: Record<string, unknown> = { eventName: 'page_view' };\n if (propertyId) {\n where.propertyId = propertyId;\n }\n return await this.list({\n where,\n orderBy: 'eventTimestamp DESC',\n });\n }\n\n /**\n * Count events by event name for a property\n *\n * @param propertyId - Property ID\n * @returns Map of event name to count\n */\n async countByEventName(propertyId: string): Promise<Map<string, number>> {\n const events = await this.findByProperty(propertyId);\n const counts = new Map<string, number>();\n\n for (const event of events) {\n const current = counts.get(event.eventName) || 0;\n counts.set(event.eventName, current + 1);\n }\n\n return counts;\n }\n\n /**\n * Get event stats for a property\n *\n * @param propertyId - Property ID\n * @returns Event statistics\n */\n async getPropertyStats(propertyId: string): Promise<{\n total: number;\n pending: number;\n sent: number;\n failed: number;\n conversions: number;\n pageviews: number;\n }> {\n const events = await this.findByProperty(propertyId);\n\n return {\n total: events.length,\n pending: events.filter((e) => e.status === TrackingEventStatus.PENDING)\n .length,\n sent: events.filter((e) => e.status === TrackingEventStatus.SENT).length,\n failed: events.filter((e) => e.status === TrackingEventStatus.FAILED)\n .length,\n conversions: events.filter((e) => e.isConversion()).length,\n pageviews: events.filter((e) => e.isPageview()).length,\n };\n }\n\n /**\n * Get day-over-day pageview stats with trend for a property.\n *\n * Compares today's pageview count against yesterday's to produce a\n * trend direction and percentage change. A threshold of 5% is used\n * to classify 'up' vs 'down' vs 'flat'; growth from a zero baseline is\n * classified `up` with a `null` percent (see {@link classifyTrend}).\n *\n * Day boundaries are computed in `timeZone` (an IANA identifier such as the\n * property's `AnalyticsProperty.timeZone`, which defaults to\n * `America/Los_Angeles`) so an event near local midnight buckets into the\n * correct calendar day. Defaults to `'UTC'` when omitted.\n *\n * @param propertyId - Property ID\n * @param now - Optional current date (for testing)\n * @param timeZone - IANA time zone for day boundaries (default `'UTC'`)\n * @returns Stats with trend\n */\n async getPropertyStatsWithTrend(\n propertyId: string,\n now?: Date,\n timeZone: string = 'UTC',\n ): Promise<PropertyStatsWithTrend> {\n const currentTime = now || new Date();\n const todayStart = this.startOfDayInZone(currentTime, timeZone);\n const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);\n\n // Single query for both days to avoid concurrent DuckDB prepared statements\n const allPageviewEvents = await this.list({\n where: {\n propertyId,\n eventName: 'page_view',\n 'eventTimestamp >=': yesterdayStart.toISOString(),\n 'eventTimestamp <=': currentTime.toISOString(),\n },\n });\n\n const todayPageviewEvents = allPageviewEvents.filter(\n (e) => new Date(e.eventTimestamp) >= todayStart,\n );\n const yesterdayPageviewEvents = allPageviewEvents.filter(\n (e) => new Date(e.eventTimestamp) < todayStart,\n );\n\n // Count unique clients (users)\n const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));\n const yesterdayClients = new Set(\n yesterdayPageviewEvents.map((e) => e.clientId),\n );\n\n const todayPageviews = todayPageviewEvents.length;\n const yesterdayPageviews = yesterdayPageviewEvents.length;\n\n const { trend, trendPercent } = this.classifyTrend(\n todayPageviews,\n yesterdayPageviews,\n );\n\n return {\n todayPageviews,\n todayUsers: todayClients.size,\n yesterdayPageviews,\n yesterdayUsers: yesterdayClients.size,\n trend,\n trendPercent,\n };\n }\n\n /**\n * Get day-over-day stats for multiple properties in batch.\n *\n * Day boundaries are computed in `timeZone` (default `'UTC'`); see\n * {@link getPropertyStatsWithTrend}. A single zone applies to the whole\n * batch, so callers mixing properties with different `timeZone` values\n * should batch per zone (or fall back to per-property calls).\n *\n * @param propertyIds - Array of property IDs\n * @param now - Optional current date (for testing)\n * @param timeZone - IANA time zone for day boundaries (default `'UTC'`)\n * @returns Map of propertyId to stats\n */\n async getBatchPropertyStats(\n propertyIds: string[],\n now?: Date,\n timeZone: string = 'UTC',\n ): Promise<Map<string, PropertyStatsWithTrend>> {\n const results = new Map<string, PropertyStatsWithTrend>();\n\n // Fetch all date-ranged events once to avoid N+1 queries\n const currentTime = now || new Date();\n const todayStart = this.startOfDayInZone(currentTime, timeZone);\n const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);\n\n // Single query for both days to avoid concurrent DuckDB prepared statements\n const allEvents = await this.list({\n where: {\n eventName: 'page_view',\n 'eventTimestamp >=': yesterdayStart.toISOString(),\n 'eventTimestamp <=': currentTime.toISOString(),\n },\n });\n\n // Pre-group events by propertyId and day in a single pass\n const todayByProperty = new Map<string, AnalyticsEvent[]>();\n const yesterdayByProperty = new Map<string, AnalyticsEvent[]>();\n for (const e of allEvents) {\n const isToday = new Date(e.eventTimestamp) >= todayStart;\n const map = isToday ? todayByProperty : yesterdayByProperty;\n const list = map.get(e.propertyId);\n if (list) list.push(e);\n else map.set(e.propertyId, [e]);\n }\n\n for (const propertyId of propertyIds) {\n const todayPageviewEvents = todayByProperty.get(propertyId) ?? [];\n const yesterdayPageviewEvents = yesterdayByProperty.get(propertyId) ?? [];\n\n const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));\n const yesterdayClients = new Set(\n yesterdayPageviewEvents.map((e) => e.clientId),\n );\n\n const todayPageviews = todayPageviewEvents.length;\n const yesterdayPageviews = yesterdayPageviewEvents.length;\n\n const { trend, trendPercent } = this.classifyTrend(\n todayPageviews,\n yesterdayPageviews,\n );\n\n results.set(propertyId, {\n todayPageviews,\n todayUsers: todayClients.size,\n yesterdayPageviews,\n yesterdayUsers: yesterdayClients.size,\n trend,\n trendPercent,\n });\n }\n\n return results;\n }\n}\n","/**\n * AnalyticsProperty model - Represents an analytics property/site\n * @packageDocumentation\n */\n\nimport {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtAnalyticsAnalyzePerformancePrompt,\n} from '../prompts.js';\nimport { AnalyticsPropertyStatus, AnalyticsProvider } from '../types/index.js';\n\n/**\n * Options for constructing an {@link AnalyticsProperty}.\n */\nexport interface AnalyticsPropertyOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n name?: string;\n displayName?: string;\n provider?: AnalyticsProvider;\n externalId?: string;\n measurementId?: string;\n apiSecret?: string;\n siteDomain?: string;\n timeZone?: string;\n currencyCode?: string;\n industryCategory?: string;\n serviceLevel?: string;\n status?: AnalyticsPropertyStatus;\n lastSyncAt?: Date | null;\n providerMetadata?: string;\n}\n\n/**\n * AnalyticsProperty represents an analytics property (GA4 property,\n * Plausible site, or Matomo site).\n *\n * @example\n * ```typescript\n * const property = await properties.create({\n * name: 'My Website',\n * displayName: 'My Website Analytics',\n * provider: AnalyticsProvider.GA4,\n * externalId: 'properties/123456789',\n * measurementId: 'G-XXXXXXXXXX'\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'sync', 'runReport'] },\n cli: true,\n})\nexport class AnalyticsProperty extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Without tenant scoping the generated `list`/`get` API and any raw query\n * return every tenant's properties — including the `@field({ sensitive })`\n * `apiSecret` / `providerMetadata` credentials. `@TenantScoped` registers\n * this class with the tenant interceptor so reads are auto-filtered and\n * writes are bound to the active tenant context.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Internal name/identifier\n */\n name: string = '';\n\n /**\n * Human-readable display name\n */\n displayName: string = '';\n\n /**\n * Analytics provider (ga4, plausible, matomo)\n */\n provider: AnalyticsProvider = AnalyticsProvider.GA4;\n\n /**\n * External ID from the provider (e.g., \"properties/123456789\" for GA4,\n * idSite for Matomo)\n */\n externalId: string = '';\n\n /**\n * Measurement ID for GA4 (G-XXXXXXXXXX)\n */\n measurementId: string = '';\n\n /**\n * Provider API secret/token (GA4 API secret, Matomo token_auth)\n *\n * Sensitive (#1540): excluded from generated API/MCP responses and rejected\n * as a `where` filter key so it can't be probed.\n */\n @field({ sensitive: true })\n apiSecret: string = '';\n\n /**\n * Site domain for Plausible/Matomo\n */\n siteDomain: string = '';\n\n /**\n * Property timezone\n */\n timeZone: string = 'America/Los_Angeles';\n\n /**\n * Currency code (e.g., 'USD', 'EUR')\n */\n currencyCode: string = 'USD';\n\n /**\n * Industry category\n */\n industryCategory: string = '';\n\n /**\n * Service level (STANDARD, PREMIUM)\n */\n serviceLevel: string = 'STANDARD';\n\n /**\n * Property status\n */\n status: AnalyticsPropertyStatus = AnalyticsPropertyStatus.ACTIVE;\n\n /**\n * Last sync timestamp with provider\n */\n lastSyncAt: Date | null = null;\n\n /**\n * Metadata from provider (JSON)\n *\n * Sensitive (#1540): may carry provider credentials/tokens, so it is excluded\n * from generated API/MCP responses and rejected as a `where` filter key.\n */\n @field({ sensitive: true })\n providerMetadata: string = '{}';\n\n constructor(options: AnalyticsPropertyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.displayName !== undefined)\n this.displayName = options.displayName;\n if (options.provider !== undefined) this.provider = options.provider;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.measurementId !== undefined)\n this.measurementId = options.measurementId;\n if (options.apiSecret !== undefined) this.apiSecret = options.apiSecret;\n if (options.siteDomain !== undefined) this.siteDomain = options.siteDomain;\n if (options.timeZone !== undefined) this.timeZone = options.timeZone;\n if (options.currencyCode !== undefined)\n this.currencyCode = options.currencyCode;\n if (options.industryCategory !== undefined)\n this.industryCategory = options.industryCategory;\n if (options.serviceLevel !== undefined)\n this.serviceLevel = options.serviceLevel;\n if (options.status !== undefined) this.status = options.status;\n if (options.lastSyncAt !== undefined) this.lastSyncAt = options.lastSyncAt;\n if (options.providerMetadata !== undefined)\n this.providerMetadata = options.providerMetadata;\n }\n\n /**\n * Check if this is a GA4 property\n */\n isGA4(): boolean {\n return this.provider === AnalyticsProvider.GA4;\n }\n\n /**\n * Check if this is a Plausible site\n */\n isPlausible(): boolean {\n return this.provider === AnalyticsProvider.PLAUSIBLE;\n }\n\n /**\n * Check if this is a Matomo site\n */\n isMatomo(): boolean {\n return this.provider === AnalyticsProvider.MATOMO;\n }\n\n /**\n * Get parsed provider metadata\n */\n getProviderMetadata(): Record<string, unknown> {\n try {\n return JSON.parse(this.providerMetadata);\n } catch {\n return {};\n }\n }\n\n /**\n * Set provider metadata\n */\n setProviderMetadata(metadata: Record<string, unknown>): void {\n this.providerMetadata = JSON.stringify(metadata);\n }\n\n /**\n * Mark as synced with provider\n */\n markSynced(): void {\n this.lastSyncAt = new Date();\n }\n\n /**\n * AI-powered: Analyze property performance.\n *\n * Uses the `smrtAnalytics.property.analyzePerformance` prompt registered\n * via `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Only non-PII fields (display name, provider label, requested period)\n * are sent to the AI provider. Internal identifiers (`id`, `externalId`,\n * `measurementId`, `apiSecret`, `providerMetadata`) are intentionally\n * excluded — see `../prompts.ts` for the full exclusion rationale.\n */\n async analyzePerformance(options: { period?: string } = {}): Promise<{\n action: string;\n period: string;\n analysis: string;\n }> {\n const period = options.period || '30 days';\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias so stored prompt overrides are honored on first call before\n // `getAiClient()` triggers full initialization. SmrtObject already types\n // both options on `SmrtClassOptions` so no `any` cast is needed — that\n // keeps a misspelt option name surfacing as a TypeScript error rather\n // than silently falling through.\n const db = this.options.db ?? this.options.persistence;\n\n // This model is `@TenantScoped` and declares a `tenantId` field, but we\n // still deliberately OMIT `tenantId` from the resolvePrompt options so the\n // resolver falls back to the AsyncLocalStorage tenancy context via\n // `getTenantId()`. Passing `this.tenantId` (which may be null for\n // tenant-agnostic rows) explicitly would short-circuit that fallback and\n // silently ignore tenant-specific prompt overrides active in the\n // surrounding `withTenant(...)` block.\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsAnalyzePerformancePrompt.key,\n {\n db,\n variables: {\n period,\n propertyDisplayName: this.displayName || '',\n propertyProvider: this.provider || '',\n },\n },\n );\n\n const ai = await this.getAiClient();\n const analysis = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return {\n action: 'analyzePerformance',\n period,\n analysis: analysis.trim(),\n };\n }\n\n /**\n * AI-powered: Check if property is performing well\n */\n async isPerformingWell(): Promise<boolean> {\n return await this.is(\n `\n Based on the property configuration and metadata:\n - Property: ${this.displayName}\n - Provider: ${this.provider}\n - Status: ${this.status}\n - Last sync: ${this.lastSyncAt}\n\n Is this property properly configured and likely performing well?\n `,\n // Property fields hand-rolled above; skip is()'s object-data injection.\n { includeData: false },\n );\n }\n}\n\nexport default AnalyticsProperty;\n","/**\n * AnalyticsPropertyCollection - Collection manager for AnalyticsProperty objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsProperty } from '../models/AnalyticsProperty.js';\nimport { AnalyticsPropertyStatus, AnalyticsProvider } from '../types/index.js';\n\nexport class AnalyticsPropertyCollection extends SmrtCollection<AnalyticsProperty> {\n static readonly _itemClass = AnalyticsProperty;\n\n /**\n * Find property by external ID\n *\n * @param externalId - External ID from provider\n * @returns Matching property or null\n */\n async findByExternalId(\n externalId: string,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find property by measurement ID (GA4)\n *\n * @param measurementId - GA4 measurement ID (G-XXXXXXXXXX)\n * @returns Matching property or null\n */\n async findByMeasurementId(\n measurementId: string,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: { measurementId },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find property by site domain and optional provider.\n *\n * @param siteDomain - Provider site domain\n * @param provider - Optional provider discriminator for migration/coexistence\n * @returns Matching property or null\n */\n async findBySiteDomain(\n siteDomain: string,\n provider?: AnalyticsProvider,\n ): Promise<AnalyticsProperty | null> {\n const results = await this.list({\n where: provider ? { siteDomain, provider } : { siteDomain },\n limit: 1,\n });\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Find properties by provider\n *\n * @param provider - Analytics provider\n * @returns Array of matching properties\n */\n async findByProvider(\n provider: AnalyticsProvider,\n ): Promise<AnalyticsProperty[]> {\n return await this.list({\n where: { provider },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all GA4 properties\n */\n async findGA4Properties(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.GA4);\n }\n\n /**\n * Find all Plausible sites\n */\n async findPlausibleSites(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.PLAUSIBLE);\n }\n\n /**\n * Find all Matomo sites\n */\n async findMatomoSites(): Promise<AnalyticsProperty[]> {\n return await this.findByProvider(AnalyticsProvider.MATOMO);\n }\n\n /**\n * Find properties by status\n *\n * @param status - Property status\n * @returns Array of matching properties\n */\n async findByStatus(\n status: AnalyticsPropertyStatus,\n ): Promise<AnalyticsProperty[]> {\n return await this.list({\n where: { status },\n orderBy: 'displayName ASC',\n });\n }\n\n /**\n * Find all active properties\n */\n async findActive(): Promise<AnalyticsProperty[]> {\n return await this.findByStatus(AnalyticsPropertyStatus.ACTIVE);\n }\n\n /**\n * Find properties that need syncing (not synced in last N hours)\n *\n * @param hoursAgo - Hours since last sync\n * @returns Array of properties needing sync\n */\n async findNeedingSync(hoursAgo: number = 24): Promise<AnalyticsProperty[]> {\n const cutoff = new Date();\n cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1000);\n\n // Get properties where lastSyncAt is null or older than cutoff\n const all = await this.findActive();\n return all.filter((p) => !p.lastSyncAt || p.lastSyncAt < cutoff);\n }\n}\n","/**\n * AnalyticsReport model - Saved report configurations and results\n * @packageDocumentation\n */\n\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtAnalyticsAnalyzeResultsPrompt,\n smrtAnalyticsHasPositiveTrendsPrompt,\n} from '../prompts.js';\nimport { ReportFrequency, ReportStatus } from '../types/index.js';\n\n/**\n * Options for constructing an {@link AnalyticsReport}.\n *\n * `metrics` is omitted from the {@link SmrtObjectOptions} base before being\n * re-declared: the framework option carries an observability `MetricsConfig`,\n * whereas this report field is a JSON-encoded list of analytics metrics.\n */\nexport interface AnalyticsReportOptions\n extends Omit<SmrtObjectOptions, 'metrics'> {\n tenantId?: string | null;\n propertyId?: string;\n name?: string;\n description?: string;\n dimensions?: string;\n metrics?: string;\n dateRangeStart?: string;\n dateRangeEnd?: string;\n dimensionFilter?: string;\n metricFilter?: string;\n orderBy?: string;\n maxResults?: number;\n status?: ReportStatus;\n frequency?: ReportFrequency;\n lastRunAt?: Date | null;\n nextRunAt?: Date | null;\n resultData?: string;\n rowCount?: number;\n lastError?: string;\n}\n\n/**\n * AnalyticsReport represents a saved report configuration with optional scheduling.\n *\n * @example\n * ```typescript\n * const report = await reports.create({\n * propertyId: property.id,\n * name: 'Weekly Traffic Report',\n * dimensions: JSON.stringify([{ name: 'country' }, { name: 'deviceCategory' }]),\n * metrics: JSON.stringify([{ name: 'activeUsers' }, { name: 'sessions' }]),\n * frequency: ReportFrequency.WEEKLY\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'run'] },\n mcp: { include: ['list', 'get', 'run', 'analyze'] },\n cli: true,\n})\nexport class AnalyticsReport extends SmrtObject {\n /**\n * Tenant ID for multi-tenancy isolation (#1410).\n *\n * Reports persist `resultData` rows that may contain tenant-private metrics\n * and PII-bearing dimensions. Without tenant scoping the generated\n * `list`/`get` API returns every tenant's cached report data, and the\n * AI-powered `analyze`/`run` operations could run over another tenant's\n * rows. `@TenantScoped` auto-filters reads and binds writes to the tenant.\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent property ID (references AnalyticsProperty)\n */\n @foreignKey('AnalyticsProperty')\n propertyId: string = '';\n\n /**\n * Report name\n */\n name: string = '';\n\n /**\n * Report description\n */\n description: string = '';\n\n /**\n * Dimensions to group by (JSON array)\n */\n dimensions: string = '[]';\n\n /**\n * Metrics to retrieve (JSON array)\n */\n metrics: string = '[]';\n\n /**\n * Date range start (relative or absolute)\n */\n dateRangeStart: string = '7daysAgo';\n\n /**\n * Date range end (relative or absolute)\n */\n dateRangeEnd: string = 'today';\n\n /**\n * Dimension filter expression (JSON)\n */\n dimensionFilter: string = '';\n\n /**\n * Metric filter expression (JSON)\n */\n metricFilter: string = '';\n\n /**\n * Sort order (JSON array)\n */\n orderBy: string = '[]';\n\n /**\n * Maximum results to return\n */\n maxResults: number = 0;\n\n /**\n * Report status\n */\n status: ReportStatus = ReportStatus.DRAFT;\n\n /**\n * Scheduling frequency\n */\n frequency: ReportFrequency = ReportFrequency.ONCE;\n\n /**\n * Last run timestamp\n */\n lastRunAt: Date | null = null;\n\n /**\n * Next scheduled run\n */\n nextRunAt: Date | null = null;\n\n /**\n * Cached result data (JSON)\n */\n resultData: string = '';\n\n /**\n * Row count from last run\n */\n rowCount: number = 0;\n\n /**\n * Error message from last failed run\n */\n lastError: string = '';\n\n constructor(options: AnalyticsReportOptions = {}) {\n // `AnalyticsReportOptions` re-types `metrics` as the report's JSON string,\n // which collides with the framework's `metrics?: MetricsConfig` observability\n // option on `SmrtObjectOptions`. The base never reads a string `metrics` as\n // config, so the value is forwarded unchanged; the assertion only reconciles\n // the two unrelated `metrics` types at the `super()` boundary.\n super(options as SmrtObjectOptions);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.propertyId !== undefined) this.propertyId = options.propertyId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.dimensions !== undefined) this.dimensions = options.dimensions;\n if (options.metrics !== undefined) this.metrics = options.metrics;\n if (options.dateRangeStart !== undefined)\n this.dateRangeStart = options.dateRangeStart;\n if (options.dateRangeEnd !== undefined)\n this.dateRangeEnd = options.dateRangeEnd;\n if (options.dimensionFilter !== undefined)\n this.dimensionFilter = options.dimensionFilter;\n if (options.metricFilter !== undefined)\n this.metricFilter = options.metricFilter;\n if (options.orderBy !== undefined) this.orderBy = options.orderBy;\n if (options.maxResults !== undefined) this.maxResults = options.maxResults;\n if (options.status !== undefined) this.status = options.status;\n if (options.frequency !== undefined) this.frequency = options.frequency;\n if (options.lastRunAt !== undefined) this.lastRunAt = options.lastRunAt;\n if (options.nextRunAt !== undefined) this.nextRunAt = options.nextRunAt;\n if (options.resultData !== undefined) this.resultData = options.resultData;\n if (options.rowCount !== undefined) this.rowCount = options.rowCount;\n if (options.lastError !== undefined) this.lastError = options.lastError;\n }\n\n /**\n * Get parsed dimensions\n */\n getDimensions(): Array<{ name: string }> {\n try {\n return JSON.parse(this.dimensions);\n } catch {\n return [];\n }\n }\n\n /**\n * Set dimensions\n */\n setDimensions(dimensions: Array<{ name: string }>): void {\n this.dimensions = JSON.stringify(dimensions);\n }\n\n /**\n * Get parsed metrics\n */\n getMetrics(): Array<{ name: string }> {\n try {\n return JSON.parse(this.metrics);\n } catch {\n return [];\n }\n }\n\n /**\n * Set metrics\n */\n setMetrics(metrics: Array<{ name: string }>): void {\n this.metrics = JSON.stringify(metrics);\n }\n\n /**\n * Get parsed result data\n */\n getResultData(): Record<string, unknown> | null {\n if (!this.resultData) return null;\n try {\n return JSON.parse(this.resultData);\n } catch {\n return null;\n }\n }\n\n /**\n * Set result data\n */\n setResultData(data: Record<string, unknown>): void {\n this.resultData = JSON.stringify(data);\n }\n\n /**\n * Mark report as running\n */\n markRunning(): void {\n this.status = ReportStatus.RUNNING;\n this.lastError = '';\n }\n\n /**\n * Mark report as completed with results\n */\n markCompleted(rowCount: number): void {\n this.status = ReportStatus.COMPLETED;\n this.lastRunAt = new Date();\n this.rowCount = rowCount;\n this.lastError = '';\n this.calculateNextRun();\n }\n\n /**\n * Mark report as failed\n */\n markFailed(error: string): void {\n this.status = ReportStatus.FAILED;\n this.lastRunAt = new Date();\n this.lastError = error;\n this.calculateNextRun();\n }\n\n /**\n * Calculate next scheduled run based on frequency\n */\n calculateNextRun(): void {\n if (this.frequency === ReportFrequency.ONCE) {\n this.nextRunAt = null;\n return;\n }\n\n const now = new Date();\n const next = new Date(now);\n\n switch (this.frequency) {\n case ReportFrequency.DAILY:\n next.setDate(next.getDate() + 1);\n break;\n case ReportFrequency.WEEKLY:\n next.setDate(next.getDate() + 7);\n break;\n case ReportFrequency.MONTHLY:\n next.setMonth(next.getMonth() + 1);\n break;\n }\n\n this.nextRunAt = next;\n }\n\n /**\n * Check if report is due to run\n */\n isDue(): boolean {\n if (this.frequency === ReportFrequency.ONCE) {\n return this.status === ReportStatus.SCHEDULED && !this.lastRunAt;\n }\n if (!this.nextRunAt) return false;\n return new Date() >= this.nextRunAt;\n }\n\n /**\n * AI-powered: Analyze report results.\n *\n * Uses the `smrtAnalytics.report.analyzeResults` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Internal identifiers (`id`, `propertyId`, `tenantId`, `lastError`, raw\n * `dimensionFilter` / `metricFilter` JSON) are excluded from the prompt\n * variables — see `../prompts.ts` for the exclusion rationale.\n *\n * **`resultData` is FORWARDED VERBATIM.** The persisted result rows are\n * JSON-stringified into the `reportData` variable; this package cannot\n * strip PII because the row schema is determined by which dimensions /\n * metrics the caller asked the analytics provider to return. If the\n * persisted rows contain `userPseudoId`, `clientId`, IP-derived\n * geolocation, or any other identifier, those fields WILL reach the AI\n * provider. Callers are responsible for excluding PII-bearing dimensions\n * before persisting, applying a column allowlist at the call site, or\n * overriding the prompt template via `PromptOverride`. The forwarding is\n * pinned by a regression test in\n * `__tests__/analytics-report-prompt.test.ts`.\n *\n * The previous implementation issued a second freeform `this.do()` call\n * to re-summarize \"top 3 insights\"; that behaviour is now folded into\n * the single registered template (which already asks for findings,\n * trends, and recommendations) — `insights` mirrors `analysis` so the\n * return shape is preserved without a redundant AI round-trip.\n */\n async analyzeResults(_options: Record<string, unknown> = {}): Promise<{\n action: string;\n analysis: string;\n insights: string;\n }> {\n const resultData = this.getResultData();\n\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias so stored prompt overrides are honored on first call before\n // `getAiClient()` triggers full initialization. SmrtObject already types\n // both options on `SmrtClassOptions` so no `any` cast is needed — that\n // keeps a misspelt option name surfacing as a TypeScript error rather\n // than silently falling through.\n const db = this.options.db ?? this.options.persistence;\n\n // This model is `@TenantScoped` and declares a `tenantId` field, but we\n // still deliberately OMIT `tenantId` from the resolvePrompt options so the\n // resolver falls back to the AsyncLocalStorage tenancy context via\n // `getTenantId()`. Passing `this.tenantId` (which may be null for\n // tenant-agnostic rows) explicitly would short-circuit that fallback and\n // silently ignore tenant-specific prompt overrides active in the\n // surrounding `withTenant(...)` block.\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsAnalyzeResultsPrompt.key,\n {\n db,\n variables: {\n reportName: this.name || '',\n reportDimensions: this.dimensions || '[]',\n reportMetrics: this.metrics || '[]',\n dateRangeStart: this.dateRangeStart || '',\n dateRangeEnd: this.dateRangeEnd || '',\n rowCount: String(this.rowCount),\n reportData: JSON.stringify(resultData, null, 2),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const analysis = (\n await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n )\n ).trim();\n\n return {\n action: 'analyzeResults',\n analysis,\n insights: analysis,\n };\n }\n\n /**\n * AI-powered: Check if results show positive trends.\n *\n * Uses the `smrtAnalytics.report.hasPositiveTrends` prompt registered\n * via `@happyvertical/smrt-prompts`. Only the metric labels and the\n * aggregate `resultData` JSON are sent to the AI provider — though as\n * with `analyzeResults`, `resultData` is forwarded verbatim and may\n * carry PII the caller persisted; see `analyzeResults` docstring and\n * `../prompts.ts`.\n *\n * Boolean coercion uses `/^\\s*(yes|true)\\b/i` against the trimmed\n * response. The registered prompt template explicitly instructs the\n * model to begin its answer with the literal word \"yes\" or \"no\" so\n * this regex is reliable; tenant overrides MUST preserve that leading-\n * word convention or the boolean will silently fall to `false`.\n */\n async hasPositiveTrends(): Promise<boolean> {\n const resultData = this.getResultData();\n\n // See `analyzeResults` above for the typed-options + tenancy-fallback rationale.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtAnalyticsHasPositiveTrendsPrompt.key,\n {\n db,\n variables: {\n reportMetrics: this.metrics || '[]',\n reportData: JSON.stringify(resultData),\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = (\n await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n )\n ).trim();\n\n return /^\\s*(yes|true)\\b/i.test(response);\n }\n}\n\nexport default AnalyticsReport;\n","/**\n * AnalyticsReportCollection - Collection manager for AnalyticsReport objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AnalyticsReport } from '../models/AnalyticsReport.js';\nimport { ReportFrequency, ReportStatus } from '../types/index.js';\n\nexport class AnalyticsReportCollection extends SmrtCollection<AnalyticsReport> {\n static readonly _itemClass = AnalyticsReport;\n\n /**\n * Find reports by property\n *\n * @param propertyId - Parent property ID\n * @returns Array of reports\n */\n async findByProperty(propertyId: string): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { propertyId },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find reports by status\n *\n * @param status - Report status\n * @returns Array of matching reports\n */\n async findByStatus(status: ReportStatus): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { status },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find all draft reports\n */\n async findDrafts(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.DRAFT);\n }\n\n /**\n * Find all scheduled reports\n */\n async findScheduled(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.SCHEDULED);\n }\n\n /**\n * Find all completed reports\n */\n async findCompleted(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.COMPLETED);\n }\n\n /**\n * Find all failed reports\n */\n async findFailed(): Promise<AnalyticsReport[]> {\n return await this.findByStatus(ReportStatus.FAILED);\n }\n\n /**\n * Find reports by frequency\n *\n * @param frequency - Report frequency\n * @returns Array of matching reports\n */\n async findByFrequency(\n frequency: ReportFrequency,\n ): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { frequency },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find all recurring reports (not one-time)\n */\n async findRecurring(): Promise<AnalyticsReport[]> {\n const all = await this.list({ orderBy: 'name ASC' });\n return all.filter((r) => r.frequency !== ReportFrequency.ONCE);\n }\n\n /**\n * Find reports due to run\n *\n * @returns Array of reports that are due\n */\n async findDue(): Promise<AnalyticsReport[]> {\n const all = await this.list({\n where: { status: ReportStatus.SCHEDULED },\n });\n return all.filter((r) => r.isDue());\n }\n\n /**\n * Find reports for a property by status\n *\n * @param propertyId - Parent property ID\n * @param status - Report status\n * @returns Array of matching reports\n */\n async findByPropertyAndStatus(\n propertyId: string,\n status: ReportStatus,\n ): Promise<AnalyticsReport[]> {\n return await this.list({\n where: { propertyId, status },\n orderBy: 'name ASC',\n });\n }\n\n /**\n * Find recently run reports\n *\n * @param hoursAgo - Hours since last run\n * @returns Array of recently run reports\n */\n async findRecentlyRun(hoursAgo: number = 24): Promise<AnalyticsReport[]> {\n const cutoff = new Date();\n cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1000);\n\n const all = await this.list({ orderBy: 'lastRunAt DESC' });\n return all.filter((r) => r.lastRunAt && r.lastRunAt >= cutoff);\n }\n}\n"],"names":["AnalyticsProvider","AnalyticsPropertyStatus","DataStreamType","DataStreamStatus","ReportStatus","ReportFrequency","CustomDimensionScope","TrackingEventStatus","CountingMethod","__decorateClass"],"mappings":";;;AAsBA,eAAe;AAAA,EACb,IAAA,IAAA,mBAAA,YAAA,GAAA;AACF;AC+BO,MAAM,wCAAwC,aAAa;AAAA,EAChE,KAAK;AAAA,EACL,UAAU;AAAA;AAAA;AAAA,EAGV,UAAU;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EAAA;AAEZ,CAAC;AAwBM,MAAM,oCAAoC,aAAa;AAAA,EAC5D,KAAK;AAAA,EACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcV,UAAU;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EAAA;AAEZ,CAAC;AAiBM,MAAM,uCAAuC,aAAa;AAAA,EAC/D,KAAK;AAAA,EACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUV,UAAU;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EAAA;AAEZ,CAAC;AAOM,SAAS,qBAAqB,IAAsB;AACzD,SAAO;AAAA,IACL,GAAI,GAAG,UAAU,CAAA;AAAA,IACjB,GAAI,GAAG,QAAQ,EAAE,OAAO,GAAG,MAAA,IAAU,CAAA;AAAA,IACrC,GAAI,OAAO,GAAG,gBAAgB,WAC1B,EAAE,aAAa,GAAG,YAAA,IAClB,CAAA;AAAA,IACJ,GAAI,OAAO,GAAG,cAAc,WAAW,EAAE,WAAW,GAAG,cAAc,CAAA;AAAA,EAAC;AAE1E;AC5JO,IAAK,sCAAAA,uBAAL;AACLA,qBAAA,KAAA,IAAM;AACNA,qBAAA,WAAA,IAAY;AACZA,qBAAA,QAAA,IAAS;AAHC,SAAAA;AAAA,GAAA,qBAAA,CAAA,CAAA;AASL,IAAK,4CAAAC,6BAAL;AACLA,2BAAA,QAAA,IAAS;AACTA,2BAAA,UAAA,IAAW;AACXA,2BAAA,SAAA,IAAU;AAHA,SAAAA;AAAA,GAAA,2BAAA,CAAA,CAAA;AASL,IAAK,mCAAAC,oBAAL;AACLA,kBAAA,KAAA,IAAM;AACNA,kBAAA,SAAA,IAAU;AACVA,kBAAA,KAAA,IAAM;AAHI,SAAAA;AAAA,GAAA,kBAAA,CAAA,CAAA;AASL,IAAK,qCAAAC,sBAAL;AACLA,oBAAA,QAAA,IAAS;AACTA,oBAAA,UAAA,IAAW;AAFD,SAAAA;AAAA,GAAA,oBAAA,CAAA,CAAA;AAQL,IAAK,iCAAAC,kBAAL;AACLA,gBAAA,OAAA,IAAQ;AACRA,gBAAA,WAAA,IAAY;AACZA,gBAAA,SAAA,IAAU;AACVA,gBAAA,WAAA,IAAY;AACZA,gBAAA,QAAA,IAAS;AALC,SAAAA;AAAA,GAAA,gBAAA,CAAA,CAAA;AAWL,IAAK,oCAAAC,qBAAL;AACLA,mBAAA,MAAA,IAAO;AACPA,mBAAA,OAAA,IAAQ;AACRA,mBAAA,QAAA,IAAS;AACTA,mBAAA,SAAA,IAAU;AAJA,SAAAA;AAAA,GAAA,mBAAA,CAAA,CAAA;AAUL,IAAK,yCAAAC,0BAAL;AACLA,wBAAA,OAAA,IAAQ;AACRA,wBAAA,MAAA,IAAO;AACPA,wBAAA,MAAA,IAAO;AAHG,SAAAA;AAAA,GAAA,wBAAA,CAAA,CAAA;AASL,IAAK,wCAAAC,yBAAL;AACLA,uBAAA,SAAA,IAAU;AACVA,uBAAA,MAAA,IAAO;AACPA,uBAAA,QAAA,IAAS;AAHC,SAAAA;AAAA,GAAA,uBAAA,CAAA,CAAA;AASL,IAAK,mCAAAC,oBAAL;AACLA,kBAAA,gBAAA,IAAiB;AACjBA,kBAAA,kBAAA,IAAmB;AAFT,SAAAA;AAAA,GAAA,kBAAA,CAAA,CAAA;;;;;;;;;;;AC5BL,IAAM,sBAAN,cAAkC,WAAW;AAAA,EAUlD,WAA0B;AAAA,EAM1B,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,cAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,aAA6B,eAAe;AAAA;AAAA;AAAA;AAAA,EAK5C,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,gBAAwB;AAAA;AAAA;AAAA;AAAA,EAKxB,gBAAwB;AAAA;AAAA;AAAA;AAAA,EAKxB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,WAAmB;AAAA;AAAA;AAAA;AAAA,EAKnB,cAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,SAA2B,iBAAiB;AAAA;AAAA;AAAA;AAAA,EAK5C,sBAA+B;AAAA,EAE/B,YAAY,UAAsC,IAAI;AACpD,UAAM,OAAO;AACb,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAC7B,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,kBAAkB;AAC5B,WAAK,gBAAgB,QAAQ;AAC/B,QAAI,QAAQ,kBAAkB;AAC5B,WAAK,gBAAgB,QAAQ;AAC/B,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAC7B,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,wBAAwB;AAClC,WAAK,sBAAsB,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAiB;AACf,WAAO,KAAK,eAAe,eAAe;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAiB;AACf,WAAO,KAAK,eAAe,eAAe;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,eAAe,eAAe;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK,WAAW,KAAK,UAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwB;AACtB,QAAI,KAAK,SAAS;AAChB,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK;AAAA,EACd;AACF;AApHEC,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GATjB,oBAUX,WAAA,YAAA,CAAA;AAMAA,kBAAA;AAAA,EADC,WAAW,mBAAmB;AAAA,GAfpB,oBAgBX,WAAA,cAAA,CAAA;AAhBW,sBAANA,kBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,QAAQ,EAAA;AAAA,IAClD,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAA;AAAA,IAC9B,KAAK;AAAA,EAAA,CACN;AAAA,GACY,mBAAA;AC5CN,MAAM,sCAAsC,eAAoC;AAAA,EACrF,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,eAAe,YAAoD;AACvE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,WAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBACJ,YACqC;AACrC,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OAAO,EAAE,WAAA;AAAA,MACT,OAAO;AAAA,IAAA,CACR;AACD,WAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,oBACJ,eACqC;AACrC,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OAAO,EAAE,cAAA;AAAA,MACT,OAAO;AAAA,IAAA,CACR;AACD,WAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAW,YAA4D;AAC3E,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,WAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiD;AACrD,WAAO,MAAM,KAAK,WAAW,eAAe,GAAG;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiD;AACrD,WAAO,MAAM,KAAK,WAAW,eAAe,GAAG;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqD;AACzD,WAAO,MAAM,KAAK,WAAW,eAAe,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoD;AACxD,UAAM,MAAM,MAAM,KAAK,eAAA;AACvB,UAAM,UAAU,MAAM,KAAK,mBAAA;AAC3B,WAAO,CAAC,GAAG,KAAK,GAAG,OAAO,EAAE;AAAA,MAAK,CAAC,GAAG,MACnC,EAAE,YAAY,cAAc,EAAE,WAAW;AAAA,IAAA;AAAA,EAE7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,QAA0D;AAC3E,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,OAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA6C;AACjD,WAAO,MAAM,KAAK,aAAa,iBAAiB,MAAM;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBACJ,YACgC;AAChC,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO;AAAA,QACL;AAAA,QACA,QAAQ,iBAAiB;AAAA,MAAA;AAAA,MAE3B,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AACF;;;;;;;;;;;ACjFO,IAAM,iBAAN,cAA6B,WAAW;AAAA,EAW7C,WAA0B;AAAA,EAM1B,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,YAAoB;AAAA;AAAA;AAAA;AAAA,EAKpB,WAAmB;AAAA;AAAA;AAAA;AAAA,EAKnB,SAAiB;AAAA;AAAA;AAAA;AAAA,EAKjB,SAAiB;AAAA;AAAA;AAAA;AAAA,EAKjB,qCAA2B,KAAA;AAAA;AAAA;AAAA;AAAA,EAK3B,SAA8B,oBAAoB;AAAA;AAAA;AAAA;AAAA,EAKlD,SAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,eAAuB;AAAA;AAAA;AAAA;AAAA,EAKvB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,qBAA8B;AAAA;AAAA;AAAA;AAAA,EAK9B,YAAoB;AAAA;AAAA;AAAA;AAAA,EAKpB,WAAmB;AAAA;AAAA;AAAA;AAAA,EAKnB,YAAoB;AAAA;AAAA;AAAA;AAAA,EAKpB,YAAoB;AAAA;AAAA;AAAA;AAAA,EAKpB,YAAoB;AAAA,EAEpB,YAAY,UAAiC,IAAI;AAC/C,UAAM,OAAO;AACb,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,mBAAmB;AAC7B,WAAK,iBAAiB,QAAQ;AAChC,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,eAAe,QAAQ;AAC9B,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,uBAAuB;AACjC,WAAK,qBAAqB,QAAQ;AACpC,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,YAAuD;AACrD,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,MAAM;AAAA,IAC/B,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAyD;AACjE,SAAK,SAAS,KAAK,UAAU,MAAM;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAa,OAAwC;AAC5D,UAAM,SAAS,KAAK,UAAA;AACpB,WAAO,GAAG,IAAI;AACd,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK,cAAc,eAAe,KAAK,cAAc;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAwB;AACtB,UAAM,mBAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,WAAO,iBAAiB,SAAS,KAAK,SAAS;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,SAAS,oBAAoB;AAClC,SAAK,6BAAa,KAAA;AAClB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,OAAqB;AAC9B,SAAK,SAAS,oBAAoB;AAClC,SAAK,eAAe;AACpB,SAAK;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACpB,SAAK,SAAS,oBAAoB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,aAAqB,GAAY;AAC3C,WACE,KAAK,WAAW,oBAAoB,UAAU,KAAK,aAAa;AAAA,EAEpE;AAAA;AAAA;AAAA;AAAA,EAKA,eAOE;AACA,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK,UAAA;AAAA,MACb,UAAU,KAAK,YAAY;AAAA,MAC3B,QAAQ,KAAK,UAAU;AAAA,MACvB,WAAW,KAAK,eAAe,QAAA,IAAY;AAAA;AAAA,MAC3C,oBAAoB,KAAK;AAAA,IAAA;AAAA,EAE7B;AACF;AAjNEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GAVjB,eAWX,WAAA,YAAA,CAAA;AAMAA,kBAAA;AAAA,EADC,WAAW,mBAAmB;AAAA,GAhBpB,eAiBX,WAAA,cAAA,CAAA;AAjBW,iBAANA,kBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,QAAQ,EAAA;AAAA,IACxC,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,OAAO,EAAA;AAAA,IACvC,KAAK;AAAA,EAAA,CACN;AAAA,GACY,cAAA;AC9CN,MAAM,iCAAiC,eAA+B;AAAA,EAC3E,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYrB,aAAa,SAAe,UAA0B;AAC5D,UAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,MAC7C;AAAA,MACA,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA,CACT,EAAE,cAAc,OAAO;AACxB,UAAM,SAAS,CAAC,SACd,OAAO,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,SAAS,KAAK,EAAE;AACtE,QAAI,OAAO,OAAO,MAAM;AAExB,QAAI,SAAS,GAAI,QAAO;AACxB,UAAM,QAAQ,KAAK;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,OAAO,OAAO,IAAI;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,IAAA;AAEjB,WAAO,QAAQ,QAAQ,QAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBU,iBAAiB,SAAe,UAAwB;AAChE,QAAI;AACJ,QAAI;AACF,YAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,QAC7C;AAAA,QACA,MAAM;AAAA,QACN,OAAO;AAAA,QACP,KAAK;AAAA,MAAA,CACN,EAAE,cAAc,OAAO;AACxB,YAAM,SAAS,CAAC,SACd,OAAO,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,SAAS,KAAK,EAAE;AACtE,cAAQ;AAAA,QACN,MAAM,OAAO,MAAM;AAAA,QACnB,OAAO,OAAO,OAAO;AAAA,QACrB,KAAK,OAAO,KAAK;AAAA,MAAA;AAAA,IAErB,QAAQ;AAEN,aAAO,IAAI;AAAA,QACT,KAAK;AAAA,UACH,QAAQ,eAAA;AAAA,UACR,QAAQ,YAAA;AAAA,UACR,QAAQ,WAAA;AAAA,QAAW;AAAA,MACrB;AAAA,IAEJ;AAKA,UAAM,QAAQ,KAAK,IAAI,MAAM,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;AAC7D,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK,KAAK,GAAG,QAAQ;AAC1D,QAAI,MAAM,QAAQ;AAClB,UAAM,UAAU,KAAK,aAAa,IAAI,KAAK,GAAG,GAAG,QAAQ;AACzD,QAAI,YAAY,OAAQ,OAAM,QAAQ;AACtC,WAAO,IAAI,KAAK,GAAG;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcU,uBAAuB,YAAkB,UAAwB;AACzE,WAAO,KAAK;AAAA,MACV,IAAI,KAAK,WAAW,QAAA,IAAY,KAAK,IAAS;AAAA,MAC9C;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeU,cACR,OACA,WACgE;AAChE,QAAI,YAAY,GAAG;AACjB,YAAM,UAAW,QAAQ,aAAa,YAAa;AACnD,YAAM,eAAe,KAAK,MAAM,MAAM;AACtC,UAAI,QAAgC;AACpC,UAAI,SAAS,EAAG,SAAQ;AAAA,eACf,SAAS,GAAI,SAAQ;AAC9B,aAAO,EAAE,OAAO,aAAA;AAAA,IAClB;AACA,QAAI,QAAQ,GAAG;AAEb,aAAO,EAAE,OAAO,MAAM,cAAc,KAAA;AAAA,IACtC;AACA,WAAO,EAAE,OAAO,QAAQ,cAAc,EAAA;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAe,YAA+C;AAClE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,WAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,WAA8C;AAClE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,UAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAe,UAA6C;AAChE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,SAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,QAA2C;AAC5D,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,OAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,QAAwD;AACzE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,OAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAyC;AAC7C,WAAO,MAAM,KAAK,aAAa,oBAAoB,OAAO;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAsC;AAC1C,WAAO,MAAM,KAAK,aAAa,oBAAoB,IAAI;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAwC;AAC5C,WAAO,MAAM,KAAK,aAAa,oBAAoB,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,aAAqB,GAA8B;AACpE,UAAM,SAAS,MAAM,KAAK,WAAA;AAC1B,WAAO,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,UAAU,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,sBAAsB,YAA+C;AACzE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO;AAAA,QACL;AAAA,QACA,QAAQ,oBAAoB;AAAA,MAAA;AAAA,MAE9B,SAAS;AAAA;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBACJ,WACA,SAC2B;AAC3B,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO;AAAA,QACL,qBAAqB,UAAU,YAAA;AAAA,QAC/B,qBAAqB,QAAQ,YAAA;AAAA,MAAY;AAAA,MAE3C,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,YAAgD;AACpE,UAAM,mBAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,MAAM,aACR,MAAM,KAAK,eAAe,UAAU,IACpC,MAAM,KAAK,KAAK,EAAE,SAAS,uBAAuB;AAEtD,WAAO,IAAI,OAAO,CAAC,MAAM,iBAAiB,SAAS,EAAE,SAAS,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,cAAc,YAAgD;AAClE,UAAM,QAAiC,EAAE,WAAW,YAAA;AACpD,QAAI,YAAY;AACd,YAAM,aAAa;AAAA,IACrB;AACA,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB;AAAA,MACA,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,YAAkD;AACvE,UAAM,SAAS,MAAM,KAAK,eAAe,UAAU;AACnD,UAAM,6BAAa,IAAA;AAEnB,eAAW,SAAS,QAAQ;AAC1B,YAAM,UAAU,OAAO,IAAI,MAAM,SAAS,KAAK;AAC/C,aAAO,IAAI,MAAM,WAAW,UAAU,CAAC;AAAA,IACzC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,YAOpB;AACD,UAAM,SAAS,MAAM,KAAK,eAAe,UAAU;AAEnD,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,oBAAoB,OAAO,EACnE;AAAA,MACH,MAAM,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,oBAAoB,IAAI,EAAE;AAAA,MAClE,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,oBAAoB,MAAM,EACjE;AAAA,MACH,aAAa,OAAO,OAAO,CAAC,MAAM,EAAE,aAAA,CAAc,EAAE;AAAA,MACpD,WAAW,OAAO,OAAO,CAAC,MAAM,EAAE,WAAA,CAAY,EAAE;AAAA,IAAA;AAAA,EAEpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,0BACJ,YACA,KACA,WAAmB,OACc;AACjC,UAAM,cAAc,OAAO,oBAAI,KAAA;AAC/B,UAAM,aAAa,KAAK,iBAAiB,aAAa,QAAQ;AAC9D,UAAM,iBAAiB,KAAK,uBAAuB,YAAY,QAAQ;AAGvE,UAAM,oBAAoB,MAAM,KAAK,KAAK;AAAA,MACxC,OAAO;AAAA,QACL;AAAA,QACA,WAAW;AAAA,QACX,qBAAqB,eAAe,YAAA;AAAA,QACpC,qBAAqB,YAAY,YAAA;AAAA,MAAY;AAAA,IAC/C,CACD;AAED,UAAM,sBAAsB,kBAAkB;AAAA,MAC5C,CAAC,MAAM,IAAI,KAAK,EAAE,cAAc,KAAK;AAAA,IAAA;AAEvC,UAAM,0BAA0B,kBAAkB;AAAA,MAChD,CAAC,MAAM,IAAI,KAAK,EAAE,cAAc,IAAI;AAAA,IAAA;AAItC,UAAM,eAAe,IAAI,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AACvE,UAAM,mBAAmB,IAAI;AAAA,MAC3B,wBAAwB,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,IAAA;AAG/C,UAAM,iBAAiB,oBAAoB;AAC3C,UAAM,qBAAqB,wBAAwB;AAEnD,UAAM,EAAE,OAAO,aAAA,IAAiB,KAAK;AAAA,MACnC;AAAA,MACA;AAAA,IAAA;AAGF,WAAO;AAAA,MACL;AAAA,MACA,YAAY,aAAa;AAAA,MACzB;AAAA,MACA,gBAAgB,iBAAiB;AAAA,MACjC;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,sBACJ,aACA,KACA,WAAmB,OAC2B;AAC9C,UAAM,8BAAc,IAAA;AAGpB,UAAM,cAAc,OAAO,oBAAI,KAAA;AAC/B,UAAM,aAAa,KAAK,iBAAiB,aAAa,QAAQ;AAC9D,UAAM,iBAAiB,KAAK,uBAAuB,YAAY,QAAQ;AAGvE,UAAM,YAAY,MAAM,KAAK,KAAK;AAAA,MAChC,OAAO;AAAA,QACL,WAAW;AAAA,QACX,qBAAqB,eAAe,YAAA;AAAA,QACpC,qBAAqB,YAAY,YAAA;AAAA,MAAY;AAAA,IAC/C,CACD;AAGD,UAAM,sCAAsB,IAAA;AAC5B,UAAM,0CAA0B,IAAA;AAChC,eAAW,KAAK,WAAW;AACzB,YAAM,UAAU,IAAI,KAAK,EAAE,cAAc,KAAK;AAC9C,YAAM,MAAM,UAAU,kBAAkB;AACxC,YAAM,OAAO,IAAI,IAAI,EAAE,UAAU;AACjC,UAAI,KAAM,MAAK,KAAK,CAAC;AAAA,eACZ,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;AAAA,IAChC;AAEA,eAAW,cAAc,aAAa;AACpC,YAAM,sBAAsB,gBAAgB,IAAI,UAAU,KAAK,CAAA;AAC/D,YAAM,0BAA0B,oBAAoB,IAAI,UAAU,KAAK,CAAA;AAEvE,YAAM,eAAe,IAAI,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AACvE,YAAM,mBAAmB,IAAI;AAAA,QAC3B,wBAAwB,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,MAAA;AAG/C,YAAM,iBAAiB,oBAAoB;AAC3C,YAAM,qBAAqB,wBAAwB;AAEnD,YAAM,EAAE,OAAO,aAAA,IAAiB,KAAK;AAAA,QACnC;AAAA,QACA;AAAA,MAAA;AAGF,cAAQ,IAAI,YAAY;AAAA,QACtB;AAAA,QACA,YAAY,aAAa;AAAA,QACzB;AAAA,QACA,gBAAgB,iBAAiB;AAAA,QACjC;AAAA,QACA;AAAA,MAAA,CACD;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;;;;;;;;;ACvcO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAWhD,WAA0B;AAAA;AAAA;AAAA;AAAA,EAK1B,OAAe;AAAA;AAAA;AAAA;AAAA,EAKf,cAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,WAA8B,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhD,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,gBAAwB;AAAA,EASxB,YAAoB;AAAA;AAAA;AAAA;AAAA,EAKpB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,WAAmB;AAAA;AAAA;AAAA;AAAA,EAKnB,eAAuB;AAAA;AAAA;AAAA;AAAA,EAKvB,mBAA2B;AAAA;AAAA;AAAA;AAAA,EAK3B,eAAuB;AAAA;AAAA;AAAA;AAAA,EAKvB,SAAkC,wBAAwB;AAAA;AAAA;AAAA;AAAA,EAK1D,aAA0B;AAAA,EAS1B,mBAA2B;AAAA,EAE3B,YAAY,UAAoC,IAAI;AAClD,UAAM,OAAO;AACb,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,SAAS,OAAW,MAAK,OAAO,QAAQ;AACpD,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAC7B,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,kBAAkB;AAC5B,WAAK,gBAAgB,QAAQ;AAC/B,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,eAAe,QAAQ;AAC9B,QAAI,QAAQ,qBAAqB;AAC/B,WAAK,mBAAmB,QAAQ;AAClC,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,eAAe,QAAQ;AAC9B,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,qBAAqB;AAC/B,WAAK,mBAAmB,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAiB;AACf,WAAO,KAAK,aAAa,kBAAkB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK,aAAa,kBAAkB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK,aAAa,kBAAkB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA+C;AAC7C,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,gBAAgB;AAAA,IACzC,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAyC;AAC3D,SAAK,mBAAmB,KAAK,UAAU,QAAQ;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,iCAAiB,KAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,mBAAmB,UAA+B,IAIrD;AACD,UAAM,SAAS,QAAQ,UAAU;AAQjC,UAAM,KAAK,KAAK,QAAQ,MAAM,KAAK,QAAQ;AAS3C,UAAM,iBAAiB,MAAM;AAAA,MAC3B,sCAAsC;AAAA,MACtC;AAAA,QACE;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,qBAAqB,KAAK,eAAe;AAAA,UACzC,kBAAkB,KAAK,YAAY;AAAA,QAAA;AAAA,MACrC;AAAA,IACF;AAGF,UAAM,KAAK,MAAM,KAAK,YAAA;AACtB,UAAM,WAAW,MAAM,GAAG;AAAA,MACxB,eAAe;AAAA,MACf,qBAAqB,eAAe,EAAE;AAAA,IAAA;AAGxC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,UAAU,SAAS,KAAA;AAAA,IAAK;AAAA,EAE5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAqC;AACzC,WAAO,MAAM,KAAK;AAAA,MAChB;AAAA;AAAA,oBAEc,KAAK,WAAW;AAAA,oBAChB,KAAK,QAAQ;AAAA,kBACf,KAAK,MAAM;AAAA,qBACR,KAAK,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,MAK9B,EAAE,aAAa,MAAA;AAAA,IAAM;AAAA,EAEzB;AACF;AArOEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GAVjB,kBAWX,WAAA,YAAA,CAAA;AAmCAA,kBAAA;AAAA,EADC,MAAM,EAAE,WAAW,KAAA,CAAM;AAAA,GA7Cf,kBA8CX,WAAA,aAAA,CAAA;AA4CAA,kBAAA;AAAA,EADC,MAAM,EAAE,WAAW,KAAA,CAAM;AAAA,GAzFf,kBA0FX,WAAA,oBAAA,CAAA;AA1FW,oBAANA,kBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,QAAQ,EAAA;AAAA,IAClD,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,QAAQ,WAAW,EAAA;AAAA,IACnD,KAAK;AAAA,EAAA,CACN;AAAA,GACY,iBAAA;ACrDN,MAAM,oCAAoC,eAAkC;AAAA,EACjF,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,iBACJ,YACmC;AACnC,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OAAO,EAAE,WAAA;AAAA,MACT,OAAO;AAAA,IAAA,CACR;AACD,WAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,oBACJ,eACmC;AACnC,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OAAO,EAAE,cAAA;AAAA,MACT,OAAO;AAAA,IAAA,CACR;AACD,WAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iBACJ,YACA,UACmC;AACnC,UAAM,UAAU,MAAM,KAAK,KAAK;AAAA,MAC9B,OAAO,WAAW,EAAE,YAAY,SAAA,IAAa,EAAE,WAAA;AAAA,MAC/C,OAAO;AAAA,IAAA,CACR;AACD,WAAO,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eACJ,UAC8B;AAC9B,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,SAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAkD;AACtD,WAAO,MAAM,KAAK,eAAe,kBAAkB,GAAG;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAmD;AACvD,WAAO,MAAM,KAAK,eAAe,kBAAkB,SAAS;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAgD;AACpD,WAAO,MAAM,KAAK,eAAe,kBAAkB,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aACJ,QAC8B;AAC9B,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,OAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA2C;AAC/C,WAAO,MAAM,KAAK,aAAa,wBAAwB,MAAM;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,WAAmB,IAAkC;AACzE,UAAM,6BAAa,KAAA;AACnB,WAAO,QAAQ,OAAO,QAAA,IAAY,WAAW,KAAK,KAAK,GAAI;AAG3D,UAAM,MAAM,MAAM,KAAK,WAAA;AACvB,WAAO,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,aAAa,MAAM;AAAA,EACjE;AACF;;;;;;;;;;;AC/DO,IAAM,kBAAN,cAA8B,WAAW;AAAA,EAW9C,WAA0B;AAAA,EAM1B,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,OAAe;AAAA;AAAA;AAAA;AAAA,EAKf,cAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,UAAkB;AAAA;AAAA;AAAA;AAAA,EAKlB,iBAAyB;AAAA;AAAA;AAAA;AAAA,EAKzB,eAAuB;AAAA;AAAA;AAAA;AAAA,EAKvB,kBAA0B;AAAA;AAAA;AAAA;AAAA,EAK1B,eAAuB;AAAA;AAAA;AAAA;AAAA,EAKvB,UAAkB;AAAA;AAAA;AAAA;AAAA,EAKlB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,SAAuB,aAAa;AAAA;AAAA;AAAA;AAAA,EAKpC,YAA6B,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAK7C,YAAyB;AAAA;AAAA;AAAA;AAAA,EAKzB,YAAyB;AAAA;AAAA;AAAA;AAAA,EAKzB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAKrB,WAAmB;AAAA;AAAA;AAAA;AAAA,EAKnB,YAAoB;AAAA,EAEpB,YAAY,UAAkC,IAAI;AAMhD,UAAM,OAA4B;AAClC,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,SAAS,OAAW,MAAK,OAAO,QAAQ;AACpD,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAC7B,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,YAAY,OAAW,MAAK,UAAU,QAAQ;AAC1D,QAAI,QAAQ,mBAAmB;AAC7B,WAAK,iBAAiB,QAAQ;AAChC,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,eAAe,QAAQ;AAC9B,QAAI,QAAQ,oBAAoB;AAC9B,WAAK,kBAAkB,QAAQ;AACjC,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,eAAe,QAAQ;AAC9B,QAAI,QAAQ,YAAY,OAAW,MAAK,UAAU,QAAQ;AAC1D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,eAAe,OAAW,MAAK,aAAa,QAAQ;AAChE,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAC5D,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAyC;AACvC,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,UAAU;AAAA,IACnC,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,YAA2C;AACvD,SAAK,aAAa,KAAK,UAAU,UAAU;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsC;AACpC,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,OAAO;AAAA,IAChC,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAwC;AACjD,SAAK,UAAU,KAAK,UAAU,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgD;AAC9C,QAAI,CAAC,KAAK,WAAY,QAAO;AAC7B,QAAI;AACF,aAAO,KAAK,MAAM,KAAK,UAAU;AAAA,IACnC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,MAAqC;AACjD,SAAK,aAAa,KAAK,UAAU,IAAI;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAoB;AAClB,SAAK,SAAS,aAAa;AAC3B,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAwB;AACpC,SAAK,SAAS,aAAa;AAC3B,SAAK,gCAAgB,KAAA;AACrB,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,iBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,OAAqB;AAC9B,SAAK,SAAS,aAAa;AAC3B,SAAK,gCAAgB,KAAA;AACrB,SAAK,YAAY;AACjB,SAAK,iBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAyB;AACvB,QAAI,KAAK,cAAc,gBAAgB,MAAM;AAC3C,WAAK,YAAY;AACjB;AAAA,IACF;AAEA,UAAM,0BAAU,KAAA;AAChB,UAAM,OAAO,IAAI,KAAK,GAAG;AAEzB,YAAQ,KAAK,WAAA;AAAA,MACX,KAAK,gBAAgB;AACnB,aAAK,QAAQ,KAAK,QAAA,IAAY,CAAC;AAC/B;AAAA,MACF,KAAK,gBAAgB;AACnB,aAAK,QAAQ,KAAK,QAAA,IAAY,CAAC;AAC/B;AAAA,MACF,KAAK,gBAAgB;AACnB,aAAK,SAAS,KAAK,SAAA,IAAa,CAAC;AACjC;AAAA,IAAA;AAGJ,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAiB;AACf,QAAI,KAAK,cAAc,gBAAgB,MAAM;AAC3C,aAAO,KAAK,WAAW,aAAa,aAAa,CAAC,KAAK;AAAA,IACzD;AACA,QAAI,CAAC,KAAK,UAAW,QAAO;AAC5B,WAAO,oBAAI,UAAU,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,MAAM,eAAe,WAAoC,IAItD;AACD,UAAM,aAAa,KAAK,cAAA;AAQxB,UAAM,KAAK,KAAK,QAAQ,MAAM,KAAK,QAAQ;AAS3C,UAAM,iBAAiB,MAAM;AAAA,MAC3B,kCAAkC;AAAA,MAClC;AAAA,QACE;AAAA,QACA,WAAW;AAAA,UACT,YAAY,KAAK,QAAQ;AAAA,UACzB,kBAAkB,KAAK,cAAc;AAAA,UACrC,eAAe,KAAK,WAAW;AAAA,UAC/B,gBAAgB,KAAK,kBAAkB;AAAA,UACvC,cAAc,KAAK,gBAAgB;AAAA,UACnC,UAAU,OAAO,KAAK,QAAQ;AAAA,UAC9B,YAAY,KAAK,UAAU,YAAY,MAAM,CAAC;AAAA,QAAA;AAAA,MAChD;AAAA,IACF;AAGF,UAAM,KAAK,MAAM,KAAK,YAAA;AACtB,UAAM,YACJ,MAAM,GAAG;AAAA,MACP,eAAe;AAAA,MACf,qBAAqB,eAAe,EAAE;AAAA,IAAA,GAExC,KAAA;AAEF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,UAAU;AAAA,IAAA;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,oBAAsC;AAC1C,UAAM,aAAa,KAAK,cAAA;AAGxB,UAAM,KAAK,KAAK,QAAQ,MAAM,KAAK,QAAQ;AAE3C,UAAM,iBAAiB,MAAM;AAAA,MAC3B,qCAAqC;AAAA,MACrC;AAAA,QACE;AAAA,QACA,WAAW;AAAA,UACT,eAAe,KAAK,WAAW;AAAA,UAC/B,YAAY,KAAK,UAAU,UAAU;AAAA,QAAA;AAAA,MACvC;AAAA,IACF;AAGF,UAAM,KAAK,MAAM,KAAK,YAAA;AACtB,UAAM,YACJ,MAAM,GAAG;AAAA,MACP,eAAe;AAAA,MACf,qBAAqB,eAAe,EAAE;AAAA,IAAA,GAExC,KAAA;AAEF,WAAO,oBAAoB,KAAK,QAAQ;AAAA,EAC1C;AACF;AArXE,gBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GAVjB,gBAWX,WAAA,YAAA,CAAA;AAMA,gBAAA;AAAA,EADC,WAAW,mBAAmB;AAAA,GAhBpB,gBAiBX,WAAA,cAAA,CAAA;AAjBW,kBAAN,gBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,UAAU,KAAK,EAAA;AAAA,IACzD,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,OAAO,SAAS,EAAA;AAAA,IAChD,KAAK;AAAA,EAAA,CACN;AAAA,GACY,eAAA;AC9DN,MAAM,kCAAkC,eAAgC;AAAA,EAC7E,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,eAAe,YAAgD;AACnE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,WAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,QAAkD;AACnE,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,OAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAyC;AAC7C,WAAO,MAAM,KAAK,aAAa,aAAa,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAA4C;AAChD,WAAO,MAAM,KAAK,aAAa,aAAa,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAA4C;AAChD,WAAO,MAAM,KAAK,aAAa,aAAa,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAyC;AAC7C,WAAO,MAAM,KAAK,aAAa,aAAa,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBACJ,WAC4B;AAC5B,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,UAAA;AAAA,MACT,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAA4C;AAChD,UAAM,MAAM,MAAM,KAAK,KAAK,EAAE,SAAS,YAAY;AACnD,WAAO,IAAI,OAAO,CAAC,MAAM,EAAE,cAAc,gBAAgB,IAAI;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAsC;AAC1C,UAAM,MAAM,MAAM,KAAK,KAAK;AAAA,MAC1B,OAAO,EAAE,QAAQ,aAAa,UAAA;AAAA,IAAU,CACzC;AACD,WAAO,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,wBACJ,YACA,QAC4B;AAC5B,WAAO,MAAM,KAAK,KAAK;AAAA,MACrB,OAAO,EAAE,YAAY,OAAA;AAAA,MACrB,SAAS;AAAA,IAAA,CACV;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,WAAmB,IAAgC;AACvE,UAAM,6BAAa,KAAA;AACnB,WAAO,QAAQ,OAAO,QAAA,IAAY,WAAW,KAAK,KAAK,GAAI;AAE3D,UAAM,MAAM,MAAM,KAAK,KAAK,EAAE,SAAS,kBAAkB;AACzD,WAAO,IAAI,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,MAAM;AAAA,EAC/D;AACF;"}
|
package/dist/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"timestamp":
|
|
3
|
+
"timestamp": 1782335249803,
|
|
4
4
|
"packageName": "@happyvertical/smrt-analytics",
|
|
5
|
-
"packageVersion": "0.
|
|
5
|
+
"packageVersion": "0.35.1",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-analytics:AnalyticsDataStreamCollection": {
|
|
8
8
|
"name": "analyticsdatastreamcollection",
|
|
@@ -3964,7 +3964,7 @@
|
|
|
3964
3964
|
"parameters": [
|
|
3965
3965
|
{
|
|
3966
3966
|
"name": "_options",
|
|
3967
|
-
"type": "
|
|
3967
|
+
"type": "Record<string>",
|
|
3968
3968
|
"optional": true
|
|
3969
3969
|
}
|
|
3970
3970
|
],
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { DataStreamStatus, DataStreamType } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for constructing an {@link AnalyticsDataStream}.
|
|
5
|
+
*/
|
|
6
|
+
export interface AnalyticsDataStreamOptions extends SmrtObjectOptions {
|
|
7
|
+
tenantId?: string | null;
|
|
8
|
+
propertyId?: string;
|
|
9
|
+
displayName?: string;
|
|
10
|
+
streamType?: DataStreamType;
|
|
11
|
+
externalId?: string;
|
|
12
|
+
measurementId?: string;
|
|
13
|
+
firebaseAppId?: string;
|
|
14
|
+
defaultUri?: string;
|
|
15
|
+
bundleId?: string;
|
|
16
|
+
packageName?: string;
|
|
17
|
+
status?: DataStreamStatus;
|
|
18
|
+
enhancedMeasurement?: boolean;
|
|
19
|
+
}
|
|
3
20
|
/**
|
|
4
21
|
* AnalyticsDataStream represents a data stream (web, iOS, Android) for an analytics property.
|
|
5
22
|
*
|
|
@@ -68,7 +85,7 @@ export declare class AnalyticsDataStream extends SmrtObject {
|
|
|
68
85
|
* Enhanced measurement enabled
|
|
69
86
|
*/
|
|
70
87
|
enhancedMeasurement: boolean;
|
|
71
|
-
constructor(options?:
|
|
88
|
+
constructor(options?: AnalyticsDataStreamOptions);
|
|
72
89
|
/**
|
|
73
90
|
* Check if this is a web stream
|
|
74
91
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsDataStream.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsDataStream.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"AnalyticsDataStream.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsDataStream.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,0BAA2B,SAAQ,iBAAiB;IACnE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAOa,mBAAoB,SAAQ,UAAU;IACjD;;;;;;;OAOG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,UAAU,EAAE,cAAc,CAAsB;IAEhD;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,MAAM,EAAE,gBAAgB,CAA2B;IAEnD;;OAEG;IACH,mBAAmB,EAAE,OAAO,CAAQ;gBAExB,OAAO,GAAE,0BAA+B;IAqBpD;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,aAAa,IAAI,MAAM;CAMxB;AAED,eAAe,mBAAmB,CAAC"}
|
|
@@ -1,5 +1,27 @@
|
|
|
1
|
-
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { TrackingEventStatus } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for constructing an {@link AnalyticsEvent}.
|
|
5
|
+
*/
|
|
6
|
+
export interface AnalyticsEventOptions extends SmrtObjectOptions {
|
|
7
|
+
tenantId?: string | null;
|
|
8
|
+
propertyId?: string;
|
|
9
|
+
eventName?: string;
|
|
10
|
+
clientId?: string;
|
|
11
|
+
userId?: string;
|
|
12
|
+
params?: string;
|
|
13
|
+
eventTimestamp?: Date;
|
|
14
|
+
status?: TrackingEventStatus;
|
|
15
|
+
sentAt?: Date | null;
|
|
16
|
+
errorMessage?: string;
|
|
17
|
+
retryCount?: number;
|
|
18
|
+
nonPersonalizedAds?: boolean;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
pagePath?: string;
|
|
21
|
+
pageTitle?: string;
|
|
22
|
+
userAgent?: string;
|
|
23
|
+
ipAddress?: string;
|
|
24
|
+
}
|
|
3
25
|
/**
|
|
4
26
|
* AnalyticsEvent represents a tracked analytics event (server-side tracking log).
|
|
5
27
|
*
|
|
@@ -89,7 +111,7 @@ export declare class AnalyticsEvent extends SmrtObject {
|
|
|
89
111
|
* IP address (anonymized)
|
|
90
112
|
*/
|
|
91
113
|
ipAddress: string;
|
|
92
|
-
constructor(options?:
|
|
114
|
+
constructor(options?: AnalyticsEventOptions);
|
|
93
115
|
/**
|
|
94
116
|
* Get parsed event parameters
|
|
95
117
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsEvent.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsEvent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"AnalyticsEvent.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsEvent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { AnalyticsPropertyStatus, AnalyticsProvider } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for constructing an {@link AnalyticsProperty}.
|
|
5
|
+
*/
|
|
6
|
+
export interface AnalyticsPropertyOptions extends SmrtObjectOptions {
|
|
7
|
+
tenantId?: string | null;
|
|
8
|
+
name?: string;
|
|
9
|
+
displayName?: string;
|
|
10
|
+
provider?: AnalyticsProvider;
|
|
11
|
+
externalId?: string;
|
|
12
|
+
measurementId?: string;
|
|
13
|
+
apiSecret?: string;
|
|
14
|
+
siteDomain?: string;
|
|
15
|
+
timeZone?: string;
|
|
16
|
+
currencyCode?: string;
|
|
17
|
+
industryCategory?: string;
|
|
18
|
+
serviceLevel?: string;
|
|
19
|
+
status?: AnalyticsPropertyStatus;
|
|
20
|
+
lastSyncAt?: Date | null;
|
|
21
|
+
providerMetadata?: string;
|
|
22
|
+
}
|
|
3
23
|
/**
|
|
4
24
|
* AnalyticsProperty represents an analytics property (GA4 property,
|
|
5
25
|
* Plausible site, or Matomo site).
|
|
@@ -89,7 +109,7 @@ export declare class AnalyticsProperty extends SmrtObject {
|
|
|
89
109
|
* from generated API/MCP responses and rejected as a `where` filter key.
|
|
90
110
|
*/
|
|
91
111
|
providerMetadata: string;
|
|
92
|
-
constructor(options?:
|
|
112
|
+
constructor(options?: AnalyticsPropertyOptions);
|
|
93
113
|
/**
|
|
94
114
|
* Check if this is a GA4 property
|
|
95
115
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsProperty.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsProperty.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"AnalyticsProperty.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsProperty.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAOlC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAOa,iBAAkB,SAAQ,UAAU;IAC/C;;;;;;;;OAQG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,QAAQ,EAAE,iBAAiB,CAAyB;IAEpD;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAM;IAE3B;;;;;OAKG;IAEH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAyB;IAEzC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAS;IAE7B;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAM;IAE9B;;OAEG;IACH,YAAY,EAAE,MAAM,CAAc;IAElC;;OAEG;IACH,MAAM,EAAE,uBAAuB,CAAkC;IAEjE;;OAEG;IACH,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE/B;;;;;OAKG;IAEH,gBAAgB,EAAE,MAAM,CAAQ;gBAEpB,OAAO,GAAE,wBAA6B;IAyBlD;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,mBAAmB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAQ9C;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QACnE,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IA2CF;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;CAe3C;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { ReportFrequency, ReportStatus } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for constructing an {@link AnalyticsReport}.
|
|
5
|
+
*
|
|
6
|
+
* `metrics` is omitted from the {@link SmrtObjectOptions} base before being
|
|
7
|
+
* re-declared: the framework option carries an observability `MetricsConfig`,
|
|
8
|
+
* whereas this report field is a JSON-encoded list of analytics metrics.
|
|
9
|
+
*/
|
|
10
|
+
export interface AnalyticsReportOptions extends Omit<SmrtObjectOptions, 'metrics'> {
|
|
11
|
+
tenantId?: string | null;
|
|
12
|
+
propertyId?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
dimensions?: string;
|
|
16
|
+
metrics?: string;
|
|
17
|
+
dateRangeStart?: string;
|
|
18
|
+
dateRangeEnd?: string;
|
|
19
|
+
dimensionFilter?: string;
|
|
20
|
+
metricFilter?: string;
|
|
21
|
+
orderBy?: string;
|
|
22
|
+
maxResults?: number;
|
|
23
|
+
status?: ReportStatus;
|
|
24
|
+
frequency?: ReportFrequency;
|
|
25
|
+
lastRunAt?: Date | null;
|
|
26
|
+
nextRunAt?: Date | null;
|
|
27
|
+
resultData?: string;
|
|
28
|
+
rowCount?: number;
|
|
29
|
+
lastError?: string;
|
|
30
|
+
}
|
|
3
31
|
/**
|
|
4
32
|
* AnalyticsReport represents a saved report configuration with optional scheduling.
|
|
5
33
|
*
|
|
@@ -97,7 +125,7 @@ export declare class AnalyticsReport extends SmrtObject {
|
|
|
97
125
|
* Error message from last failed run
|
|
98
126
|
*/
|
|
99
127
|
lastError: string;
|
|
100
|
-
constructor(options?:
|
|
128
|
+
constructor(options?: AnalyticsReportOptions);
|
|
101
129
|
/**
|
|
102
130
|
* Get parsed dimensions
|
|
103
131
|
*/
|
|
@@ -179,7 +207,7 @@ export declare class AnalyticsReport extends SmrtObject {
|
|
|
179
207
|
* trends, and recommendations) — `insights` mirrors `analysis` so the
|
|
180
208
|
* return shape is preserved without a redundant AI round-trip.
|
|
181
209
|
*/
|
|
182
|
-
analyzeResults(_options?:
|
|
210
|
+
analyzeResults(_options?: Record<string, unknown>): Promise<{
|
|
183
211
|
action: string;
|
|
184
212
|
analysis: string;
|
|
185
213
|
insights: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsReport.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsReport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"AnalyticsReport.d.ts","sourceRoot":"","sources":["../../src/models/AnalyticsReport.ts"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-24T21:07:30.192Z",
|
|
4
4
|
"packageName": "@happyvertical/smrt-analytics",
|
|
5
|
-
"packageVersion": "0.
|
|
5
|
+
"packageVersion": "0.35.1",
|
|
6
6
|
"sourceManifestPath": "dist/manifest.json",
|
|
7
7
|
"agentDocPath": "AGENTS.md",
|
|
8
8
|
"sourceHashes": {
|
|
9
|
-
"manifest": "
|
|
10
|
-
"packageJson": "
|
|
9
|
+
"manifest": "0bb280d548749ebd375171b3fedb192ff0939558f1ead3ab71f7faacfce7221c",
|
|
10
|
+
"packageJson": "2ac3ed5a2b56a03715ecc3e6e25c8261662e44d794a6ade8bb0c84dcb16852e0",
|
|
11
11
|
"agents": "c529837279bd11914a91124b475e177a17fab2a572cc5341589a4d4f122f6b8b"
|
|
12
12
|
},
|
|
13
13
|
"exports": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-analytics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "Analytics integration models for SMRT framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@happyvertical/smrt-core": "0.
|
|
36
|
-
"@happyvertical/smrt-prompts": "0.
|
|
37
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
38
|
-
"@happyvertical/smrt-
|
|
39
|
-
"@happyvertical/smrt-
|
|
35
|
+
"@happyvertical/smrt-core": "0.35.1",
|
|
36
|
+
"@happyvertical/smrt-prompts": "0.35.1",
|
|
37
|
+
"@happyvertical/smrt-tenancy": "0.35.1",
|
|
38
|
+
"@happyvertical/smrt-types": "0.35.1",
|
|
39
|
+
"@happyvertical/smrt-ui": "0.35.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"svelte": "^5.18.0"
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"typescript": "^5.9.3",
|
|
57
57
|
"vite": "^7.3.1",
|
|
58
58
|
"vitest": "^4.0.17",
|
|
59
|
-
"@happyvertical/smrt-vitest": "0.
|
|
59
|
+
"@happyvertical/smrt-vitest": "0.35.1"
|
|
60
60
|
},
|
|
61
61
|
"keywords": [
|
|
62
62
|
"ai",
|