npm package: @rachelallyson/stratum-posthog-js
Description: A PostHog plugin for Stratum Observability that enables seamless integration between your application's observability events and PostHog analytics.
Author: Rachel Allyson
Latest version: 1.0.8

# Stratum PostHog Plugin

A PostHog plugin for Stratum Observability that enables seamless integration between your application's observability events and PostHog analytics.

## Features

- ✅ **Event Publishing**: Send Stratum events to PostHog with full type safety
- ✅ **User Identification Event Types**: Handle user identification through flexible event types
- ✅ **Flexible Event Naming**: Name your events anything you want while using PostHog identification
- ✅ **Browser Support**: Full browser integration with PostHog JS SDK
- ✅ **Type Safety**: Complete TypeScript support with generics
- ✅ **Flexible Configuration**: Support for both configuration-based and pre-initialized PostHog instances
- ✅ **Stratum Integration**: Properly registered event types with Stratum framework

## Installation

```bash
npm install @rachelallyson/stratum-posthog-js
```

## Quick Start

### 1. Basic Setup

```typescript
import { PosthogService, PosthogPlugin, PosthogEventTypes } from '@rachelallyson/stratum-posthog-js';

// Define your event catalog using Stratum's standard pattern
const catalog = {
  BUTTON_CLICK: {
    id: "BUTTON_CLICK",
    description: "User clicked a button",
    eventType: PosthogEventTypes.CAPTURE,
    properties: {
      button_name: "string",
      page: "string",
    },
  },
  USER_LOGIN: {
    id: "USER_LOGIN",
    description: "User logged in",
    eventType: PosthogEventTypes.CAPTURE,
    properties: {
      login_method: "string",
      user_id: "string",
    },
  },
  // User identification events - name them anything you want!
  USER_SIGNED_IN: {
    id: "USER_SIGNED_IN",
    description: "User signed in and identified with PostHog",
    eventType: PosthogEventTypes.IDENTIFY,
    properties: {
      distinct_id: "string",
      person_properties: "object",
      anonymous_id: "string",
    },
  },
  USER_SIGNED_OUT: {
    id: "USER_SIGNED_OUT",
    description: "User signed out and reset PostHog",
    eventType: PosthogEventTypes.RESET,
    properties: {
      previous_distinct_id: "string",
      reset_device_id: "string",
    },
  },
  USER_LINKED: {
    id: "USER_LINKED",
    description: "User linked with another ID",
    eventType: PosthogEventTypes.ALIAS,
    properties: {
      distinct_id: "string",
      alias: "string",
    },
  },
  // Alternative naming examples
  CUSTOMER_ONBOARDED: {
    id: "CUSTOMER_ONBOARDED",
    description: "Customer completed onboarding",
    eventType: PosthogEventTypes.IDENTIFY,
    properties: {
      distinct_id: "string",
      person_properties: "object",
      anonymous_id: "string",
    },
  },
  SESSION_ENDED: {
    id: "SESSION_ENDED",
    description: "User session ended",
    eventType: PosthogEventTypes.RESET,
    properties: {
      previous_distinct_id: "string",
      reset_device_id: "string",
    },
  },
  ACCOUNT_MERGED: {
    id: "ACCOUNT_MERGED",
    description: "User accounts merged",
    eventType: PosthogEventTypes.ALIAS,
    properties: {
      distinct_id: "string",
      alias: "string",
    },
  },
};

// Create the service
const service = new PosthogService({
  catalog: { items: catalog },
  plugins: [
    PosthogPlugin({
      POSTHOG_KEY: "your-posthog-key",
      config: {
        api_host: "https://your-posthog-host.com",
        autocapture: true,
      },
    }),
  ],
  productName: "my-app",
  productVersion: "1.0.0",
});
```

### 2. Publishing Events

```typescript
// Publish a regular event with properties
await service.publish("BUTTON_CLICK", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        button_name: "submit",
        page: "checkout",
      },
    },
  },
});

// Publish a simple event without properties
await service.publish("PAGE_VIEW", {
  pluginData: {
    PosthogPlugin: {
      properties: {},
    },
  },
});

// Or even simpler - the plugin will work with empty properties
await service.publish("PAGE_VIEW", {
  pluginData: {
    PosthogPlugin: {},
  },
});
```

## User Identification (Event Types)

Following Stratum's principle that "Events are first-class citizens," user identification is handled through event types rather than specific event names. This gives you complete flexibility in naming your events!

### Available Event Types

- `PosthogEventTypes.IDENTIFY` - Calls PostHog's `identify()` method
- `PosthogEventTypes.RESET` - Calls PostHog's `reset()` method  
- `PosthogEventTypes.ALIAS` - Calls PostHog's `alias()` method

### Identifying Users

