@od-oneapp/analytics 2026.1.1301

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.
Files changed (184) hide show
  1. package/README.md +509 -0
  2. package/dist/ai-YMnynb-t.mjs +3347 -0
  3. package/dist/ai-YMnynb-t.mjs.map +1 -0
  4. package/dist/chunk-DQk6qfdC.mjs +18 -0
  5. package/dist/client-CTzJVFU5.mjs +9 -0
  6. package/dist/client-CTzJVFU5.mjs.map +1 -0
  7. package/dist/client-CcFTauAh.mjs +54 -0
  8. package/dist/client-CcFTauAh.mjs.map +1 -0
  9. package/dist/client-CeOLjbac.mjs +281 -0
  10. package/dist/client-CeOLjbac.mjs.map +1 -0
  11. package/dist/client-D339NFJS.mjs +267 -0
  12. package/dist/client-D339NFJS.mjs.map +1 -0
  13. package/dist/client-next.d.mts +62 -0
  14. package/dist/client-next.d.mts.map +1 -0
  15. package/dist/client-next.mjs +525 -0
  16. package/dist/client-next.mjs.map +1 -0
  17. package/dist/client.d.mts +30 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +186 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/config-DPS6bSYo.d.mts +34 -0
  22. package/dist/config-DPS6bSYo.d.mts.map +1 -0
  23. package/dist/config-P6P5adJg.mjs +287 -0
  24. package/dist/config-P6P5adJg.mjs.map +1 -0
  25. package/dist/console-8bND3mMU.mjs +128 -0
  26. package/dist/console-8bND3mMU.mjs.map +1 -0
  27. package/dist/ecommerce-Cgu4wlux.mjs +993 -0
  28. package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
  29. package/dist/emitters-6-nKo8i-.mjs +208 -0
  30. package/dist/emitters-6-nKo8i-.mjs.map +1 -0
  31. package/dist/emitters-DldkVSPp.d.mts +12 -0
  32. package/dist/emitters-DldkVSPp.d.mts.map +1 -0
  33. package/dist/index-BfNWgfa5.d.mts +1494 -0
  34. package/dist/index-BfNWgfa5.d.mts.map +1 -0
  35. package/dist/index-BkIWe--N.d.mts +953 -0
  36. package/dist/index-BkIWe--N.d.mts.map +1 -0
  37. package/dist/index-jPzXRn52.d.mts +184 -0
  38. package/dist/index-jPzXRn52.d.mts.map +1 -0
  39. package/dist/manager-DvRRjza6.d.mts +76 -0
  40. package/dist/manager-DvRRjza6.d.mts.map +1 -0
  41. package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
  42. package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
  43. package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
  44. package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
  45. package/dist/providers-http-client.d.mts +37 -0
  46. package/dist/providers-http-client.d.mts.map +1 -0
  47. package/dist/providers-http-client.mjs +320 -0
  48. package/dist/providers-http-client.mjs.map +1 -0
  49. package/dist/providers-http-server.d.mts +31 -0
  50. package/dist/providers-http-server.d.mts.map +1 -0
  51. package/dist/providers-http-server.mjs +297 -0
  52. package/dist/providers-http-server.mjs.map +1 -0
  53. package/dist/providers-http.d.mts +46 -0
  54. package/dist/providers-http.d.mts.map +1 -0
  55. package/dist/providers-http.mjs +4 -0
  56. package/dist/server-edge.d.mts +9 -0
  57. package/dist/server-edge.d.mts.map +1 -0
  58. package/dist/server-edge.mjs +373 -0
  59. package/dist/server-edge.mjs.map +1 -0
  60. package/dist/server-next.d.mts +67 -0
  61. package/dist/server-next.d.mts.map +1 -0
  62. package/dist/server-next.mjs +193 -0
  63. package/dist/server-next.mjs.map +1 -0
  64. package/dist/server.d.mts +10 -0
  65. package/dist/server.mjs +7 -0
  66. package/dist/service-cYtBBL8x.mjs +945 -0
  67. package/dist/service-cYtBBL8x.mjs.map +1 -0
  68. package/dist/shared.d.mts +16 -0
  69. package/dist/shared.d.mts.map +1 -0
  70. package/dist/shared.mjs +93 -0
  71. package/dist/shared.mjs.map +1 -0
  72. package/dist/types-BxBnNQ0V.d.mts +354 -0
  73. package/dist/types-BxBnNQ0V.d.mts.map +1 -0
  74. package/dist/types-CBvxUEaF.d.mts +216 -0
  75. package/dist/types-CBvxUEaF.d.mts.map +1 -0
  76. package/dist/types.d.mts +4 -0
  77. package/dist/types.mjs +0 -0
  78. package/dist/vercel-types-lwakUfoI.d.mts +102 -0
  79. package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
  80. package/package.json +129 -0
  81. package/src/client/index.ts +164 -0
  82. package/src/client/manager.ts +71 -0
  83. package/src/client/next/components.tsx +270 -0
  84. package/src/client/next/hooks.ts +217 -0
  85. package/src/client/next/manager.ts +141 -0
  86. package/src/client/next.ts +144 -0
  87. package/src/client-next.ts +101 -0
  88. package/src/client.ts +89 -0
  89. package/src/examples/ai-sdk-patterns.ts +583 -0
  90. package/src/examples/emitter-patterns.ts +476 -0
  91. package/src/examples/nextjs-emitter-patterns.tsx +403 -0
  92. package/src/next/app-router.tsx +564 -0
  93. package/src/next/client.ts +419 -0
  94. package/src/next/index.ts +84 -0
  95. package/src/next/middleware.ts +429 -0
  96. package/src/next/rsc.tsx +300 -0
  97. package/src/next/server.ts +253 -0
  98. package/src/next/types.d.ts +220 -0
  99. package/src/providers/base-provider.ts +419 -0
  100. package/src/providers/console/client.ts +10 -0
  101. package/src/providers/console/index.ts +152 -0
  102. package/src/providers/console/server.ts +6 -0
  103. package/src/providers/console/types.ts +15 -0
  104. package/src/providers/http/client.ts +464 -0
  105. package/src/providers/http/index.ts +30 -0
  106. package/src/providers/http/server.ts +396 -0
  107. package/src/providers/http/types.ts +135 -0
  108. package/src/providers/posthog/client.ts +518 -0
  109. package/src/providers/posthog/index.ts +11 -0
  110. package/src/providers/posthog/server.ts +329 -0
  111. package/src/providers/posthog/types.ts +104 -0
  112. package/src/providers/segment/client.ts +113 -0
  113. package/src/providers/segment/index.ts +11 -0
  114. package/src/providers/segment/server.ts +115 -0
  115. package/src/providers/segment/types.ts +51 -0
  116. package/src/providers/vercel/client.ts +102 -0
  117. package/src/providers/vercel/index.ts +11 -0
  118. package/src/providers/vercel/server.ts +89 -0
  119. package/src/providers/vercel/types.ts +27 -0
  120. package/src/server/index.ts +103 -0
  121. package/src/server/manager.ts +62 -0
  122. package/src/server/next.ts +210 -0
  123. package/src/server-edge.ts +442 -0
  124. package/src/server-next.ts +39 -0
  125. package/src/server.ts +106 -0
  126. package/src/shared/emitters/ai/README.md +981 -0
  127. package/src/shared/emitters/ai/events/agent.ts +130 -0
  128. package/src/shared/emitters/ai/events/artifacts.ts +167 -0
  129. package/src/shared/emitters/ai/events/chat.ts +126 -0
  130. package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
  131. package/src/shared/emitters/ai/events/completion.ts +103 -0
  132. package/src/shared/emitters/ai/events/content-generation.ts +347 -0
  133. package/src/shared/emitters/ai/events/conversation.ts +332 -0
  134. package/src/shared/emitters/ai/events/product-features.ts +1402 -0
  135. package/src/shared/emitters/ai/events/streaming.ts +114 -0
  136. package/src/shared/emitters/ai/events/tool.ts +93 -0
  137. package/src/shared/emitters/ai/index.ts +69 -0
  138. package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
  139. package/src/shared/emitters/ai/track-ai.ts +50 -0
  140. package/src/shared/emitters/ai/types.ts +1041 -0
  141. package/src/shared/emitters/ai/utils.ts +468 -0
  142. package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
  143. package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
  144. package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
  145. package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
  146. package/src/shared/emitters/ecommerce/events/order.ts +199 -0
  147. package/src/shared/emitters/ecommerce/events/product.ts +205 -0
  148. package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
  149. package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
  150. package/src/shared/emitters/ecommerce/index.ts +46 -0
  151. package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
  152. package/src/shared/emitters/ecommerce/types.ts +314 -0
  153. package/src/shared/emitters/ecommerce/utils.ts +216 -0
  154. package/src/shared/emitters/emitter-types.ts +974 -0
  155. package/src/shared/emitters/emitters.ts +292 -0
  156. package/src/shared/emitters/helpers.ts +419 -0
  157. package/src/shared/emitters/index.ts +66 -0
  158. package/src/shared/index.ts +142 -0
  159. package/src/shared/ingestion/index.ts +66 -0
  160. package/src/shared/ingestion/schemas.ts +386 -0
  161. package/src/shared/ingestion/service.ts +628 -0
  162. package/src/shared/node22-features.ts +848 -0
  163. package/src/shared/providers/console-provider.ts +160 -0
  164. package/src/shared/types/base-types.ts +54 -0
  165. package/src/shared/types/console-types.ts +19 -0
  166. package/src/shared/types/posthog-types.ts +131 -0
  167. package/src/shared/types/segment-types.ts +15 -0
  168. package/src/shared/types/types.ts +397 -0
  169. package/src/shared/types/vercel-types.ts +19 -0
  170. package/src/shared/utils/config-client.ts +19 -0
  171. package/src/shared/utils/config.ts +250 -0
  172. package/src/shared/utils/emitter-adapter.ts +212 -0
  173. package/src/shared/utils/manager.test.ts +36 -0
  174. package/src/shared/utils/manager.ts +1322 -0
  175. package/src/shared/utils/posthog-bootstrap.ts +136 -0
  176. package/src/shared/utils/posthog-client-utils.ts +48 -0
  177. package/src/shared/utils/posthog-next-utils.ts +282 -0
  178. package/src/shared/utils/posthog-server-utils.ts +210 -0
  179. package/src/shared/utils/rate-limit.ts +289 -0
  180. package/src/shared/utils/security.ts +545 -0
  181. package/src/shared/utils/validation-client.ts +161 -0
  182. package/src/shared/utils/validation.ts +399 -0
  183. package/src/shared.ts +155 -0
  184. package/src/types/index.ts +62 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * @fileoverview Core analytics emitter functions following Segment.io specification
