@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.
- package/README.md +509 -0
- package/dist/ai-YMnynb-t.mjs +3347 -0
- package/dist/ai-YMnynb-t.mjs.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client-CTzJVFU5.mjs +9 -0
- package/dist/client-CTzJVFU5.mjs.map +1 -0
- package/dist/client-CcFTauAh.mjs +54 -0
- package/dist/client-CcFTauAh.mjs.map +1 -0
- package/dist/client-CeOLjbac.mjs +281 -0
- package/dist/client-CeOLjbac.mjs.map +1 -0
- package/dist/client-D339NFJS.mjs +267 -0
- package/dist/client-D339NFJS.mjs.map +1 -0
- package/dist/client-next.d.mts +62 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +525 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +30 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +186 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-DPS6bSYo.d.mts +34 -0
- package/dist/config-DPS6bSYo.d.mts.map +1 -0
- package/dist/config-P6P5adJg.mjs +287 -0
- package/dist/config-P6P5adJg.mjs.map +1 -0
- package/dist/console-8bND3mMU.mjs +128 -0
- package/dist/console-8bND3mMU.mjs.map +1 -0
- package/dist/ecommerce-Cgu4wlux.mjs +993 -0
- package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
- package/dist/emitters-6-nKo8i-.mjs +208 -0
- package/dist/emitters-6-nKo8i-.mjs.map +1 -0
- package/dist/emitters-DldkVSPp.d.mts +12 -0
- package/dist/emitters-DldkVSPp.d.mts.map +1 -0
- package/dist/index-BfNWgfa5.d.mts +1494 -0
- package/dist/index-BfNWgfa5.d.mts.map +1 -0
- package/dist/index-BkIWe--N.d.mts +953 -0
- package/dist/index-BkIWe--N.d.mts.map +1 -0
- package/dist/index-jPzXRn52.d.mts +184 -0
- package/dist/index-jPzXRn52.d.mts.map +1 -0
- package/dist/manager-DvRRjza6.d.mts +76 -0
- package/dist/manager-DvRRjza6.d.mts.map +1 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
- package/dist/providers-http-client.d.mts +37 -0
- package/dist/providers-http-client.d.mts.map +1 -0
- package/dist/providers-http-client.mjs +320 -0
- package/dist/providers-http-client.mjs.map +1 -0
- package/dist/providers-http-server.d.mts +31 -0
- package/dist/providers-http-server.d.mts.map +1 -0
- package/dist/providers-http-server.mjs +297 -0
- package/dist/providers-http-server.mjs.map +1 -0
- package/dist/providers-http.d.mts +46 -0
- package/dist/providers-http.d.mts.map +1 -0
- package/dist/providers-http.mjs +4 -0
- package/dist/server-edge.d.mts +9 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +373 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +67 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +193 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +7 -0
- package/dist/service-cYtBBL8x.mjs +945 -0
- package/dist/service-cYtBBL8x.mjs.map +1 -0
- package/dist/shared.d.mts +16 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +93 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types-BxBnNQ0V.d.mts +354 -0
- package/dist/types-BxBnNQ0V.d.mts.map +1 -0
- package/dist/types-CBvxUEaF.d.mts +216 -0
- package/dist/types-CBvxUEaF.d.mts.map +1 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +0 -0
- package/dist/vercel-types-lwakUfoI.d.mts +102 -0
- package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
- package/package.json +129 -0
- package/src/client/index.ts +164 -0
- package/src/client/manager.ts +71 -0
- package/src/client/next/components.tsx +270 -0
- package/src/client/next/hooks.ts +217 -0
- package/src/client/next/manager.ts +141 -0
- package/src/client/next.ts +144 -0
- package/src/client-next.ts +101 -0
- package/src/client.ts +89 -0
- package/src/examples/ai-sdk-patterns.ts +583 -0
- package/src/examples/emitter-patterns.ts +476 -0
- package/src/examples/nextjs-emitter-patterns.tsx +403 -0
- package/src/next/app-router.tsx +564 -0
- package/src/next/client.ts +419 -0
- package/src/next/index.ts +84 -0
- package/src/next/middleware.ts +429 -0
- package/src/next/rsc.tsx +300 -0
- package/src/next/server.ts +253 -0
- package/src/next/types.d.ts +220 -0
- package/src/providers/base-provider.ts +419 -0
- package/src/providers/console/client.ts +10 -0
- package/src/providers/console/index.ts +152 -0
- package/src/providers/console/server.ts +6 -0
- package/src/providers/console/types.ts +15 -0
- package/src/providers/http/client.ts +464 -0
- package/src/providers/http/index.ts +30 -0
- package/src/providers/http/server.ts +396 -0
- package/src/providers/http/types.ts +135 -0
- package/src/providers/posthog/client.ts +518 -0
- package/src/providers/posthog/index.ts +11 -0
- package/src/providers/posthog/server.ts +329 -0
- package/src/providers/posthog/types.ts +104 -0
- package/src/providers/segment/client.ts +113 -0
- package/src/providers/segment/index.ts +11 -0
- package/src/providers/segment/server.ts +115 -0
- package/src/providers/segment/types.ts +51 -0
- package/src/providers/vercel/client.ts +102 -0
- package/src/providers/vercel/index.ts +11 -0
- package/src/providers/vercel/server.ts +89 -0
- package/src/providers/vercel/types.ts +27 -0
- package/src/server/index.ts +103 -0
- package/src/server/manager.ts +62 -0
- package/src/server/next.ts +210 -0
- package/src/server-edge.ts +442 -0
- package/src/server-next.ts +39 -0
- package/src/server.ts +106 -0
- package/src/shared/emitters/ai/README.md +981 -0
- package/src/shared/emitters/ai/events/agent.ts +130 -0
- package/src/shared/emitters/ai/events/artifacts.ts +167 -0
- package/src/shared/emitters/ai/events/chat.ts +126 -0
- package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
- package/src/shared/emitters/ai/events/completion.ts +103 -0
- package/src/shared/emitters/ai/events/content-generation.ts +347 -0
- package/src/shared/emitters/ai/events/conversation.ts +332 -0
- package/src/shared/emitters/ai/events/product-features.ts +1402 -0
- package/src/shared/emitters/ai/events/streaming.ts +114 -0
- package/src/shared/emitters/ai/events/tool.ts +93 -0
- package/src/shared/emitters/ai/index.ts +69 -0
- package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
- package/src/shared/emitters/ai/track-ai.ts +50 -0
- package/src/shared/emitters/ai/types.ts +1041 -0
- package/src/shared/emitters/ai/utils.ts +468 -0
- package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
- package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
- package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
- package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
- package/src/shared/emitters/ecommerce/events/order.ts +199 -0
- package/src/shared/emitters/ecommerce/events/product.ts +205 -0
- package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
- package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
- package/src/shared/emitters/ecommerce/index.ts +46 -0
- package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
- package/src/shared/emitters/ecommerce/types.ts +314 -0
- package/src/shared/emitters/ecommerce/utils.ts +216 -0
- package/src/shared/emitters/emitter-types.ts +974 -0
- package/src/shared/emitters/emitters.ts +292 -0
- package/src/shared/emitters/helpers.ts +419 -0
- package/src/shared/emitters/index.ts +66 -0
- package/src/shared/index.ts +142 -0
- package/src/shared/ingestion/index.ts +66 -0
- package/src/shared/ingestion/schemas.ts +386 -0
- package/src/shared/ingestion/service.ts +628 -0
- package/src/shared/node22-features.ts +848 -0
- package/src/shared/providers/console-provider.ts +160 -0
- package/src/shared/types/base-types.ts +54 -0
- package/src/shared/types/console-types.ts +19 -0
- package/src/shared/types/posthog-types.ts +131 -0
- package/src/shared/types/segment-types.ts +15 -0
- package/src/shared/types/types.ts +397 -0
- package/src/shared/types/vercel-types.ts +19 -0
- package/src/shared/utils/config-client.ts +19 -0
- package/src/shared/utils/config.ts +250 -0
- package/src/shared/utils/emitter-adapter.ts +212 -0
- package/src/shared/utils/manager.test.ts +36 -0
- package/src/shared/utils/manager.ts +1322 -0
- package/src/shared/utils/posthog-bootstrap.ts +136 -0
- package/src/shared/utils/posthog-client-utils.ts +48 -0
- package/src/shared/utils/posthog-next-utils.ts +282 -0
- package/src/shared/utils/posthog-server-utils.ts +210 -0
- package/src/shared/utils/rate-limit.ts +289 -0
- package/src/shared/utils/security.ts +545 -0
- package/src/shared/utils/validation-client.ts +161 -0
- package/src/shared/utils/validation.ts +399 -0
- package/src/shared.ts +155 -0
- 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
|
+
}
|