@stacksee/analytics 0.4.2 → 0.4.3

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.
package/dist/providers.js CHANGED
@@ -1,6 +1,87 @@
1
- import { B as e, P as s, a } from "./server-BKqARaUf.js";
1
+ var l = Object.defineProperty;
2
+ var h = (r, s, i) => s in r ? l(r, s, { enumerable: !0, configurable: !0, writable: !0, value: i }) : r[s] = i;
3
+ var t = (r, s, i) => h(r, typeof s != "symbol" ? s + "" : s, i);
4
+ import { B as n } from "./client-MwIAm9fk.js";
5
+ import { P as m } from "./client-MwIAm9fk.js";
6
+ import { PostHog as p } from "posthog-node";
7
+ class u extends n {
8
+ constructor(i) {
9
+ super({ debug: i.debug, enabled: i.enabled });
10
+ t(this, "name", "PostHog-Server");
11
+ t(this, "client");
12
+ t(this, "initialized", !1);
13
+ t(this, "config");
14
+ this.config = i;
15
+ }
16
+ initialize() {
17
+ if (this.isEnabled() && !this.initialized) {
18
+ if (!this.config.apiKey || typeof this.config.apiKey != "string")
19
+ throw new Error("PostHog requires an apiKey");
20
+ try {
21
+ const { apiKey: i, ...e } = this.config;
22
+ this.client = new p(i, {
23
+ host: "https://app.posthog.com",
24
+ flushAt: 20,
25
+ flushInterval: 1e4,
26
+ ...e
27
+ }), this.initialized = !0, this.log("Initialized successfully", this.config);
28
+ } catch (i) {
29
+ throw console.error("[PostHog-Server] Failed to initialize:", i), i;
30
+ }
31
+ }
32
+ }
33
+ identify(i, e) {
34
+ !this.isEnabled() || !this.initialized || !this.client || (this.client.identify({
35
+ distinctId: i,
36
+ properties: e
37
+ }), this.log("Identified user", { userId: i, traits: e }));
38
+ }
39
+ track(i, e) {
40
+ if (!this.isEnabled() || !this.initialized || !this.client) return;
41
+ const a = {
42
+ ...i.properties,
43
+ category: i.category,
44
+ timestamp: i.timestamp ? new Date(i.timestamp) : /* @__PURE__ */ new Date(),
45
+ ...i.sessionId && { sessionId: i.sessionId },
46
+ ...(e == null ? void 0 : e.page) && {
47
+ $current_url: e.page.path,
48
+ $page_title: e.page.title,
49
+ $referrer: e.page.referrer
50
+ },
51
+ ...(e == null ? void 0 : e.device) && { device: e.device },
52
+ ...(e == null ? void 0 : e.utm) && { utm: e.utm }
53
+ };
54
+ this.client.capture({
55
+ distinctId: i.userId || "anonymous",
56
+ event: i.action,
57
+ properties: a
58
+ }), this.log("Tracked event", { event: i, context: e });
59
+ }
60
+ pageView(i, e) {
61
+ if (!this.isEnabled() || !this.initialized || !this.client) return;
62
+ const a = {
63
+ ...i,
64
+ ...(e == null ? void 0 : e.page) && {
65
+ path: e.page.path,
66
+ title: e.page.title,
67
+ referrer: e.page.referrer
68
+ }
69
+ };
70
+ this.client.capture({
71
+ distinctId: "anonymous",
72
+ event: "$pageview",
73
+ properties: a
74
+ }), this.log("Tracked page view", { properties: i, context: e });
75
+ }
76
+ async reset() {
77
+ !this.isEnabled() || !this.initialized || !this.client || (await this.client.flush(), this.log("Flushed pending events"));
78
+ }
79
+ async shutdown() {
80
+ this.client && (await this.client.shutdown(), this.log("Shutdown complete"));
81
+ }
82
+ }
2
83
  export {
3
- e as BaseAnalyticsProvider,
4
- s as PostHogClientProvider,
5
- a as PostHogServerProvider
84
+ n as BaseAnalyticsProvider,
85
+ m as PostHogClientProvider,
86
+ u as PostHogServerProvider
6
87
  };
