diff --git a/packages/browser/package.json b/packages/browser/package.json index 15de47825..8dff31a47 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -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": "hey@posthog.com", diff --git a/packages/browser/src/posthog-core.ts b/packages/browser/src/posthog-core.ts index fa48b3050..975b2c30f 100644 --- a/packages/browser/src/posthog-core.ts +++ b/packages/browser/src/posthog-core.ts @@ -97,6 +97,8 @@ import { isArray, isEmptyObject, isObject, + TypedEventCapture, + createTypedEventCapture, } from '@posthog/core' import { uuidv7 } from './uuidv7' import { WebExperiments } from './web-experiments' @@ -340,6 +342,8 @@ export class PostHog { SentryIntegration: typeof SentryIntegration sentryIntegration: (options?: SentryIntegrationOptions) => ReturnType + readonly typed: TypedEventCapture + private _internalEventEmitter = new SimpleEventEmitter() // Legacy property to support existing usage - this isn't technically correct but it's what it has always been - a proxy for flags being loaded @@ -370,6 +374,7 @@ export class PostHog { this._visibilityStateListener = null this._initialPersonProfilesConfig = null this._cachedPersonProperties = null + this.typed = createTypedEventCapture(this) this.featureFlags = new PostHogFeatureFlags(this) this.toolbar = new Toolbar(this) this.scrollManager = new ScrollManager(this) diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 13c551fd7..93c62f53b 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -3,6 +3,9 @@ import type { SegmentAnalytics } from './extensions/segment-integration' import { PostHog } from './posthog-core' import { KnownUnsafeEditableEvent } from '@posthog/core' import { Survey } from './posthog-surveys-types' + +// Re-export PostHogEventSchemas from @posthog/core for typed events +export type { PostHogEventSchemas } from '@posthog/core' // only importing types here, so won't affect the bundle // eslint-disable-next-line posthog-js/no-external-replay-imports import type { SAMPLED } from './extensions/replay/external/triggerMatching' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 19ef23b2a..ba8cdeeaf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,3 +5,4 @@ export { uuidv7 } from './vendor/uuidv7' export * from './posthog-core' export * from './posthog-core-stateless' export * from './types' +export * from './typed-events' diff --git a/packages/core/src/posthog-core.ts b/packages/core/src/posthog-core.ts index ebe51938d..b94313e09 100644 --- a/packages/core/src/posthog-core.ts +++ b/packages/core/src/posthog-core.ts @@ -28,6 +28,7 @@ import { Compression, PostHogPersistedProperty } from './types' import { maybeAdd, PostHogCoreStateless, QuotaLimitedFeature } from './posthog-core-stateless' import { uuidv7 } from './vendor/uuidv7' import { isPlainError } from './utils' +import { TypedEventCapture, createTypedEventCapture } from './typed-events' export abstract class PostHogCore extends PostHogCoreStateless { // options @@ -40,6 +41,9 @@ export abstract class PostHogCore extends PostHogCoreStateless { private _sessionMaxLengthSeconds: number = 24 * 60 * 60 // 24 hours protected sessionProps: PostHogEventProperties = {} + // Typed event capture instance + public readonly typed: TypedEventCapture + constructor(apiKey: string, options?: PostHogCoreOptions) { // Default for stateful mode is to not disable geoip. Only override if explicitly set const disableGeoipOption = options?.disableGeoip ?? false @@ -51,6 +55,9 @@ export abstract class PostHogCore extends PostHogCoreStateless { this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800 // 30 minutes + + // Initialize typed event capture + this.typed = createTypedEventCapture(this) } protected setupBootstrap(options?: Partial): void { diff --git a/packages/core/src/typed-events.ts b/packages/core/src/typed-events.ts new file mode 100644 index 000000000..2290b5e4e --- /dev/null +++ b/packages/core/src/typed-events.ts @@ -0,0 +1,51 @@ +/** + * Typed event capture infrastructure for PostHog. + * + * Event schemas can be augmented in PostHogEventSchemas interface via module augmentation. + * This is typically done by generated types from `posthog-cli schema pull`. + * + * @example + * // In your types file or generated file: + * declare module '@posthog/core' { + * interface PostHogEventSchemas { + * 'user_signed_up': { plan: string; trial: boolean } + * 'purchase_completed': { amount: number; currency: string } + * } + * } + * + * // Usage: + * posthog.typed.user_signed_up({ plan: 'pro', trial: true }) + * posthog.typed.purchase_completed({ amount: 99.99, currency: 'USD' }) + */ + +import type { PostHogEventSchemas } from './types' + +// Utility type that allows schema properties plus any additional properties +// The schema properties are strictly typed, additional ones are any +export type EventWithAdditionalProperties = T & Record + +/** + * Mapped type that creates typed methods for each event in PostHogEventSchemas. + * Methods are generated dynamically based on the augmented interface. + */ +export type TypedEventCapture any }> = { + [K in keyof PostHogEventSchemas]: ( + properties: EventWithAdditionalProperties + ) => ReturnType +} + +/** + * Creates a Proxy that dynamically generates typed event methods + * based on the PostHogEventSchemas interface. + */ +export function createTypedEventCapture any }>( + client: Client +): TypedEventCapture { + return new Proxy({} as TypedEventCapture, { + get: (_target, eventName: string) => { + return (properties: any) => { + return client.capture(eventName, properties) + } + }, + }) +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5612372a9..3f8094e06 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -129,6 +129,12 @@ export type PostHogEventProperties = { [key: string]: JsonType } +// Event schemas can be augmented by generated types from posthog-cli schema pull +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostHogEventSchemas { + // This interface is intentionally empty and should be augmented by generated types +} + export type PostHogGroupProperties = { [type: string]: string | number }