3
+ *
4
+ * This module provides type-safe emitter functions that return event payloads following
5
+ * the Segment.io specification. These payloads can be passed to `analytics.emit()` or
6
+ * `analytics.track()` for tracking.
7
+ *
8
+ * **Emitter Functions**:
9
+ * - `identify()`: Identify users with traits
10
+ * - `track()`: Track custom events
11
+ * - `page()`: Track page views
12
+ * - `screen()`: Track screen views (mobile)
13
+ * - `group()`: Associate users with groups
14
+ * - `alias()`: Alias user IDs
15
+ *
16
+ * **Benefits**:
17
+ * - Type-safe event tracking
18
+ * - Consistent with Segment.io spec
19
+ * - Can be composed and batched
20
+ * - Works with all analytics providers
21
+ *
22
+ * **Reference**: https://segment.com/docs/connections/spec/
23
+ *
24
+ * @module @od-oneapp/analytics/shared/emitters
25
+ */
26
+
27
+ import type {
28
+ EmitterAliasPayload,
29
+ EmitterGroupPayload,
30
+ EmitterIdentifyPayload,
31
+ EmitterOptions,
32
+ EmitterPagePayload,
33
+ EmitterScreenPayload,
34
+ EmitterTrackPayload,
35
+ } from './emitter-types';
36
+ import type { GroupTraits, PageProperties, PropertyObject, UserTraits } from '../types/types';
37
+
38
+ /**
39
+ * Identify - who is the customer?
40
+ *
41
+ * Ties a user to their actions and records traits about them. This is the core
42
+ * method for identifying users in your analytics system.
43
+ *
44
+ * **When to call**:
45
+ * - After a user first registers
46
+ * - After a user logs in
47
+ * - When a user updates their info
48
+ *
49
+ * **Reference**: https://segment.com/docs/connections/spec/identify/
50
+ *
51
+ * @param {string} userId - Unique identifier for the user in your database (required unless anonymousId is set)
52
+ * @param {UserTraits} [traits] - Free-form dictionary of traits about the user, like email or name
53
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, anonymousId, context, integrations
54
+ * @returns {EmitterIdentifyPayload} Identify payload ready to emit
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * await analytics.emit(identify('user-123', {
59
+ * email: 'user@example.com',
60
+ * name: 'John Doe'
61
+ * }));
62
+ * ```
63
+ */
64
+ export function identify(
65
+ userId: string,
66
+ traits?: UserTraits,
67
+ options?: EmitterOptions,
68
+ ): EmitterIdentifyPayload {
69
+ const payload: EmitterIdentifyPayload = {
70
+ type: 'identify',
71
+ userId,
72
+ ...(traits && { traits }),
73
+ ...(options?.timestamp && { timestamp: options.timestamp }),
74
+ ...(options?.context && { context: options.context }),
75
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
76
+ ...(options?.integrations && { integrations: options.integrations }),
77
+ };
78
+
79
+ return payload;
80
+ }
81
+
82
+ /**
83
+ * Track - what are they doing?
84
+ *
85
+ * Records any actions your users perform, along with properties that describe the action.
86
+ * This is the most common method for tracking user behavior.
87
+ *
88
+ * **Reference**: https://segment.com/docs/connections/spec/track/
89
+ *
90
+ * @param {string} event - Name of the action that a user has performed (required)
91
+ * @param {PropertyObject} [properties] - Free-form dictionary of properties of the event, like revenue
92
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, anonymousId, context, integrations
93
+ * @returns {EmitterTrackPayload} Track payload ready to emit
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * await analytics.emit(track('Button Clicked', {
98
+ * buttonName: 'Sign Up',
99
+ * location: 'header'
100
+ * }));
101
+ * ```
102
+ */
103
+ export function track(
104
+ event: string,
105
+ properties?: PropertyObject,
106
+ options?: EmitterOptions,
107
+ ): EmitterTrackPayload {
108
+ const payload: EmitterTrackPayload = {
109
+ type: 'track',
110
+ event,
111
+ ...(properties && { properties }),
112
+ ...(options?.timestamp && { timestamp: options.timestamp }),
113
+ ...(options?.context && { context: options.context }),
114
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
115
+ ...(options?.integrations && { integrations: options.integrations }),
116
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
117
+ };
118
+
119
+ // userId should be passed through the main function params or via options
120
+
121
+ return payload;
122
+ }
123
+
124
+ /**
125
+ * Page - what web page are they on?
126
+ *
127
+ * Records page views on your website, along with optional properties about the page.
128
+ * Automatically merges category into properties if provided.
129
+ *
130
+ * **Reference**: https://segment.com/docs/connections/spec/page/
131
+ *
132
+ * @param {string} [category] - Category of the page (optional)
133
+ * @param {string} [name] - Name of the page (optional)
134
+ * @param {PageProperties} [properties] - Free-form dictionary of properties of the page, like url and referrer
135
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, anonymousId, context, integrations
136
+ * @returns {EmitterPagePayload} Page payload ready to emit
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * await analytics.emit(page('Home', 'Landing', {
141
+ * url: 'https://example.com',
142
+ * referrer: 'https://google.com'
143
+ * }));
144
+ * ```
145
+ */
146
+ export function page(
147
+ category?: string,
148
+ name?: string,
149
+ properties?: PageProperties,
150
+ options?: EmitterOptions,
151
+ ): EmitterPagePayload {
152
+ // Merge category into properties if provided
153
+ const mergedProperties = {
154
+ ...(properties ?? {}),
155
+ ...(category && { category }),
156
+ };
157
+
158
+ const payload: EmitterPagePayload = {
159
+ type: 'page',
160
+ ...(name && { name }),
161
+ ...(Object.keys(mergedProperties).length > 0 && { properties: mergedProperties }),
162
+ ...(options?.timestamp && { timestamp: options.timestamp }),
163
+ ...(options?.context && { context: options.context }),
164
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
165
+ ...(options?.integrations && { integrations: options.integrations }),
166
+ };
167
+
168
+ // userId should be passed through the main function params or via options
169
+
170
+ return payload;
171
+ }
172
+
173
+ /**
174
+ * Screen - what app screen are they on?
175
+ *
176
+ * The mobile equivalent of page, records screen views in your mobile app.
177
+ * Use this for React Native, iOS, and Android applications.
178
+ *
179
+ * **Reference**: https://segment.com/docs/connections/spec/screen/
180
+ *
181
+ * @param {string} [name] - Name of the screen (optional)
182
+ * @param {PropertyObject} [properties] - Free-form dictionary of properties of the screen
183
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, anonymousId, context, integrations
184
+ * @returns {EmitterScreenPayload} Screen payload ready to emit
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * await analytics.emit(screen('Home Screen', {
189
+ * screenType: 'main',
190
+ * userId: 'user-123'
191
+ * }));
192
+ * ```
193
+ */
194
+ export function screen(
195
+ name?: string,
196
+ properties?: PropertyObject,
197
+ options?: EmitterOptions,
198
+ ): EmitterScreenPayload {
199
+ const payload: EmitterScreenPayload = {
200
+ type: 'screen',
201
+ ...(name && { name }),
202
+ ...(properties && { properties }),
203
+ ...(options?.timestamp && { timestamp: options.timestamp }),
204
+ ...(options?.context && { context: options.context }),
205
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
206
+ ...(options?.integrations && { integrations: options.integrations }),
207
+ };
208
+
209
+ // userId should be passed through the main function params or via options
210
+
211
+ return payload;
212
+ }
213
+
214
+ /**
215
+ * Group - what account or organization are they part of?
216
+ *
217
+ * Associates an individual user with a group—a company, organization, account, project, or team.
218
+ * Useful for B2B applications where users belong to organizations.
219
+ *
220
+ * **Reference**: https://segment.com/docs/connections/spec/group/
221
+ *
222
+ * @param {string} groupId - Unique identifier for the group in your database (required)
223
+ * @param {GroupTraits} [traits] - Free-form dictionary of traits of the group, like name or industry
224
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, anonymousId, context, integrations
225
+ * @returns {EmitterGroupPayload} Group payload ready to emit
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * await analytics.emit(group('org-456', {
230
+ * name: 'Acme Corp',
231
+ * industry: 'Technology',
232
+ * plan: 'enterprise'
233
+ * }));
234
+ * ```
235
+ */
236
+ export function group(
237
+ groupId: string,
238
+ traits?: GroupTraits,
239
+ options?: EmitterOptions,
240
+ ): EmitterGroupPayload {
241
+ const payload: EmitterGroupPayload = {
242
+ type: 'group',
243
+ groupId,
244
+ ...(traits && { traits }),
245
+ ...(options?.timestamp && { timestamp: options.timestamp }),
246
+ ...(options?.context && { context: options.context }),
247
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
248
+ ...(options?.integrations && { integrations: options.integrations }),
249
+ };
250
+
251
+ // userId should be passed through the main function params or via options
252
+
253
+ return payload;
254
+ }
255
+
256
+ /**
257
+ * Alias - what was their past identity?
258
+ *
259
+ * Merges two user identities, effectively connecting two sets of user data in one profile.
260
+ * This is an advanced method that should only be used when required for downstream
261
+ * destination compatibility (e.g., when a user signs up after browsing anonymously).
262
+ *
263
+ * **Reference**: https://segment.com/docs/connections/spec/alias/
264
+ *
265
+ * @param {string} userId - The user's new identity, or an existing identity to merge with previousId (required)
266
+ * @param {string} previousId - The existing ID you've referred to the user by (required)
267
+ * @param {EmitterOptions} [options] - Optional fields like timestamp, context, integrations
268
+ * @returns {EmitterAliasPayload} Alias payload ready to emit
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * // When anonymous user signs up
273
+ * await analytics.emit(alias('user-123', 'anonymous-456'));
274
+ * ```
275
+ */
276
+ export function alias(
277
+ userId: string,
278
+ previousId: string,
279
+ options?: EmitterOptions,
280
+ ): EmitterAliasPayload {
281
+ const payload: EmitterAliasPayload = {
282
+ type: 'alias',
283
+ previousId,
284
+ userId,
285
+ ...(options?.timestamp && { timestamp: options.timestamp }),
286
+ ...(options?.context && { context: options.context }),
287
+ ...(options?.anonymousId && { anonymousId: options.anonymousId }),
288
+ ...(options?.integrations && { integrations: options.integrations }),
289
+ };
290
+
291
+ return payload;
292
+ }
@@ -0,0 +1,419 @@
1
+ /**
2
+ * @fileoverview Helper Functions for Analytics Emitters
3
+ *
4
+ * These utilities make it easier to use the emitter-first approach for analytics.
5
+ * Provides builders, batch processing, session management, and convenience
6
+ * functions for common tracking patterns.
7
+ *
8
+ * **Builders**:
9
+ * - `ContextBuilder`: Build consistent context across events
10
+ * - `PayloadBuilder`: Chain emitter creation with shared options
11
+ * - `EventBatch`: Batch related events with shared context
12
+ *
13
+ * **Session Management**:
14
+ * - `createUserSession()`: Create a user session tracking flow
15
+ * - `createAnonymousSession()`: Create an anonymous user tracking flow
16
+ *
17
+ * **Convenience Functions**:
18
+ * - `withMetadata()`: Add consistent metadata to events
19
+ * - `withUTM()`: Add UTM parameters to events
20
+ * - Type guards: `isTrackPayload()`, `isIdentifyPayload()`, etc.
21
+ *
22
+ * @module @od-oneapp/analytics/shared/emitters/helpers
23
+ */
24
+
25
+ import { alias, group, identify, page, track } from './emitters';
26
+
27
+ import type {
28
+ EmitterAliasPayload,
29
+ EmitterContext,
30
+ EmitterGroupPayload,
31
+ EmitterIdentifyPayload,
32
+ EmitterOptions,
33
+ EmitterPagePayload,
34
+ EmitterPayload,
35
+ EmitterTrackPayload,
36
+ } from './emitter-types';
37
+
38
+ /**
39
+ * Builder for creating consistent context across analytics events.
40
+ *
41
+ * @remarks
42
+ * Use this builder to create a reusable context object that can be applied
43
+ * to multiple events. This ensures consistency and reduces repetition.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const context = new ContextBuilder()
48
+ * .setUser('user-123', { plan: 'pro' })
49
+ * .setOrganization('org-456')
50
+ * .setPage({ path: '/dashboard', title: 'Dashboard' })
51
+ * .build();
52
+ *
53
+ * await analytics.emit(track('Button Clicked', {}, { context }));
54
+ * ```
55
+ */
56
+ export class ContextBuilder {
57
+ private context: EmitterContext = {};
58
+
59
+ constructor(initialContext?: Partial<EmitterContext>) {
60
+ if (initialContext) {
61
+ this.context = { ...initialContext };
62
+ }
63
+ }
64
+
65
+ setUser(_userId: string, traits?: Record<string, any>): this {
66
+ this.context.traits = { ...this.context.traits, ...traits };
67
+ return this;
68
+ }
69
+
70
+ setOrganization(groupId: string): this {
71
+ this.context.groupId = groupId;
72
+ return this;
73
+ }
74
+
75
+ setPage(pageInfo: { path?: string; url?: string; title?: string; referrer?: string }): this {
76
+ this.context.page = { ...this.context.page, ...pageInfo };
77
+ return this;
78
+ }
79
+
80
+ setCampaign(utmParams: Record<string, string>): this {
81
+ this.context.campaign = { ...this.context.campaign, ...utmParams };
82
+ return this;
83
+ }
84
+
85
+ setDevice(deviceInfo: Record<string, any>): this {
86
+ this.context.device = { ...this.context.device, ...deviceInfo };
87
+ return this;
88
+ }
89
+
90
+ build(): EmitterContext {
91
+ return { ...this.context };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Builder for chaining emitter creation with shared options.
97
+ *
98
+ * @remarks
99
+ * Use this builder to create multiple events with shared options like
100
+ * timestamp, anonymousId, or integrations. This is useful when tracking
101
+ * multiple related events in a single flow.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const builder = new PayloadBuilder(context)
106
+ * .withTimestamp(new Date())
107
+ * .withAnonymousId('anon-123');
108
+ *
109
+ * await analytics.emit(builder.track('Event 1', {}));
110
+ * await analytics.emit(builder.track('Event 2', {}));
111
+ * ```
112
+ */
113
+ export class PayloadBuilder {
114
+ private options: EmitterOptions = {};
115
+
116
+ constructor(context?: EmitterContext) {
117
+ if (context) {
118
+ this.options.context = context;
119
+ }
120
+ }
121
+
122
+ withTimestamp(timestamp: Date | string): this {
123
+ this.options.timestamp = timestamp;
124
+ return this;
125
+ }
126
+
127
+ withAnonymousId(anonymousId: string): this {
128
+ this.options.anonymousId = anonymousId;
129
+ return this;
130
+ }
131
+
132
+ withIntegrations(integrations: Record<string, boolean | Record<string, any>>): this {
133
+ this.options.integrations = integrations;
134
+ return this;
135
+ }
136
+
137
+ track(event: string, properties?: Record<string, any>): EmitterTrackPayload {
138
+ return track(event, properties, this.options);
139
+ }
140
+
141
+ identify(userId: string, traits?: Record<string, any>): EmitterIdentifyPayload {
142
+ return identify(userId, traits, this.options);
143
+ }
144
+
145
+ page(name?: string, properties?: Record<string, any>): EmitterPagePayload {
146
+ return page(undefined, name, properties, this.options);
147
+ }
148
+
149
+ group(groupId: string, traits?: Record<string, any>): EmitterGroupPayload {
150
+ return group(groupId, traits, this.options);
151
+ }
152
+
153
+ alias(userId: string, previousId: string): EmitterAliasPayload {
154
+ return alias(userId, previousId, this.options);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Batch processor for related events with shared context.
160
+ *
161
+ * @remarks
162
+ * Use this class to collect multiple events and emit them together with
163
+ * a shared context. This is useful for tracking user flows or multi-step
164
+ * processes where events should be grouped together.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const batch = new EventBatch(context)
169
+ * .addTrack('Step 1 Completed', { step: 1 })
170
+ * .addTrack('Step 2 Completed', { step: 2 })
171
+ * .addTrack('Flow Completed', { totalSteps: 2 });
172
+ *
173
+ * for (const event of batch.getEvents()) {
174
+ * await analytics.emit(event);
175
+ * }
176
+ * ```
177
+ */
178
+ export class EventBatch {
179
+ private events: EmitterPayload[] = [];
180
+ private sharedContext: EmitterContext;
181
+
182
+ constructor(context?: EmitterContext) {
183
+ this.sharedContext = context ?? {};
184
+ }
185
+
186
+ add(payload: EmitterPayload): this {
187
+ // Merge shared context with payload context
188
+ const enrichedPayload = {
189
+ ...payload,
190
+ context: { ...this.sharedContext, ...payload.context },
191
+ };
192
+ this.events.push(enrichedPayload);
193
+ return this;
194
+ }
195
+
196
+ addTrack(event: string, properties?: Record<string, any>): this {
197
+ return this.add(track(event, properties, { context: this.sharedContext }));
198
+ }
199
+
200
+ addIdentify(userId: string, traits?: Record<string, any>): this {
201
+ return this.add(identify(userId, traits, { context: this.sharedContext }));
202
+ }
203
+
204
+ addPage(name?: string, properties?: Record<string, any>): this {
205
+ return this.add(page(undefined, name, properties, { context: this.sharedContext }));
206
+ }
207
+
208
+ addGroup(groupId: string, traits?: Record<string, any>): this {
209
+ return this.add(group(groupId, traits, { context: this.sharedContext }));
210
+ }
211
+
212
+ getEvents(): EmitterPayload[] {
213
+ return [...this.events];
214
+ }
215
+
216
+ clear(): void {
217
+ this.events = [];
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Creates a user session tracking flow with pre-configured context.
223
+ *
224
+ * @remarks
225
+ * This helper creates a session object with methods for tracking events
226
+ * within a specific user session. All events will include the sessionId
227
+ * and user context automatically.
228
+ *
229
+ * @param userId - Unique identifier for the user
230
+ * @param sessionId - Unique identifier for the session
231
+ * @returns Session object with `identify`, `track`, `page`, and `group` methods
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const session = createUserSession('user-123', 'session-456');
236
+ * await analytics.emit(session.track('Button Clicked', { button: 'signup' }));
237
+ * await analytics.emit(session.page('Dashboard'));
238
+ * ```
239
+ */
240
+ export function createUserSession(userId: string, sessionId: string) {
241
+ const context = new ContextBuilder().setUser(userId).build();
242
+
243
+ return {
244
+ // Identify the user
245
+ identify: (traits?: Record<string, any>) =>
246
+ identify(userId, { ...traits, sessionId }, { context }),
247
+
248
+ // Track an event in this session
249
+ track: (event: string, properties?: Record<string, any>) =>
250
+ track(event, { ...properties, sessionId }, { context }),
251
+
252
+ // Track a page view in this session
253
+ page: (name?: string, properties?: Record<string, any>) =>
254
+ page(undefined, name, { ...properties, sessionId }, { context }),
255
+
256
+ // Associate with a group
257
+ group: (groupId: string, traits?: Record<string, any>) =>
258
+ group(groupId, { ...traits, sessionId }, { context }),
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Creates an anonymous user tracking flow.
264
+ *
265
+ * @remarks
266
+ * This helper creates a session object for tracking anonymous users before
267
+ * they are identified. When the user signs up or logs in, use the `alias`
268
+ * method to link their anonymous activity to their user ID.
269
+ *
270
+ * @param anonymousId - Unique identifier for the anonymous user
271
+ * @returns Session object with `track`, `page`, `identify`, and `alias` methods
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const anonymousSession = createAnonymousSession('anon-123');
276
+ * await analytics.emit(anonymousSession.track('Page Viewed', { page: 'home' }));
277
+ *
278
+ * // Later, when user signs up
279
+ * await analytics.emit(anonymousSession.alias('user-123'));
280
+ * await analytics.emit(anonymousSession.identify('user-123', { email: 'user@example.com' }));
281
+ * ```
282
+ */
283
+ export function createAnonymousSession(anonymousId: string) {
284
+ const options: EmitterOptions = { anonymousId };
285
+
286
+ return {
287
+ // Track an event
288
+ track: (event: string, properties?: Record<string, any>) => track(event, properties, options),
289
+
290
+ // Track a page view
291
+ page: (name?: string, properties?: Record<string, any>) =>
292
+ page(undefined, name, properties, options),
293
+
294
+ // Convert to identified user
295
+ identify: (userId: string, traits?: Record<string, any>) => identify(userId, traits, options),
296
+
297
+ // Alias when user signs up
298
+ alias: (userId: string) => alias(userId, anonymousId, options),
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Type guard to check if a payload is a track event.
304
+ *
305
+ * @param payload - The payload to check
306
+ * @returns True if the payload is a track event
307
+ */
308
+ export const isTrackPayload = (payload: EmitterPayload): payload is EmitterTrackPayload =>
309
+ payload.type === 'track';
310
+
311
+ /**
312
+ * Type guard to check if a payload is an identify event.
313
+ *
314
+ * @param payload - The payload to check
315
+ * @returns True if the payload is an identify event
316
+ */
317
+ export const isIdentifyPayload = (payload: EmitterPayload): payload is EmitterIdentifyPayload =>
318
+ payload.type === 'identify';
319
+
320
+ /**
321
+ * Type guard to check if a payload is a page event.
322
+ *
323
+ * @param payload - The payload to check
324
+ * @returns True if the payload is a page event
325
+ */
326
+ export const isPagePayload = (payload: EmitterPayload): payload is EmitterPagePayload =>
327
+ payload.type === 'page';
328
+
329
+ /**
330
+ * Type guard to check if a payload is a group event.
331
+ *
332
+ * @param payload - The payload to check
333
+ * @returns True if the payload is a group event
334
+ */
335
+ export const isGroupPayload = (payload: EmitterPayload): payload is EmitterGroupPayload =>
336
+ payload.type === 'group';
337
+
338
+ /**
339
+ * Type guard to check if a payload is an alias event.
340
+ *
341
+ * @param payload - The payload to check
342
+ * @returns True if the payload is an alias event
343
+ */
344
+ export const isAliasPayload = (payload: EmitterPayload): payload is EmitterAliasPayload =>
345
+ payload.type === 'alias';
346
+
347
+ /**
348
+ * Adds consistent metadata to an analytics event payload.
349
+ *
350
+ * @remarks
351
+ * This helper enriches an event payload with metadata that will be included
352
+ * in the event's context.app object. Useful for tracking version, source,
353
+ * or other application-level metadata.
354
+ *
355
+ * @param payload - The event payload to enrich
356
+ * @param metadata - Metadata to add (version, source, or custom properties)
357
+ * @returns The enriched payload with metadata in context.app
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * const event = track('Button Clicked', { button: 'signup' });
362
+ * const enriched = withMetadata(event, { version: '1.0.0', source: 'webapp' });
363
+ * await analytics.emit(enriched);
364
+ * ```
365
+ */
366
+ export function withMetadata<T extends EmitterPayload>(
367
+ payload: T,
368
+ metadata: { version?: string; source?: string; [key: string]: unknown },
369
+ ): T {
370
+ return {
371
+ ...payload,
372
+ context: {
373
+ ...payload.context,
374
+ app: {
375
+ ...payload.context?.app,
376
+ ...metadata,
377
+ },
378
+ },
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Adds UTM parameters to an analytics event payload.
384
+ *
385
+ * @remarks
386
+ * This helper enriches an event payload with UTM campaign parameters that
387
+ * will be included in the event's context.campaign object. Useful for
388
+ * tracking marketing campaign attribution.
389
+ *
390
+ * @param payload - The event payload to enrich
391
+ * @param utm - UTM parameters (source, medium, campaign, term, content)
392
+ * @returns The enriched payload with UTM parameters in context.campaign
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * const event = track('Signup Started', {});
397
+ * const enriched = withUTM(event, {
398
+ * source: 'google',
399
+ * medium: 'cpc',
400
+ * campaign: 'summer-sale'
401
+ * });
402
+ * await analytics.emit(enriched);
403
+ * ```
404
+ */
405
+ export function withUTM<T extends EmitterPayload>(
406
+ payload: T,
407
+ utm: { source?: string; medium?: string; campaign?: string; term?: string; content?: string },
408
+ ): T {
409
+ return {
410
+ ...payload,
411
+ context: {
412
+ ...payload.context,
413
+ campaign: {
414
+ ...payload.context?.campaign,
415
+ ...utm,
416
+ },
417
+ },
418
+ };
419
+ }