/** * @module AnalyticsTypes * @package @geenius/analytics * @description Defines the shared analytics domain model used across the core * tracker, provider integrations, UI variants, and Convex support package. * These types establish the public contract that every variant builds on. */ /** * Analytics event category used by the shared tracker and validators. */ type EventType = "page_view" | "click" | "form_submit" | "custom"; /** * Browser and device metadata captured alongside analytics events. * * @property device Canonical device bucket derived from the client environment. */ interface DeviceInfo { browser: string; os: string; device: "desktop" | "tablet" | "mobile"; screenWidth: number; screenHeight: number; language: string; } /** * Normalized analytics event payload tracked by the shared runtime. * * @property properties Optional event-specific metadata payload. * @property ipAddress Optional client IP address, truncated before storage when IP masking is enabled. * @property sessionId Session identifier associated with the event. */ interface AnalyticsEvent { id: string; type: EventType; name: string; properties?: Record; url: string; referrer: string; ipAddress?: string; sessionId: string; userId?: string; traits?: UserTraits; device: DeviceInfo; timestamp: number; } /** * Normalized page-view payload tracked by the shared runtime. * * @property properties Optional page-view metadata such as campaign or route data. * @property duration Optional dwell time in milliseconds for the page. * @property ipAddress Optional client IP address, truncated before storage when IP masking is enabled. * @property sessionId Session identifier associated with the page view. */ interface PageView { url: string; title: string; referrer: string; properties?: Record; duration?: number; ipAddress?: string; sessionId: string; userId?: string; timestamp: number; } /** * Ranked page summary row consumed by top-page tables. * * @property uniqueVisitors Distinct user or session count for the route. * @property averageTimeMs Average dwell time for the route, in milliseconds. */ interface TopPageSummary { url: string; views: number; uniqueVisitors?: number; averageTimeMs?: number; averageTimeSeconds?: number; averageTime?: string; } /** * Aggregated analytics dashboard payload consumed by UI variants. * * @property topPages Ranked page summary rows by view count, visitors, and dwell time. * @property deviceBreakdown Device counts grouped by canonical device bucket. */ interface AnalyticsDashboardData { totalPageViews: number; uniqueVisitors: number; topPages: TopPageSummary[]; topReferrers: Array<{ referrer: string; visits: number; }>; deviceBreakdown: { desktop: number; tablet: number; mobile: number; }; browserBreakdown: Record; } /** * User traits forwarded to analytics providers during identification. */ interface UserTraits { email?: string; name?: string; plan?: string; company?: string; avatar?: string; [key: string]: unknown; } /** * Paginated read result returned by storage adapters. * * @property items Records returned for the current page. * @property nextCursor Cursor supplied when another page is available. * @property total Optional total count when the provider can calculate one cheaply. */ interface AnalyticsReadResult { items: TRecord[]; nextCursor?: string; total?: number; } /** * Query options for reading stored analytics events. */ interface AnalyticsEventQuery { userId?: string; sessionId?: string; eventName?: string; eventType?: EventType; timeRange?: TimeRange; limit?: number; cursor?: string; } /** * Query options for reading stored page-view records. */ interface AnalyticsPageViewQuery { url?: string; userId?: string; sessionId?: string; timeRange?: TimeRange; limit?: number; cursor?: string; } /** * Query options for materializing dashboard aggregate data. */ interface AnalyticsDashboardQuery { timeRange?: TimeRange; limit?: number; cursor?: string; } /** * Query options for realtime aggregate reads. */ interface RealtimeStatsQuery { since?: number; now?: number; windowMs?: number; topPageLimit?: number; } /** * Query options for funnel aggregate reads. */ interface FunnelResultsQuery { steps: FunnelStep[]; timeRange?: TimeRange; limit?: number; } /** * Query options for cohort aggregate reads. */ interface CohortAnalysisQuery { cohortType?: "user" | "session"; intervals?: number[]; timeRange?: TimeRange; segment?: Record; } /** * Declarative definition of a funnel step in shared analytics analysis. * * @property properties Optional event-property filter that must match the step. */ interface FunnelStep { name: string; event: string; properties?: Record; } /** * Computed analytics result for a single funnel step. * * @property dropoffRate Percentage of entries lost before reaching the step. * @property conversionRate Percentage of initial entries that reached the step. * @property averageTimeFromPreviousMs Average elapsed time from the previous completed step. */ interface FunnelStepResult { name: string; event: string; count: number; dropoffRate: number; conversionRate: number; averageTimeFromPreviousMs?: number; } /** * Aggregated result for a complete funnel analysis. * * @property steps Ordered funnel step results from entry to conversion. * @property overallConversionRate Percentage of entries that completed the funnel. * @property averageTimeToConvertMs Average elapsed time from first to final step. */ interface FunnelResult { name: string; steps: FunnelStepResult[]; totalEntries: number; totalConversions: number; overallConversionRate: number; averageTimeToConvertMs?: number; analyzedAt: number; } /** * Snapshot of realtime visitor and page activity. * * @property activeSessions Number of currently active analytics sessions. * @property eventsPerSecond Recent event throughput for live dashboards. * @property conversions Number of conversion events in the active realtime window. * @property conversionRate Conversion percentage in the active realtime window. * @property deviceBreakdown Live visitor distribution by device bucket. * @property topActivePages Ranked active pages with live visitor counts. * @property lastUpdated Timestamp at which the snapshot was produced. */ interface RealtimeStats { activeVisitors: number; currentPageViews: number; activeSessions?: number; eventsPerSecond?: number; conversions?: number; conversionRate?: number; deviceBreakdown?: AnalyticsDashboardData["deviceBreakdown"]; topActivePages: Array<{ url: string; visitors: number; }>; lastUpdated: number; } /** * Inclusive analytics time range used by filters and queries. */ interface TimeRange { start: number; end: number; } /** * Retention datapoint for a single cohort interval. * * @property retainedUsers Number of users retained for the interval. * @property retentionRate Percentage retained at the interval boundary. */ interface CohortRetentionPoint { day: number; retainedUsers: number; retentionRate: number; } /** * Cohort bucket produced by the shared cohort analysis helpers. * * @property retained Retention datapoints for the cohort over time. */ interface CohortBucket { cohort: string; size: number; retained: CohortRetentionPoint[]; lastActivity: number; } /** * Full retention cohort analysis returned by the shared analytics helpers. * * @property intervals Day intervals included in the analysis. * @property cohorts Cohort buckets included in the analysis. */ interface CohortAnalysis { name: string; cohortType: "user" | "session"; intervals: number[]; cohorts: CohortBucket[]; generatedAt: number; } /** * @module GoogleAnalyticsProvider * @package @geenius/analytics * @description Implements the shared Google Analytics 4 provider using the * global `gtag.js` browser script. The provider injects the runtime script on * demand and adapts shared event/page payloads to GA4 conventions. */ declare global { interface Window { dataLayer: unknown[]; gtag: (...args: unknown[]) => void; } } /** * @module NeonAnalyticsStore * @package @geenius/analytics/neon * @description Neon/Postgres analytics provider contract with append-only * migrations, a deterministic in-memory test fallback, and a native * @neondatabase/serverless client factory for production use. */ /** * SQL execution callback used by the Neon analytics store. * * @param sql SQL statement to execute. * @param parameters Optional positional parameters for the statement. * @returns Driver-specific result payload. */ type NeonSqlExecutor = (sql: string, parameters?: readonly unknown[]) => Promise; /** * Minimal SQL client contract accepted by the Neon store factory. */ interface NeonAnalyticsClient { query(sql: string, parameters?: readonly unknown[]): Promise; } /** * Options used to create the Neon analytics store. */ interface NeonAnalyticsStoreOptions { execute?: NeonSqlExecutor; /** * Tenant identifier written to analytics rows and used to scope SQL reads. * Use this in multi-tenant Neon deployments alongside the packaged RLS policy. */ tenantId?: string; } /** * Public Neon analytics store contract for event, page-view, identity, and migration operations. * * @property setUserProperties Merges profile traits into a stored Neon user row. * @property flush Resolves after synchronous SQL-backed writes have completed. * @property shutdown Closes owned connection resources when the store created them. * @property purgeExpiredData Deletes records older than the retention cutoff. */ interface NeonAnalyticsStore { trackEvent(event: AnalyticsEvent): Promise; trackPageView(pageView: PageView): Promise; identifyUser(userId: string, traits?: UserTraits): Promise; setUserProperties(userId: string, traits: UserTraits): Promise; listEvents(): Promise; getEvents(query?: AnalyticsEventQuery): Promise>; getPageViews(query?: AnalyticsPageViewQuery): Promise>; getDashboardData(query?: AnalyticsDashboardQuery): Promise; getRealtimeStats(query?: RealtimeStatsQuery): Promise; getFunnelResults(query: FunnelResultsQuery): Promise; getCohortAnalysis(query?: CohortAnalysisQuery): Promise; flush(): Promise; shutdown(): Promise; purgeExpiredData(cutoffTimestamp: number): Promise; deleteUserData(userId: string): Promise; runMigrations(): Promise; } /** * Append-only SQL migrations required by the Neon analytics provider. */ declare const analyticsMigrations: readonly [{ readonly id: "0001_create_analytics_events"; readonly sql: "CREATE TABLE IF NOT EXISTS geenius_analytics_events (\n id text PRIMARY KEY,\n type text NOT NULL,\n name text NOT NULL,\n properties jsonb,\n url text NOT NULL DEFAULT '',\n referrer text NOT NULL DEFAULT '',\n session_id text NOT NULL,\n user_id text,\n device jsonb NOT NULL,\n timestamp bigint NOT NULL\n);"; }, { readonly id: "0002_create_analytics_pageviews"; readonly sql: "CREATE TABLE IF NOT EXISTS geenius_analytics_pageviews (\n id bigserial PRIMARY KEY,\n url text NOT NULL,\n title text NOT NULL DEFAULT '',\n referrer text NOT NULL DEFAULT '',\n duration integer,\n session_id text NOT NULL,\n user_id text,\n timestamp bigint NOT NULL\n);"; }, { readonly id: "0003_create_analytics_users"; readonly sql: "CREATE TABLE IF NOT EXISTS geenius_analytics_users (\n user_id text PRIMARY KEY,\n traits jsonb,\n updated_at bigint NOT NULL\n);"; }, { readonly id: "0004_enable_tenant_rls"; readonly sql: "ALTER TABLE geenius_analytics_events\n ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT 'default';\nALTER TABLE geenius_analytics_pageviews\n ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT 'default';\nALTER TABLE geenius_analytics_users\n ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT 'default';\nCREATE INDEX IF NOT EXISTS geenius_analytics_events_tenant_idx\n ON geenius_analytics_events (tenant_id, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_pageviews_tenant_idx\n ON geenius_analytics_pageviews (tenant_id, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_users_tenant_idx\n ON geenius_analytics_users (tenant_id);\nALTER TABLE geenius_analytics_events ENABLE ROW LEVEL SECURITY;\nALTER TABLE geenius_analytics_pageviews ENABLE ROW LEVEL SECURITY;\nALTER TABLE geenius_analytics_users ENABLE ROW LEVEL SECURITY;\nALTER TABLE geenius_analytics_events FORCE ROW LEVEL SECURITY;\nALTER TABLE geenius_analytics_pageviews FORCE ROW LEVEL SECURITY;\nALTER TABLE geenius_analytics_users FORCE ROW LEVEL SECURITY;\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_policies\n WHERE schemaname = current_schema()\n AND tablename = 'geenius_analytics_events'\n AND policyname = 'geenius_analytics_events_tenant_isolation'\n ) THEN\n CREATE POLICY geenius_analytics_events_tenant_isolation\n ON geenius_analytics_events\n TO PUBLIC\n USING (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'))\n WITH CHECK (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'));\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_policies\n WHERE schemaname = current_schema()\n AND tablename = 'geenius_analytics_pageviews'\n AND policyname = 'geenius_analytics_pageviews_tenant_isolation'\n ) THEN\n CREATE POLICY geenius_analytics_pageviews_tenant_isolation\n ON geenius_analytics_pageviews\n TO PUBLIC\n USING (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'))\n WITH CHECK (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'));\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_policies\n WHERE schemaname = current_schema()\n AND tablename = 'geenius_analytics_users'\n AND policyname = 'geenius_analytics_users_tenant_isolation'\n ) THEN\n CREATE POLICY geenius_analytics_users_tenant_isolation\n ON geenius_analytics_users\n TO PUBLIC\n USING (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'))\n WITH CHECK (tenant_id = COALESCE(NULLIF(current_setting('geenius.analytics_tenant_id', true), ''), 'default'));\n END IF;\nEND $$;"; }, { readonly id: "0005_add_query_filter_indexes"; readonly sql: "CREATE INDEX IF NOT EXISTS geenius_analytics_events_tenant_user_idx\n ON geenius_analytics_events (tenant_id, user_id, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_events_tenant_session_idx\n ON geenius_analytics_events (tenant_id, session_id, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_events_tenant_name_idx\n ON geenius_analytics_events (tenant_id, name, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_events_tenant_type_idx\n ON geenius_analytics_events (tenant_id, type, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_pageviews_tenant_url_idx\n ON geenius_analytics_pageviews (tenant_id, url, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_pageviews_tenant_user_idx\n ON geenius_analytics_pageviews (tenant_id, user_id, timestamp);\nCREATE INDEX IF NOT EXISTS geenius_analytics_pageviews_tenant_session_idx\n ON geenius_analytics_pageviews (tenant_id, session_id, timestamp);"; }]; /** * Applies the analytics migration set using the supplied SQL executor. * * @param execute SQL executor bound to a Neon or Postgres connection. * @returns A promise that settles when every migration has been applied idempotently. */ declare function runAnalyticsMigrations(execute: NeonSqlExecutor): Promise; /** * Creates a Neon analytics store with injectable SQL execution. * * @param options Store options, including an optional SQL executor for tests or serverless runtimes. * @returns A Neon analytics store implementation. */ declare function createNeonAnalyticsStore(options?: NeonAnalyticsStoreOptions): NeonAnalyticsStore; /** * Creates a Neon analytics store from any query-capable driver client. * * @param client A Neon Pool, Client, or compatible query client. * @returns A Neon analytics store backed by the supplied driver client. */ declare function createNeonAnalyticsStoreFromClient(client: NeonAnalyticsClient, options?: Omit): NeonAnalyticsStore; /** * Creates a Neon analytics store from a Neon connection string. * * @param connectionString Neon/Postgres connection string. * @returns A Neon analytics store backed by a native `@neondatabase/serverless` Pool. */ declare function createNeonAnalyticsStoreFromConnectionString(connectionString: string): Promise; export { type NeonAnalyticsClient, type NeonAnalyticsStore, type NeonAnalyticsStoreOptions, type NeonSqlExecutor, analyticsMigrations, createNeonAnalyticsStore, createNeonAnalyticsStoreFromClient, createNeonAnalyticsStoreFromConnectionString, runAnalyticsMigrations };