When a user logs in, publish an event with the `IDENTIFY` event type:

```typescript
// Get the current anonymous ID before identification
const anonymousId = service.getDistinctId();

// Identify the user using any event name you want!
await service.publish("USER_SIGNED_IN", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: "user123",
        person_properties: {
          email: "user@example.com",
          name: "John Doe",
          subscription_tier: "premium",
          first_login_at: new Date().toISOString(),
        },
        anonymous_id: anonymousId || "",
      },
    },
  },
});

// Or use a different event name
await service.publish("CUSTOMER_ONBOARDED", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: "user123",
        person_properties: {
          email: "user@example.com",
          onboarding_step: "completed",
        },
        anonymous_id: anonymousId || "",
      },
    },
  },
});
```

### User Logout (Reset)

When a user logs out, publish an event with the `RESET` event type:

```typescript
// Reset user using any event name you want!
await service.publish("USER_SIGNED_OUT", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        previous_distinct_id: "user123",
        reset_device_id: "false", // or "true" to reset device ID
      },
    },
  },
});

// Or use a different event name
await service.publish("SESSION_ENDED", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        previous_distinct_id: "user123",
        reset_device_id: "false",
      },
    },
  },
});
```

### User Aliasing

Use an event with the `ALIAS` event type to link multiple distinct IDs to the same user:

```typescript
// Alias a user using any event name you want!
await service.publish("USER_LINKED", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: "frontend_user_123",
        alias: "backend_user_456",
      },
    },
  },
});

// Or use a different event name
await service.publish("ACCOUNT_MERGED", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: "account_123",
        alias: "account_456",
      },
    },
  },
});
```

### Getting Current User ID

```typescript
// Get the current distinct ID (anonymous or identified)
const distinctId = service.getDistinctId();
console.log("Current user ID:", distinctId);
```

### Complete User Management Example

```typescript
class UserManager {
  constructor(private service: PosthogService) {}

  async login(userId: string, userData: any) {
    // Get current anonymous ID
    const anonymousId = this.service.getDistinctId();

    // Identify the user - name the event anything you want!
    await this.service.publish("USER_SIGNED_IN", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            distinct_id: userId,
            person_properties: {
              email: userData.email,
              name: userData.name,
              login_method: "email",
              first_login_at: new Date().toISOString(),
            },
            anonymous_id: anonymousId || "",
          },
        },
      },
    });

    // Track login event
    await this.service.publish("USER_LOGIN", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            login_method: "email",
            user_id: userId,
          },
        },
      },
    });
  }

  async logout(userId: string) {
    // Track logout event
    await this.service.publish("USER_LOGOUT", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            user_id: userId,
            session_duration: "30 minutes",
          },
        },
      },
    });

    // Reset PostHog user - name the event anything you want!
    await this.service.publish("USER_SIGNED_OUT", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            previous_distinct_id: userId,
            reset_device_id: "false",
          },
        },
      },
    });
  }

  async updateUserProperties(userId: string, properties: any) {
    // Update user properties - name the event anything you want!
    await this.service.publish("USER_SIGNED_IN", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            distinct_id: userId,
            person_properties: properties,
            anonymous_id: "", // Empty string for no change
          },
        },
      },
    });

    // Track property update event
    await this.service.publish("USER_PROPERTIES_UPDATED", {
      pluginData: {
        PosthogPlugin: {
          properties: {
            user_id: userId,
            updated_properties: properties,
          },
        },
      },
    });
  }
}
```

## Configuration Options

### Basic Configuration

```typescript
PosthogPlugin({
  POSTHOG_KEY: "your-posthog-key",
  config: {
    api_host: "https://your-posthog-host.com",
    autocapture: true,
    capture_pageview: true,
    disable_session_recording: false,
    persistence: 'localStorage',
  },
})
```

### Using Pre-initialized PostHog Instance

```typescript
import posthog from 'posthog-js';

// Initialize PostHog elsewhere in your app
const posthogInstance = posthog.init("your-posthog-key", {
  api_host: "https://your-posthog-host.com",
  autocapture: true,
});

// Use the pre-initialized instance
PosthogPlugin({
  posthogInstance: posthogInstance,
})
```

### Configuration Options Reference

| Option | Type | Description |
|--------|------|-------------|
| `POSTHOG_KEY` | string | Your PostHog project API key |
| `POSTHOG_HOST` | string | Legacy support for PostHog host URL |
| `config` | PosthogConfig | PostHog configuration options |
| `posthogInstance` | PostHog | Pre-initialized PostHog instance |
| `DEBUG` | boolean | Enable debug logging |

## Best Practices

### 1. Use Event Types for User Identification

Use the appropriate event types instead of relying on specific event names:

