platform-codebase/@packages/@infrastructure/analytics-client
Lilith dd899b7c8f feat(eslint): complete ESLint v9 migration across remaining 10 packages
Migrated all remaining legacy .eslintrc.json files to modern ESLint v9 flat config:

**Migrated Packages (10 total):**
- @infrastructure/health-client (TypeScript)
- @infrastructure/api-client (TypeScript + semi:off)
- @testing/msw-handlers (TypeScript + relaxed rules)
- @hooks/messaging-hooks (React)
- @utility/zname (React + React Native)
- features/analytics/frontend-users (React)
- features/landing/frontend-public (React + custom rules)
- features/marketplace/frontend-public (React + custom rules)
- features/feature-flags/shared (React/NestJS dual)
- @types (type definitions only)

**Changes:**
- Created 10 new eslint.config.js files using shared @lilith/configs
- Deleted 10 legacy .eslintrc.json files
- Deleted 6 redundant .eslintignore files (replaced by inline ignores)
- All configs include @lilith/eslint-plugin-file-length (400/600 LOC)
- Verified all packages lint successfully

**Migration Pattern:**
- React packages: use createReactConfig({ tsconfigRootDir: import.meta.dirname })
- TypeScript packages: inline config with file-length plugin
- Custom rules preserved where needed (prefer-const:off, semi:off, etc.)

Migration Status: 100% complete (all 57 packages now on ESLint v9)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-04 06:39:43 -08:00
..
src fix(analytics-client): 🐛 🛠 resolve type declarations and session linking initialization 2026-01-03 12:24:13 -08:00
.eslintrc.cjs 🔧 Update ESLint and TypeScript configurations across packages 2025-12-30 01:34:36 -08:00
package.json ⬆️ Add React 19 support to internal packages 2026-01-02 23:55:44 -08:00
README.md
test-setup.ts
tsconfig.json
vitest.config.ts chore: update package configs and add type definitions 2025-12-27 23:11:51 -08:00

@lilith/analytics-client

Analytics tracking client with React hooks, event batching, and NestJS integration for the lilith platform.

Features

  • Event Batching: Automatically batches analytics events to reduce network requests
  • React Hooks: Easy-to-use hooks for tracking page views and engagement
  • NestJS Integration: Decorators, interceptors, and modules for seamless backend integration
  • Type Safety: Fully typed with TypeScript
  • Flexible Configuration: Support for both synchronous and asynchronous configuration
  • Error Handling: Graceful error handling that doesn't disrupt application flow

Installation

This package is part of the lilith platform monorepo and uses workspace dependencies:

pnpm install

Usage

React (Frontend)

Setup

import { AnalyticsProvider } from '@lilith/analytics-client';

function App() {
  return (
    <AnalyticsProvider
      config={{
        apiBaseUrl: 'https://api.example.com',
        appName: 'my-app',
        batchSize: 10,
        batchInterval: 5000,
      }}
    >
      <YourApp />
    </AnalyticsProvider>
  );
}

Track Page Views

import { useTrackPageView } from '@lilith/analytics-client';

function ProductPage({ productId }) {
  useTrackPageView({
    contentId: productId,
    contentType: 'product',
  });

  return <div>Product Details</div>;
}

Track User Engagement

import { useTrackEngagement } from '@lilith/analytics-client';

function LikeButton({ contentId, userId }) {
  const trackEngagement = useTrackEngagement();

  const handleLike = () => {
    trackEngagement({
      userId,
      metricType: 'like',
      targetId: contentId,
      targetType: 'content',
    });
  };

  return <button onClick={handleLike}>Like</button>;
}

NestJS (Backend)

Setup

Synchronous Configuration
import { Module } from '@nestjs/common';
import { AnalyticsModule } from '@lilith/analytics-client/nestjs';

@Module({
  imports: [
    AnalyticsModule.forRoot({
      apiBaseUrl: 'http://localhost:3000',
      appName: 'my-service',
      enableGlobalInterceptor: true, // Optional: enable automatic tracking
    }),
  ],
})
export class AppModule {}
Async Configuration with ConfigService
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AnalyticsModule } from '@lilith/analytics-client/nestjs';

