@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,945 @@
1
+ import { t as ConsoleProvider } from "./console-8bND3mMU.mjs";
2
+ import { b as validateEventName, v as createAnalyticsManager, y as sanitizeProperties } from "./posthog-bootstrap-CYfIy_WS.mjs";
3
+ import { t as PROVIDER_REQUIREMENTS } from "./config-P6P5adJg.mjs";
4
+ import { HttpServerProvider } from "./providers-http-server.mjs";
5
+ import { logError, logInfo, logWarn } from "@od-oneapp/shared/logger";
6
+ import { logDebug as logDebug$1, logError as logError$1, logInfo as logInfo$1, logWarn as logWarn$1 } from "@od-oneapp/shared/logs";
7
+ import { SegmentServerProvider } from "@integrations/segment/analytics-provider/server";
8
+ import { VercelServerProvider } from "@integrations/vercel/analytics-provider/server";
9
+ import { z } from "zod";
10
+
11
+ //#region src/server/manager.ts
12
+ /**
13
+ * @fileoverview Server analytics manager with static provider registry
14
+ * Server analytics manager with static provider registry
15
+ */
16
+ const SERVER_PROVIDERS = {
17
+ console: (config) => new ConsoleProvider(config),
18
+ http: (config) => new HttpServerProvider(config),
19
+ segment: (config) => new SegmentServerProvider(config),
20
+ vercel: (config) => new VercelServerProvider(config)
21
+ };
22
+ /**
23
+ * Create and initialize a server analytics instance
24
+ * This is the primary way to create analytics for server-side applications
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const analytics = await createServerAnalytics({
29
+ * providers: {
30
+ * segment: { writeKey: process.env.SEGMENT_KEY! },
31
+ * },
32
+ * });
33
+ * await analytics.page('/admin', { title: 'Admin Dashboard' });
34
+ * ```
35
+ * @param config - Analytics configuration including providers and settings
36
+ * @returns Promise resolving to initialized analytics manager
37
+ */
38
+ async function createServerAnalytics(config) {
39
+ const manager = createAnalyticsManager(config, SERVER_PROVIDERS);
40
+ await manager.initialize();
41
+ return manager;
42
+ }
43
+ /**
44
+ * Create a server analytics instance without initializing
45
+ * Useful when you need to control initialization timing
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const analytics = createServerAnalyticsUninitialized(config);
50
+ * if (shouldEmit) {
51
+ * await analytics.initialize();
52
+ * await analytics.track('CRON Completed');
53
+ * }
54
+ * ```
55
+ * @param config - Analytics configuration including providers and settings
56
+ * @returns Uninitialized analytics manager instance
57
+ */
58
+ function createServerAnalyticsUninitialized(config) {
59
+ return createAnalyticsManager(config, SERVER_PROVIDERS);
60
+ }
61
+
62
+ //#endregion
63
+ //#region src/shared/utils/validation.ts
64
+ /**
65
+ * @fileoverview Validation utilities for analytics configuration
66
+ *
67
+ * This module provides comprehensive validation for analytics configurations,
68
+ * including provider validation, environment-specific checks, and helpful
69
+ * warnings for common misconfigurations.
70
+ *
71
+ * **Features**:
72
+ * - Configuration structure validation
73
+ * - Provider-specific field validation
74
+ * - Environment variable validation
75
+ * - Environment-specific warnings
76
+ * - Detailed error reporting
77
+ *
78
+ * @module @od-oneapp/analytics/shared/utils/validation
79
+ */
80
+ /**
81
+ * Comprehensive configuration validation.
82
+ *
83
+ * Validates the entire analytics configuration structure, including:
84
+ * - Configuration object structure
85
+ * - Providers object existence
86
+ * - Individual provider configurations
87
+ * - Environment-specific warnings
88
+ *
89
+ * Accepts `unknown` input for defensive validation of potentially malformed configs.
90
+ *
91
+ * @param {unknown} config - Analytics configuration to validate
92
+ * @returns {ValidationResult} Validation result with errors, warnings, and validity status
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const result = validateAnalyticsConfig(config);
97
+ * if (!result.isValid) {
98
+ * console.error('Validation errors:', result.errors);
99
+ * }
100
+ * if (result.warnings.length > 0) {
101
+ * console.warn('Warnings:', result.warnings);
102
+ * }
103
+ * ```
104
+ */
105
+ function validateAnalyticsConfig(config) {
106
+ const errors = [];
107
+ const warnings = [];
108
+ if (!config || typeof config !== "object") {
109
+ errors.push({
110
+ provider: "global",
111
+ field: "config",
112
+ message: "Analytics configuration is required and must be an object"
113
+ });
114
+ return {
115
+ isValid: false,
116
+ errors,
117
+ warnings
118
+ };
119
+ }
120
+ const typedConfig = config;
121
+ if (!typedConfig.providers || typeof typedConfig.providers !== "object") {
122
+ errors.push({
123
+ provider: "global",
124
+ field: "providers",
125
+ message: "Providers configuration is required and must be an object"
126
+ });
127
+ return {
128
+ isValid: false,
129
+ errors,
130
+ warnings
131
+ };
132
+ }
133
+ if (Object.keys(typedConfig.providers).length === 0) warnings.push("No providers configured. Analytics will not track any events.");
134
+ for (const [providerName, providerConfig] of Object.entries(typedConfig.providers)) {
135
+ const providerErrors = validateProvider(providerName, providerConfig);
136
+ errors.push(...providerErrors);
137
+ }
138
+ const isBrowser = typeof window !== "undefined";
139
+ if (isBrowser && typedConfig.providers.mixpanel) warnings.push("Mixpanel provider configured on client-side. Consider using server-side for better performance.");
140
+ if (!isBrowser && typedConfig.providers.vercel) warnings.push("Vercel Analytics has limited server-side support. Consider using client-side for better features.");
141
+ return {
142
+ isValid: errors.length === 0,
143
+ errors,
144
+ warnings
145
+ };
146
+ }
147
+ /**
148
+ * Validate a single provider configuration.
149
+ *
150
+ * Checks that the provider is known and that all required fields are present.
151
+ *
152
+ * @param {string} providerName - Name of the provider to validate
153
+ * @param {ProviderConfig} config - Provider configuration to validate
154
+ * @returns {ValidationError[]} Array of validation errors (empty if valid)
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * const errors = validateProvider('posthog', { apiKey: 'phc_xxx' });
159
+ * if (errors.length > 0) {
160
+ * console.error('Provider errors:', errors);
161
+ * }
162
+ * ```
163
+ */
164
+ function validateProvider(providerName, config) {
165
+ const errors = [];
166
+ const knownProviders = [
167
+ "segment",
168
+ "posthog",
169
+ "vercel",
170
+ "console",
171
+ "mixpanel"
172
+ ];
173
+ if (!knownProviders.includes(providerName)) {
174
+ errors.push({
175
+ provider: providerName,
176
+ field: "name",
177
+ message: `Unknown provider '${providerName}'. Known providers: ${knownProviders.join(", ")}`
178
+ });
179
+ return errors;
180
+ }
181
+ const requiredFields = PROVIDER_REQUIREMENTS[providerName] ?? [];
182
+ for (const field of requiredFields) {
183
+ const value = config[field];
184
+ if (!value) errors.push({
185
+ provider: providerName,
186
+ field,
187
+ message: `Required field '${field}' is missing for provider '${providerName}'`
188
+ });
189
+ else if (typeof value === "string" && value.trim() === "") errors.push({
190
+ provider: providerName,
191
+ field,
192
+ message: `Required field '${field}' cannot be empty for provider '${providerName}'`
193
+ });
194
+ }
195
+ switch (providerName) {
196
+ case "segment":
197
+ if (config.writeKey && !isValidSegmentWriteKey(config.writeKey)) errors.push({
198
+ provider: providerName,
199
+ field: "writeKey",
200
+ message: "Segment writeKey appears to be invalid format"
201
+ });
202
+ break;
203
+ case "posthog":
204
+ if (config.apiKey && !isValidPostHogApiKey(config.apiKey)) errors.push({
205
+ provider: providerName,
206
+ field: "apiKey",
207
+ message: "PostHog apiKey appears to be invalid format"
208
+ });
209
+ break;
210
+ }
211
+ return errors;
212
+ }
213
+ /**
214
+ * Validates Segment write key format.
215
+ *
216
+ * Checks that the write key matches Segment's expected format:
217
+ * - Exactly 32 alphanumeric characters
218
+ * - Not a placeholder value
219
+ *
220
+ * @param {string} writeKey - Write key to validate
221
+ * @returns {boolean} `true` if valid, `false` otherwise
222
+ *
223
+ * @internal
224
+ */
225
+ function isValidSegmentWriteKey(writeKey) {
226
+ if (!/^[\dA-Za-z]{32}$/.test(writeKey)) return false;
227
+ if ([
228
+ "your-write-key",
229
+ "paste-key-here",
230
+ "xxxxxxxx",
231
+ "example"
232
+ ].some((p) => writeKey.toLowerCase().includes(p.toLowerCase()))) return false;
233
+ return true;
234
+ }
235
+ /**
236
+ * Validates PostHog API key format.
237
+ *
238
+ * Checks that the API key matches PostHog's expected format:
239
+ * - Starts with `phc_` prefix
240
+ * - Followed by 43 alphanumeric/dash/underscore characters
241
+ * - Not a placeholder value
242
+ *
243
+ * @param {string} apiKey - API key to validate
244
+ * @returns {boolean} `true` if valid, `false` otherwise
245
+ *
246
+ * @internal
247
+ */
248
+ function isValidPostHogApiKey(apiKey) {
249
+ if (!/^phc_[\w-]{43}$/.test(apiKey)) return false;
250
+ if ([
251
+ "your-api-key",
252
+ "paste-key-here",
253
+ "xxxxxxxx",
254
+ "example"
255
+ ].some((p) => apiKey.toLowerCase().includes(p.toLowerCase()))) return false;
256
+ return true;
257
+ }
258
+ /**
259
+ * Utility to throw validation errors (for strict validation).
260
+ *
261
+ * Validates the configuration and throws an error if validation fails.
262
+ * Useful for ensuring configuration is valid before using analytics.
263
+ *
264
+ * @param {AnalyticsConfig} config - Analytics configuration to validate
265
+ * @throws {Error} If configuration validation fails
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * try {
270
+ * validateConfigOrThrow(config);
271
+ * // Configuration is valid, proceed
272
+ * } catch (error) {
273
+ * console.error('Invalid config:', error.message);
274
+ * }
275
+ * ```
276
+ */
277
+ function validateConfigOrThrow(config) {
278
+ const result = validateAnalyticsConfig(config);
279
+ if (!result.isValid) {
280
+ const errorMessages = result.errors.map((error) => `${error.provider}.${error.field}: ${error.message}`).join("\n");
281
+ throw new Error(`Analytics configuration validation failed:\n${errorMessages}`);
282
+ }
283
+ if (result.warnings.length > 0 && config.onError) config.onError(/* @__PURE__ */ new Error("Analytics configuration warnings"), {
284
+ provider: "analytics",
285
+ method: "validateConfig",
286
+ warnings: result.warnings
287
+ });
288
+ }
289
+ /**
290
+ * Development helper to check configuration.
291
+ *
292
+ * Logs configuration details and validation results for debugging.
293
+ * Only useful in development environments.
294
+ *
295
+ * @param {AnalyticsConfig} config - Analytics configuration to debug
296
+ * @returns {Promise<void>} Promise that resolves when debugging is complete
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * if (process.env.NODE_ENV === 'development') {
301
+ * await debugConfig(config);
302
+ * }
303
+ * ```
304
+ */
305
+ async function debugConfig(config) {
306
+ const result = validateAnalyticsConfig(config);
307
+ logInfo("Analytics Configuration Debug", {
308
+ config,
309
+ validationResult: result
310
+ });
311
+ if (result.errors.length > 0) logError("Analytics configuration errors: Validation failed", { errors: result.errors });
312
+ if (result.warnings.length > 0) logWarn("Analytics configuration warnings", { warnings: result.warnings });
313
+ }
314
+
315
+ //#endregion
316
+ //#region src/shared/ingestion/schemas.ts
317
+ /**
318
+ * @fileoverview Event Ingestion Schemas and Validation
319
+ *
320
+ * Defines Zod schemas for validating event ingestion payloads. These schemas
321
+ * ensure type-safe, secure event ingestion with proper validation.
322
+ *
323
+ * **Key Features**:
324
+ * - CloudEvents-style fields for future interoperability
325
+ * - Support for single events and batched arrays
326
+ * - Strict validation of required fields
327
+ * - Typed extensions per event category
328
+ *
329
+ * @module @od-oneapp/analytics/shared/ingestion/schemas
330
+ */
331
+ /**
332
+ * Property value schema - safe, serializable values only.
333
+ */
334
+ const PropertyValueSchema = z.union([
335
+ z.string(),
336
+ z.number(),
337
+ z.boolean(),
338
+ z.null(),
339
+ z.date()
340
+ ]);
341
+ /**
342
+ * Property object schema with nested structure support.
343
+ */
344
+ const PropertyObjectSchema = z.record(z.string(), z.lazy(() => z.union([
345
+ PropertyValueSchema,
346
+ z.array(PropertyValueSchema),
347
+ z.record(z.string(), z.unknown())
348
+ ])));
349
+ /**
350
+ * Emitter context schema - contextual information about the environment.
351
+ */
352
+ const EmitterContextSchema = z.object({
353
+ app: z.object({
354
+ name: z.string().optional(),
355
+ version: z.string().optional(),
356
+ build: z.string().optional(),
357
+ namespace: z.string().optional()
358
+ }).optional(),
359
+ campaign: z.object({
360
+ name: z.string().optional(),
361
+ source: z.string().optional(),
362
+ medium: z.string().optional(),
363
+ term: z.string().optional(),
364
+ content: z.string().optional()
365
+ }).passthrough().optional(),
366
+ device: z.object({
367
+ id: z.string().optional(),
368
+ manufacturer: z.string().optional(),
369
+ model: z.string().optional(),
370
+ name: z.string().optional(),
371
+ type: z.string().optional(),
372
+ version: z.string().optional()
373
+ }).optional(),
374
+ ip: z.string().optional(),
375
+ library: z.object({
376
+ name: z.string(),
377
+ version: z.string()
378
+ }).optional(),
379
+ locale: z.string().optional(),
380
+ network: z.object({
381
+ bluetooth: z.boolean().optional(),
382
+ carrier: z.string().optional(),
383
+ cellular: z.boolean().optional(),
384
+ wifi: z.boolean().optional()
385
+ }).optional(),
386
+ os: z.object({
387
+ name: z.string().optional(),
388
+ version: z.string().optional()
389
+ }).optional(),
390
+ page: z.object({
391
+ path: z.string().optional(),
392
+ referrer: z.string().optional(),
393
+ search: z.string().optional(),
394
+ title: z.string().optional(),
395
+ url: z.string().optional()
396
+ }).optional(),
397
+ screen: z.object({
398
+ density: z.number().optional(),
399
+ height: z.number().int().positive().optional(),
400
+ width: z.number().int().positive().optional()
401
+ }).optional(),
402
+ timezone: z.string().optional(),
403
+ groupId: z.string().optional(),
404
+ userAgent: z.string().optional(),
405
+ channel: z.enum([
406
+ "server",
407
+ "browser",
408
+ "mobile",
409
+ "api"
410
+ ]).optional(),
411
+ location: z.object({
412
+ city: z.string().optional(),
413
+ country: z.string().optional(),
414
+ latitude: z.number().optional(),
415
+ longitude: z.number().optional(),
416
+ region: z.string().optional()
417
+ }).optional()
418
+ }).passthrough();
419
+ /**
420
+ * Base payload schema - common fields for all event types.
421
+ */
422
+ const BasePayloadSchema = z.object({
423
+ anonymousId: z.string().max(255).optional(),
424
+ userId: z.string().max(255).optional(),
425
+ timestamp: z.union([z.string().datetime(), z.date()]).optional(),
426
+ originalTimestamp: z.union([z.string().datetime(), z.date()]).optional(),
427
+ context: EmitterContextSchema.optional(),
428
+ messageId: z.string().uuid().optional()
429
+ }).refine((data) => Boolean(data.anonymousId) || Boolean(data.userId), { message: "Either anonymousId or userId must be provided" });
430
+ /**
431
+ * Track event payload schema.
432
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
433
+ */
434
+ const TrackEventSchema = BasePayloadSchema.safeExtend({
435
+ type: z.literal("track"),
436
+ event: z.string().min(1).max(255),
437
+ properties: PropertyObjectSchema.optional()
438
+ });
439
+ /**
440
+ * Identify event payload schema.
441
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
442
+ */
443
+ const IdentifyEventSchema = BasePayloadSchema.safeExtend({
444
+ type: z.literal("identify"),
445
+ userId: z.string().min(1).max(255),
446
+ traits: PropertyObjectSchema.optional()
447
+ });
448
+ /**
449
+ * Page event payload schema.
450
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
451
+ */
452
+ const PageEventSchema = BasePayloadSchema.safeExtend({
453
+ type: z.literal("page"),
454
+ name: z.string().max(255).optional(),
455
+ category: z.string().max(255).optional(),
456
+ properties: PropertyObjectSchema.optional()
457
+ });
458
+ /**
459
+ * Screen event payload schema.
460
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
461
+ */
462
+ const ScreenEventSchema = BasePayloadSchema.safeExtend({
463
+ type: z.literal("screen"),
464
+ name: z.string().max(255).optional(),
465
+ category: z.string().max(255).optional(),
466
+ properties: PropertyObjectSchema.optional()
467
+ });
468
+ /**
469
+ * Group event payload schema.
470
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
471
+ */
472
+ const GroupEventSchema = BasePayloadSchema.safeExtend({
473
+ type: z.literal("group"),
474
+ groupId: z.string().min(1).max(255),
475
+ traits: PropertyObjectSchema.optional()
476
+ });
477
+ /**
478
+ * Alias event payload schema.
479
+ * Uses safeExtend() because BasePayloadSchema has refinements (Zod v4 requirement).
480
+ */
481
+ const AliasEventSchema = BasePayloadSchema.safeExtend({
482
+ type: z.literal("alias"),
483
+ userId: z.string().min(1).max(255),
484
+ previousId: z.string().min(1).max(255)
485
+ });
486
+ /**
487
+ * Union of all event payload schemas.
488
+ */
489
+ const EventPayloadSchema = z.discriminatedUnion("type", [
490
+ TrackEventSchema,
491
+ IdentifyEventSchema,
492
+ PageEventSchema,
493
+ ScreenEventSchema,
494
+ GroupEventSchema,
495
+ AliasEventSchema
496
+ ]);
497
+ /**
498
+ * Single event ingestion request schema.
499
+ */
500
+ const SingleEventRequestSchema = EventPayloadSchema;
501
+ /**
502
+ * Batch event ingestion request schema.
503
+ *
504
+ * The batch size limit is enforced at the service level via `maxBatchSize` config
505
+ * (default: 100) rather than in the schema, allowing for flexible configuration.
506
+ */
507
+ const BatchEventRequestSchema = z.object({ batch: z.array(EventPayloadSchema).min(1) });
508
+ /**
509
+ * Combined ingestion request schema - accepts single event or batch.
510
+ */
511
+ const IngestionRequestSchema = z.union([BatchEventRequestSchema, SingleEventRequestSchema]);
512
+ /**
513
+ * Event processing result.
514
+ */
515
+ const EventResultSchema = z.object({
516
+ id: z.string().uuid(),
517
+ messageId: z.string().uuid().optional(),
518
+ type: z.enum([
519
+ "track",
520
+ "identify",
521
+ "page",
522
+ "screen",
523
+ "group",
524
+ "alias"
525
+ ]),
526
+ status: z.enum(["accepted", "rejected"]),
527
+ error: z.string().optional()
528
+ });
529
+ /**
530
+ * Successful ingestion response schema.
531
+ */
532
+ const IngestionSuccessResponseSchema = z.object({
533
+ success: z.literal(true),
534
+ accepted: z.number().int().nonnegative(),
535
+ rejected: z.number().int().nonnegative(),
536
+ results: z.array(EventResultSchema),
537
+ receivedAt: z.string().datetime()
538
+ });
539
+ /**
540
+ * Error response schema.
541
+ */
542
+ const IngestionErrorResponseSchema = z.object({
543
+ success: z.literal(false),
544
+ code: z.string(),
545
+ error: z.string(),
546
+ fieldErrors: z.array(z.object({
547
+ path: z.array(z.union([z.string(), z.number()])),
548
+ message: z.string()
549
+ })).optional()
550
+ });
551
+ /**
552
+ * Combined response schema.
553
+ */
554
+ const IngestionResponseSchema = z.union([IngestionSuccessResponseSchema, IngestionErrorResponseSchema]);
555
+
556
+ //#endregion
557
+ //#region src/shared/ingestion/service.ts
558
+ /**
559
+ * @fileoverview Event Ingestion Service
560
+ *
561
+ * Provides server-side event ingestion functionality. Handles validation,
562
+ * normalization, and forwarding of events to the analytics system.
563
+ *
564
+ * **Key Features**:
565
+ * - Validates incoming events against Zod schemas
566
+ * - Normalizes event payloads with defaults
567
+ * - Forwards events to AnalyticsManager
568
+ * - Tracks ingestion metrics
569
+ * - Handles batch processing efficiently
570
+ *
571
+ * @module @od-oneapp/analytics/shared/ingestion/service
572
+ */
573
+ const DEFAULT_CONFIG = {
574
+ maxBatchSize: 100,
575
+ maxPayloadSize: 1024 * 1024,
576
+ stripPII: true,
577
+ stripHTML: true,
578
+ eventTimeout: 5e3,
579
+ batchConcurrency: 10
580
+ };
581
+ /**
582
+ * Generate a UUID v4.
583
+ */
584
+ function generateUUID() {
585
+ return crypto.randomUUID();
586
+ }
587
+ /**
588
+ * Get current ISO timestamp.
589
+ */
590
+ function getCurrentTimestamp() {
591
+ return (/* @__PURE__ */ new Date()).toISOString();
592
+ }
593
+ /**
594
+ * Check if request is a batch request.
595
+ */
596
+ function isBatchRequest(request) {
597
+ return "batch" in request && Array.isArray(request.batch);
598
+ }
599
+ /**
600
+ * Normalize an event payload with defaults and server metadata.
601
+ */
602
+ function normalizeEvent(event, context, receivedAt) {
603
+ const normalized = { ...event };
604
+ if (!normalized.messageId) normalized.messageId = generateUUID();
605
+ if (!normalized.timestamp) normalized.timestamp = receivedAt;
606
+ if (!normalized.originalTimestamp) normalized.originalTimestamp = normalized.timestamp;
607
+ normalized.context = {
608
+ ...normalized.context,
609
+ channel: normalized.context?.channel ?? "api",
610
+ ip: context.ip ?? normalized.context?.ip,
611
+ userAgent: context.userAgent ?? normalized.context?.userAgent,
612
+ library: normalized.context?.library ?? {
613
+ name: context.source,
614
+ version: context.sdkVersion ?? "unknown"
615
+ }
616
+ };
617
+ if (!normalized.userId && context.userId) normalized.userId = context.userId;
618
+ return normalized;
619
+ }
620
+ /**
621
+ * Convert EventPayload to EmitterPayload format for AnalyticsManager.
622
+ * Explicitly maps fields to ensure type safety.
623
+ */
624
+ function toEmitterPayload(event) {
625
+ const basePayload = {
626
+ userId: event.userId,
627
+ anonymousId: event.anonymousId,
628
+ timestamp: event.timestamp,
629
+ context: event.context,
630
+ messageId: event.messageId
631
+ };
632
+ switch (event.type) {
633
+ case "track": return {
634
+ type: "track",
635
+ event: event.event,
636
+ properties: event.properties,
637
+ ...basePayload
638
+ };
639
+ case "identify": return {
640
+ type: "identify",
641
+ traits: event.traits,
642
+ ...basePayload
643
+ };
644
+ case "page": return {
645
+ type: "page",
646
+ name: event.name,
647
+ category: event.category,
648
+ properties: event.properties,
649
+ ...basePayload
650
+ };
651
+ case "screen": return {
652
+ type: "screen",
653
+ name: event.name,
654
+ category: event.category,
655
+ properties: event.properties,
656
+ ...basePayload
657
+ };
658
+ case "group": return {
659
+ type: "group",
660
+ groupId: event.groupId,
661
+ traits: event.traits,
662
+ ...basePayload
663
+ };
664
+ case "alias": return {
665
+ type: "alias",
666
+ previousId: event.previousId,
667
+ ...basePayload
668
+ };
669
+ default: throw new Error(`Unknown event type: ${event.type}`);
670
+ }
671
+ }
672
+ /**
673
+ * Event Ingestion Service.
674
+ *
675
+ * Handles validation, normalization, and forwarding of analytics events.
676
+ * Designed for high-volume ingestion with batching and rate limiting support.
677
+ *
678
+ * @example
679
+ * ```typescript
680
+ * const service = new IngestionService(analyticsManager);
681
+ *
682
+ * const result = await service.ingest(requestBody, {
683
+ * source: 'web-app',
684
+ * tenantId: 'tenant-123',
685
+ * userId: 'user-456',
686
+ * });
687
+ *
688
+ * if (result.success) {
689
+ * console.log(`Accepted ${result.accepted} events`);
690
+ * }
691
+ * ```
692
+ */
693
+ var IngestionService = class {
694
+ config;
695
+ constructor(analyticsManager, config = {}) {
696
+ this.analyticsManager = analyticsManager;
697
+ this.config = {
698
+ ...DEFAULT_CONFIG,
699
+ ...config
700
+ };
701
+ }
702
+ /**
703
+ * Parse and validate the ingestion request payload.
704
+ *
705
+ * @param payload - Raw request payload (parsed JSON)
706
+ * @returns Parsed and validated request, or error response
707
+ */
708
+ parseRequest(payload) {
709
+ try {
710
+ const payloadSize = JSON.stringify(payload).length;
711
+ if (payloadSize > this.config.maxPayloadSize) return {
712
+ success: false,
713
+ error: {
714
+ success: false,
715
+ code: "PAYLOAD_TOO_LARGE",
716
+ error: `Payload size ${payloadSize} exceeds maximum ${this.config.maxPayloadSize} bytes`
717
+ }
718
+ };
719
+ } catch {
720
+ return {
721
+ success: false,
722
+ error: {
723
+ success: false,
724
+ code: "INVALID_JSON",
725
+ error: "Failed to serialize payload for size check"
726
+ }
727
+ };
728
+ }
729
+ const result = IngestionRequestSchema.safeParse(payload);
730
+ if (!result.success) return {
731
+ success: false,
732
+ error: {
733
+ success: false,
734
+ code: "VALIDATION_ERROR",
735
+ error: "Request validation failed",
736
+ fieldErrors: result.error.issues.map((err) => ({
737
+ path: err.path.map((p) => typeof p === "symbol" ? String(p) : p),
738
+ message: err.message
739
+ }))
740
+ }
741
+ };
742
+ if (isBatchRequest(result.data) && result.data.batch.length > this.config.maxBatchSize) return {
743
+ success: false,
744
+ error: {
745
+ success: false,
746
+ code: "BATCH_TOO_LARGE",
747
+ error: `Batch size ${result.data.batch.length} exceeds maximum ${this.config.maxBatchSize}`
748
+ }
749
+ };
750
+ return {
751
+ success: true,
752
+ data: result.data
753
+ };
754
+ }
755
+ /**
756
+ * Ingest events from a validated request.
757
+ *
758
+ * @param request - Validated ingestion request
759
+ * @param context - Ingestion context from the caller
760
+ * @returns Ingestion response with per-event results
761
+ */
762
+ async ingest(request, context) {
763
+ const startTime = process.hrtime.bigint();
764
+ const receivedAt = getCurrentTimestamp();
765
+ const events = isBatchRequest(request) ? request.batch : [request];
766
+ const results = [];
767
+ const metrics = {
768
+ totalReceived: events.length,
769
+ accepted: 0,
770
+ rejected: 0,
771
+ byType: {},
772
+ processingTimeMs: 0
773
+ };
774
+ const processedEvents = [];
775
+ for (const event of events) {
776
+ const eventId = generateUUID();
777
+ try {
778
+ if (event.type === "track") {
779
+ const validation = validateEventName(event.event);
780
+ if (!validation.valid) {
781
+ results.push({
782
+ id: eventId,
783
+ messageId: event.messageId,
784
+ type: event.type,
785
+ status: "rejected",
786
+ error: `Invalid event name: ${validation.reason}`
787
+ });
788
+ metrics.rejected++;
789
+ continue;
790
+ }
791
+ }
792
+ const normalized = normalizeEvent(event, context, receivedAt);
793
+ if ("properties" in normalized && normalized.properties) {
794
+ const sanitized = sanitizeProperties(normalized.properties, {
795
+ stripPII: this.config.stripPII,
796
+ stripHTML: this.config.stripHTML,
797
+ allowDangerousKeys: false
798
+ });
799
+ normalized.properties = sanitized.data;
800
+ if (sanitized.warnings.length > 0) logDebug$1("Event properties sanitized", {
801
+ eventId,
802
+ warnings: sanitized.warnings
803
+ });
804
+ }
805
+ if ("traits" in normalized && normalized.traits) normalized.traits = sanitizeProperties(normalized.traits, {
806
+ stripPII: this.config.stripPII,
807
+ stripHTML: this.config.stripHTML,
808
+ allowDangerousKeys: false
809
+ }).data;
810
+ const emitterPayload = toEmitterPayload(normalized);
811
+ processedEvents.push(emitterPayload);
812
+ results.push({
813
+ id: eventId,
814
+ messageId: normalized.messageId,
815
+ type: event.type,
816
+ status: "accepted"
817
+ });
818
+ metrics.accepted++;
819
+ metrics.byType[event.type] = (metrics.byType[event.type] ?? 0) + 1;
820
+ } catch (error) {
821
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
822
+ results.push({
823
+ id: eventId,
824
+ messageId: event.messageId,
825
+ type: event.type,
826
+ status: "rejected",
827
+ error: errorMessage
828
+ });
829
+ metrics.rejected++;
830
+ logWarn$1("Event processing failed", {
831
+ eventId,
832
+ type: event.type,
833
+ error: errorMessage
834
+ });
835
+ }
836
+ }
837
+ if (processedEvents.length > 0) (async () => {
838
+ try {
839
+ await this.analyticsManager.emitBatch(processedEvents, {
840
+ timeout: this.config.eventTimeout,
841
+ concurrency: this.config.batchConcurrency,
842
+ failFast: false
843
+ });
844
+ } catch (error) {
845
+ logError$1("Failed to emit events to analytics manager", {
846
+ error: error instanceof Error ? error.message : "Unknown error",
847
+ eventCount: processedEvents.length,
848
+ traceId: context.traceId
849
+ });
850
+ }
851
+ })();
852
+ const endTime = process.hrtime.bigint();
853
+ metrics.processingTimeMs = Number(endTime - startTime) / 1e6;
854
+ logInfo$1("Event ingestion completed", {
855
+ traceId: context.traceId,
856
+ source: context.source,
857
+ tenantId: context.tenantId,
858
+ totalReceived: metrics.totalReceived,
859
+ accepted: metrics.accepted,
860
+ rejected: metrics.rejected,
861
+ processingTimeMs: metrics.processingTimeMs.toFixed(2),
862
+ byType: metrics.byType
863
+ });
864
+ return {
865
+ success: true,
866
+ accepted: metrics.accepted,
867
+ rejected: metrics.rejected,
868
+ results,
869
+ receivedAt
870
+ };
871
+ }
872
+ /**
873
+ * Process a raw request body through parsing and ingestion.
874
+ *
875
+ * Convenience method that combines parseRequest and ingest.
876
+ *
877
+ * @param payload - Raw request payload
878
+ * @param context - Ingestion context
879
+ * @returns Ingestion response (success or error)
880
+ */
881
+ async processRequest(payload, context) {
882
+ const parseResult = this.parseRequest(payload);
883
+ if (!parseResult.success) return parseResult.error;
884
+ return this.ingest(parseResult.data, context);
885
+ }
886
+ };
887
+ /**
888
+ * Create an ingestion service instance.
889
+ *
890
+ * @param analyticsManager - Initialized AnalyticsManager instance
891
+ * @param config - Optional service configuration
892
+ * @returns Configured IngestionService instance
893
+ *
894
+ * @example
895
+ * ```typescript
896
+ * import { createServerAnalytics } from '@od-oneapp/analytics/server';
897
+ * import { createIngestionService } from '@od-oneapp/analytics/server';
898
+ *
899
+ * const analytics = await createServerAnalytics(config);
900
+ * const ingestionService = createIngestionService(analytics);
901
+ * ```
902
+ */
903
+ function createIngestionService(analyticsManager, config) {
904
+ return new IngestionService(analyticsManager, config);
905
+ }
906
+ /**
907
+ * Validate a single event payload.
908
+ *
909
+ * Useful for pre-validation before queuing events.
910
+ *
911
+ * @param payload - Event payload to validate
912
+ * @returns Validation result with parsed data or errors
913
+ */
914
+ function validateEventPayload(payload) {
915
+ const result = EventPayloadSchema.safeParse(payload);
916
+ if (!result.success) return {
917
+ success: false,
918
+ errors: result.error.issues.map((e) => `${e.path.map((p) => typeof p === "symbol" ? String(p) : p).join(".")}: ${e.message}`)
919
+ };
920
+ return {
921
+ success: true,
922
+ data: result.data
923
+ };
924
+ }
925
+ /**
926
+ * Validate a batch of event payloads.
927
+ *
928
+ * @param payloads - Array of event payloads to validate
929
+ * @returns Validation result with parsed data or errors
930
+ */
931
+ function validateBatchPayload(payloads) {
932
+ const result = BatchEventRequestSchema.safeParse({ batch: payloads });
933
+ if (!result.success) return {
934
+ success: false,
935
+ errors: result.error.issues.map((e) => `${e.path.map((p) => typeof p === "symbol" ? String(p) : p).join(".")}: ${e.message}`)
936
+ };
937
+ return {
938
+ success: true,
939
+ data: result.data.batch
940
+ };
941
+ }
942
+
943
+ //#endregion
944
+ export { debugConfig as a, validateProvider as c, validateEventPayload as i, createServerAnalytics as l, createIngestionService as n, validateAnalyticsConfig as o, validateBatchPayload as r, validateConfigOrThrow as s, IngestionService as t, createServerAnalyticsUninitialized as u };
945
+ //# sourceMappingURL=service-cYtBBL8x.mjs.map