Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.275.1",
"version": "1.275.2",
"description": "Posthog-js allows you to automatically capture usage and send events to PostHog.",
"repository": "https://github.com/PostHog/posthog-js",
"author": "[email protected]",
Expand Down
39 changes: 39 additions & 0 deletions packages/browser/playground/nextjs/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# PostHog Typed Events - Generated File Example

## Overview

This directory contains an example of a generated typed PostHog wrapper that provides type-safe event tracking.

## File: `posthog-typed.ts`

This file demonstrates what the PostHog CLI would generate to provide type safety for event tracking.

### Features

- **`captureTyped()`** - Type-safe event capture with compile-time validation
- **`captureUntyped()`** - Flexible capture for dynamic/untyped events
- Enforces required properties and types
- Allows additional properties beyond the schema
- Works with all event naming patterns (spaces, hyphens, single words, etc.)
- Backward compatible with deprecated `capture()` method

### Usage

```typescript
import posthog from './posthog-typed'

// Type-safe capture - TypeScript enforces all required properties
posthog.captureTyped('Product Added', {
product_id: '123',
name: 'Widget',
price: 42,
quantity: 1
})

// Flexible capture for dynamic events
posthog.captureUntyped('Custom Event', { any: 'data' })
```

## Test File

See `../../../test-captureTyped-simple.ts` for examples of type checking in action.
163 changes: 163 additions & 0 deletions packages/browser/playground/nextjs/src/posthog-typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* GENERATED FILE - DO NOT EDIT
*
* This file was auto-generated by PostHog CLI
*
* Provides captureTyped() for type-safe events and captureUntyped() for flexibility
*/

import originalPostHog from 'posthog-js'
import type { PostHog as OriginalPostHog, CaptureOptions, CaptureResult, Properties } from 'posthog-js'

// Product type used in multiple events
interface Product {
product_id: string
name: string
price: number
}

// Define event schemas
interface EventSchemas {
'Product Added': {
product_id: string
name: string
price: number
quantity: number
}
'Products Searched': {
products: Product[]
}
'Product Added to Wishlist': {
product_id: string
name: string
price: number
quantity: number
wishlist_id: string
wishlist_name: string
}
'Order Completed': {
products: Product[]
total: number
currency: string
}
'Custom Event': {
foo: string
}
'Logged in': Record<string, any>
'Logged out': Record<string, any>
'Clicked button': Record<string, any>
}

// Enhanced PostHog interface with both typed and untyped capture
interface TypedPostHog extends Omit<OriginalPostHog, 'capture'> {
/**
* Type-safe capture for defined events
*
* Note: Additional properties beyond the schema are allowed
*
* @example
* posthog.captureTyped('Product Added', {
* product_id: '123',
* name: 'Widget',
* price: 42,
* quantity: 1,
* custom_field: 'extra' // additional properties allowed
* })
*/
captureTyped<K extends keyof EventSchemas, P extends EventSchemas[K]>(
event_name: K,
properties: P & Record<string, any>,
options?: CaptureOptions
): CaptureResult | undefined

/**
* Flexible capture for any event (original behavior)
*
* @example
* posthog.captureUntyped('Custom Event Name', { any: 'data' })
*/
captureUntyped(
event_name: string,
properties?: Properties | null,
options?: CaptureOptions
): CaptureResult | undefined

/**
* @deprecated Use captureTyped() for type safety or captureUntyped() for flexibility
*/
capture(event_name: string, properties?: Properties | null, options?: CaptureOptions): CaptureResult | undefined
}

// Create the implementation
const createTypedPostHog = (original: OriginalPostHog): TypedPostHog => {
// Create the enhanced PostHog object
const enhanced: TypedPostHog = Object.create(original)

// Add captureTyped method
enhanced.captureTyped = function <K extends keyof EventSchemas, P extends EventSchemas[K]>(
event_name: K,
properties: P & Record<string, any>,
options?: CaptureOptions
): CaptureResult | undefined {
return original.capture(event_name, properties, options)
}

// Add captureUntyped method
enhanced.captureUntyped = function (
event_name: string,
properties?: Properties | null,
options?: CaptureOptions
): CaptureResult | undefined {
return original.capture(event_name, properties, options)
}

// Keep capture for backward compatibility (deprecated)
enhanced.capture = function (
event_name: string,
properties?: Properties | null,
options?: CaptureOptions
): CaptureResult | undefined {
console.warn(
'posthog.capture() is deprecated. Use captureTyped() for type safety or captureUntyped() for flexibility.'
)
return original.capture(event_name, properties, options)
}

// Proxy to delegate all other properties/methods to the original
return new Proxy(enhanced, {
get(target, prop) {
if (prop in target) {
return (target as any)[prop]
}
return (original as any)[prop]
},
set(target, prop, value) {
;(original as any)[prop] = value
return true
},
})
}

// Create and export the typed instance
const posthog = createTypedPostHog(originalPostHog as OriginalPostHog)

export default posthog
export { posthog }
export type { EventSchemas, TypedPostHog }

// Re-export everything else from posthog-js
export * from 'posthog-js'

/**
* MIGRATION GUIDE
* ===============
*
* Old code:
* posthog.capture('Product Added', { product_id: '123', name: 'Widget', price: 42, quantity: 1 })
*
* New code (with type safety):
* posthog.captureTyped('Product Added', { product_id: '123', name: 'Widget', price: 42, quantity: 1 })
*
* For untyped/dynamic events:
* posthog.captureUntyped('Custom Event', { any: 'data' })
*/
82 changes: 82 additions & 0 deletions packages/browser/test-captureTyped-simple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* eslint-disable no-console */
/**
* Test that captureTyped() provides type safety and captureUntyped() is flexible
*/

import posthog from './playground/nextjs/src/posthog-typed'

// ========================================
// TEST: captureTyped() with type safety
// ========================================

// ✅ Should work - correct properties
posthog.captureTyped('Product Added', {
product_id: '123',
name: 'Widget',
price: 42.99,
quantity: 1,
})

// ✅ Should work - additional properties allowed
posthog.captureTyped('Product Added', {
product_id: '123',
name: 'Widget',
price: 42,
quantity: 1,
custom_field: 'extra data',
})

// ❌ Should error - missing required properties
// @ts-expect-error - Missing required properties: name, price, quantity
posthog.captureTyped('Product Added', {
product_id: '123',
})

// ❌ Should error - wrong type for price
posthog.captureTyped('Product Added', {
product_id: '123',
name: 'Widget',
// @ts-expect-error - Type 'string' is not assignable to type 'number'
price: 'not-a-number',
quantity: 1,
})

// ❌ Should error - unknown event name
// @ts-expect-error - Argument of type '"Unknown Event"' is not assignable
posthog.captureTyped('Unknown Event', {
some: 'data',
})

// ========================================
// TEST: captureUntyped() is flexible
// ========================================

// ✅ All of these should work with captureUntyped
posthog.captureUntyped('Product Added', {
product_id: '123',
name: 'Widget',
price: 42,
quantity: 1,
})

posthog.captureUntyped('Product Added', {
product_id: '123',
// Missing properties is OK with untyped
})

posthog.captureUntyped('Product Added', {
product_id: '123',
name: 'Widget',
price: 'string is OK here',
quantity: '1',
})

posthog.captureUntyped('Any Random Event Name', {
any: 'properties',
work: 'here',
})

posthog.captureUntyped('Simple Event')
posthog.captureUntyped('Event With Null', null)

console.log('Type checking tests complete!')
Loading