autotel-posthog 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,369 @@
1
+ import { EventPayload, EventSubscriber } from "autotel-subscribers";
2
+ import { EventAttributes } from "autotel/event-subscriber";
3
+ import { PostHog } from "posthog-node";
4
+ //#region src/values.d.ts
5
+ /**
6
+ * Reading values that arrive from outside: a page's PostHog instance, an
7
+ * event's properties, an error someone threw. Nothing about them is known
8
+ * until one of these asks, and each answers with the type it looked for or
9
+ * `undefined`.
10
+ *
11
+ * This is the boundary. The `typeof` checks and the `unknown` parameters below
12
+ * are what a boundary is made of; past it, the package works with types.
13
+ *
14
+ * Internal - not exported from the package entry points.
15
+ */
16
+ /** An object whose fields have not been read yet. */
17
+ interface UnknownRecord {
18
+ [key: string]: unknown;
19
+ }
20
+ //#endregion
21
+ //#region src/subscriber/index.d.ts
22
+ /**
23
+ * Error context for enhanced error handling
24
+ *
25
+ * Provides detailed context about the event that caused an error.
26
+ */
27
+ interface ErrorContext {
28
+ /** The error that occurred */
29
+ error: Error;
30
+ /** Event name (if applicable) */
31
+ eventName?: string;
32
+ /** Event type (event, funnel, outcome, value) */
33
+ eventType?: 'event' | 'funnel' | 'outcome' | 'value';
34
+ /** Event attributes (filtered) */
35
+ attributes?: EventAttributes;
36
+ /** Subscriber name */
37
+ subscriberName: string;
38
+ }
39
+ type StringRedactor = (value: string) => string;
40
+ interface PostHogConfig {
41
+ /** PostHog API key (starts with phc_) - required if not providing custom client */
42
+ apiKey?: string;
43
+ /** PostHog host (defaults to US cloud) */
44
+ host?: string;
45
+ /** Enable/disable the subscriber */
46
+ enabled?: boolean;
47
+ /** Custom PostHog client instance (bypasses apiKey/host) */
48
+ client?: PostHog;
49
+ /**
50
+ * Use global browser client (window.posthog)
51
+ *
52
+ * When true, uses the PostHog client already loaded on the page via script tag.
53
+ * This is useful for Next.js apps that initialize PostHog in _app.tsx.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * // Browser - uses window.posthog
58
+ * const subscriber = new PostHogSubscriber({
59
+ * useGlobalClient: true,
60
+ * });
61
+ * ```
62
+ */
63
+ useGlobalClient?: boolean;
64
+ /**
65
+ * Serverless mode preset (AWS Lambda, Vercel Functions, Next.js API routes)
66
+ *
67
+ * When true, auto-configures for serverless environments:
68
+ * - flushAt: 1 (send immediately, don't batch)
69
+ * - flushInterval: 0 (disable interval-based flushing)
70
+ * - requestTimeout: 3000 (shorter timeout for fast responses)
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Vercel / Next.js API route
75
+ * const subscriber = new PostHogSubscriber({
76
+ * apiKey: 'phc_...',
77
+ * serverless: true,
78
+ * });
79
+ * ```
80
+ */
81
+ serverless?: boolean;
82
+ /** Flush batch when it reaches this size (default: 20, set to 1 for immediate send) */
83
+ flushAt?: number;
84
+ /** Flush interval in milliseconds (default: 10000, set to 0 to disable) */
85
+ flushInterval?: number;
86
+ /** Disable geoip lookup to reduce request size (default: false) */
87
+ disableGeoip?: boolean;
88
+ /** Request timeout in milliseconds (default: 10000) */
89
+ requestTimeout?: number;
90
+ /** Send feature flag evaluation events (default: true) */
91
+ sendFeatureFlags?: boolean;
92
+ /**
93
+ * Automatically filter out undefined and null values from attributes
94
+ *
95
+ * When true (default), undefined and null values are removed before sending.
96
+ * This improves DX when passing objects with optional properties.
97
+ *
98
+ * @default true
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // With filterUndefinedValues: true (default)
103
+ * subscriber.trackEvent('user.action', {
104
+ * userId: user.id,
105
+ * email: user.email, // might be undefined - will be filtered
106
+ * plan: user.subscription, // might be null - will be filtered
107
+ * });
108
+ * ```
109
+ */
110
+ filterUndefinedValues?: boolean;
111
+ /** Error callback for debugging and monitoring */
112
+ onError?: (error: Error) => void;
113
+ /**
114
+ * Enhanced error callback with event context
115
+ *
116
+ * Provides detailed context about the event that caused the error.
117
+ * If both onError and onErrorWithContext are provided, both are called.
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const subscriber = new PostHogSubscriber({
122
+ * apiKey: 'phc_...',
123
+ * onErrorWithContext: (ctx) => {
124
+ * console.error(`Failed to track ${ctx.eventType}: ${ctx.eventName}`, ctx.error);
125
+ * Sentry.captureException(ctx.error, { extra: ctx });
126
+ * }
127
+ * });
128
+ * ```
129
+ */
130
+ onErrorWithContext?: (context: ErrorContext) => void;
131
+ /** Known attribute paths to redact using slow-redact (path-based, immutable). */
132
+ redactPaths?: string[];
133
+ /** String redactor for value-based PII scanning. Applied after path-based redaction. */
134
+ stringRedactor?: StringRedactor;
135
+ /** Enable debug logging (default: false) */
136
+ debug?: boolean;
137
+ }
138
+ /**
139
+ * PostHog feature flag options
140
+ */
141
+ interface FeatureFlagOptions {
142
+ /** Group context for group-based feature flags */
143
+ groups?: Record<string, string | number>;
144
+ /** Group properties for feature flag evaluation */
145
+ groupProperties?: Record<string, Record<string, any>>;
146
+ /** Person properties for feature flag evaluation */
147
+ personProperties?: Record<string, any>;
148
+ /** Only evaluate locally, don't send $feature_flag_called event */
149
+ onlyEvaluateLocally?: boolean;
150
+ /** Send feature flag events even if disabled globally */
151
+ sendFeatureFlagEvents?: boolean;
152
+ }
153
+ /**
154
+ * Person properties for identify calls
155
+ */
156
+ interface PersonProperties {
157
+ /** Set properties (will update existing values) */
158
+ $set?: Record<string, any>;
159
+ /** Set properties only if they don't exist */
160
+ $set_once?: Record<string, any>;
161
+ /** Any custom properties */
162
+ [key: string]: any;
163
+ }
164
+ declare class PostHogSubscriber extends EventSubscriber {
165
+ readonly name = "PostHogSubscriber";
166
+ readonly version = "2.0.0";
167
+ private posthog;
168
+ private config;
169
+ private initPromise;
170
+ /** True when using browser's window.posthog (different API signature) */
171
+ private isBrowserClient;
172
+ /**
173
+ * The same client seen through the browser SDK's shape.
174
+ *
175
+ * The two SDKs disagree about `capture`: Node takes one object, the browser
176
+ * takes a name and properties. Rather than cast at each call site, the cast
177
+ * happens once here and the calls are typed against `posthog-js` itself — so
178
+ * a change to the browser signature is a compile error rather than a runtime
179
+ * one in a page.
180
+ */
181
+ private get browserClient();
182
+ private pathRedactor;
183
+ private stringRedactor;
184
+ constructor(config: PostHogConfig);
185
+ private initialize;
186
+ private setupErrorHandling;
187
+ private ensureInitialized;
188
+ /**
189
+ * Feature-flag options as posthog-node's own parameter type.
190
+ *
191
+ * SAFETY: FeatureFlagOptions above mirrors the fields posthog-node accepts -
192
+ * groups, person and group properties, and the two evaluation switches. The
193
+ * SDK declares them inline on each method rather than as a named type, so
194
+ * the correspondence is stated here once instead of at each call.
195
+ */
196
+ private flagOptions;
197
+ private extractDistinctId;
198
+ private redactProperties;
199
+ private redactStringValues;
200
+ private redactArray;
201
+ /** One value, redacted: a string masked, a container walked. */
202
+ private redactValue;
203
+ /**
204
+ * Set the string redactor. Called by autotel init() when attributeRedactor is configured.
205
+ * Can also be called manually.
206
+ */
207
+ setStringRedactor(redactor: StringRedactor): void;
208
+ /**
209
+ * Send payload to PostHog
210
+ *
211
+ * Maps autotel context to PostHog-specific field names:
212
+ * - autotel.trace_id → $trace_id
213
+ * - autotel.span_id → $span_id
214
+ * - autotel.correlation_id → $correlation_id
215
+ * - autotel.trace_url → $trace_url
216
+ */
217
+ protected sendToDestination(payload: EventPayload): Promise<void>;
218
+ /**
219
+ * Check if a feature flag is enabled for a user
220
+ *
221
+ * @param flagKey - Feature flag key
222
+ * @param distinctId - User ID or anonymous ID
223
+ * @param options - Feature flag evaluation options
224
+ * @returns true if enabled, false otherwise
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * const isEnabled = await subscriber.isFeatureEnabled('new-checkout', 'user-123');
229
+ *
230
+ * // With groups
231
+ * const isEnabled = await subscriber.isFeatureEnabled('beta-features', 'user-123', {
232
+ * groups: { company: 'acme-corp' }
233
+ * });
234
+ * ```
235
+ */
236
+ isFeatureEnabled(flagKey: string, distinctId: string, options?: FeatureFlagOptions): Promise<boolean>;
237
+ /**
238
+ * Get feature flag value for a user
239
+ *
240
+ * @param flagKey - Feature flag key
241
+ * @param distinctId - User ID or anonymous ID
242
+ * @param options - Feature flag evaluation options
243
+ * @returns Flag value (string, boolean, or undefined)
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * const variant = await subscriber.getFeatureFlag('experiment-variant', 'user-123');
248
+ * // Returns: 'control' | 'test' | 'test-2' | undefined
249
+ *
250
+ * // With person properties
251
+ * const variant = await subscriber.getFeatureFlag('premium-feature', 'user-123', {
252
+ * personProperties: { plan: 'premium' }
253
+ * });
254
+ * ```
255
+ */
256
+ getFeatureFlag(flagKey: string, distinctId: string, options?: FeatureFlagOptions): Promise<string | boolean | undefined>;
257
+ /**
258
+ * Get all feature flags for a user
259
+ *
260
+ * @param distinctId - User ID or anonymous ID
261
+ * @param options - Feature flag evaluation options
262
+ * @returns Object mapping flag keys to their values
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const flags = await subscriber.getAllFlags('user-123');
267
+ * // Returns: { 'new-checkout': true, 'experiment-variant': 'test', ... }
268
+ * ```
269
+ */
270
+ getAllFlags(distinctId: string, options?: FeatureFlagOptions): Promise<Record<string, string | number | boolean>>;
271
+ /**
272
+ * Reload feature flags from PostHog server
273
+ *
274
+ * Call this to refresh feature flag definitions without restarting.
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * await subscriber.reloadFeatureFlags();
279
+ * ```
280
+ */
281
+ reloadFeatureFlags(): Promise<void>;
282
+ /**
283
+ * Identify a user and set their properties
284
+ *
285
+ * @param distinctId - User ID
286
+ * @param properties - Person properties ($set, $set_once, or custom properties)
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * // Set properties (will update existing values)
291
+ * await subscriber.identify('user-123', {
292
+ * $set: {
293
+ * email: 'user@example.com',
294
+ * plan: 'premium'
295
+ * }
296
+ * });
297
+ *
298
+ * // Set properties only once (won't update if already exists)
299
+ * await subscriber.identify('user-123', {
300
+ * $set_once: {
301
+ * signup_date: '2025-01-17'
302
+ * }
303
+ * });
304
+ * ```
305
+ */
306
+ identify(distinctId: string, properties?: PersonProperties): Promise<void>;
307
+ /**
308
+ * Identify a group and set its properties
309
+ *
310
+ * Groups are useful for B2B SaaS to track organizations, teams, or accounts.
311
+ *
312
+ * @param groupType - Type of group (e.g., 'company', 'organization', 'team')
313
+ * @param groupKey - Unique identifier for the group
314
+ * @param properties - Group properties
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * await subscriber.groupIdentify('company', 'acme-corp', {
319
+ * $set: {
320
+ * name: 'Acme Corporation',
321
+ * industry: 'saas',
322
+ * employees: 500,
323
+ * plan: 'enterprise'
324
+ * }
325
+ * });
326
+ * ```
327
+ */
328
+ groupIdentify(groupType: string, groupKey: string | number, properties?: Record<string, any>): Promise<void>;
329
+ /**
330
+ * Track an event with group context
331
+ *
332
+ * Use this to associate events with groups (e.g., organizations).
333
+ *
334
+ * @param name - Event name
335
+ * @param attributes - Event attributes
336
+ * @param groups - Group context (e.g., { company: 'acme-corp' })
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * await subscriber.trackEventWithGroups('feature.used', {
341
+ * userId: 'user-123',
342
+ * feature: 'advanced-events'
343
+ * }, {
344
+ * company: 'acme-corp'
345
+ * });
346
+ * ```
347
+ */
348
+ trackEventWithGroups(name: string, attributes?: EventAttributes, groups?: Record<string, string | number>): Promise<void>;
349
+ /**
350
+ * Capture an exception and send to PostHog error tracking.
351
+ *
352
+ * If using browser client (window.posthog), delegates to its captureException.
353
+ * Otherwise, formats and sends via posthog-node capture API.
354
+ */
355
+ captureException(error: unknown, options?: {
356
+ distinctId?: string;
357
+ additionalProperties?: UnknownRecord;
358
+ }): Promise<void>;
359
+ /**
360
+ * Flush pending events and clean up resources
361
+ */
362
+ shutdown(): Promise<void>;
363
+ /**
364
+ * Handle errors with custom error handler
365
+ */
366
+ protected handleError(error: Error, payload: EventPayload): void;
367
+ }
368
+ //#endregion
369
+ export { ErrorContext, FeatureFlagOptions, PersonProperties, PostHogConfig, PostHogSubscriber };