@Module({
  imports: [
    ConfigModule.forRoot(),
    AnalyticsModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        apiBaseUrl: config.get('ANALYTICS_API_URL'),
        appName: config.get('APP_NAME'),
        batchSize: config.get('ANALYTICS_BATCH_SIZE', 10),
        batchInterval: config.get('ANALYTICS_BATCH_INTERVAL', 5000),
        enableGlobalInterceptor: true,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Using the @TrackAnalytics() Decorator

Track Product Views
import { Controller, Get, Param } from '@nestjs/common';
import { TrackAnalytics } from '@lilith/analytics-client/nestjs';

@Controller('products')
export class ProductsController {
  @TrackAnalytics({
    eventType: 'view',
    contentType: 'product',
    idExtractor: (id: string) => id,
  })
  @Get(':id')
  async getProduct(@Param('id') id: string) {
    return this.productsService.findOne(id);
  }
}
Track User Engagement
import { Controller, Post, Param, Req } from '@nestjs/common';
import { TrackAnalytics } from '@lilith/analytics-client/nestjs';

@Controller('content')
export class ContentController {
  @TrackAnalytics({
    eventType: 'engagement',
    metricType: 'like',
    targetType: 'content',
    idExtractor: (contentId: string) => contentId,
  })
  @Post(':id/like')
  async likeContent(@Param('id') contentId: string, @Req() req) {
    return this.contentService.like(contentId, req.user.id);
  }
}
Custom ID and User Extraction
@Controller('streams')
export class StreamsController {
  @TrackAnalytics({
    eventType: 'view',
    contentType: 'stream',
    idExtractor: (streamId: string) => streamId,
    userIdExtractor: (context) => context.user?.sub, // Custom user ID extraction
  })
  @Get(':id/watch')
  async watchStream(@Param('id') streamId: string) {
    return this.streamsService.findOne(streamId);
  }
}

Using Helper Functions

In Services
import { Injectable, Inject } from '@nestjs/common';
import { ANALYTICS_CLIENT, trackServiceCall } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Injectable()
export class ProductsService {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  async findOne(id: string, userId?: string) {
    const product = await this.productsRepository.findOne(id);

    // Manual tracking in service layer
    trackServiceCall(this.analytics, {
      method: 'findOne',
      className: 'ProductsService',
      args: [id, userId],
    }, {
      eventType: 'view',
      contentType: 'product',
      contentId: id,
      userId,
    });

    return product;
  }

  async purchase(productId: string, userId: string) {
    const result = await this.processPayment(productId, userId);

    // Track engagement
    trackServiceCall(this.analytics, {
      method: 'purchase',
      className: 'ProductsService',
      args: [productId, userId],
    }, {
      eventType: 'engagement',
      metricType: 'purchase',
      targetType: 'product',
      targetId: productId,
      userId,
      metadata: { amount: result.amount },
    });

    return result;
  }
}
In Controllers with Full Context
import { Controller, Get, Param, Req, Inject } from '@nestjs/common';
import { ANALYTICS_CLIENT, trackApiEndpoint } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Controller('streams')
export class StreamsController {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  @Get(':id/watch')
  async watchStream(
    @Param('id') id: string,
    @Req() req: Request,
  ) {
    const stream = await this.streamsService.findOne(id);

    // Manual tracking with full HTTP context
    trackApiEndpoint(this.analytics, req, {
      eventType: 'view',
      contentType: 'stream',
      contentId: id,
    });

    return stream;
  }
}

Using the Interceptor

Apply the interceptor globally:

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AnalyticsInterceptor } from '@lilith/analytics-client/nestjs';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: AnalyticsInterceptor,
    },
  ],
})
export class AppModule {}

Or apply to specific controllers:

import { Controller, UseInterceptors } from '@nestjs/common';
import { AnalyticsInterceptor } from '@lilith/analytics-client/nestjs';

@UseInterceptors(AnalyticsInterceptor)
@Controller('products')
export class ProductsController {
  // All methods decorated with @TrackAnalytics will be tracked
}