package/dist/server.js CHANGED
@@ -2,12 +2,78 @@ var o = Object.defineProperty;
2
2
  var f = (r, e, t) => e in r ? o(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
3
  var n = (r, e, t) => f(r, typeof e != "symbol" ? e + "" : e, t);
4
4
  class u {
5
+ /**
6
+ * Creates a new ServerAnalytics instance for server-side event tracking.
7
+ *
8
+ * The server analytics instance is designed for Node.js environments including
9
+ * long-running servers, serverless functions, and edge computing environments.
10
+ *
11
+ * @param config Analytics configuration including providers and default context
12
+ * @param config.providers Array of analytics provider instances (e.g., PostHogServerProvider)
13
+ * @param config.defaultContext Optional default context to include with all events
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { ServerAnalytics } from '@stacksee/analytics/server';
18
+ * import { PostHogServerProvider } from '@stacksee/analytics/providers/posthog';
19
+ *
20
+ * const analytics = new ServerAnalytics({
21
+ * providers: [
22
+ * new PostHogServerProvider({
23
+ * apiKey: process.env.POSTHOG_API_KEY,
24
+ * host: process.env.POSTHOG_HOST
25
+ * })
26
+ * ],
27
+ * defaultContext: {
28
+ * app: { version: '1.0.0', environment: 'production' }
29
+ * }
30
+ * });
31
+ *
32
+ * analytics.initialize();
33
+ * ```
34
+ */
5
35
  constructor(e) {
6
36
  n(this, "providers", []);
7
37
  n(this, "config");
8
38
  n(this, "initialized", !1);
9
39
  this.config = e, this.providers = e.providers;
10
40
  }
41
+ /**
42
+ * Initializes all analytics providers.
43
+ *
44
+ * This method must be called before tracking events. It initializes all configured
45
+ * providers synchronously. Unlike the browser version, server initialization is
46
+ * typically synchronous as providers don't need to load external scripts.
47
+ *
48
+ * The method is safe to call multiple times and will not re-initialize if already done.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const analytics = new ServerAnalytics({ providers: [] });
53
+ *
54
+ * // Initialize before tracking events
55
+ * analytics.initialize();
56
+ *
57
+ * // Now ready to track events
58
+ * await analytics.track('api_request', { endpoint: '/users' });
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // In a serverless function
64
+ * export async function handler(req, res) {
65
+ * const analytics = new ServerAnalytics({ providers: [] });
66
+ * analytics.initialize(); // Quick synchronous initialization
67
+ *
68
+ * await analytics.track('function_invoked', {
69
+ * path: req.path,
70
+ * method: req.method
71
+ * });
72
+ *
73
+ * await analytics.shutdown(); // Important for serverless
74
+ * }
75
+ * ```
76
+ */
11
77
  initialize() {
12
78
  if (!this.initialized) {
13
79
  for (const e of this.providers)
@@ -15,10 +81,144 @@ class u {
15
81
  this.initialized = !0;
16
82
  }
17
83
  }
84
+ /**
85
+ * Identifies a user with optional traits.
86
+ *
87
+ * Associates subsequent events with the specified user ID and optionally
88
+ * sets user properties. This method is typically called when processing
89
+ * authentication or when you have user context available on the server.
90
+ *
91
+ * @param userId Unique identifier for the user (e.g., database ID, email)
92
+ * @param traits Optional user properties and characteristics
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // Basic user identification
97
+ * analytics.identify('user-123');
98
+ * ```
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // Identify with user traits from database
103
+ * analytics.identify('user-123', {
104
+ * email: 'john@example.com',
105
+ * name: 'John Doe',
106
+ * plan: 'enterprise',
107
+ * company: 'Acme Corp',
108
+ * createdAt: '2024-01-15T10:00:00Z',
109
+ * lastSeenAt: new Date().toISOString()
110
+ * });
111
+ * ```
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * // In an API authentication middleware
116
+ * async function authMiddleware(req, res, next) {
117
+ * const user = await getUserFromToken(req.headers.authorization);
118
+ *
119
+ * analytics.identify(user.id, {
120
+ * email: user.email,
121
+ * role: user.role,
122
+ * organization: user.organization
123
+ * });
124
+ *
125
+ * req.user = user;
126
+ * next();
127
+ * }
128
+ * ```
129
+ */
18
130
  identify(e, t) {
19
131
  for (const i of this.providers)
20
132
  i.identify(e, t);
21
133
  }
134
+ /**
135
+ * Tracks a custom event with properties and optional context.
136
+ *
137
+ * This is the main method for tracking business events on the server side.
138
+ * The method sends the event to all configured providers and waits for completion.
139
+ * Failed providers don't prevent others from succeeding.
140
+ *
141
+ * Server-side tracking typically includes additional context like IP addresses,
142
+ * user agents, and server-specific metadata that isn't available on the client.
143
+ *
144
+ * @param eventName Name of the event to track (must match your event definitions)
145
+ * @param properties Event-specific properties and data
146
+ * @param options Optional configuration including user ID, session ID, and context
147
+ * @param options.userId User ID to associate with this event
148
+ * @param options.sessionId Session ID to associate with this event
149
+ * @param options.context Additional context for this event
150
+ * @returns Promise that resolves when tracking is complete for all providers
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * // Basic event tracking
155
+ * await analytics.track('api_request', {
156
+ * endpoint: '/api/users',
157
+ * method: 'GET',
158
+ * responseTime: 150,
159
+ * statusCode: 200
160
+ * });
161
+ * ```
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // Track with user context
166
+ * await analytics.track('purchase_completed', {
167
+ * orderId: 'order-123',
168
+ * amount: 99.99,
169
+ * currency: 'USD',
170
+ * itemCount: 3
171
+ * }, {
172
+ * userId: 'user-456',
173
+ * sessionId: 'session-789',
174
+ * context: {
175
+ * page: { path: '/checkout/complete' },
176
+ * device: { userAgent: req.headers['user-agent'] },
177
+ * ip: req.ip
178
+ * }
179
+ * });
180
+ * ```
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * // In an Express.js route handler
185
+ * app.post('/api/users', async (req, res) => {
186
+ * const user = await createUser(req.body);
187
+ *
188
+ * // Track user creation with server context
189
+ * await analytics.track('user_created', {
190
+ * userId: user.id,
191
+ * email: user.email,
192
+ * plan: user.plan
193
+ * }, {
194
+ * userId: user.id,
195
+ * context: {
196
+ * page: { path: req.path },
197
+ * device: { userAgent: req.headers['user-agent'] },
198
+ * ip: req.ip,
199
+ * server: { version: process.env.APP_VERSION }
200
+ * }
201
+ * });
202
+ *
203
+ * res.json(user);
204
+ * });
205
+ * ```
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * // Error handling in tracking
210
+ * try {
211
+ * await analytics.track('payment_processed', {
212
+ * amount: 100,
213
+ * currency: 'USD'
214
+ * });
215
+ * } catch (error) {
216
+ * // This only catches initialization errors
217
+ * // Individual provider failures are logged but don't throw
218
+ * console.error('Failed to track event:', error);
219
+ * }
220
+ * ```
221
+ */
22
222
  async track(e, t, i) {
23
223
  if (!this.initialized) {
24
224
  console.warn("[Analytics] Not initialized. Call initialize() first.");
@@ -46,6 +246,66 @@ class u {
46
246
  });
47
247
  await Promise.all(d);
48
248
  }
249
+ /**
250
+ * Tracks a page view event from the server side.
251
+ *
252
+ * Server-side page view tracking is useful for server-rendered applications,
253
+ * SSR frameworks, or when you want to ensure page views are tracked even
254
+ * if client-side JavaScript fails.
255
+ *
256
+ * @param properties Optional properties to include with the page view
257
+ * @param options Optional configuration including context
258
+ * @param options.context Additional context for this page view
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * // Basic server-side page view
263
+ * analytics.pageView();
264
+ * ```
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * // Page view with server context
269
+ * analytics.pageView({
270
+ * loadTime: 250,
271
+ * template: 'product-detail',
272
+ * ssr: true
273
+ * }, {
274
+ * context: {
275
+ * page: {
276
+ * path: '/products/widget-123',
277
+ * title: 'Amazing Widget - Product Details'
278
+ * },
279
+ * device: {
280
+ * userAgent: req.headers['user-agent']
281
+ * },
282
+ * server: {
283
+ * renderTime: 45,
284
+ * cacheHit: false
285
+ * }
286
+ * }
287
+ * });
288
+ * ```
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * // In a Next.js API route or middleware
293
+ * export async function middleware(req) {
294
+ * if (req.nextUrl.pathname.startsWith('/product/')) {
295
+ * analytics.pageView({
296
+ * category: 'product',
297
+ * productId: req.nextUrl.pathname.split('/').pop()
298
+ * }, {
299
+ * context: {
300
+ * page: { path: req.nextUrl.pathname },
301
+ * device: { userAgent: req.headers.get('user-agent') },
302
+ * referrer: req.headers.get('referer')
303
+ * }
304
+ * });
305
+ * }
306
+ * }
307
+ * ```
308
+ */
49
309
  pageView(e, t) {
50
310
  if (!this.initialized) return;
51
311
  const i = {
@@ -55,6 +315,65 @@ class u {
55
315
  for (const a of this.providers)
56
316
  a.pageView(e, i);
57
317
  }
318
+ /**
319
+ * Tracks when a user leaves a page from the server side.
320
+ *
321
+ * Server-side page leave tracking is less common than client-side but can be
322
+ * useful in certain scenarios like tracking session timeouts, or when combined
323
+ * with server-side session management.
324
+ *
325
+ * Note: Not all analytics providers support page leave events. The method
326
+ * will only call providers that implement the pageLeave method.
327
+ *
328
+ * @param properties Optional properties to include with the page leave event
329
+ * @param options Optional configuration including context
330
+ * @param options.context Additional context for this page leave
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * // Basic page leave tracking
335
+ * analytics.pageLeave();
336
+ * ```
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * // Page leave with session context
341
+ * analytics.pageLeave({
342
+ * sessionDuration: 45000, // 45 seconds
343
+ * pagesViewed: 3,
344
+ * exitReason: 'session_timeout'
345
+ * }, {
346
+ * context: {
347
+ * session: {
348
+ * id: 'session-123',
349
+ * startTime: sessionStartTime,
350
+ * endTime: Date.now()
351
+ * },
352
+ * server: {
353
+ * reason: 'inactivity_timeout'
354
+ * }
355
+ * }
356
+ * });
357
+ * ```
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * // In a session cleanup job
362
+ * async function cleanupExpiredSessions() {
363
+ * const expiredSessions = await getExpiredSessions();
364
+ *
365
+ * for (const session of expiredSessions) {
366
+ * analytics.pageLeave({
367
+ * sessionId: session.id,
368
+ * duration: session.duration,
369
+ * reason: 'expired'
370
+ * });
371
+ *
372
+ * await removeSession(session.id);
373
+ * }
374
+ * }
375
+ * ```
376
+ */
58
377
  pageLeave(e, t) {
59
378
  if (!this.initialized) return;
60
379
  const i = {
@@ -64,6 +383,90 @@ class u {
64
383
  for (const a of this.providers)
65
384
  a.pageLeave && a.pageLeave(e, i);
66
385
  }
386
+ /**
387
+ * Shuts down all analytics providers and flushes pending events.
388
+ *
389
+ * This method is crucial for server environments, especially serverless functions,
390
+ * as it ensures all events are sent before the process terminates. Some providers
391
+ * batch events and need an explicit flush to send them.
392
+ *
393
+ * Always call this method before your server shuts down or before a serverless
394
+ * function completes execution.
395
+ *
396
+ * @returns Promise that resolves when all providers have been shut down
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * // Basic shutdown
401
+ * await analytics.shutdown();
402
+ * ```
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * // In a serverless function
407
+ * export async function handler(event, context) {
408
+ * const analytics = new ServerAnalytics({ providers: [] });
409
+ * analytics.initialize();
410
+ *
411
+ * try {
412
+ * // Process the event
413
+ * await processEvent(event);
414
+ *
415
+ * // Track completion
416
+ * await analytics.track('function_completed', {
417
+ * duration: Date.now() - startTime,
418
+ * success: true
419
+ * });
420
+ * } catch (error) {
421
+ * await analytics.track('function_failed', {
422
+ * error: error.message,
423
+ * duration: Date.now() - startTime
424
+ * });
425
+ * } finally {
426
+ * // Always shutdown to flush events
427
+ * await analytics.shutdown();
428
+ * }
429
+ *
430
+ * return { statusCode: 200 };
431
+ * }
432
+ * ```
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * // In an Express.js server
437
+ * const server = app.listen(3000);
438
+ *
439
+ * // Graceful shutdown
440
+ * process.on('SIGTERM', async () => {
441
+ * console.log('Shutting down gracefully...');
442
+ *
443
+ * server.close(async () => {
444
+ * // Flush analytics events before exit
445
+ * await analytics.shutdown();
446
+ * process.exit(0);
447
+ * });
448
+ * });
449
+ * ```
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * // With Vercel's waitUntil
454
+ * import { waitUntil } from '@vercel/functions';
455
+ *
456
+ * export default async function handler(req, res) {
457
+ * // Process request
458
+ * const result = await processRequest(req);
459
+ *
460
+ * // Track in background without blocking response
461
+ * waitUntil(
462
+ * analytics.track('api_request', { endpoint: req.url })
463
+ * .then(() => analytics.shutdown())
464
+ * );
465
+ *
466
+ * return res.json(result);
467
+ * }
468
+ * ```
469
+ */
67
470
  async shutdown() {
68
471
  const e = this.providers.map((t) => "shutdown" in t && typeof t.shutdown == "function" ? t.shutdown() : Promise.resolve());
69
472
  await Promise.all(e);