@happyvertical/analytics 0.80.0 → 0.80.2
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/chunks/ga4-B69in3EQ.js +668 -0
- package/dist/chunks/ga4-B69in3EQ.js.map +1 -0
- package/dist/chunks/matomo-CUUG_Drb.js +853 -0
- package/dist/chunks/matomo-CUUG_Drb.js.map +1 -0
- package/dist/chunks/plausible-dXl_rz_8.js +396 -0
- package/dist/chunks/plausible-dXl_rz_8.js.map +1 -0
- package/dist/chunks/types-zsbKROGX.js +93 -0
- package/dist/chunks/types-zsbKROGX.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +104 -127
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunks/ga4-6gyDPZRn.js +0 -863
- package/dist/chunks/ga4-6gyDPZRn.js.map +0 -1
- package/dist/chunks/matomo-Ds_oRmZ6.js +0 -1043
- package/dist/chunks/matomo-Ds_oRmZ6.js.map +0 -1
- package/dist/chunks/plausible-BxpNa6qF.js +0 -479
- package/dist/chunks/plausible-BxpNa6qF.js.map +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//#region src/shared/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Base error class for analytics operations
|
|
4
|
+
*/
|
|
5
|
+
var AnalyticsError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
provider;
|
|
8
|
+
propertyId;
|
|
9
|
+
constructor(message, code, provider, propertyId) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.provider = provider;
|
|
13
|
+
this.propertyId = propertyId;
|
|
14
|
+
this.name = "AnalyticsError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Authentication failed
|
|
19
|
+
*/
|
|
20
|
+
var AuthenticationError = class extends AnalyticsError {
|
|
21
|
+
constructor(provider, message = "Authentication failed", code = "AUTH_ERROR") {
|
|
22
|
+
super(message, code, provider);
|
|
23
|
+
this.name = "AuthenticationError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Rate limit exceeded
|
|
28
|
+
*/
|
|
29
|
+
var RateLimitError = class extends AnalyticsError {
|
|
30
|
+
retryAfter;
|
|
31
|
+
constructor(provider, retryAfter) {
|
|
32
|
+
super(`Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ""}`, "RATE_LIMIT", provider);
|
|
33
|
+
this.name = "RateLimitError";
|
|
34
|
+
this.retryAfter = retryAfter;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Property not found
|
|
39
|
+
*/
|
|
40
|
+
var PropertyNotFoundError = class extends AnalyticsError {
|
|
41
|
+
constructor(propertyId, provider) {
|
|
42
|
+
super(`Property not found: ${propertyId}`, "PROPERTY_NOT_FOUND", provider, propertyId);
|
|
43
|
+
this.name = "PropertyNotFoundError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Invalid dimension specified
|
|
48
|
+
*/
|
|
49
|
+
var InvalidDimensionError = class extends AnalyticsError {
|
|
50
|
+
dimension;
|
|
51
|
+
constructor(dimension, provider) {
|
|
52
|
+
super(`Invalid dimension: ${dimension}`, "INVALID_DIMENSION", provider);
|
|
53
|
+
this.name = "InvalidDimensionError";
|
|
54
|
+
this.dimension = dimension;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Invalid metric specified
|
|
59
|
+
*/
|
|
60
|
+
var InvalidMetricError = class extends AnalyticsError {
|
|
61
|
+
metric;
|
|
62
|
+
constructor(metric, provider) {
|
|
63
|
+
super(`Invalid metric: ${metric}`, "INVALID_METRIC", provider);
|
|
64
|
+
this.name = "InvalidMetricError";
|
|
65
|
+
this.metric = metric;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Quota exceeded
|
|
70
|
+
*/
|
|
71
|
+
var QuotaExceededError = class extends AnalyticsError {
|
|
72
|
+
quotaType;
|
|
73
|
+
constructor(provider, quotaType) {
|
|
74
|
+
super(`Quota exceeded${quotaType ? `: ${quotaType}` : ""}`, "QUOTA_EXCEEDED", provider);
|
|
75
|
+
this.name = "QuotaExceededError";
|
|
76
|
+
this.quotaType = quotaType;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Feature not supported by this provider
|
|
81
|
+
*/
|
|
82
|
+
var NotSupportedError = class extends AnalyticsError {
|
|
83
|
+
feature;
|
|
84
|
+
constructor(feature, provider) {
|
|
85
|
+
super(`Feature not supported: ${feature}`, "NOT_SUPPORTED", provider);
|
|
86
|
+
this.name = "NotSupportedError";
|
|
87
|
+
this.feature = feature;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
export { NotSupportedError as a, RateLimitError as c, InvalidMetricError as i, AuthenticationError as n, PropertyNotFoundError as o, InvalidDimensionError as r, QuotaExceededError as s, AnalyticsError as t };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=types-zsbKROGX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-zsbKROGX.js","names":[],"sources":["../../src/shared/types.ts"],"sourcesContent":["/**\n * Core types and interfaces for the Analytics library\n */\n\n// =============================================================================\n// Base Options\n// =============================================================================\n\n/**\n * Base configuration options for all analytics providers\n */\nexport interface BaseAnalyticsOptions {\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Maximum retry attempts for failed requests */\n maxRetries?: number;\n /** Cache TTL in milliseconds for metadata operations */\n cacheTTL?: number;\n}\n\n/**\n * Service account credentials for Google APIs\n */\nexport interface ServiceAccountCredentials {\n type: 'service_account';\n project_id: string;\n private_key_id: string;\n private_key: string;\n client_email: string;\n client_id: string;\n auth_uri: string;\n token_uri: string;\n auth_provider_x509_cert_url: string;\n client_x509_cert_url: string;\n}\n\n// =============================================================================\n// Provider Options (Discriminated Union)\n// =============================================================================\n\n/**\n * Google Analytics 4 provider options\n */\nexport interface GA4Options extends BaseAnalyticsOptions {\n type: 'ga4';\n /**\n * Service account credentials for Admin API and Data API\n * Can be path to JSON key file or parsed JSON object\n */\n serviceAccountKey?: string | ServiceAccountCredentials;\n /**\n * Measurement ID (G-XXXXXXX) for Measurement Protocol\n * Required for server-side event tracking\n */\n measurementId?: string;\n /**\n * API Secret for Measurement Protocol\n * Required for server-side event tracking\n */\n apiSecret?: string;\n /**\n * Default property ID for operations that don't specify one\n */\n defaultPropertyId?: string;\n}\n\n/**\n * Plausible Analytics provider options\n */\nexport interface PlausibleOptions extends BaseAnalyticsOptions {\n type: 'plausible';\n /**\n * Plausible API key\n */\n apiKey: string;\n /**\n * Base URL for self-hosted instances\n * @default \"https://plausible.io\"\n */\n baseUrl?: string;\n /**\n * Default site ID (domain) for operations\n */\n defaultSiteId?: string;\n}\n\n/**\n * Matomo Analytics provider options\n *\n * Matomo's Reporting API authenticates via per-user `token_auth` values; admin\n * provisioning operations require a token belonging to a super-user.\n */\nexport interface MatomoOptions extends BaseAnalyticsOptions {\n type: 'matomo';\n /**\n * Matomo base URL (e.g. `https://matomo.example.com`).\n *\n * Trailing slashes and explicit `/index.php` suffixes are normalized.\n */\n baseUrl: string;\n /**\n * `token_auth` for the calling user. Required for all reporting calls.\n *\n * For admin provisioning (createSite/createUser/etc.) this token must\n * belong to a Matomo super-user.\n */\n tokenAuth: string;\n /**\n * Default site ID (`idSite`) for operations that don't specify one.\n */\n defaultSiteId?: string;\n}\n\n/**\n * Union type for all provider options\n */\nexport type GetAnalyticsOptions = GA4Options | PlausibleOptions | MatomoOptions;\n\n// =============================================================================\n// Property Types\n// =============================================================================\n\n/**\n * Analytics property/site representation\n */\nexport interface Property {\n /** Unique identifier */\n id: string;\n /** Internal name (GA4: properties/123456789) */\n name: string;\n /** Human-readable display name */\n displayName: string;\n /** Creation timestamp (ISO 8601) */\n createTime: string;\n /** Last update timestamp (ISO 8601) */\n updateTime?: string;\n /** Property timezone */\n timeZone?: string;\n /** Currency code (e.g., 'USD', 'EUR') */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n /** Service level */\n serviceLevel?: 'STANDARD' | 'PREMIUM';\n}\n\n/**\n * Options for listing properties\n */\nexport interface ListPropertiesOptions {\n /**\n * Controls whether the provider should hydrate discovered properties with\n * full metadata.\n *\n * Providers that support hydration should default this to `true` to\n * preserve the existing `listProperties()` behavior. Set this to `false`\n * to use discovery-only metadata, which may omit fields like `createTime`,\n * `updateTime`, `timeZone`, and `currencyCode`.\n */\n hydrate?: boolean;\n}\n\n/**\n * Options for creating a new property\n */\nexport interface CreatePropertyOptions {\n /** Human-readable display name */\n displayName: string;\n /** Timezone (e.g., 'America/Los_Angeles') */\n timeZone?: string;\n /** Currency code (e.g., 'USD') */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n /** Parent account (GA4: accounts/{account_id}) */\n parent?: string;\n}\n\n/**\n * Options for updating a property\n */\nexport interface UpdatePropertyOptions {\n /** Human-readable display name */\n displayName?: string;\n /** Timezone */\n timeZone?: string;\n /** Currency code */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n}\n\n// =============================================================================\n// Data Stream Types\n// =============================================================================\n\n/**\n * Data stream types\n */\nexport type DataStreamType =\n | 'WEB_DATA_STREAM'\n | 'ANDROID_APP_DATA_STREAM'\n | 'IOS_APP_DATA_STREAM';\n\n/**\n * Data stream representation\n */\nexport interface DataStream {\n /** Unique identifier */\n id: string;\n /** Stream type */\n type: DataStreamType;\n /** Human-readable display name */\n displayName: string;\n /** Measurement ID for web streams (G-XXXXXXX) */\n measurementId?: string;\n /** Firebase App ID for app streams */\n firebaseAppId?: string;\n /** Default URI for web streams */\n defaultUri?: string;\n /** Creation timestamp */\n createTime: string;\n /** Last update timestamp */\n updateTime?: string;\n}\n\n/**\n * Options for creating a data stream\n */\nexport interface CreateDataStreamOptions {\n /** Stream type */\n type: DataStreamType;\n /** Human-readable display name */\n displayName: string;\n /** Default URI for web streams */\n defaultUri?: string;\n /** Bundle ID for iOS apps */\n bundleId?: string;\n /** Package name for Android apps */\n packageName?: string;\n}\n\n// =============================================================================\n// Custom Dimension/Metric Types\n// =============================================================================\n\n/**\n * Scope for custom dimensions\n */\nexport type CustomDimensionScope = 'EVENT' | 'USER' | 'ITEM';\n\n/**\n * Custom dimension representation\n */\nexport interface CustomDimension {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope of the dimension */\n scope: CustomDimensionScope;\n /** Whether to disallow ads personalization */\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Options for creating a custom dimension\n */\nexport interface CustomDimensionOptions {\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope of the dimension */\n scope: CustomDimensionScope;\n /** Whether to disallow ads personalization */\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Measurement unit for custom metrics\n */\nexport type MeasurementUnit =\n | 'STANDARD'\n | 'CURRENCY'\n | 'FEET'\n | 'METERS'\n | 'KILOMETERS'\n | 'MILES'\n | 'MILLISECONDS'\n | 'SECONDS'\n | 'MINUTES'\n | 'HOURS';\n\n/**\n * Custom metric representation\n */\nexport interface CustomMetric {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope (always EVENT for metrics) */\n scope: 'EVENT';\n /** Measurement unit */\n measurementUnit: MeasurementUnit;\n /** Restricted metric type */\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n/**\n * Options for creating a custom metric\n */\nexport interface CustomMetricOptions {\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Measurement unit */\n measurementUnit: MeasurementUnit;\n /** Restricted metric type */\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n// =============================================================================\n// Key Event (Conversion) Types\n// =============================================================================\n\n/**\n * Counting method for key events\n */\nexport type CountingMethod = 'ONCE_PER_EVENT' | 'ONCE_PER_SESSION';\n\n/**\n * Key event (conversion) representation\n */\nexport interface KeyEvent {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Event name that triggers this key event */\n eventName: string;\n /** Creation timestamp */\n createTime: string;\n /** How to count the conversion */\n countingMethod?: CountingMethod;\n /** Default value for the conversion */\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n/**\n * Options for creating a key event\n */\nexport interface KeyEventOptions {\n /** Event name that triggers this key event */\n eventName: string;\n /** How to count the conversion */\n countingMethod?: CountingMethod;\n /** Default value for the conversion */\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n// =============================================================================\n// Reporting Types\n// =============================================================================\n\n/**\n * Date range for reports\n */\nexport interface DateRange {\n /** Start date (YYYY-MM-DD or relative: 'today', 'yesterday', '7daysAgo', '30daysAgo') */\n startDate: string;\n /** End date (YYYY-MM-DD or relative: 'today', 'yesterday') */\n endDate: string;\n /** Optional name for this date range */\n name?: string;\n}\n\n/**\n * Dimension specification for reports\n */\nexport interface Dimension {\n /** Dimension name (e.g., 'country', 'deviceCategory') */\n name: string;\n}\n\n/**\n * Metric specification for reports\n */\nexport interface Metric {\n /** Metric name (e.g., 'activeUsers', 'sessions') */\n name: string;\n}\n\n/**\n * Filter match types\n */\nexport type StringMatchType =\n | 'EXACT'\n | 'BEGINS_WITH'\n | 'ENDS_WITH'\n | 'CONTAINS'\n | 'FULL_REGEXP'\n | 'PARTIAL_REGEXP';\n\n/**\n * Numeric filter operations\n */\nexport type NumericOperation =\n | 'EQUAL'\n | 'LESS_THAN'\n | 'LESS_THAN_OR_EQUAL'\n | 'GREATER_THAN'\n | 'GREATER_THAN_OR_EQUAL';\n\n/**\n * Filter value (numeric)\n */\nexport interface NumericValue {\n int64Value?: string;\n doubleValue?: number;\n}\n\n/**\n * String filter specification\n */\nexport interface StringFilter {\n matchType: StringMatchType;\n value: string;\n caseSensitive?: boolean;\n}\n\n/**\n * In-list filter specification\n */\nexport interface InListFilter {\n values: string[];\n caseSensitive?: boolean;\n}\n\n/**\n * Numeric filter specification\n */\nexport interface NumericFilter {\n operation: NumericOperation;\n value: NumericValue;\n}\n\n/**\n * Between filter specification\n */\nexport interface BetweenFilter {\n fromValue: NumericValue;\n toValue: NumericValue;\n}\n\n/**\n * Individual filter specification\n */\nexport interface Filter {\n fieldName: string;\n stringFilter?: StringFilter;\n inListFilter?: InListFilter;\n numericFilter?: NumericFilter;\n betweenFilter?: BetweenFilter;\n}\n\n/**\n * Filter expression (supports AND/OR/NOT combinations)\n */\nexport interface FilterExpression {\n andGroup?: { expressions: FilterExpression[] };\n orGroup?: { expressions: FilterExpression[] };\n notExpression?: FilterExpression;\n filter?: Filter;\n}\n\n/**\n * Order type for dimension sorting\n */\nexport type DimensionOrderType =\n | 'ALPHANUMERIC'\n | 'CASE_INSENSITIVE_ALPHANUMERIC'\n | 'NUMERIC';\n\n/**\n * Order specification\n */\nexport interface OrderBy {\n metric?: { metricName: string };\n dimension?: {\n dimensionName: string;\n orderType?: DimensionOrderType;\n };\n desc?: boolean;\n}\n\n/**\n * Minute range for realtime reports\n */\nexport interface MinuteRange {\n name?: string;\n startMinutesAgo: number;\n endMinutesAgo: number;\n}\n\n/**\n * Report request options\n */\nexport interface ReportOptions {\n /** Date ranges to query */\n dateRanges: DateRange[];\n /** Dimensions to group by */\n dimensions?: Dimension[];\n /** Metrics to retrieve */\n metrics: Metric[];\n /** Dimension filter expression */\n dimensionFilter?: FilterExpression;\n /** Metric filter expression */\n metricFilter?: FilterExpression;\n /** Result offset for pagination */\n offset?: number;\n /** Maximum results to return */\n limit?: number;\n /** Sort order */\n orderBys?: OrderBy[];\n /** Whether to keep empty rows */\n keepEmptyRows?: boolean;\n /** Whether to return property quota information */\n returnPropertyQuota?: boolean;\n}\n\n/**\n * Realtime report options\n */\nexport interface RealtimeReportOptions {\n /** Dimensions to group by */\n dimensions?: Dimension[];\n /** Metrics to retrieve */\n metrics?: Metric[];\n /** Dimension filter expression */\n dimensionFilter?: FilterExpression;\n /** Metric filter expression */\n metricFilter?: FilterExpression;\n /** Maximum results to return */\n limit?: number;\n /** Minute ranges to query */\n minuteRanges?: MinuteRange[];\n}\n\n/**\n * Report row\n */\nexport interface ReportRow {\n dimensionValues: { value: string }[];\n metricValues: { value: string }[];\n}\n\n/**\n * Quota information\n */\nexport interface QuotaInfo {\n consumed: number;\n remaining: number;\n}\n\n/**\n * Property quota information\n */\nexport interface PropertyQuota {\n tokensPerDay: QuotaInfo;\n tokensPerHour: QuotaInfo;\n concurrentRequests: QuotaInfo;\n}\n\n/**\n * Report result\n */\nexport interface ReportResult {\n /** Column headers for dimensions */\n dimensionHeaders: { name: string }[];\n /** Column headers for metrics */\n metricHeaders: { name: string; type: string }[];\n /** Data rows */\n rows: ReportRow[];\n /** Total row count */\n rowCount?: number;\n /** Report metadata */\n metadata?: {\n currencyCode?: string;\n timeZone?: string;\n dataLossFromOtherRow?: boolean;\n emptyReason?: string;\n };\n /** Property quota usage */\n propertyQuota?: PropertyQuota;\n}\n\n// =============================================================================\n// Metadata Types\n// =============================================================================\n\n/**\n * Metric type\n */\nexport type MetricType =\n | 'METRIC_TYPE_UNSPECIFIED'\n | 'TYPE_INTEGER'\n | 'TYPE_FLOAT'\n | 'TYPE_SECONDS'\n | 'TYPE_MILLISECONDS'\n | 'TYPE_MINUTES'\n | 'TYPE_HOURS'\n | 'TYPE_STANDARD'\n | 'TYPE_CURRENCY'\n | 'TYPE_FEET'\n | 'TYPE_MILES'\n | 'TYPE_METERS'\n | 'TYPE_KILOMETERS';\n\n/**\n * Metric metadata\n */\nexport interface MetricMetadata {\n /** API name for the metric */\n apiName: string;\n /** UI display name */\n uiName: string;\n /** Description */\n description: string;\n /** Deprecated API names */\n deprecatedApiNames?: string[];\n /** Metric data type */\n type: MetricType;\n /** Expression for calculated metrics */\n expression?: string;\n /** Whether this is a custom definition */\n customDefinition?: boolean;\n /** Reasons metric may be blocked */\n blockedReasons?: string[];\n /** Category */\n category?: string;\n}\n\n/**\n * Dimension metadata\n */\nexport interface DimensionMetadata {\n /** API name for the dimension */\n apiName: string;\n /** UI display name */\n uiName: string;\n /** Description */\n description: string;\n /** Deprecated API names */\n deprecatedApiNames?: string[];\n /** Whether this is a custom definition */\n customDefinition?: boolean;\n /** Category */\n category?: string;\n}\n\n// =============================================================================\n// Event Tracking Types\n// =============================================================================\n\n/**\n * Track event payload\n */\nexport interface TrackEvent {\n /** Event name */\n name: string;\n /** Event parameters */\n params?: Record<string, string | number | boolean>;\n /** Client ID for anonymous tracking */\n clientId?: string;\n /** User ID for identified tracking */\n userId?: string;\n /** Event timestamp (Unix epoch in microseconds) */\n timestamp?: number;\n /** Whether to disable personalized ads */\n nonPersonalizedAds?: boolean;\n}\n\n/**\n * Pageview event payload\n */\nexport interface PageviewEvent {\n /** Page path */\n pagePath: string;\n /** Page title */\n pageTitle?: string;\n /** Full page location URL */\n pageLocation?: string;\n /** Client ID for anonymous tracking */\n clientId?: string;\n /** User ID for identified tracking */\n userId?: string;\n /** Additional parameters */\n params?: Record<string, string | number | boolean>;\n}\n\n// =============================================================================\n// Client-Side Snippet Types\n// =============================================================================\n\n/**\n * Generated tracking snippet\n */\nexport interface TrackingSnippet {\n /** HTML snippet ready to embed */\n html: string;\n /** Configuration object */\n config: Record<string, unknown>;\n /** External script URLs */\n scripts: string[];\n}\n\n/**\n * Options for snippet generation\n */\nexport interface SnippetOptions {\n /** Whether to anonymize IP addresses */\n anonymizeIp?: boolean;\n /** Whether to send initial pageview */\n sendPageView?: boolean;\n /** Cookie flags */\n cookieFlags?: string;\n /** Custom configuration options */\n customConfig?: Record<string, unknown>;\n}\n\n/**\n * Options for config generation\n */\nexport interface ConfigOptions {\n /** Whether to anonymize IP addresses */\n anonymizeIp?: boolean;\n /** Whether to send initial pageview */\n sendPageView?: boolean;\n /** User ID for cross-device tracking */\n userId?: string;\n /** Custom dimensions to set */\n customDimensions?: Record<string, string>;\n}\n\n// =============================================================================\n// Capabilities\n// =============================================================================\n\n/**\n * Analytics provider capabilities\n */\nexport interface AnalyticsCapabilities {\n /** Whether property management is supported */\n propertyManagement: boolean;\n /** Whether data streams are supported */\n dataStreams: boolean;\n /** Whether custom dimensions are supported */\n customDimensions: boolean;\n /** Whether custom metrics are supported */\n customMetrics: boolean;\n /** Whether key events (conversions) are supported */\n keyEvents: boolean;\n /** Whether reporting is supported */\n reporting: boolean;\n /** Whether realtime reporting is supported */\n realtimeReporting: boolean;\n /** Whether server-side tracking is supported */\n serverSideTracking: boolean;\n /** Whether client-side snippet generation is supported */\n clientSideSnippet: boolean;\n /** Whether user identification is supported */\n userIdentification: boolean;\n /** Whether batch tracking is supported */\n batchTracking: boolean;\n}\n\n// =============================================================================\n// Analytics Interface\n// =============================================================================\n\n/**\n * Core analytics interface that all providers must implement\n */\nexport interface AnalyticsInterface {\n // -------------------------------------------------------------------------\n // Property Management\n // -------------------------------------------------------------------------\n\n /**\n * Create a new analytics property\n */\n createProperty(options: CreatePropertyOptions): Promise<Property>;\n\n /**\n * List all properties accessible to this account\n */\n listProperties(options?: ListPropertiesOptions): Promise<Property[]>;\n\n /**\n * Get a specific property by ID\n */\n getProperty(propertyId: string): Promise<Property>;\n\n /**\n * Update a property\n */\n updateProperty(\n propertyId: string,\n data: UpdatePropertyOptions,\n ): Promise<Property>;\n\n /**\n * Delete a property\n */\n deleteProperty(propertyId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Data Streams\n // -------------------------------------------------------------------------\n\n /**\n * Get all data streams for a property\n */\n getDataStreams(propertyId: string): Promise<DataStream[]>;\n\n /**\n * Create a new data stream\n */\n createDataStream(\n propertyId: string,\n options: CreateDataStreamOptions,\n ): Promise<DataStream>;\n\n /**\n * Delete a data stream\n */\n deleteDataStream(propertyId: string, streamId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Custom Definitions\n // -------------------------------------------------------------------------\n\n /**\n * Get all custom dimensions for a property\n */\n getCustomDimensions(propertyId: string): Promise<CustomDimension[]>;\n\n /**\n * Create a new custom dimension\n */\n createCustomDimension(\n propertyId: string,\n options: CustomDimensionOptions,\n ): Promise<CustomDimension>;\n\n /**\n * Archive (soft-delete) a custom dimension\n */\n archiveCustomDimension(\n propertyId: string,\n dimensionId: string,\n ): Promise<void>;\n\n /**\n * Get all custom metrics for a property\n */\n getCustomMetrics(propertyId: string): Promise<CustomMetric[]>;\n\n /**\n * Create a new custom metric\n */\n createCustomMetric(\n propertyId: string,\n options: CustomMetricOptions,\n ): Promise<CustomMetric>;\n\n /**\n * Archive (soft-delete) a custom metric\n */\n archiveCustomMetric(propertyId: string, metricId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Key Events (Conversions)\n // -------------------------------------------------------------------------\n\n /**\n * Get all key events for a property\n */\n getKeyEvents(propertyId: string): Promise<KeyEvent[]>;\n\n /**\n * Create a new key event\n */\n createKeyEvent(\n propertyId: string,\n options: KeyEventOptions,\n ): Promise<KeyEvent>;\n\n /**\n * Delete a key event\n */\n deleteKeyEvent(propertyId: string, eventId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Reporting\n // -------------------------------------------------------------------------\n\n /**\n * Run a report query\n */\n runReport(propertyId: string, options: ReportOptions): Promise<ReportResult>;\n\n /**\n * Run a realtime report query\n */\n runRealtimeReport(\n propertyId: string,\n options?: RealtimeReportOptions,\n ): Promise<ReportResult>;\n\n /**\n * Get available metrics for a property\n */\n getMetrics(propertyId: string): Promise<MetricMetadata[]>;\n\n /**\n * Get available dimensions for a property\n */\n getDimensions(propertyId: string): Promise<DimensionMetadata[]>;\n\n // -------------------------------------------------------------------------\n // Event Tracking (Server-Side)\n // -------------------------------------------------------------------------\n\n /**\n * Track a single event\n */\n track(event: TrackEvent): Promise<void>;\n\n /**\n * Track a pageview\n */\n trackPageview(pageview: PageviewEvent): Promise<void>;\n\n /**\n * Track multiple events in a batch\n */\n trackBatch(events: TrackEvent[]): Promise<void>;\n\n /**\n * Identify a user with traits\n */\n identify(userId: string, traits?: Record<string, unknown>): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Client-Side Helpers\n // -------------------------------------------------------------------------\n\n /**\n * Generate a tracking snippet for embedding in HTML\n */\n generateTrackingSnippet(\n propertyId: string,\n options?: SnippetOptions,\n ): TrackingSnippet;\n\n /**\n * Generate a configuration object for programmatic use\n */\n generateConfig(\n propertyId: string,\n options?: ConfigOptions,\n ): Record<string, unknown>;\n\n // -------------------------------------------------------------------------\n // Provider Info\n // -------------------------------------------------------------------------\n\n /**\n * Get provider capabilities\n */\n getCapabilities(): Promise<AnalyticsCapabilities>;\n\n /**\n * Provisioning admin operations exposed by providers that support them.\n *\n * Optional. Providers that don't support tenant/site/user provisioning leave\n * this undefined. Implementations should mirror the pattern used by\n * `@happyvertical/ai`'s `AIAdminInterface`.\n */\n admin?: AnalyticsAdminInterface;\n}\n\n// =============================================================================\n// Admin Provisioning\n// =============================================================================\n\n/**\n * Site descriptor returned by admin providers.\n */\nexport interface AnalyticsSite {\n /**\n * Provider site ID. For Matomo this is the numeric `idSite` as a string.\n */\n id: string;\n /**\n * Human-readable site name as stored by the provider.\n */\n name: string;\n /**\n * Primary URL or domain for the site.\n */\n url?: string;\n /**\n * Site timezone (e.g. `America/Edmonton`).\n */\n timezone?: string;\n /**\n * Currency code where applicable (e.g. `CAD`).\n */\n currency?: string;\n /**\n * Tenant identifier this site is associated with, when supplied at create time.\n */\n tenantId?: string;\n /**\n * Provider that owns this site descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Options for creating a site.\n */\nexport interface CreateAnalyticsSiteOptions {\n /**\n * Human-readable site name.\n */\n name: string;\n /**\n * Site URL(s). At least one is required for Matomo.\n */\n urls: string[];\n /**\n * Site timezone (IANA, e.g. `America/Edmonton`). Defaults to provider default.\n */\n timezone?: string;\n /**\n * Currency code. Defaults to provider default.\n */\n currency?: string;\n /**\n * Optional tenant identifier to associate with this site.\n */\n tenantId?: string;\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Options for updating an existing site.\n *\n * Reuses the `createSite` field shapes — every field except `siteId` is\n * optional, and omitted fields are left unchanged on the provider (partial\n * update).\n */\nexport interface UpdateAnalyticsSiteOptions\n extends Partial<CreateAnalyticsSiteOptions> {\n /**\n * Provider site ID of the site to update. For Matomo this is the numeric\n * `idSite` as a string.\n */\n siteId: string;\n}\n\n/**\n * User access role assignable to an analytics site.\n */\nexport type AnalyticsAccessRole = 'noaccess' | 'view' | 'write' | 'admin';\n\n/**\n * Analytics user descriptor returned by admin providers.\n */\nexport interface AnalyticsUser {\n /**\n * Login or username used to authenticate the user.\n */\n login: string;\n /**\n * Email address recorded for the user.\n */\n email?: string;\n /**\n * Tenant identifier this user is associated with, when supplied at create time.\n */\n tenantId?: string;\n /**\n * Whether the user has been granted super-user access.\n */\n isSuperUser?: boolean;\n /**\n * Provider that owns this user descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Options for creating a user.\n */\nexport interface CreateAnalyticsUserOptions {\n /**\n * Login or username for the new user.\n */\n login: string;\n /**\n * Email address for the new user.\n */\n email: string;\n /**\n * Initial password. Implementations may generate one if omitted, but should\n * surface it on the returned user. For Matomo the password is required.\n */\n password?: string;\n /**\n * Optional tenant identifier to associate with this user (stored in\n * provider metadata where supported).\n */\n tenantId?: string;\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Options for granting site access to a user.\n */\nexport interface SetUserAccessOptions {\n /**\n * Login or username to grant access to.\n */\n login: string;\n /**\n * Access level to grant.\n */\n access: AnalyticsAccessRole;\n /**\n * Site IDs the access applies to.\n */\n siteIds: string[];\n}\n\n/**\n * Options for verifying that a user has sufficient access to a site.\n */\nexport interface VerifyUserSiteAccessOptions {\n /**\n * Login or username to inspect.\n */\n login: string;\n /**\n * Site ID the user should be able to access.\n */\n siteId: string;\n /**\n * Minimum required access. Defaults to `view`.\n */\n minimumAccess?: AnalyticsAccessRole;\n}\n\n/**\n * Options for verifying that a token can read a site.\n */\nexport interface VerifyTokenSiteAccessOptions {\n /**\n * Token to probe. This is the token under test, not necessarily the admin\n * token used by the provider instance.\n */\n tokenAuth: string;\n /**\n * Site ID the token should be able to read.\n */\n siteId: string;\n}\n\n/**\n * Result returned by access-verification helpers.\n */\nexport interface AnalyticsAccessVerificationResult {\n /**\n * Whether the requested access check passed.\n */\n ok: boolean;\n /**\n * Provider that performed the check.\n */\n provider: string;\n /**\n * Login that was inspected, when the check targets a user.\n */\n login?: string;\n /**\n * Site ID that was inspected.\n */\n siteId: string;\n /**\n * Observed access level. Token probes report `view` when the token can read\n * the site and `noaccess` when it cannot.\n */\n access?: AnalyticsAccessRole;\n /**\n * Required access level used by the check.\n */\n requiredAccess?: AnalyticsAccessRole;\n /**\n * Failure reason when `ok` is false.\n */\n error?: string;\n /**\n * Provider-specific failure code when `ok` is false.\n */\n errorCode?: string;\n /**\n * Raw provider response. This is intended for debugging and may include more\n * provider data than the specific access check asked for; avoid logging it\n * verbatim in application logs.\n */\n raw?: unknown;\n}\n\n/**\n * Options for minting a per-user auth token scoped to a user's permissions.\n */\nexport interface MintUserTokenOptions {\n /**\n * Login or username to mint the token for.\n */\n login: string;\n /**\n * Description of the token (where supported).\n */\n description?: string;\n /**\n * The target user's password. Matomo's `createAppSpecificTokenAuth`\n * confirms the *target* user's password (not the caller's) — this prevents\n * a stolen super-user token from minting tokens for arbitrary other users.\n *\n * Other providers may not need this; it's optional in the shared interface\n * but Matomo will throw without it.\n */\n passwordConfirmation?: string;\n}\n\n/**\n * Auth token descriptor returned by admin providers.\n */\nexport interface AnalyticsUserToken {\n /**\n * The token value. Show-once for most providers; not stored in plaintext on\n * the provider side.\n */\n token: string;\n /**\n * Login the token belongs to.\n */\n login: string;\n /**\n * Description recorded on the provider.\n */\n description?: string;\n /**\n * Provider that owns this token descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Result of a provider health probe.\n */\nexport interface AnalyticsHealthResult {\n /**\n * Whether the probe was able to reach the provider AND complete an authed\n * round-trip (if the probe is authed).\n */\n ok: boolean;\n /**\n * Provider version string, when available.\n */\n version?: string;\n /**\n * Failure reason when `ok` is false.\n */\n error?: string;\n}\n\n/**\n * Admin operations exposed by analytics providers that support provisioning.\n *\n * Mirrors the shape of `@happyvertical/ai`'s `AIAdminInterface`. Methods that a\n * particular provider can't support (e.g. `mintUserToken` against GA4) are left\n * out of that provider's admin object — callers should feature-check via\n * `typeof admin.mintUserToken === 'function'`.\n *\n * Because that feature-check idiom typically narrows a method into a local\n * (`const fn = admin.mintUserToken; if (fn) await fn(...)`), implementations\n * MUST keep these methods detachment-safe — i.e. bind them to the instance so a\n * detached/destructured reference still carries `this`. See `MatomoAdmin`'s\n * constructor for the reference implementation (https://github.com/happyvertical/sdk/issues/1043).\n */\nexport interface AnalyticsAdminInterface {\n // ---------------------------------------------------------------------------\n // Site provisioning\n // ---------------------------------------------------------------------------\n\n /**\n * Create a site/property for a tenant.\n */\n createSite(options: CreateAnalyticsSiteOptions): Promise<AnalyticsSite>;\n\n /**\n * List all sites visible to the calling token.\n */\n listSites(): Promise<AnalyticsSite[]>;\n\n /**\n * Look up a site by id. Resolves to undefined when the site does not exist.\n */\n getSite(siteId: string): Promise<AnalyticsSite | undefined>;\n\n /**\n * Update an existing site in place. Optional capability — partial update:\n * only the fields supplied in the options change on the provider.\n *\n * Implementations should resolve to the post-update site as read back from\n * the provider, normalized the same way `createSite`'s result is.\n */\n updateSite?(options: UpdateAnalyticsSiteOptions): Promise<AnalyticsSite>;\n\n /**\n * Delete a site.\n */\n deleteSite(siteId: string): Promise<void>;\n\n // ---------------------------------------------------------------------------\n // User provisioning (optional capability)\n // ---------------------------------------------------------------------------\n\n /**\n * Create a user.\n */\n createUser?(options: CreateAnalyticsUserOptions): Promise<AnalyticsUser>;\n\n /**\n * Look up a user by login. Resolves to undefined when the user does not exist.\n */\n getUser?(login: string): Promise<AnalyticsUser | undefined>;\n\n /**\n * Delete a user.\n */\n deleteUser?(login: string): Promise<void>;\n\n /**\n * Grant a user access to one or more sites at the given role.\n */\n setUserAccess?(options: SetUserAccessOptions): Promise<void>;\n\n /**\n * Verify that a user has at least the requested access to a site.\n */\n verifyUserSiteAccess?(\n options: VerifyUserSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult>;\n\n /**\n * Verify that a token can read a site.\n */\n verifyTokenSiteAccess?(\n options: VerifyTokenSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult>;\n\n /**\n * Mint a per-user auth token scoped to that user's existing permissions.\n *\n * The returned token is shown-once on most providers — implementations are\n * expected to return the live value in the response and not refetch it.\n */\n mintUserToken?(options: MintUserTokenOptions): Promise<AnalyticsUserToken>;\n\n // ---------------------------------------------------------------------------\n // Health\n // ---------------------------------------------------------------------------\n\n /**\n * Probe provider reachability and auth. Always available — providers that\n * lack a public health endpoint should make a cheap authed call instead.\n */\n health(): Promise<AnalyticsHealthResult>;\n}\n\n// =============================================================================\n// Error Classes\n// =============================================================================\n\n/**\n * Base error class for analytics operations\n */\nexport class AnalyticsError extends Error {\n constructor(\n message: string,\n public code: string,\n public provider?: string,\n public propertyId?: string,\n ) {\n super(message);\n this.name = 'AnalyticsError';\n }\n}\n\n/**\n * Authentication failed\n */\nexport class AuthenticationError extends AnalyticsError {\n constructor(\n provider?: string,\n message: string = 'Authentication failed',\n code: string = 'AUTH_ERROR',\n ) {\n super(message, code, provider);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Rate limit exceeded\n */\nexport class RateLimitError extends AnalyticsError {\n public retryAfter?: number;\n\n constructor(provider?: string, retryAfter?: number) {\n super(\n `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ''}`,\n 'RATE_LIMIT',\n provider,\n );\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Property not found\n */\nexport class PropertyNotFoundError extends AnalyticsError {\n constructor(propertyId: string, provider?: string) {\n super(\n `Property not found: ${propertyId}`,\n 'PROPERTY_NOT_FOUND',\n provider,\n propertyId,\n );\n this.name = 'PropertyNotFoundError';\n }\n}\n\n/**\n * Invalid dimension specified\n */\nexport class InvalidDimensionError extends AnalyticsError {\n public dimension: string;\n\n constructor(dimension: string, provider?: string) {\n super(`Invalid dimension: ${dimension}`, 'INVALID_DIMENSION', provider);\n this.name = 'InvalidDimensionError';\n this.dimension = dimension;\n }\n}\n\n/**\n * Invalid metric specified\n */\nexport class InvalidMetricError extends AnalyticsError {\n public metric: string;\n\n constructor(metric: string, provider?: string) {\n super(`Invalid metric: ${metric}`, 'INVALID_METRIC', provider);\n this.name = 'InvalidMetricError';\n this.metric = metric;\n }\n}\n\n/**\n * Quota exceeded\n */\nexport class QuotaExceededError extends AnalyticsError {\n public quotaType?: string;\n\n constructor(provider?: string, quotaType?: string) {\n super(\n `Quota exceeded${quotaType ? `: ${quotaType}` : ''}`,\n 'QUOTA_EXCEEDED',\n provider,\n );\n this.name = 'QuotaExceededError';\n this.quotaType = quotaType;\n }\n}\n\n/**\n * Feature not supported by this provider\n */\nexport class NotSupportedError extends AnalyticsError {\n public feature: string;\n\n constructor(feature: string, provider?: string) {\n super(`Feature not supported: ${feature}`, 'NOT_SUPPORTED', provider);\n this.name = 'NotSupportedError';\n this.feature = feature;\n }\n}\n"],"mappings":";;;;AAs6CA,IAAa,iBAAb,cAAoC,MAAM;CAG/B;CACA;CACA;CAJT,YACE,SACA,MACA,UACA,YACA;EACA,MAAM,OAAO;EAJN,KAAA,OAAA;EACA,KAAA,WAAA;EACA,KAAA,aAAA;EAGP,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,sBAAb,cAAyC,eAAe;CACtD,YACE,UACA,UAAkB,yBAClB,OAAe,cACf;EACA,MAAM,SAAS,MAAM,QAAQ;EAC7B,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,iBAAb,cAAoC,eAAe;CACjD;CAEA,YAAY,UAAmB,YAAqB;EAClD,MACE,sBAAsB,aAAa,iBAAiB,WAAW,KAAK,MACpE,cACA,QACF;EACA,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;;;;AAKA,IAAa,wBAAb,cAA2C,eAAe;CACxD,YAAY,YAAoB,UAAmB;EACjD,MACE,uBAAuB,cACvB,sBACA,UACA,UACF;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,wBAAb,cAA2C,eAAe;CACxD;CAEA,YAAY,WAAmB,UAAmB;EAChD,MAAM,sBAAsB,aAAa,qBAAqB,QAAQ;EACtE,KAAK,OAAO;EACZ,KAAK,YAAY;CACnB;AACF;;;;AAKA,IAAa,qBAAb,cAAwC,eAAe;CACrD;CAEA,YAAY,QAAgB,UAAmB;EAC7C,MAAM,mBAAmB,UAAU,kBAAkB,QAAQ;EAC7D,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;AACF;;;;AAKA,IAAa,qBAAb,cAAwC,eAAe;CACrD;CAEA,YAAY,UAAmB,WAAoB;EACjD,MACE,iBAAiB,YAAY,KAAK,cAAc,MAChD,kBACA,QACF;EACA,KAAK,OAAO;EACZ,KAAK,YAAY;CACnB;AACF;;;;AAKA,IAAa,oBAAb,cAAuC,eAAe;CACpD;CAEA,YAAY,SAAiB,UAAmB;EAC9C,MAAM,0BAA0B,WAAW,iBAAiB,QAAQ;EACpE,KAAK,OAAO;EACZ,KAAK,UAAU;CACjB;AACF"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (existsSync(metaSrc)) {
|
|
18
|
-
copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
19
|
-
}
|
|
5
|
+
//#region src/cli/claude-context.ts
|
|
6
|
+
/**
|
|
7
|
+
* CLI script to install agent context for @happyvertical/analytics
|
|
8
|
+
* Run the published context installer binary for this package.
|
|
9
|
+
*/
|
|
10
|
+
var pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
11
|
+
var targetDir = join(process.cwd(), ".claude");
|
|
12
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
13
|
+
var pkgName = "analytics";
|
|
14
|
+
var agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
15
|
+
var metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
16
|
+
if (existsSync(agentMdSrc)) copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
17
|
+
if (existsSync(metaSrc)) copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
20
18
|
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
21
|
-
//#
|
|
19
|
+
//#endregion
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=claude-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-context.js","sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/analytics\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'analytics';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"
|
|
1
|
+
{"version":3,"file":"claude-context.js","names":[],"sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/analytics\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'analytics';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"mappings":";;;;;;;;;AAUA,IAAM,UAAU,KADA,QAAQ,cAAc,OAAO,KAAK,GAAG,CAChC,GAAS,OAAO;AACrC,IAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAG1C,IAAM,UAAU;AAChB,IAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,IAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GACvB,aAAa,YAAY,KAAK,WAAW,QAAQ,QAAQ,IAAI,CAAC;AAGhE,IAAI,WAAW,OAAO,GACpB,aAAa,SAAS,KAAK,WAAW,QAAQ,QAAQ,WAAW,CAAC;AAGpE,QAAQ,IAAI,8BAA8B,QAAQ,qBAAqB"}
|
package/dist/index.js
CHANGED
|
@@ -1,135 +1,112 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as NotSupportedError, c as RateLimitError, i as InvalidMetricError, n as AuthenticationError, o as PropertyNotFoundError, r as InvalidDimensionError, s as QuotaExceededError, t as AnalyticsError } from "./chunks/types-zsbKROGX.js";
|
|
2
|
+
import { ValidationError, loadEnvConfig } from "@happyvertical/utils";
|
|
3
|
+
//#region src/shared/factory.ts
|
|
4
|
+
/**
|
|
5
|
+
* Factory function for creating analytics provider instances
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for GA4 options
|
|
9
|
+
*/
|
|
2
10
|
function isGA4Options(options) {
|
|
3
|
-
|
|
11
|
+
return options.type === "ga4";
|
|
4
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Type guard for Plausible options
|
|
15
|
+
*/
|
|
5
16
|
function isPlausibleOptions(options) {
|
|
6
|
-
|
|
17
|
+
return options.type === "plausible";
|
|
7
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Type guard for Matomo options
|
|
21
|
+
*/
|
|
8
22
|
function isMatomoOptions(options) {
|
|
9
|
-
|
|
23
|
+
return options.type === "matomo";
|
|
10
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates an analytics provider instance based on the provided options.
|
|
27
|
+
*
|
|
28
|
+
* Supports environment variable configuration using the pattern:
|
|
29
|
+
* - HAVE_ANALYTICS_TYPE → provider type ('ga4' | 'plausible' | 'matomo')
|
|
30
|
+
* - HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY → path to service account JSON or JSON string
|
|
31
|
+
* - HAVE_ANALYTICS_MEASUREMENT_ID → GA4 measurement ID (G-XXXXXXX)
|
|
32
|
+
* - HAVE_ANALYTICS_API_SECRET → GA4 API secret for Measurement Protocol
|
|
33
|
+
* - HAVE_ANALYTICS_DEFAULT_PROPERTY_ID → default property ID
|
|
34
|
+
* - HAVE_ANALYTICS_API_KEY → Plausible API key
|
|
35
|
+
* - HAVE_ANALYTICS_BASE_URL → Plausible / Matomo base URL (for self-hosted)
|
|
36
|
+
* - HAVE_ANALYTICS_DEFAULT_SITE_ID → Plausible / Matomo default site ID
|
|
37
|
+
* - HAVE_ANALYTICS_TOKEN_AUTH → Matomo per-user token_auth
|
|
38
|
+
* - HAVE_ANALYTICS_TIMEOUT → request timeout in milliseconds
|
|
39
|
+
* - HAVE_ANALYTICS_MAX_RETRIES → maximum retry attempts
|
|
40
|
+
* - HAVE_ANALYTICS_CACHE_TTL → cache TTL in milliseconds
|
|
41
|
+
*
|
|
42
|
+
* User-provided options always take precedence over environment variables.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Configuration options for the analytics provider
|
|
45
|
+
* @returns Promise resolving to an analytics provider instance
|
|
46
|
+
* @throws {ValidationError} When the provider type is unsupported or required options are missing
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* // Create GA4 client with explicit options
|
|
51
|
+
* const ga4 = await getAnalytics({
|
|
52
|
+
* type: 'ga4',
|
|
53
|
+
* serviceAccountKey: '/path/to/service-account.json',
|
|
54
|
+
* measurementId: 'G-XXXXXXXXXX',
|
|
55
|
+
* apiSecret: 'your-api-secret'
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* // Create Plausible client
|
|
59
|
+
* const plausible = await getAnalytics({
|
|
60
|
+
* type: 'plausible',
|
|
61
|
+
* apiKey: 'your-api-key',
|
|
62
|
+
* baseUrl: 'https://plausible.io' // or self-hosted URL
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* // Use environment variables
|
|
66
|
+
* // Set: HAVE_ANALYTICS_TYPE=ga4, HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY=/path/to/key.json, etc.
|
|
67
|
+
* const client = await getAnalytics({ type: 'ga4' });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
11
70
|
async function getAnalytics(options) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
options = loadEnvConfig(options, {
|
|
72
|
+
packageName: "analytics",
|
|
73
|
+
schema: {
|
|
74
|
+
type: "string",
|
|
75
|
+
serviceAccountKey: "string",
|
|
76
|
+
measurementId: "string",
|
|
77
|
+
apiSecret: "string",
|
|
78
|
+
defaultPropertyId: "string",
|
|
79
|
+
apiKey: "string",
|
|
80
|
+
baseUrl: "string",
|
|
81
|
+
defaultSiteId: "string",
|
|
82
|
+
tokenAuth: "string",
|
|
83
|
+
timeout: "number",
|
|
84
|
+
maxRetries: "number",
|
|
85
|
+
cacheTTL: "number"
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (isGA4Options(options)) {
|
|
89
|
+
const { GA4Provider } = await import("./chunks/ga4-B69in3EQ.js");
|
|
90
|
+
return new GA4Provider(options);
|
|
91
|
+
}
|
|
92
|
+
if (isPlausibleOptions(options)) {
|
|
93
|
+
const { PlausibleProvider } = await import("./chunks/plausible-dXl_rz_8.js");
|
|
94
|
+
return new PlausibleProvider(options);
|
|
95
|
+
}
|
|
96
|
+
if (isMatomoOptions(options)) {
|
|
97
|
+
const { MatomoProvider } = await import("./chunks/matomo-CUUG_Drb.js");
|
|
98
|
+
return new MatomoProvider(options);
|
|
99
|
+
}
|
|
100
|
+
throw new ValidationError("Unsupported analytics provider type", {
|
|
101
|
+
supportedTypes: [
|
|
102
|
+
"ga4",
|
|
103
|
+
"plausible",
|
|
104
|
+
"matomo"
|
|
105
|
+
],
|
|
106
|
+
providedType: options.type
|
|
107
|
+
});
|
|
49
108
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.provider = provider;
|
|
55
|
-
this.propertyId = propertyId;
|
|
56
|
-
this.name = "AnalyticsError";
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
class AuthenticationError extends AnalyticsError {
|
|
60
|
-
constructor(provider, message = "Authentication failed", code = "AUTH_ERROR") {
|
|
61
|
-
super(message, code, provider);
|
|
62
|
-
this.name = "AuthenticationError";
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
class RateLimitError extends AnalyticsError {
|
|
66
|
-
retryAfter;
|
|
67
|
-
constructor(provider, retryAfter) {
|
|
68
|
-
super(
|
|
69
|
-
`Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ""}`,
|
|
70
|
-
"RATE_LIMIT",
|
|
71
|
-
provider
|
|
72
|
-
);
|
|
73
|
-
this.name = "RateLimitError";
|
|
74
|
-
this.retryAfter = retryAfter;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
class PropertyNotFoundError extends AnalyticsError {
|
|
78
|
-
constructor(propertyId, provider) {
|
|
79
|
-
super(
|
|
80
|
-
`Property not found: ${propertyId}`,
|
|
81
|
-
"PROPERTY_NOT_FOUND",
|
|
82
|
-
provider,
|
|
83
|
-
propertyId
|
|
84
|
-
);
|
|
85
|
-
this.name = "PropertyNotFoundError";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
class InvalidDimensionError extends AnalyticsError {
|
|
89
|
-
dimension;
|
|
90
|
-
constructor(dimension, provider) {
|
|
91
|
-
super(`Invalid dimension: ${dimension}`, "INVALID_DIMENSION", provider);
|
|
92
|
-
this.name = "InvalidDimensionError";
|
|
93
|
-
this.dimension = dimension;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
class InvalidMetricError extends AnalyticsError {
|
|
97
|
-
metric;
|
|
98
|
-
constructor(metric, provider) {
|
|
99
|
-
super(`Invalid metric: ${metric}`, "INVALID_METRIC", provider);
|
|
100
|
-
this.name = "InvalidMetricError";
|
|
101
|
-
this.metric = metric;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
class QuotaExceededError extends AnalyticsError {
|
|
105
|
-
quotaType;
|
|
106
|
-
constructor(provider, quotaType) {
|
|
107
|
-
super(
|
|
108
|
-
`Quota exceeded${quotaType ? `: ${quotaType}` : ""}`,
|
|
109
|
-
"QUOTA_EXCEEDED",
|
|
110
|
-
provider
|
|
111
|
-
);
|
|
112
|
-
this.name = "QuotaExceededError";
|
|
113
|
-
this.quotaType = quotaType;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
class NotSupportedError extends AnalyticsError {
|
|
117
|
-
feature;
|
|
118
|
-
constructor(feature, provider) {
|
|
119
|
-
super(`Feature not supported: ${feature}`, "NOT_SUPPORTED", provider);
|
|
120
|
-
this.name = "NotSupportedError";
|
|
121
|
-
this.feature = feature;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
export {
|
|
125
|
-
AnalyticsError,
|
|
126
|
-
AuthenticationError,
|
|
127
|
-
InvalidDimensionError,
|
|
128
|
-
InvalidMetricError,
|
|
129
|
-
NotSupportedError,
|
|
130
|
-
PropertyNotFoundError,
|
|
131
|
-
QuotaExceededError,
|
|
132
|
-
RateLimitError,
|
|
133
|
-
getAnalytics
|
|
134
|
-
};
|
|
135
|
-
//# sourceMappingURL=index.js.map
|
|
109
|
+
//#endregion
|
|
110
|
+
export { AnalyticsError, AuthenticationError, InvalidDimensionError, InvalidMetricError, NotSupportedError, PropertyNotFoundError, QuotaExceededError, RateLimitError, getAnalytics };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/shared/factory.ts","../src/shared/types.ts"],"sourcesContent":["/**\n * Factory function for creating analytics provider instances\n */\n\nimport { loadEnvConfig, ValidationError } from '@happyvertical/utils';\n\nimport type {\n AnalyticsInterface,\n GA4Options,\n GetAnalyticsOptions,\n MatomoOptions,\n PlausibleOptions,\n} from './types.js';\n\n/**\n * Type guard for GA4 options\n */\nfunction isGA4Options(options: GetAnalyticsOptions): options is GA4Options {\n return options.type === 'ga4';\n}\n\n/**\n * Type guard for Plausible options\n */\nfunction isPlausibleOptions(\n options: GetAnalyticsOptions,\n): options is PlausibleOptions {\n return options.type === 'plausible';\n}\n\n/**\n * Type guard for Matomo options\n */\nfunction isMatomoOptions(\n options: GetAnalyticsOptions,\n): options is MatomoOptions {\n return options.type === 'matomo';\n}\n\n/**\n * Creates an analytics provider instance based on the provided options.\n *\n * Supports environment variable configuration using the pattern:\n * - HAVE_ANALYTICS_TYPE → provider type ('ga4' | 'plausible' | 'matomo')\n * - HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY → path to service account JSON or JSON string\n * - HAVE_ANALYTICS_MEASUREMENT_ID → GA4 measurement ID (G-XXXXXXX)\n * - HAVE_ANALYTICS_API_SECRET → GA4 API secret for Measurement Protocol\n * - HAVE_ANALYTICS_DEFAULT_PROPERTY_ID → default property ID\n * - HAVE_ANALYTICS_API_KEY → Plausible API key\n * - HAVE_ANALYTICS_BASE_URL → Plausible / Matomo base URL (for self-hosted)\n * - HAVE_ANALYTICS_DEFAULT_SITE_ID → Plausible / Matomo default site ID\n * - HAVE_ANALYTICS_TOKEN_AUTH → Matomo per-user token_auth\n * - HAVE_ANALYTICS_TIMEOUT → request timeout in milliseconds\n * - HAVE_ANALYTICS_MAX_RETRIES → maximum retry attempts\n * - HAVE_ANALYTICS_CACHE_TTL → cache TTL in milliseconds\n *\n * User-provided options always take precedence over environment variables.\n *\n * @param options - Configuration options for the analytics provider\n * @returns Promise resolving to an analytics provider instance\n * @throws {ValidationError} When the provider type is unsupported or required options are missing\n *\n * @example\n * ```typescript\n * // Create GA4 client with explicit options\n * const ga4 = await getAnalytics({\n * type: 'ga4',\n * serviceAccountKey: '/path/to/service-account.json',\n * measurementId: 'G-XXXXXXXXXX',\n * apiSecret: 'your-api-secret'\n * });\n *\n * // Create Plausible client\n * const plausible = await getAnalytics({\n * type: 'plausible',\n * apiKey: 'your-api-key',\n * baseUrl: 'https://plausible.io' // or self-hosted URL\n * });\n *\n * // Use environment variables\n * // Set: HAVE_ANALYTICS_TYPE=ga4, HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY=/path/to/key.json, etc.\n * const client = await getAnalytics({ type: 'ga4' });\n * ```\n */\nexport async function getAnalytics(\n options: GetAnalyticsOptions,\n): Promise<AnalyticsInterface> {\n // Load environment variables with user options taking precedence\n const loadedConfig = loadEnvConfig(\n options as unknown as Record<string, unknown>,\n {\n packageName: 'analytics',\n schema: {\n type: 'string',\n serviceAccountKey: 'string',\n measurementId: 'string',\n apiSecret: 'string',\n defaultPropertyId: 'string',\n apiKey: 'string',\n baseUrl: 'string',\n defaultSiteId: 'string',\n tokenAuth: 'string',\n timeout: 'number',\n maxRetries: 'number',\n cacheTTL: 'number',\n },\n },\n );\n options = loadedConfig as unknown as GetAnalyticsOptions;\n\n if (isGA4Options(options)) {\n const { GA4Provider } = await import('./providers/ga4.js');\n return new GA4Provider(options);\n }\n\n if (isPlausibleOptions(options)) {\n const { PlausibleProvider } = await import('./providers/plausible.js');\n return new PlausibleProvider(options);\n }\n\n if (isMatomoOptions(options)) {\n const { MatomoProvider } = await import('./providers/matomo.js');\n return new MatomoProvider(options);\n }\n\n throw new ValidationError('Unsupported analytics provider type', {\n supportedTypes: ['ga4', 'plausible', 'matomo'],\n providedType: (options as { type?: string }).type,\n });\n}\n","/**\n * Core types and interfaces for the Analytics library\n */\n\n// =============================================================================\n// Base Options\n// =============================================================================\n\n/**\n * Base configuration options for all analytics providers\n */\nexport interface BaseAnalyticsOptions {\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Maximum retry attempts for failed requests */\n maxRetries?: number;\n /** Cache TTL in milliseconds for metadata operations */\n cacheTTL?: number;\n}\n\n/**\n * Service account credentials for Google APIs\n */\nexport interface ServiceAccountCredentials {\n type: 'service_account';\n project_id: string;\n private_key_id: string;\n private_key: string;\n client_email: string;\n client_id: string;\n auth_uri: string;\n token_uri: string;\n auth_provider_x509_cert_url: string;\n client_x509_cert_url: string;\n}\n\n// =============================================================================\n// Provider Options (Discriminated Union)\n// =============================================================================\n\n/**\n * Google Analytics 4 provider options\n */\nexport interface GA4Options extends BaseAnalyticsOptions {\n type: 'ga4';\n /**\n * Service account credentials for Admin API and Data API\n * Can be path to JSON key file or parsed JSON object\n */\n serviceAccountKey?: string | ServiceAccountCredentials;\n /**\n * Measurement ID (G-XXXXXXX) for Measurement Protocol\n * Required for server-side event tracking\n */\n measurementId?: string;\n /**\n * API Secret for Measurement Protocol\n * Required for server-side event tracking\n */\n apiSecret?: string;\n /**\n * Default property ID for operations that don't specify one\n */\n defaultPropertyId?: string;\n}\n\n/**\n * Plausible Analytics provider options\n */\nexport interface PlausibleOptions extends BaseAnalyticsOptions {\n type: 'plausible';\n /**\n * Plausible API key\n */\n apiKey: string;\n /**\n * Base URL for self-hosted instances\n * @default \"https://plausible.io\"\n */\n baseUrl?: string;\n /**\n * Default site ID (domain) for operations\n */\n defaultSiteId?: string;\n}\n\n/**\n * Matomo Analytics provider options\n *\n * Matomo's Reporting API authenticates via per-user `token_auth` values; admin\n * provisioning operations require a token belonging to a super-user.\n */\nexport interface MatomoOptions extends BaseAnalyticsOptions {\n type: 'matomo';\n /**\n * Matomo base URL (e.g. `https://matomo.example.com`).\n *\n * Trailing slashes and explicit `/index.php` suffixes are normalized.\n */\n baseUrl: string;\n /**\n * `token_auth` for the calling user. Required for all reporting calls.\n *\n * For admin provisioning (createSite/createUser/etc.) this token must\n * belong to a Matomo super-user.\n */\n tokenAuth: string;\n /**\n * Default site ID (`idSite`) for operations that don't specify one.\n */\n defaultSiteId?: string;\n}\n\n/**\n * Union type for all provider options\n */\nexport type GetAnalyticsOptions = GA4Options | PlausibleOptions | MatomoOptions;\n\n// =============================================================================\n// Property Types\n// =============================================================================\n\n/**\n * Analytics property/site representation\n */\nexport interface Property {\n /** Unique identifier */\n id: string;\n /** Internal name (GA4: properties/123456789) */\n name: string;\n /** Human-readable display name */\n displayName: string;\n /** Creation timestamp (ISO 8601) */\n createTime: string;\n /** Last update timestamp (ISO 8601) */\n updateTime?: string;\n /** Property timezone */\n timeZone?: string;\n /** Currency code (e.g., 'USD', 'EUR') */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n /** Service level */\n serviceLevel?: 'STANDARD' | 'PREMIUM';\n}\n\n/**\n * Options for listing properties\n */\nexport interface ListPropertiesOptions {\n /**\n * Controls whether the provider should hydrate discovered properties with\n * full metadata.\n *\n * Providers that support hydration should default this to `true` to\n * preserve the existing `listProperties()` behavior. Set this to `false`\n * to use discovery-only metadata, which may omit fields like `createTime`,\n * `updateTime`, `timeZone`, and `currencyCode`.\n */\n hydrate?: boolean;\n}\n\n/**\n * Options for creating a new property\n */\nexport interface CreatePropertyOptions {\n /** Human-readable display name */\n displayName: string;\n /** Timezone (e.g., 'America/Los_Angeles') */\n timeZone?: string;\n /** Currency code (e.g., 'USD') */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n /** Parent account (GA4: accounts/{account_id}) */\n parent?: string;\n}\n\n/**\n * Options for updating a property\n */\nexport interface UpdatePropertyOptions {\n /** Human-readable display name */\n displayName?: string;\n /** Timezone */\n timeZone?: string;\n /** Currency code */\n currencyCode?: string;\n /** Industry category */\n industryCategory?: string;\n}\n\n// =============================================================================\n// Data Stream Types\n// =============================================================================\n\n/**\n * Data stream types\n */\nexport type DataStreamType =\n | 'WEB_DATA_STREAM'\n | 'ANDROID_APP_DATA_STREAM'\n | 'IOS_APP_DATA_STREAM';\n\n/**\n * Data stream representation\n */\nexport interface DataStream {\n /** Unique identifier */\n id: string;\n /** Stream type */\n type: DataStreamType;\n /** Human-readable display name */\n displayName: string;\n /** Measurement ID for web streams (G-XXXXXXX) */\n measurementId?: string;\n /** Firebase App ID for app streams */\n firebaseAppId?: string;\n /** Default URI for web streams */\n defaultUri?: string;\n /** Creation timestamp */\n createTime: string;\n /** Last update timestamp */\n updateTime?: string;\n}\n\n/**\n * Options for creating a data stream\n */\nexport interface CreateDataStreamOptions {\n /** Stream type */\n type: DataStreamType;\n /** Human-readable display name */\n displayName: string;\n /** Default URI for web streams */\n defaultUri?: string;\n /** Bundle ID for iOS apps */\n bundleId?: string;\n /** Package name for Android apps */\n packageName?: string;\n}\n\n// =============================================================================\n// Custom Dimension/Metric Types\n// =============================================================================\n\n/**\n * Scope for custom dimensions\n */\nexport type CustomDimensionScope = 'EVENT' | 'USER' | 'ITEM';\n\n/**\n * Custom dimension representation\n */\nexport interface CustomDimension {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope of the dimension */\n scope: CustomDimensionScope;\n /** Whether to disallow ads personalization */\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Options for creating a custom dimension\n */\nexport interface CustomDimensionOptions {\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope of the dimension */\n scope: CustomDimensionScope;\n /** Whether to disallow ads personalization */\n disallowAdsPersonalization?: boolean;\n}\n\n/**\n * Measurement unit for custom metrics\n */\nexport type MeasurementUnit =\n | 'STANDARD'\n | 'CURRENCY'\n | 'FEET'\n | 'METERS'\n | 'KILOMETERS'\n | 'MILES'\n | 'MILLISECONDS'\n | 'SECONDS'\n | 'MINUTES'\n | 'HOURS';\n\n/**\n * Custom metric representation\n */\nexport interface CustomMetric {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Scope (always EVENT for metrics) */\n scope: 'EVENT';\n /** Measurement unit */\n measurementUnit: MeasurementUnit;\n /** Restricted metric type */\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n/**\n * Options for creating a custom metric\n */\nexport interface CustomMetricOptions {\n /** Parameter name used in events */\n parameterName: string;\n /** Human-readable display name */\n displayName: string;\n /** Description */\n description?: string;\n /** Measurement unit */\n measurementUnit: MeasurementUnit;\n /** Restricted metric type */\n restrictedMetricType?: 'COST_DATA' | 'REVENUE_DATA';\n}\n\n// =============================================================================\n// Key Event (Conversion) Types\n// =============================================================================\n\n/**\n * Counting method for key events\n */\nexport type CountingMethod = 'ONCE_PER_EVENT' | 'ONCE_PER_SESSION';\n\n/**\n * Key event (conversion) representation\n */\nexport interface KeyEvent {\n /** Unique identifier */\n id: string;\n /** Resource name */\n name: string;\n /** Event name that triggers this key event */\n eventName: string;\n /** Creation timestamp */\n createTime: string;\n /** How to count the conversion */\n countingMethod?: CountingMethod;\n /** Default value for the conversion */\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n/**\n * Options for creating a key event\n */\nexport interface KeyEventOptions {\n /** Event name that triggers this key event */\n eventName: string;\n /** How to count the conversion */\n countingMethod?: CountingMethod;\n /** Default value for the conversion */\n defaultValue?: {\n numericValue?: number;\n currencyCode?: string;\n };\n}\n\n// =============================================================================\n// Reporting Types\n// =============================================================================\n\n/**\n * Date range for reports\n */\nexport interface DateRange {\n /** Start date (YYYY-MM-DD or relative: 'today', 'yesterday', '7daysAgo', '30daysAgo') */\n startDate: string;\n /** End date (YYYY-MM-DD or relative: 'today', 'yesterday') */\n endDate: string;\n /** Optional name for this date range */\n name?: string;\n}\n\n/**\n * Dimension specification for reports\n */\nexport interface Dimension {\n /** Dimension name (e.g., 'country', 'deviceCategory') */\n name: string;\n}\n\n/**\n * Metric specification for reports\n */\nexport interface Metric {\n /** Metric name (e.g., 'activeUsers', 'sessions') */\n name: string;\n}\n\n/**\n * Filter match types\n */\nexport type StringMatchType =\n | 'EXACT'\n | 'BEGINS_WITH'\n | 'ENDS_WITH'\n | 'CONTAINS'\n | 'FULL_REGEXP'\n | 'PARTIAL_REGEXP';\n\n/**\n * Numeric filter operations\n */\nexport type NumericOperation =\n | 'EQUAL'\n | 'LESS_THAN'\n | 'LESS_THAN_OR_EQUAL'\n | 'GREATER_THAN'\n | 'GREATER_THAN_OR_EQUAL';\n\n/**\n * Filter value (numeric)\n */\nexport interface NumericValue {\n int64Value?: string;\n doubleValue?: number;\n}\n\n/**\n * String filter specification\n */\nexport interface StringFilter {\n matchType: StringMatchType;\n value: string;\n caseSensitive?: boolean;\n}\n\n/**\n * In-list filter specification\n */\nexport interface InListFilter {\n values: string[];\n caseSensitive?: boolean;\n}\n\n/**\n * Numeric filter specification\n */\nexport interface NumericFilter {\n operation: NumericOperation;\n value: NumericValue;\n}\n\n/**\n * Between filter specification\n */\nexport interface BetweenFilter {\n fromValue: NumericValue;\n toValue: NumericValue;\n}\n\n/**\n * Individual filter specification\n */\nexport interface Filter {\n fieldName: string;\n stringFilter?: StringFilter;\n inListFilter?: InListFilter;\n numericFilter?: NumericFilter;\n betweenFilter?: BetweenFilter;\n}\n\n/**\n * Filter expression (supports AND/OR/NOT combinations)\n */\nexport interface FilterExpression {\n andGroup?: { expressions: FilterExpression[] };\n orGroup?: { expressions: FilterExpression[] };\n notExpression?: FilterExpression;\n filter?: Filter;\n}\n\n/**\n * Order type for dimension sorting\n */\nexport type DimensionOrderType =\n | 'ALPHANUMERIC'\n | 'CASE_INSENSITIVE_ALPHANUMERIC'\n | 'NUMERIC';\n\n/**\n * Order specification\n */\nexport interface OrderBy {\n metric?: { metricName: string };\n dimension?: {\n dimensionName: string;\n orderType?: DimensionOrderType;\n };\n desc?: boolean;\n}\n\n/**\n * Minute range for realtime reports\n */\nexport interface MinuteRange {\n name?: string;\n startMinutesAgo: number;\n endMinutesAgo: number;\n}\n\n/**\n * Report request options\n */\nexport interface ReportOptions {\n /** Date ranges to query */\n dateRanges: DateRange[];\n /** Dimensions to group by */\n dimensions?: Dimension[];\n /** Metrics to retrieve */\n metrics: Metric[];\n /** Dimension filter expression */\n dimensionFilter?: FilterExpression;\n /** Metric filter expression */\n metricFilter?: FilterExpression;\n /** Result offset for pagination */\n offset?: number;\n /** Maximum results to return */\n limit?: number;\n /** Sort order */\n orderBys?: OrderBy[];\n /** Whether to keep empty rows */\n keepEmptyRows?: boolean;\n /** Whether to return property quota information */\n returnPropertyQuota?: boolean;\n}\n\n/**\n * Realtime report options\n */\nexport interface RealtimeReportOptions {\n /** Dimensions to group by */\n dimensions?: Dimension[];\n /** Metrics to retrieve */\n metrics?: Metric[];\n /** Dimension filter expression */\n dimensionFilter?: FilterExpression;\n /** Metric filter expression */\n metricFilter?: FilterExpression;\n /** Maximum results to return */\n limit?: number;\n /** Minute ranges to query */\n minuteRanges?: MinuteRange[];\n}\n\n/**\n * Report row\n */\nexport interface ReportRow {\n dimensionValues: { value: string }[];\n metricValues: { value: string }[];\n}\n\n/**\n * Quota information\n */\nexport interface QuotaInfo {\n consumed: number;\n remaining: number;\n}\n\n/**\n * Property quota information\n */\nexport interface PropertyQuota {\n tokensPerDay: QuotaInfo;\n tokensPerHour: QuotaInfo;\n concurrentRequests: QuotaInfo;\n}\n\n/**\n * Report result\n */\nexport interface ReportResult {\n /** Column headers for dimensions */\n dimensionHeaders: { name: string }[];\n /** Column headers for metrics */\n metricHeaders: { name: string; type: string }[];\n /** Data rows */\n rows: ReportRow[];\n /** Total row count */\n rowCount?: number;\n /** Report metadata */\n metadata?: {\n currencyCode?: string;\n timeZone?: string;\n dataLossFromOtherRow?: boolean;\n emptyReason?: string;\n };\n /** Property quota usage */\n propertyQuota?: PropertyQuota;\n}\n\n// =============================================================================\n// Metadata Types\n// =============================================================================\n\n/**\n * Metric type\n */\nexport type MetricType =\n | 'METRIC_TYPE_UNSPECIFIED'\n | 'TYPE_INTEGER'\n | 'TYPE_FLOAT'\n | 'TYPE_SECONDS'\n | 'TYPE_MILLISECONDS'\n | 'TYPE_MINUTES'\n | 'TYPE_HOURS'\n | 'TYPE_STANDARD'\n | 'TYPE_CURRENCY'\n | 'TYPE_FEET'\n | 'TYPE_MILES'\n | 'TYPE_METERS'\n | 'TYPE_KILOMETERS';\n\n/**\n * Metric metadata\n */\nexport interface MetricMetadata {\n /** API name for the metric */\n apiName: string;\n /** UI display name */\n uiName: string;\n /** Description */\n description: string;\n /** Deprecated API names */\n deprecatedApiNames?: string[];\n /** Metric data type */\n type: MetricType;\n /** Expression for calculated metrics */\n expression?: string;\n /** Whether this is a custom definition */\n customDefinition?: boolean;\n /** Reasons metric may be blocked */\n blockedReasons?: string[];\n /** Category */\n category?: string;\n}\n\n/**\n * Dimension metadata\n */\nexport interface DimensionMetadata {\n /** API name for the dimension */\n apiName: string;\n /** UI display name */\n uiName: string;\n /** Description */\n description: string;\n /** Deprecated API names */\n deprecatedApiNames?: string[];\n /** Whether this is a custom definition */\n customDefinition?: boolean;\n /** Category */\n category?: string;\n}\n\n// =============================================================================\n// Event Tracking Types\n// =============================================================================\n\n/**\n * Track event payload\n */\nexport interface TrackEvent {\n /** Event name */\n name: string;\n /** Event parameters */\n params?: Record<string, string | number | boolean>;\n /** Client ID for anonymous tracking */\n clientId?: string;\n /** User ID for identified tracking */\n userId?: string;\n /** Event timestamp (Unix epoch in microseconds) */\n timestamp?: number;\n /** Whether to disable personalized ads */\n nonPersonalizedAds?: boolean;\n}\n\n/**\n * Pageview event payload\n */\nexport interface PageviewEvent {\n /** Page path */\n pagePath: string;\n /** Page title */\n pageTitle?: string;\n /** Full page location URL */\n pageLocation?: string;\n /** Client ID for anonymous tracking */\n clientId?: string;\n /** User ID for identified tracking */\n userId?: string;\n /** Additional parameters */\n params?: Record<string, string | number | boolean>;\n}\n\n// =============================================================================\n// Client-Side Snippet Types\n// =============================================================================\n\n/**\n * Generated tracking snippet\n */\nexport interface TrackingSnippet {\n /** HTML snippet ready to embed */\n html: string;\n /** Configuration object */\n config: Record<string, unknown>;\n /** External script URLs */\n scripts: string[];\n}\n\n/**\n * Options for snippet generation\n */\nexport interface SnippetOptions {\n /** Whether to anonymize IP addresses */\n anonymizeIp?: boolean;\n /** Whether to send initial pageview */\n sendPageView?: boolean;\n /** Cookie flags */\n cookieFlags?: string;\n /** Custom configuration options */\n customConfig?: Record<string, unknown>;\n}\n\n/**\n * Options for config generation\n */\nexport interface ConfigOptions {\n /** Whether to anonymize IP addresses */\n anonymizeIp?: boolean;\n /** Whether to send initial pageview */\n sendPageView?: boolean;\n /** User ID for cross-device tracking */\n userId?: string;\n /** Custom dimensions to set */\n customDimensions?: Record<string, string>;\n}\n\n// =============================================================================\n// Capabilities\n// =============================================================================\n\n/**\n * Analytics provider capabilities\n */\nexport interface AnalyticsCapabilities {\n /** Whether property management is supported */\n propertyManagement: boolean;\n /** Whether data streams are supported */\n dataStreams: boolean;\n /** Whether custom dimensions are supported */\n customDimensions: boolean;\n /** Whether custom metrics are supported */\n customMetrics: boolean;\n /** Whether key events (conversions) are supported */\n keyEvents: boolean;\n /** Whether reporting is supported */\n reporting: boolean;\n /** Whether realtime reporting is supported */\n realtimeReporting: boolean;\n /** Whether server-side tracking is supported */\n serverSideTracking: boolean;\n /** Whether client-side snippet generation is supported */\n clientSideSnippet: boolean;\n /** Whether user identification is supported */\n userIdentification: boolean;\n /** Whether batch tracking is supported */\n batchTracking: boolean;\n}\n\n// =============================================================================\n// Analytics Interface\n// =============================================================================\n\n/**\n * Core analytics interface that all providers must implement\n */\nexport interface AnalyticsInterface {\n // -------------------------------------------------------------------------\n // Property Management\n // -------------------------------------------------------------------------\n\n /**\n * Create a new analytics property\n */\n createProperty(options: CreatePropertyOptions): Promise<Property>;\n\n /**\n * List all properties accessible to this account\n */\n listProperties(options?: ListPropertiesOptions): Promise<Property[]>;\n\n /**\n * Get a specific property by ID\n */\n getProperty(propertyId: string): Promise<Property>;\n\n /**\n * Update a property\n */\n updateProperty(\n propertyId: string,\n data: UpdatePropertyOptions,\n ): Promise<Property>;\n\n /**\n * Delete a property\n */\n deleteProperty(propertyId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Data Streams\n // -------------------------------------------------------------------------\n\n /**\n * Get all data streams for a property\n */\n getDataStreams(propertyId: string): Promise<DataStream[]>;\n\n /**\n * Create a new data stream\n */\n createDataStream(\n propertyId: string,\n options: CreateDataStreamOptions,\n ): Promise<DataStream>;\n\n /**\n * Delete a data stream\n */\n deleteDataStream(propertyId: string, streamId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Custom Definitions\n // -------------------------------------------------------------------------\n\n /**\n * Get all custom dimensions for a property\n */\n getCustomDimensions(propertyId: string): Promise<CustomDimension[]>;\n\n /**\n * Create a new custom dimension\n */\n createCustomDimension(\n propertyId: string,\n options: CustomDimensionOptions,\n ): Promise<CustomDimension>;\n\n /**\n * Archive (soft-delete) a custom dimension\n */\n archiveCustomDimension(\n propertyId: string,\n dimensionId: string,\n ): Promise<void>;\n\n /**\n * Get all custom metrics for a property\n */\n getCustomMetrics(propertyId: string): Promise<CustomMetric[]>;\n\n /**\n * Create a new custom metric\n */\n createCustomMetric(\n propertyId: string,\n options: CustomMetricOptions,\n ): Promise<CustomMetric>;\n\n /**\n * Archive (soft-delete) a custom metric\n */\n archiveCustomMetric(propertyId: string, metricId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Key Events (Conversions)\n // -------------------------------------------------------------------------\n\n /**\n * Get all key events for a property\n */\n getKeyEvents(propertyId: string): Promise<KeyEvent[]>;\n\n /**\n * Create a new key event\n */\n createKeyEvent(\n propertyId: string,\n options: KeyEventOptions,\n ): Promise<KeyEvent>;\n\n /**\n * Delete a key event\n */\n deleteKeyEvent(propertyId: string, eventId: string): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Reporting\n // -------------------------------------------------------------------------\n\n /**\n * Run a report query\n */\n runReport(propertyId: string, options: ReportOptions): Promise<ReportResult>;\n\n /**\n * Run a realtime report query\n */\n runRealtimeReport(\n propertyId: string,\n options?: RealtimeReportOptions,\n ): Promise<ReportResult>;\n\n /**\n * Get available metrics for a property\n */\n getMetrics(propertyId: string): Promise<MetricMetadata[]>;\n\n /**\n * Get available dimensions for a property\n */\n getDimensions(propertyId: string): Promise<DimensionMetadata[]>;\n\n // -------------------------------------------------------------------------\n // Event Tracking (Server-Side)\n // -------------------------------------------------------------------------\n\n /**\n * Track a single event\n */\n track(event: TrackEvent): Promise<void>;\n\n /**\n * Track a pageview\n */\n trackPageview(pageview: PageviewEvent): Promise<void>;\n\n /**\n * Track multiple events in a batch\n */\n trackBatch(events: TrackEvent[]): Promise<void>;\n\n /**\n * Identify a user with traits\n */\n identify(userId: string, traits?: Record<string, unknown>): Promise<void>;\n\n // -------------------------------------------------------------------------\n // Client-Side Helpers\n // -------------------------------------------------------------------------\n\n /**\n * Generate a tracking snippet for embedding in HTML\n */\n generateTrackingSnippet(\n propertyId: string,\n options?: SnippetOptions,\n ): TrackingSnippet;\n\n /**\n * Generate a configuration object for programmatic use\n */\n generateConfig(\n propertyId: string,\n options?: ConfigOptions,\n ): Record<string, unknown>;\n\n // -------------------------------------------------------------------------\n // Provider Info\n // -------------------------------------------------------------------------\n\n /**\n * Get provider capabilities\n */\n getCapabilities(): Promise<AnalyticsCapabilities>;\n\n /**\n * Provisioning admin operations exposed by providers that support them.\n *\n * Optional. Providers that don't support tenant/site/user provisioning leave\n * this undefined. Implementations should mirror the pattern used by\n * `@happyvertical/ai`'s `AIAdminInterface`.\n */\n admin?: AnalyticsAdminInterface;\n}\n\n// =============================================================================\n// Admin Provisioning\n// =============================================================================\n\n/**\n * Site descriptor returned by admin providers.\n */\nexport interface AnalyticsSite {\n /**\n * Provider site ID. For Matomo this is the numeric `idSite` as a string.\n */\n id: string;\n /**\n * Human-readable site name as stored by the provider.\n */\n name: string;\n /**\n * Primary URL or domain for the site.\n */\n url?: string;\n /**\n * Site timezone (e.g. `America/Edmonton`).\n */\n timezone?: string;\n /**\n * Currency code where applicable (e.g. `CAD`).\n */\n currency?: string;\n /**\n * Tenant identifier this site is associated with, when supplied at create time.\n */\n tenantId?: string;\n /**\n * Provider that owns this site descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Options for creating a site.\n */\nexport interface CreateAnalyticsSiteOptions {\n /**\n * Human-readable site name.\n */\n name: string;\n /**\n * Site URL(s). At least one is required for Matomo.\n */\n urls: string[];\n /**\n * Site timezone (IANA, e.g. `America/Edmonton`). Defaults to provider default.\n */\n timezone?: string;\n /**\n * Currency code. Defaults to provider default.\n */\n currency?: string;\n /**\n * Optional tenant identifier to associate with this site.\n */\n tenantId?: string;\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Options for updating an existing site.\n *\n * Reuses the `createSite` field shapes — every field except `siteId` is\n * optional, and omitted fields are left unchanged on the provider (partial\n * update).\n */\nexport interface UpdateAnalyticsSiteOptions\n extends Partial<CreateAnalyticsSiteOptions> {\n /**\n * Provider site ID of the site to update. For Matomo this is the numeric\n * `idSite` as a string.\n */\n siteId: string;\n}\n\n/**\n * User access role assignable to an analytics site.\n */\nexport type AnalyticsAccessRole = 'noaccess' | 'view' | 'write' | 'admin';\n\n/**\n * Analytics user descriptor returned by admin providers.\n */\nexport interface AnalyticsUser {\n /**\n * Login or username used to authenticate the user.\n */\n login: string;\n /**\n * Email address recorded for the user.\n */\n email?: string;\n /**\n * Tenant identifier this user is associated with, when supplied at create time.\n */\n tenantId?: string;\n /**\n * Whether the user has been granted super-user access.\n */\n isSuperUser?: boolean;\n /**\n * Provider that owns this user descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Options for creating a user.\n */\nexport interface CreateAnalyticsUserOptions {\n /**\n * Login or username for the new user.\n */\n login: string;\n /**\n * Email address for the new user.\n */\n email: string;\n /**\n * Initial password. Implementations may generate one if omitted, but should\n * surface it on the returned user. For Matomo the password is required.\n */\n password?: string;\n /**\n * Optional tenant identifier to associate with this user (stored in\n * provider metadata where supported).\n */\n tenantId?: string;\n /**\n * Provider-specific request body overrides.\n */\n raw?: Record<string, unknown>;\n}\n\n/**\n * Options for granting site access to a user.\n */\nexport interface SetUserAccessOptions {\n /**\n * Login or username to grant access to.\n */\n login: string;\n /**\n * Access level to grant.\n */\n access: AnalyticsAccessRole;\n /**\n * Site IDs the access applies to.\n */\n siteIds: string[];\n}\n\n/**\n * Options for verifying that a user has sufficient access to a site.\n */\nexport interface VerifyUserSiteAccessOptions {\n /**\n * Login or username to inspect.\n */\n login: string;\n /**\n * Site ID the user should be able to access.\n */\n siteId: string;\n /**\n * Minimum required access. Defaults to `view`.\n */\n minimumAccess?: AnalyticsAccessRole;\n}\n\n/**\n * Options for verifying that a token can read a site.\n */\nexport interface VerifyTokenSiteAccessOptions {\n /**\n * Token to probe. This is the token under test, not necessarily the admin\n * token used by the provider instance.\n */\n tokenAuth: string;\n /**\n * Site ID the token should be able to read.\n */\n siteId: string;\n}\n\n/**\n * Result returned by access-verification helpers.\n */\nexport interface AnalyticsAccessVerificationResult {\n /**\n * Whether the requested access check passed.\n */\n ok: boolean;\n /**\n * Provider that performed the check.\n */\n provider: string;\n /**\n * Login that was inspected, when the check targets a user.\n */\n login?: string;\n /**\n * Site ID that was inspected.\n */\n siteId: string;\n /**\n * Observed access level. Token probes report `view` when the token can read\n * the site and `noaccess` when it cannot.\n */\n access?: AnalyticsAccessRole;\n /**\n * Required access level used by the check.\n */\n requiredAccess?: AnalyticsAccessRole;\n /**\n * Failure reason when `ok` is false.\n */\n error?: string;\n /**\n * Provider-specific failure code when `ok` is false.\n */\n errorCode?: string;\n /**\n * Raw provider response. This is intended for debugging and may include more\n * provider data than the specific access check asked for; avoid logging it\n * verbatim in application logs.\n */\n raw?: unknown;\n}\n\n/**\n * Options for minting a per-user auth token scoped to a user's permissions.\n */\nexport interface MintUserTokenOptions {\n /**\n * Login or username to mint the token for.\n */\n login: string;\n /**\n * Description of the token (where supported).\n */\n description?: string;\n /**\n * The target user's password. Matomo's `createAppSpecificTokenAuth`\n * confirms the *target* user's password (not the caller's) — this prevents\n * a stolen super-user token from minting tokens for arbitrary other users.\n *\n * Other providers may not need this; it's optional in the shared interface\n * but Matomo will throw without it.\n */\n passwordConfirmation?: string;\n}\n\n/**\n * Auth token descriptor returned by admin providers.\n */\nexport interface AnalyticsUserToken {\n /**\n * The token value. Show-once for most providers; not stored in plaintext on\n * the provider side.\n */\n token: string;\n /**\n * Login the token belongs to.\n */\n login: string;\n /**\n * Description recorded on the provider.\n */\n description?: string;\n /**\n * Provider that owns this token descriptor.\n */\n provider: string;\n /**\n * Raw provider response.\n */\n raw?: unknown;\n}\n\n/**\n * Result of a provider health probe.\n */\nexport interface AnalyticsHealthResult {\n /**\n * Whether the probe was able to reach the provider AND complete an authed\n * round-trip (if the probe is authed).\n */\n ok: boolean;\n /**\n * Provider version string, when available.\n */\n version?: string;\n /**\n * Failure reason when `ok` is false.\n */\n error?: string;\n}\n\n/**\n * Admin operations exposed by analytics providers that support provisioning.\n *\n * Mirrors the shape of `@happyvertical/ai`'s `AIAdminInterface`. Methods that a\n * particular provider can't support (e.g. `mintUserToken` against GA4) are left\n * out of that provider's admin object — callers should feature-check via\n * `typeof admin.mintUserToken === 'function'`.\n *\n * Because that feature-check idiom typically narrows a method into a local\n * (`const fn = admin.mintUserToken; if (fn) await fn(...)`), implementations\n * MUST keep these methods detachment-safe — i.e. bind them to the instance so a\n * detached/destructured reference still carries `this`. See `MatomoAdmin`'s\n * constructor for the reference implementation (https://github.com/happyvertical/sdk/issues/1043).\n */\nexport interface AnalyticsAdminInterface {\n // ---------------------------------------------------------------------------\n // Site provisioning\n // ---------------------------------------------------------------------------\n\n /**\n * Create a site/property for a tenant.\n */\n createSite(options: CreateAnalyticsSiteOptions): Promise<AnalyticsSite>;\n\n /**\n * List all sites visible to the calling token.\n */\n listSites(): Promise<AnalyticsSite[]>;\n\n /**\n * Look up a site by id. Resolves to undefined when the site does not exist.\n */\n getSite(siteId: string): Promise<AnalyticsSite | undefined>;\n\n /**\n * Update an existing site in place. Optional capability — partial update:\n * only the fields supplied in the options change on the provider.\n *\n * Implementations should resolve to the post-update site as read back from\n * the provider, normalized the same way `createSite`'s result is.\n */\n updateSite?(options: UpdateAnalyticsSiteOptions): Promise<AnalyticsSite>;\n\n /**\n * Delete a site.\n */\n deleteSite(siteId: string): Promise<void>;\n\n // ---------------------------------------------------------------------------\n // User provisioning (optional capability)\n // ---------------------------------------------------------------------------\n\n /**\n * Create a user.\n */\n createUser?(options: CreateAnalyticsUserOptions): Promise<AnalyticsUser>;\n\n /**\n * Look up a user by login. Resolves to undefined when the user does not exist.\n */\n getUser?(login: string): Promise<AnalyticsUser | undefined>;\n\n /**\n * Delete a user.\n */\n deleteUser?(login: string): Promise<void>;\n\n /**\n * Grant a user access to one or more sites at the given role.\n */\n setUserAccess?(options: SetUserAccessOptions): Promise<void>;\n\n /**\n * Verify that a user has at least the requested access to a site.\n */\n verifyUserSiteAccess?(\n options: VerifyUserSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult>;\n\n /**\n * Verify that a token can read a site.\n */\n verifyTokenSiteAccess?(\n options: VerifyTokenSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult>;\n\n /**\n * Mint a per-user auth token scoped to that user's existing permissions.\n *\n * The returned token is shown-once on most providers — implementations are\n * expected to return the live value in the response and not refetch it.\n */\n mintUserToken?(options: MintUserTokenOptions): Promise<AnalyticsUserToken>;\n\n // ---------------------------------------------------------------------------\n // Health\n // ---------------------------------------------------------------------------\n\n /**\n * Probe provider reachability and auth. Always available — providers that\n * lack a public health endpoint should make a cheap authed call instead.\n */\n health(): Promise<AnalyticsHealthResult>;\n}\n\n// =============================================================================\n// Error Classes\n// =============================================================================\n\n/**\n * Base error class for analytics operations\n */\nexport class AnalyticsError extends Error {\n constructor(\n message: string,\n public code: string,\n public provider?: string,\n public propertyId?: string,\n ) {\n super(message);\n this.name = 'AnalyticsError';\n }\n}\n\n/**\n * Authentication failed\n */\nexport class AuthenticationError extends AnalyticsError {\n constructor(\n provider?: string,\n message: string = 'Authentication failed',\n code: string = 'AUTH_ERROR',\n ) {\n super(message, code, provider);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Rate limit exceeded\n */\nexport class RateLimitError extends AnalyticsError {\n public retryAfter?: number;\n\n constructor(provider?: string, retryAfter?: number) {\n super(\n `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ''}`,\n 'RATE_LIMIT',\n provider,\n );\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Property not found\n */\nexport class PropertyNotFoundError extends AnalyticsError {\n constructor(propertyId: string, provider?: string) {\n super(\n `Property not found: ${propertyId}`,\n 'PROPERTY_NOT_FOUND',\n provider,\n propertyId,\n );\n this.name = 'PropertyNotFoundError';\n }\n}\n\n/**\n * Invalid dimension specified\n */\nexport class InvalidDimensionError extends AnalyticsError {\n public dimension: string;\n\n constructor(dimension: string, provider?: string) {\n super(`Invalid dimension: ${dimension}`, 'INVALID_DIMENSION', provider);\n this.name = 'InvalidDimensionError';\n this.dimension = dimension;\n }\n}\n\n/**\n * Invalid metric specified\n */\nexport class InvalidMetricError extends AnalyticsError {\n public metric: string;\n\n constructor(metric: string, provider?: string) {\n super(`Invalid metric: ${metric}`, 'INVALID_METRIC', provider);\n this.name = 'InvalidMetricError';\n this.metric = metric;\n }\n}\n\n/**\n * Quota exceeded\n */\nexport class QuotaExceededError extends AnalyticsError {\n public quotaType?: string;\n\n constructor(provider?: string, quotaType?: string) {\n super(\n `Quota exceeded${quotaType ? `: ${quotaType}` : ''}`,\n 'QUOTA_EXCEEDED',\n provider,\n );\n this.name = 'QuotaExceededError';\n this.quotaType = quotaType;\n }\n}\n\n/**\n * Feature not supported by this provider\n */\nexport class NotSupportedError extends AnalyticsError {\n public feature: string;\n\n constructor(feature: string, provider?: string) {\n super(`Feature not supported: ${feature}`, 'NOT_SUPPORTED', provider);\n this.name = 'NotSupportedError';\n this.feature = feature;\n }\n}\n"],"names":[],"mappings":";AAiBA,SAAS,aAAa,SAAqD;AACzE,SAAO,QAAQ,SAAS;AAC1B;AAKA,SAAS,mBACP,SAC6B;AAC7B,SAAO,QAAQ,SAAS;AAC1B;AAKA,SAAS,gBACP,SAC0B;AAC1B,SAAO,QAAQ,SAAS;AAC1B;AA+CA,eAAsB,aACpB,SAC6B;AAE7B,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,mBAAmB;AAAA,QACnB,eAAe;AAAA,QACf,WAAW;AAAA,QACX,mBAAmB;AAAA,QACnB,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,eAAe;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,UAAU;AAAA,MAAA;AAAA,IACZ;AAAA,EACF;AAEF,YAAU;AAEV,MAAI,aAAa,OAAO,GAAG;AACzB,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,0BAAoB;AACzD,WAAO,IAAI,YAAY,OAAO;AAAA,EAChC;AAEA,MAAI,mBAAmB,OAAO,GAAG;AAC/B,UAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,gCAA0B;AACrE,WAAO,IAAI,kBAAkB,OAAO;AAAA,EACtC;AAEA,MAAI,gBAAgB,OAAO,GAAG;AAC5B,UAAM,EAAE,eAAA,IAAmB,MAAM,OAAO,6BAAuB;AAC/D,WAAO,IAAI,eAAe,OAAO;AAAA,EACnC;AAEA,QAAM,IAAI,gBAAgB,uCAAuC;AAAA,IAC/D,gBAAgB,CAAC,OAAO,aAAa,QAAQ;AAAA,IAC7C,cAAe,QAA8B;AAAA,EAAA,CAC9C;AACH;ACqyCO,MAAM,uBAAuB,MAAM;AAAA,EACxC,YACE,SACO,MACA,UACA,YACP;AACA,UAAM,OAAO;AAJN,SAAA,OAAA;AACA,SAAA,WAAA;AACA,SAAA,aAAA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,4BAA4B,eAAe;AAAA,EACtD,YACE,UACA,UAAkB,yBAClB,OAAe,cACf;AACA,UAAM,SAAS,MAAM,QAAQ;AAC7B,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,uBAAuB,eAAe;AAAA,EAC1C;AAAA,EAEP,YAAY,UAAmB,YAAqB;AAClD;AAAA,MACE,sBAAsB,aAAa,iBAAiB,UAAU,MAAM,EAAE;AAAA,MACtE;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAKO,MAAM,8BAA8B,eAAe;AAAA,EACxD,YAAY,YAAoB,UAAmB;AACjD;AAAA,MACE,uBAAuB,UAAU;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,8BAA8B,eAAe;AAAA,EACjD;AAAA,EAEP,YAAY,WAAmB,UAAmB;AAChD,UAAM,sBAAsB,SAAS,IAAI,qBAAqB,QAAQ;AACtE,SAAK,OAAO;AACZ,SAAK,YAAY;AAAA,EACnB;AACF;AAKO,MAAM,2BAA2B,eAAe;AAAA,EAC9C;AAAA,EAEP,YAAY,QAAgB,UAAmB;AAC7C,UAAM,mBAAmB,MAAM,IAAI,kBAAkB,QAAQ;AAC7D,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAKO,MAAM,2BAA2B,eAAe;AAAA,EAC9C;AAAA,EAEP,YAAY,UAAmB,WAAoB;AACjD;AAAA,MACE,iBAAiB,YAAY,KAAK,SAAS,KAAK,EAAE;AAAA,MAClD;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AACZ,SAAK,YAAY;AAAA,EACnB;AACF;AAKO,MAAM,0BAA0B,eAAe;AAAA,EAC7C;AAAA,EAEP,YAAY,SAAiB,UAAmB;AAC9C,UAAM,0BAA0B,OAAO,IAAI,iBAAiB,QAAQ;AACpE,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/shared/factory.ts"],"sourcesContent":["/**\n * Factory function for creating analytics provider instances\n */\n\nimport { loadEnvConfig, ValidationError } from '@happyvertical/utils';\n\nimport type {\n AnalyticsInterface,\n GA4Options,\n GetAnalyticsOptions,\n MatomoOptions,\n PlausibleOptions,\n} from './types.js';\n\n/**\n * Type guard for GA4 options\n */\nfunction isGA4Options(options: GetAnalyticsOptions): options is GA4Options {\n return options.type === 'ga4';\n}\n\n/**\n * Type guard for Plausible options\n */\nfunction isPlausibleOptions(\n options: GetAnalyticsOptions,\n): options is PlausibleOptions {\n return options.type === 'plausible';\n}\n\n/**\n * Type guard for Matomo options\n */\nfunction isMatomoOptions(\n options: GetAnalyticsOptions,\n): options is MatomoOptions {\n return options.type === 'matomo';\n}\n\n/**\n * Creates an analytics provider instance based on the provided options.\n *\n * Supports environment variable configuration using the pattern:\n * - HAVE_ANALYTICS_TYPE → provider type ('ga4' | 'plausible' | 'matomo')\n * - HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY → path to service account JSON or JSON string\n * - HAVE_ANALYTICS_MEASUREMENT_ID → GA4 measurement ID (G-XXXXXXX)\n * - HAVE_ANALYTICS_API_SECRET → GA4 API secret for Measurement Protocol\n * - HAVE_ANALYTICS_DEFAULT_PROPERTY_ID → default property ID\n * - HAVE_ANALYTICS_API_KEY → Plausible API key\n * - HAVE_ANALYTICS_BASE_URL → Plausible / Matomo base URL (for self-hosted)\n * - HAVE_ANALYTICS_DEFAULT_SITE_ID → Plausible / Matomo default site ID\n * - HAVE_ANALYTICS_TOKEN_AUTH → Matomo per-user token_auth\n * - HAVE_ANALYTICS_TIMEOUT → request timeout in milliseconds\n * - HAVE_ANALYTICS_MAX_RETRIES → maximum retry attempts\n * - HAVE_ANALYTICS_CACHE_TTL → cache TTL in milliseconds\n *\n * User-provided options always take precedence over environment variables.\n *\n * @param options - Configuration options for the analytics provider\n * @returns Promise resolving to an analytics provider instance\n * @throws {ValidationError} When the provider type is unsupported or required options are missing\n *\n * @example\n * ```typescript\n * // Create GA4 client with explicit options\n * const ga4 = await getAnalytics({\n * type: 'ga4',\n * serviceAccountKey: '/path/to/service-account.json',\n * measurementId: 'G-XXXXXXXXXX',\n * apiSecret: 'your-api-secret'\n * });\n *\n * // Create Plausible client\n * const plausible = await getAnalytics({\n * type: 'plausible',\n * apiKey: 'your-api-key',\n * baseUrl: 'https://plausible.io' // or self-hosted URL\n * });\n *\n * // Use environment variables\n * // Set: HAVE_ANALYTICS_TYPE=ga4, HAVE_ANALYTICS_SERVICE_ACCOUNT_KEY=/path/to/key.json, etc.\n * const client = await getAnalytics({ type: 'ga4' });\n * ```\n */\nexport async function getAnalytics(\n options: GetAnalyticsOptions,\n): Promise<AnalyticsInterface> {\n // Load environment variables with user options taking precedence\n const loadedConfig = loadEnvConfig(\n options as unknown as Record<string, unknown>,\n {\n packageName: 'analytics',\n schema: {\n type: 'string',\n serviceAccountKey: 'string',\n measurementId: 'string',\n apiSecret: 'string',\n defaultPropertyId: 'string',\n apiKey: 'string',\n baseUrl: 'string',\n defaultSiteId: 'string',\n tokenAuth: 'string',\n timeout: 'number',\n maxRetries: 'number',\n cacheTTL: 'number',\n },\n },\n );\n options = loadedConfig as unknown as GetAnalyticsOptions;\n\n if (isGA4Options(options)) {\n const { GA4Provider } = await import('./providers/ga4.js');\n return new GA4Provider(options);\n }\n\n if (isPlausibleOptions(options)) {\n const { PlausibleProvider } = await import('./providers/plausible.js');\n return new PlausibleProvider(options);\n }\n\n if (isMatomoOptions(options)) {\n const { MatomoProvider } = await import('./providers/matomo.js');\n return new MatomoProvider(options);\n }\n\n throw new ValidationError('Unsupported analytics provider type', {\n supportedTypes: ['ga4', 'plausible', 'matomo'],\n providedType: (options as { type?: string }).type,\n });\n}\n"],"mappings":";;;;;;;;;AAiBA,SAAS,aAAa,SAAqD;CACzE,OAAO,QAAQ,SAAS;AAC1B;;;;AAKA,SAAS,mBACP,SAC6B;CAC7B,OAAO,QAAQ,SAAS;AAC1B;;;;AAKA,SAAS,gBACP,SAC0B;CAC1B,OAAO,QAAQ,SAAS;AAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,eAAsB,aACpB,SAC6B;CAsB7B,UApBqB,cACnB,SACA;EACE,aAAa;EACb,QAAQ;GACN,MAAM;GACN,mBAAmB;GACnB,eAAe;GACf,WAAW;GACX,mBAAmB;GACnB,QAAQ;GACR,SAAS;GACT,eAAe;GACf,WAAW;GACX,SAAS;GACT,YAAY;GACZ,UAAU;EACZ;CACF,CAEQ;CAEV,IAAI,aAAa,OAAO,GAAG;EACzB,MAAM,EAAE,gBAAgB,MAAM,OAAO;EACrC,OAAO,IAAI,YAAY,OAAO;CAChC;CAEA,IAAI,mBAAmB,OAAO,GAAG;EAC/B,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,OAAO,IAAI,kBAAkB,OAAO;CACtC;CAEA,IAAI,gBAAgB,OAAO,GAAG;EAC5B,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,OAAO,IAAI,eAAe,OAAO;CACnC;CAEA,MAAM,IAAI,gBAAgB,uCAAuC;EAC/D,gBAAgB;GAAC;GAAO;GAAa;EAAQ;EAC7C,cAAe,QAA8B;CAC/C,CAAC;AACH"}
|