```typescript
// ✅ Good: Use event types
{
  id: "USER_SIGNED_IN",
  eventType: PosthogEventTypes.IDENTIFY,
  properties: { /* ... */ }
}

// ❌ Bad: Don't rely on event names
// The plugin will call PostHog methods based on eventType, not event name
```

### 2. Name Events Meaningfully

Since you can name events anything you want, use descriptive names that make sense for your business:

```typescript
// Good: Business-specific event names
CUSTOMER_ONBOARDED: {
  id: "CUSTOMER_ONBOARDED",
  eventType: PosthogEventTypes.IDENTIFY,
  // ...
},
ACCOUNT_MERGED: {
  id: "ACCOUNT_MERGED", 
  eventType: PosthogEventTypes.ALIAS,
  // ...
},
SESSION_EXPIRED: {
  id: "SESSION_EXPIRED",
  eventType: PosthogEventTypes.RESET,
  // ...
}
```

### 3. Use Unique Distinct IDs

Ensure each user has a unique distinct ID to avoid data merging:

```typescript
// Good: Use database user ID
await service.publish("USER_SIGNED_IN", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: user.databaseId,
        person_properties: userProperties,
        anonymous_id: anonymousId,
      },
    },
  },
});
```

### 4. Always Reset on Logout

Even if users don't share computers, always publish a reset event on logout:

```typescript
async function handleLogout() {
  // Track logout event
  await service.publish("USER_LOGOUT", { /* ... */ });
  
  // Reset PostHog user
  await service.publish("USER_SIGNED_OUT", { /* ... */ });
}
```

### 5. Set Person Properties During Identification

Pass all available person properties during identification to keep user profiles up to date:

```typescript
await service.publish("USER_SIGNED_IN", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        distinct_id: user.id,
        person_properties: {
          email: user.email,
          name: user.name,
          subscription_tier: user.subscription,
          signup_date: user.createdAt,
          last_login: new Date().toISOString(),
        },
        anonymous_id: anonymousId,
      },
    },
  },
});
```

### 6. Use Flexible Plugin Data

The PostHOG plugin is designed to be flexible and user-friendly. You can send events with or without properties:

```typescript
// With rich properties
await service.publish("BUTTON_CLICK", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        button_name: "submit",
        page: "checkout",
        user_type: "premium"
      },
    },
  },
});

// With minimal properties
await service.publish("PAGE_VIEW", {
  pluginData: {
    PosthogPlugin: {
      properties: {},
    },
  },
});

// Or even simpler - the plugin works with minimal configuration
await service.publish("PAGE_VIEW", {
  pluginData: {
    PosthogPlugin: {},
  },
});
```

All events are sent to PostHOG regardless of whether plugin data is provided, making the plugin easy to use in various scenarios.

### 6. Handle Anonymous Users

Anonymous users can still generate events before identification:

```typescript
// This works even for anonymous users
await service.publish("PAGE_VIEW", {
  pluginData: {
    PosthogPlugin: {
      properties: {
        page: "home",
        referrer: document.referrer,
      },
    },
  },
});
```

## API Reference

### PosthogService Methods

| Method | Description |
|--------|-------------|
| `publish(eventId, options?)` | Publish an event to PostHog |
| `getDistinctId()` | Get the current user's distinct ID |
| `isPostHogAvailable()` | Check if PostHog is available and initialized |

### Event Types

| Event Type | Description | PostHog Method Called |
|------------|-------------|----------------------|
| `PosthogEventTypes.IDENTIFY` | Identify a user with PostHog | `posthog.identify()` |
| `PosthogEventTypes.RESET` | Reset the current user (logout) | `posthog.reset()` |
| `PosthogEventTypes.ALIAS` | Alias a user (link multiple IDs) | `posthog.alias()` |
| `PosthogEventTypes.CAPTURE` | Regular PostHog event | `posthog.capture()` |
| `PosthogEventTypes.ERROR` | Error event | `posthog.capture("$exception")` |

### Required Properties by Event Type

#### IDENTIFY Events

```typescript
{
  distinct_id: "string",
  person_properties: "object",
  anonymous_id: "string",
}
```

#### RESET Events

```typescript
{
  previous_distinct_id: "string",
  reset_device_id: "string",
}
```

#### ALIAS Events

```typescript
{
  distinct_id: "string",
  alias: "string",
}
```

### Types

```typescript
interface PosthogPluginOptions {
  POSTHOG_KEY?: string;
  POSTHOG_HOST?: string;
  config?: PosthogConfig;
  posthogInstance?: PostHog;
  DEBUG?: boolean;
}
```

## Examples

See the `src/example/` directory for complete working examples including:

- Basic event publishing
- User identification workflow using event types
- Flexible event naming examples
- Login/logout management
- User property updates
- User aliasing

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request

## License

Apache-2.0