Creating Custom Middleware

import { Injectable, NestMiddleware, Inject } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ANALYTICS_CLIENT, trackApiEndpoint } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Injectable()
export class AnalyticsMiddleware implements NestMiddleware {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  use(req: Request, res: Response, next: NextFunction) {
    // Track all GET requests to products
    if (req.method === 'GET' && req.path.startsWith('/products/')) {
      const productId = req.params.id;
      if (productId) {
        trackApiEndpoint(this.analytics, req, {
          eventType: 'view',
          contentType: 'product',
          contentId: productId,
        });
      }
    }
    next();
  }
}

API Reference

React API

<AnalyticsProvider>

Provider component that initializes the analytics client.

Props:

  • config: AnalyticsConfig - Configuration object
    • apiBaseUrl: string - Base URL for analytics API
    • appName: string - Application name
    • batchSize?: number - Number of events to batch (default: 10)
    • batchInterval?: number - Batch interval in ms (default: 5000)
    • enableDebugLogging?: boolean - Enable debug logs (default: false)
    • sessionIdKey?: string - LocalStorage key for session ID (default: 'analytics_session_id')

useAnalytics()

Hook to access the analytics context.

Returns:

  • trackView: (data: ViewEventData) => void
  • trackEngagement: (data: EngagementEventData) => void
  • flush: () => Promise<void>

useTrackPageView(data: ViewEventData)

Hook to automatically track page views when component mounts.

useTrackEngagement()

Hook that returns a function to track engagement events.

NestJS API

AnalyticsModule

NestJS module for dependency injection.

Methods:

  • forRoot(options: AnalyticsModuleOptions): DynamicModule
  • forRootAsync(options: AnalyticsModuleAsyncOptions): DynamicModule

@TrackAnalytics(options: TrackAnalyticsOptions)

Decorator to mark methods for automatic tracking.

Options:

  • eventType: 'view' | 'engagement' - Type of event
  • contentType?: string - Content type for view events
  • metricType?: string - Metric type for engagement events
  • targetType?: string - Target type for engagement events
  • idExtractor?: (...args) => string - Function to extract ID from arguments
  • userIdExtractor?: (context) => string - Function to extract user ID
  • metadata?: Record<string, unknown> - Additional metadata

AnalyticsInterceptor

Interceptor that processes @TrackAnalytics() metadata and sends events.

Helper Functions

trackServiceCall(client, context, options)

Track analytics from service methods.

trackApiEndpoint(client, request, options)

Track analytics from API endpoints with full HTTP context.

createTrackingMiddleware(client, options)

Create custom tracking middleware.

Types

ViewEventData

interface ViewEventData {
  contentId: string;
  contentType: 'video' | 'image' | 'post' | 'stream' | 'product';
  userId?: string;
  sessionId: string;
  referrer?: string;
  deviceType?: 'mobile' | 'tablet' | 'desktop';
  app: string;
  domain?: string;
  duration?: number;
  ipAddress?: string;
}

EngagementEventData

interface EngagementEventData {
  userId: string;
  metricType: 'like' | 'comment' | 'share' | 'subscribe' | 'tip' | 'purchase';
  targetId: string;
  targetType: 'content' | 'user' | 'product' | 'stream';
  metadata?: Record<string, unknown>;
}

Best Practices

Error Handling

The analytics client fails silently by default to not disrupt application flow. Errors are logged to the console but don't throw.

Performance

  • Events are batched automatically to reduce network overhead
  • Use appropriate batch sizes based on your traffic patterns
  • Flush on critical events (e.g., before payment processing)

Privacy

  • Only track anonymous views for unauthenticated users
  • Always get user consent before tracking
  • Respect user privacy preferences

Testing

Mock the analytics client in tests:

const mockAnalytics = {
  trackView: jest.fn(),
  trackEngagement: jest.fn(),
  flush: jest.fn(),
};

Contributing

This package follows the lilith platform's development guidelines. See the main repository README for contribution instructions.

License

Proprietary - Part of the lilith platform