flowgrid-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/README.md +224 -250
  2. package/dist/237.flowgrid.min.js +2 -0
  3. package/dist/237.flowgrid.min.js.map +1 -0
  4. package/dist/546.flowgrid.min.js +3 -0
  5. package/dist/546.flowgrid.min.js.LICENSE.txt +14 -0
  6. package/dist/546.flowgrid.min.js.map +1 -0
  7. package/dist/EXAMPLE.d.ts +1 -0
  8. package/dist/EXAMPLE.js +55 -0
  9. package/dist/EXAMPLE.js.map +1 -0
  10. package/dist/browser.d.ts +46 -103
  11. package/dist/browser.js +102 -0
  12. package/dist/browser.js.map +1 -0
  13. package/dist/flowgrid.min.js +1 -1
  14. package/dist/flowgrid.min.js.map +1 -1
  15. package/dist/index.d.ts +7 -8
  16. package/dist/index.js +64 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/client/transport.d.ts +14 -4
  19. package/dist/lib/client/transport.js +432 -0
  20. package/dist/lib/client/transport.js.map +1 -0
  21. package/dist/lib/consent/index.d.ts +0 -5
  22. package/dist/lib/consent/index.js +32 -0
  23. package/dist/lib/consent/index.js.map +1 -0
  24. package/dist/lib/consent/manager.js +336 -0
  25. package/dist/lib/consent/manager.js.map +1 -0
  26. package/dist/lib/consent/types.d.ts +3 -20
  27. package/dist/lib/consent/types.js +10 -0
  28. package/dist/lib/consent/types.js.map +1 -0
  29. package/dist/lib/features/analytics/attribution.d.ts +6 -47
  30. package/dist/lib/features/analytics/attribution.js +181 -0
  31. package/dist/lib/features/analytics/attribution.js.map +1 -0
  32. package/dist/lib/features/analytics/events.d.ts +1 -28
  33. package/dist/lib/features/analytics/events.js +256 -0
  34. package/dist/lib/features/analytics/events.js.map +1 -0
  35. package/dist/lib/features/analytics/funnels.js +260 -0
  36. package/dist/lib/features/analytics/funnels.js.map +1 -0
  37. package/dist/lib/features/analytics/heatmaps.d.ts +12 -19
  38. package/dist/lib/features/analytics/heatmaps.js +258 -0
  39. package/dist/lib/features/analytics/heatmaps.js.map +1 -0
  40. package/dist/lib/features/analytics/identify.d.ts +1 -21
  41. package/dist/lib/features/analytics/identify.js +213 -0
  42. package/dist/lib/features/analytics/identify.js.map +1 -0
  43. package/dist/lib/features/analytics/index.d.ts +2 -0
  44. package/dist/lib/features/analytics/index.js +15 -0
  45. package/dist/lib/features/analytics/index.js.map +1 -0
  46. package/dist/lib/features/analytics/page-views.d.ts +13 -30
  47. package/dist/lib/features/analytics/page-views.js +268 -0
  48. package/dist/lib/features/analytics/page-views.js.map +1 -0
  49. package/dist/lib/features/analytics/performance.d.ts +18 -24
  50. package/dist/lib/features/analytics/performance.js +232 -0
  51. package/dist/lib/features/analytics/performance.js.map +1 -0
  52. package/dist/lib/features/analytics/replay.d.ts +170 -0
  53. package/dist/lib/features/analytics/replay.js +381 -0
  54. package/dist/lib/features/analytics/replay.js.map +1 -0
  55. package/dist/lib/features/analytics/retention.d.ts +1 -76
  56. package/dist/lib/features/analytics/retention.js +135 -0
  57. package/dist/lib/features/analytics/retention.js.map +1 -0
  58. package/dist/lib/features/analytics/sessions.d.ts +12 -37
  59. package/dist/lib/features/analytics/sessions.js +213 -0
  60. package/dist/lib/features/analytics/sessions.js.map +1 -0
  61. package/dist/lib/features/core/activation.d.ts +1 -24
  62. package/dist/lib/features/core/activation.js +175 -0
  63. package/dist/lib/features/core/activation.js.map +1 -0
  64. package/dist/lib/features/core/experiment.d.ts +53 -21
  65. package/dist/lib/features/core/experiment.js +554 -0
  66. package/dist/lib/features/core/experiment.js.map +1 -0
  67. package/dist/lib/features/core/index.js +9 -0
  68. package/dist/lib/features/core/index.js.map +1 -0
  69. package/dist/lib/features/core/track-feature.d.ts +1 -8
  70. package/dist/lib/features/core/track-feature.js +119 -0
  71. package/dist/lib/features/core/track-feature.js.map +1 -0
  72. package/dist/lib/features/core/track-prompt.d.ts +5 -8
  73. package/dist/lib/features/core/track-prompt.js +181 -0
  74. package/dist/lib/features/core/track-prompt.js.map +1 -0
  75. package/dist/lib/features/ecommerce/cart.d.ts +1 -24
  76. package/dist/lib/features/ecommerce/cart.js +244 -0
  77. package/dist/lib/features/ecommerce/cart.js.map +1 -0
  78. package/dist/lib/features/ecommerce/checkout.d.ts +1 -28
  79. package/dist/lib/features/ecommerce/checkout.js +254 -0
  80. package/dist/lib/features/ecommerce/checkout.js.map +1 -0
  81. package/dist/lib/features/ecommerce/index.d.ts +0 -2
  82. package/dist/lib/features/ecommerce/index.js +15 -0
  83. package/dist/lib/features/ecommerce/index.js.map +1 -0
  84. package/dist/lib/features/ecommerce/ltv.d.ts +1 -69
  85. package/dist/lib/features/ecommerce/ltv.js +114 -0
  86. package/dist/lib/features/ecommerce/ltv.js.map +1 -0
  87. package/dist/lib/features/ecommerce/products.d.ts +1 -33
  88. package/dist/lib/features/ecommerce/products.js +223 -0
  89. package/dist/lib/features/ecommerce/products.js.map +1 -0
  90. package/dist/lib/features/ecommerce/promotions.d.ts +1 -30
  91. package/dist/lib/features/ecommerce/promotions.js +197 -0
  92. package/dist/lib/features/ecommerce/promotions.js.map +1 -0
  93. package/dist/lib/features/ecommerce/purchases.d.ts +1 -48
  94. package/dist/lib/features/ecommerce/purchases.js +193 -0
  95. package/dist/lib/features/ecommerce/purchases.js.map +1 -0
  96. package/dist/lib/features/ecommerce/refunds.d.ts +1 -27
  97. package/dist/lib/features/ecommerce/refunds.js +195 -0
  98. package/dist/lib/features/ecommerce/refunds.js.map +1 -0
  99. package/dist/lib/features/ecommerce/search.d.ts +1 -24
  100. package/dist/lib/features/ecommerce/search.js +156 -0
  101. package/dist/lib/features/ecommerce/search.js.map +1 -0
  102. package/dist/lib/features/ecommerce/subscriptions.d.ts +1 -28
  103. package/dist/lib/features/ecommerce/subscriptions.js +207 -0
  104. package/dist/lib/features/ecommerce/subscriptions.js.map +1 -0
  105. package/dist/lib/features/ecommerce/wishlist.d.ts +1 -28
  106. package/dist/lib/features/ecommerce/wishlist.js +186 -0
  107. package/dist/lib/features/ecommerce/wishlist.js.map +1 -0
  108. package/dist/lib/features/enterprise/acquisition.d.ts +2 -57
  109. package/dist/lib/features/enterprise/acquisition.js +166 -0
  110. package/dist/lib/features/enterprise/acquisition.js.map +1 -0
  111. package/dist/lib/features/enterprise/alerts.d.ts +1 -37
  112. package/dist/lib/features/enterprise/alerts.js +246 -0
  113. package/dist/lib/features/enterprise/alerts.js.map +1 -0
  114. package/dist/lib/features/enterprise/churn.d.ts +1 -42
  115. package/dist/lib/features/enterprise/churn.js +175 -0
  116. package/dist/lib/features/enterprise/churn.js.map +1 -0
  117. package/dist/lib/features/enterprise/cohorts.d.ts +1 -37
  118. package/dist/lib/features/enterprise/cohorts.js +201 -0
  119. package/dist/lib/features/enterprise/cohorts.js.map +1 -0
  120. package/dist/lib/features/enterprise/engagement.d.ts +40 -46
  121. package/dist/lib/features/enterprise/engagement.js +335 -0
  122. package/dist/lib/features/enterprise/engagement.js.map +1 -0
  123. package/dist/lib/features/enterprise/forecasting.d.ts +0 -50
  124. package/dist/lib/features/enterprise/forecasting.js +232 -0
  125. package/dist/lib/features/enterprise/forecasting.js.map +1 -0
  126. package/dist/lib/features/enterprise/index.d.ts +1 -1
  127. package/dist/lib/features/enterprise/index.js +76 -0
  128. package/dist/lib/features/enterprise/index.js.map +1 -0
  129. package/dist/lib/features/enterprise/monetization.d.ts +1 -60
  130. package/dist/lib/features/enterprise/monetization.js +146 -0
  131. package/dist/lib/features/enterprise/monetization.js.map +1 -0
  132. package/dist/lib/features/enterprise/multi-path-funnels.d.ts +1 -17
  133. package/dist/lib/features/enterprise/multi-path-funnels.js +205 -0
  134. package/dist/lib/features/enterprise/multi-path-funnels.js.map +1 -0
  135. package/dist/lib/features/enterprise/paths.d.ts +1 -56
  136. package/dist/lib/features/enterprise/paths.js +149 -0
  137. package/dist/lib/features/enterprise/paths.js.map +1 -0
  138. package/dist/lib/features/enterprise/security.d.ts +8 -57
  139. package/dist/lib/features/enterprise/security.js +202 -0
  140. package/dist/lib/features/enterprise/security.js.map +1 -0
  141. package/dist/lib/features/enterprise/support.d.ts +3 -42
  142. package/dist/lib/features/enterprise/support.js +162 -0
  143. package/dist/lib/features/enterprise/support.js.map +1 -0
  144. package/dist/lib/flowgrid.d.ts +278 -0
  145. package/dist/lib/flowgrid.js +323 -0
  146. package/dist/lib/flowgrid.js.map +1 -0
  147. package/dist/lib/frameworks/index.d.ts +13 -6
  148. package/dist/lib/frameworks/index.js +18 -0
  149. package/dist/lib/frameworks/index.js.map +1 -0
  150. package/dist/lib/frameworks/nextjs/client.js +355 -0
  151. package/dist/lib/frameworks/nextjs/client.js.map +1 -0
  152. package/dist/lib/frameworks/nextjs/index.js +31 -0
  153. package/dist/lib/frameworks/nextjs/index.js.map +1 -0
  154. package/dist/lib/frameworks/nextjs/server.js +259 -0
  155. package/dist/lib/frameworks/nextjs/server.js.map +1 -0
  156. package/dist/lib/frameworks/node/index.js +391 -0
  157. package/dist/lib/frameworks/node/index.js.map +1 -0
  158. package/dist/lib/frameworks/nuxt/index.js +288 -0
  159. package/dist/lib/frameworks/nuxt/index.js.map +1 -0
  160. package/dist/lib/frameworks/react/CookieBanner.js +166 -0
  161. package/dist/lib/frameworks/react/CookieBanner.js.map +1 -0
  162. package/dist/lib/frameworks/react/index.d.ts +2 -2
  163. package/dist/lib/frameworks/react/index.js +271 -0
  164. package/dist/lib/frameworks/react/index.js.map +1 -0
  165. package/dist/lib/frameworks/vue/CookieBanner.js +165 -0
  166. package/dist/lib/frameworks/vue/CookieBanner.js.map +1 -0
  167. package/dist/lib/frameworks/vue/index.d.ts +2 -2
  168. package/dist/lib/frameworks/vue/index.js +390 -0
  169. package/dist/lib/frameworks/vue/index.js.map +1 -0
  170. package/dist/lib/tracking/analytics/attribution.d.ts +14 -0
  171. package/dist/lib/tracking/analytics/attribution.js +18 -0
  172. package/dist/lib/tracking/analytics/attribution.js.map +1 -0
  173. package/dist/lib/tracking/analytics/events.d.ts +26 -0
  174. package/dist/lib/tracking/analytics/events.js +30 -0
  175. package/dist/lib/tracking/analytics/events.js.map +1 -0
  176. package/dist/lib/tracking/analytics/funnels.d.ts +16 -0
  177. package/dist/lib/tracking/analytics/funnels.js +22 -0
  178. package/dist/lib/tracking/analytics/funnels.js.map +1 -0
  179. package/dist/lib/tracking/analytics/heatmaps.d.ts +27 -0
  180. package/dist/lib/tracking/analytics/heatmaps.js +22 -0
  181. package/dist/lib/tracking/analytics/heatmaps.js.map +1 -0
  182. package/dist/lib/tracking/analytics/identify.d.ts +16 -0
  183. package/dist/lib/tracking/analytics/identify.js +22 -0
  184. package/dist/lib/tracking/analytics/identify.js.map +1 -0
  185. package/dist/lib/tracking/analytics/index.d.ts +10 -0
  186. package/dist/lib/tracking/analytics/index.js +11 -0
  187. package/dist/lib/tracking/analytics/index.js.map +1 -0
  188. package/dist/lib/tracking/analytics/page-views.d.ts +29 -0
  189. package/dist/lib/tracking/analytics/page-views.js +50 -0
  190. package/dist/lib/tracking/analytics/page-views.js.map +1 -0
  191. package/dist/lib/tracking/analytics/performance.d.ts +22 -0
  192. package/dist/lib/tracking/analytics/performance.js +22 -0
  193. package/dist/lib/tracking/analytics/performance.js.map +1 -0
  194. package/dist/lib/tracking/analytics/replay.d.ts +34 -0
  195. package/dist/lib/tracking/analytics/replay.js +46 -0
  196. package/dist/lib/tracking/analytics/replay.js.map +1 -0
  197. package/dist/lib/tracking/analytics/retention.d.ts +10 -0
  198. package/dist/lib/tracking/analytics/retention.js +10 -0
  199. package/dist/lib/tracking/analytics/retention.js.map +1 -0
  200. package/dist/lib/tracking/analytics/sessions.d.ts +20 -0
  201. package/dist/lib/tracking/analytics/sessions.js +26 -0
  202. package/dist/lib/tracking/analytics/sessions.js.map +1 -0
  203. package/dist/lib/tracking/core/activation.d.ts +23 -0
  204. package/dist/lib/tracking/core/activation.js +33 -0
  205. package/dist/lib/tracking/core/activation.js.map +1 -0
  206. package/dist/lib/tracking/core/experiment.d.ts +122 -0
  207. package/dist/lib/tracking/core/experiment.js +150 -0
  208. package/dist/lib/tracking/core/experiment.js.map +1 -0
  209. package/dist/lib/tracking/core/feature.d.ts +13 -0
  210. package/dist/lib/tracking/core/feature.js +14 -0
  211. package/dist/lib/tracking/core/feature.js.map +1 -0
  212. package/dist/lib/tracking/core/index.d.ts +4 -0
  213. package/dist/lib/tracking/core/index.js +5 -0
  214. package/dist/lib/tracking/core/index.js.map +1 -0
  215. package/dist/lib/tracking/core/prompt.d.ts +18 -0
  216. package/dist/lib/tracking/core/prompt.js +26 -0
  217. package/dist/lib/tracking/core/prompt.js.map +1 -0
  218. package/dist/lib/tracking/ecommerce/cart.d.ts +17 -0
  219. package/dist/lib/tracking/ecommerce/cart.js +22 -0
  220. package/dist/lib/tracking/ecommerce/cart.js.map +1 -0
  221. package/dist/lib/tracking/ecommerce/checkout.d.ts +17 -0
  222. package/dist/lib/tracking/ecommerce/checkout.js +22 -0
  223. package/dist/lib/tracking/ecommerce/checkout.js.map +1 -0
  224. package/dist/lib/tracking/ecommerce/index.d.ts +10 -0
  225. package/dist/lib/tracking/ecommerce/index.js +11 -0
  226. package/dist/lib/tracking/ecommerce/index.js.map +1 -0
  227. package/dist/lib/tracking/ecommerce/ltv.d.ts +15 -0
  228. package/dist/lib/tracking/ecommerce/ltv.js +14 -0
  229. package/dist/lib/tracking/ecommerce/ltv.js.map +1 -0
  230. package/dist/lib/tracking/ecommerce/products.d.ts +15 -0
  231. package/dist/lib/tracking/ecommerce/products.js +18 -0
  232. package/dist/lib/tracking/ecommerce/products.js.map +1 -0
  233. package/dist/lib/tracking/ecommerce/promotions.d.ts +14 -0
  234. package/dist/lib/tracking/ecommerce/promotions.js +25 -0
  235. package/dist/lib/tracking/ecommerce/promotions.js.map +1 -0
  236. package/dist/lib/tracking/ecommerce/purchases.d.ts +11 -0
  237. package/dist/lib/tracking/ecommerce/purchases.js +10 -0
  238. package/dist/lib/tracking/ecommerce/purchases.js.map +1 -0
  239. package/dist/lib/tracking/ecommerce/refunds.d.ts +17 -0
  240. package/dist/lib/tracking/ecommerce/refunds.js +22 -0
  241. package/dist/lib/tracking/ecommerce/refunds.js.map +1 -0
  242. package/dist/lib/tracking/ecommerce/search.d.ts +24 -0
  243. package/dist/lib/tracking/ecommerce/search.js +22 -0
  244. package/dist/lib/tracking/ecommerce/search.js.map +1 -0
  245. package/dist/lib/tracking/ecommerce/subscriptions.d.ts +21 -0
  246. package/dist/lib/tracking/ecommerce/subscriptions.js +38 -0
  247. package/dist/lib/tracking/ecommerce/subscriptions.js.map +1 -0
  248. package/dist/lib/tracking/ecommerce/wishlist.d.ts +19 -0
  249. package/dist/lib/tracking/ecommerce/wishlist.js +26 -0
  250. package/dist/lib/tracking/ecommerce/wishlist.js.map +1 -0
  251. package/dist/lib/tracking/enterprise/acquisition.d.ts +25 -0
  252. package/dist/lib/tracking/enterprise/acquisition.js +22 -0
  253. package/dist/lib/tracking/enterprise/acquisition.js.map +1 -0
  254. package/dist/lib/tracking/enterprise/alerts.d.ts +32 -0
  255. package/dist/lib/tracking/enterprise/alerts.js +30 -0
  256. package/dist/lib/tracking/enterprise/alerts.js.map +1 -0
  257. package/dist/lib/tracking/enterprise/churn.d.ts +23 -0
  258. package/dist/lib/tracking/enterprise/churn.js +22 -0
  259. package/dist/lib/tracking/enterprise/churn.js.map +1 -0
  260. package/dist/lib/tracking/enterprise/cohorts.d.ts +18 -0
  261. package/dist/lib/tracking/enterprise/cohorts.js +18 -0
  262. package/dist/lib/tracking/enterprise/cohorts.js.map +1 -0
  263. package/dist/lib/tracking/enterprise/engagement.d.ts +31 -0
  264. package/dist/lib/tracking/enterprise/engagement.js +44 -0
  265. package/dist/lib/tracking/enterprise/engagement.js.map +1 -0
  266. package/dist/lib/tracking/enterprise/forecasting.d.ts +18 -0
  267. package/dist/lib/tracking/enterprise/forecasting.js +22 -0
  268. package/dist/lib/tracking/enterprise/forecasting.js.map +1 -0
  269. package/dist/lib/tracking/enterprise/index.d.ts +11 -0
  270. package/dist/lib/tracking/enterprise/index.js +12 -0
  271. package/dist/lib/tracking/enterprise/index.js.map +1 -0
  272. package/dist/lib/tracking/enterprise/monetization.d.ts +14 -0
  273. package/dist/lib/tracking/enterprise/monetization.js +10 -0
  274. package/dist/lib/tracking/enterprise/monetization.js.map +1 -0
  275. package/dist/lib/tracking/enterprise/multi-path-funnels.d.ts +20 -0
  276. package/dist/lib/tracking/enterprise/multi-path-funnels.js +18 -0
  277. package/dist/lib/tracking/enterprise/multi-path-funnels.js.map +1 -0
  278. package/dist/lib/tracking/enterprise/paths.d.ts +20 -0
  279. package/dist/lib/tracking/enterprise/paths.js +18 -0
  280. package/dist/lib/tracking/enterprise/paths.js.map +1 -0
  281. package/dist/lib/tracking/enterprise/security.d.ts +28 -0
  282. package/dist/lib/tracking/enterprise/security.js +26 -0
  283. package/dist/lib/tracking/enterprise/security.js.map +1 -0
  284. package/dist/lib/tracking/enterprise/support.d.ts +25 -0
  285. package/dist/lib/tracking/enterprise/support.js +22 -0
  286. package/dist/lib/tracking/enterprise/support.js.map +1 -0
  287. package/dist/lib/tracking/index.d.ts +4 -0
  288. package/dist/lib/tracking/index.js +5 -0
  289. package/dist/lib/tracking/index.js.map +1 -0
  290. package/dist/lib/types/analytics.d.ts +10 -2
  291. package/dist/lib/types/analytics.js +6 -0
  292. package/dist/lib/types/analytics.js.map +1 -0
  293. package/dist/lib/types/common.js +6 -0
  294. package/dist/lib/types/common.js.map +1 -0
  295. package/dist/lib/types/ecommerce.js +6 -0
  296. package/dist/lib/types/ecommerce.js.map +1 -0
  297. package/dist/lib/types/index.js +8 -0
  298. package/dist/lib/types/index.js.map +1 -0
  299. package/dist/lib/utils/browser.d.ts +16 -0
  300. package/dist/lib/utils/browser.js +32 -0
  301. package/dist/lib/utils/browser.js.map +1 -0
  302. package/dist/lib/utils/identity.js +346 -0
  303. package/dist/lib/utils/identity.js.map +1 -0
  304. package/dist/lib/utils/storage.js +105 -0
  305. package/dist/lib/utils/storage.js.map +1 -0
  306. package/dist/umd/237.index.js +2 -0
  307. package/dist/umd/237.index.js.map +1 -0
  308. package/dist/umd/546.index.js +3 -0
  309. package/dist/umd/546.index.js.LICENSE.txt +14 -0
  310. package/dist/umd/546.index.js.map +1 -0
  311. package/dist/umd/index.js +2 -0
  312. package/dist/umd/index.js.map +1 -0
  313. package/package.json +27 -80
  314. package/dist/lib/features/ecommerce/inventory.d.ts +0 -219
@@ -38,7 +38,8 @@
38
38
  * ```
39
39
  */
40
40
  import { FlowGridTransport } from "../../client/transport";
41
- import { DateRangeFilter, TimeSeriesData } from "../../types";
41
+ import { TimeSeriesData } from "../../types";
42
+ import { ServerCookieReader } from "../../utils/identity";
42
43
  /**
43
44
  * Configuration for tracking an experiment-related event.
44
45
  *
@@ -65,14 +66,19 @@ export interface ExperimentDefinition {
65
66
  name: string;
66
67
  /** Optional description of experiment purpose */
67
68
  description?: string;
68
- /** Variants participating in the experiment */
69
+ /**
70
+ * Variants participating in the experiment.
71
+ *
72
+ * Shape matches the verify endpoint and the client-side engine:
73
+ * `{ id, weight }` (with optional human-readable `name`).
74
+ */
69
75
  variants: Array<{
70
76
  /** Unique identifier for the variant */
71
- variantId: string;
72
- /** Human-readable variant name */
73
- name: string;
77
+ id: string;
74
78
  /** Traffic allocation weight (relative distribution) */
75
79
  weight: number;
80
+ /** Optional human-readable variant name */
81
+ name?: string;
76
82
  }>;
77
83
  /**
78
84
  * Optional audience configuration.
@@ -339,6 +345,18 @@ export declare class Experiment extends FlowGridTransport {
339
345
  private experiments;
340
346
  /** Local variant assignments: { [experiment_id]: variant_id } */
341
347
  private assignments;
348
+ /**
349
+ * Decode the cookie’s URL-encoded JSON payload into an assignment map.
350
+ * Returns `{}` on any parse failure so a stale cookie never throws.
351
+ */
352
+ private parseAssignmentCookie;
353
+ private readAssignmentCookie;
354
+ /**
355
+ * Persist `this.assignments` to a first-party cookie so SSR requests on
356
+ * subsequent navigations see the same variant. Best-effort — no-op in
357
+ * non-browser contexts or when document.cookie is unavailable.
358
+ */
359
+ private writeAssignmentCookie;
342
360
  private loadAssignments;
343
361
  private saveAssignments;
344
362
  /**
@@ -351,6 +369,34 @@ export declare class Experiment extends FlowGridTransport {
351
369
  * Returns all current assignments (for SSR hydration).
352
370
  */
353
371
  getAssignments(): Record<string, string>;
372
+ /**
373
+ * Hydrate assignments from a server-side cookie reader
374
+ * (Next.js `cookies()`, Nuxt `useCookie()`, Hono ctx, etc.).
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * import { cookies } from "next/headers";
379
+ * fg.experiments.client.hydrateFromCookies(cookies());
380
+ * const variant = fg.experiments.variant("checkout_v2");
381
+ * ```
382
+ */
383
+ hydrateFromCookies(reader: ServerCookieReader): void;
384
+ /**
385
+ * Hydrate assignments from a raw `Cookie` request header string.
386
+ * Useful for runtimes without a structured cookie reader (Workers, Bun, etc.).
387
+ */
388
+ hydrateFromCookieHeader(cookieHeader: string | null | undefined): void;
389
+ /**
390
+ * Serialise current assignments as a `Set-Cookie` header value.
391
+ * Use server-side after a server-only assignment so the response can
392
+ * seed the visitor’s browser with the same variant.
393
+ */
394
+ serializeAssignmentCookie(): string;
395
+ /**
396
+ * Clear all locally cached assignments (cookie + localStorage + memory).
397
+ * Useful for logout flows or test resets.
398
+ */
399
+ clearAssignments(): void;
354
400
  private static readonly ANON_ID_KEY;
355
401
  /**
356
402
  * Returns a stable anonymous ID. Generates a UUID on first call and
@@ -389,10 +435,8 @@ export declare class Experiment extends FlowGridTransport {
389
435
  */
390
436
  trackLocalConversion(experimentId: string, metricName?: string, metricValue?: number): Promise<void>;
391
437
  /**
392
- * @deprecated Use `assign()`, `trackExposure()`, or `trackConversion()` instead.
393
- * The generic `track()` method sends events without a subtype, which the
394
- * backend handler rejects. It is retained only for backwards compatibility
395
- * and will be removed in a future major version.
438
+ * @deprecated Prefer `assign()`, `trackExposure()`, or `trackConversion()`.
439
+ * Retained as a write-only compatibility shim for generic experiment events.
396
440
  */
397
441
  track(config: ExperimentConfig): Promise<ExperimentResponse>;
398
442
  /**
@@ -411,17 +455,5 @@ export declare class Experiment extends FlowGridTransport {
411
455
  * Records that a user was exposed to a variant.
412
456
  */
413
457
  trackExposure(experimentId: string, userId: string, variantId: string): Promise<ExperimentResponse>;
414
- /**
415
- * Retrieves statistical results for an experiment.
416
- */
417
- getResults(experimentId: string): Promise<ExperimentResultsData>;
418
- /**
419
- * Gets experiment analytics overview.
420
- */
421
- getAnalytics(filter?: DateRangeFilter): Promise<ExperimentAnalyticsData>;
422
- /**
423
- * Retrieves the variant assigned to a user for a specific experiment (server-side).
424
- */
425
- getVariant(experimentId: string, userId: string): Promise<VariantResponse | null>;
426
458
  }
427
459
  export default Experiment;
@@ -0,0 +1,554 @@
1
+ /**
2
+ * @fileoverview Core - A/B Testing & Experiments
3
+ * @module features/core/experiment
4
+ *
5
+ * @description
6
+ * Track A/B tests, experiments, and variant assignments
7
+ * to measure impact and optimize product decisions.
8
+ *
9
+ * @useCases
10
+ * - Run A/B tests on features
11
+ * - Track variant assignments
12
+ * - Measure experiment impact
13
+ * - Statistical significance analysis
14
+ * - Multi-variant testing
15
+ *
16
+ * @metrics
17
+ * - Conversion by variant
18
+ * - Statistical significance
19
+ * - Sample size per variant
20
+ * - Experiment duration
21
+ * - Impact on key metrics
22
+ *
23
+ * @chartData
24
+ * - Bar chart: Conversion by variant
25
+ * - Line chart: Metric trends by variant
26
+ * - Table: Experiment results summary
27
+ * - Confidence interval visualization
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const experiment = new Experiment(webId, endpoint, apiKey);
32
+ *
33
+ * await experiment.track({
34
+ * eventName: 'checkout_flow_test',
35
+ * variant: 'simplified_v2',
36
+ * properties: { userId: 'user_123' }
37
+ * });
38
+ * ```
39
+ */
40
+ import { FlowGridTransport } from "../../client/transport";
41
+ import { getCookie } from "../../utils/storage";
42
+ // ============================================================================
43
+ // HASHING UTILITY (FNV-1a)
44
+ // ============================================================================
45
+ /**
46
+ * FNV-1a 32-bit hash — lightweight deterministic hash for variant assignment.
47
+ * Ensures the same user always gets the same variant regardless of device.
48
+ */
49
+ function fnv1a(input) {
50
+ let hash = 0x811c9dc5; // FNV offset basis
51
+ for (let i = 0; i < input.length; i++) {
52
+ hash ^= input.charCodeAt(i);
53
+ hash = (hash * 0x01000193) >>> 0; // FNV prime, unsigned 32-bit
54
+ }
55
+ return hash;
56
+ }
57
+ // ============================================================================
58
+ // CONSTANTS
59
+ // ============================================================================
60
+ const ASSIGNMENT_STORAGE_KEY = 'fg_exp_assignments';
61
+ const ASSIGNMENT_COOKIE_KEY = 'fg_exp_assignments';
62
+ const ASSIGNMENT_COOKIE_MAX_AGE_DAYS = 365;
63
+ // ============================================================================
64
+ // EXPERIMENT CLASS
65
+ // ============================================================================
66
+ /**
67
+ * Experiment - A/B Testing & Experimentation SDK
68
+ *
69
+ * @extends FlowGridTransport
70
+ *
71
+ * @description
72
+ * Provides structured experiment lifecycle management including:
73
+ * - Experiment definition
74
+ * - Deterministic or server-based variant assignment
75
+ * - Exposure tracking
76
+ * - Conversion tracking
77
+ * - Statistical results & analysis
78
+ *
79
+ * This class enforces correct HTTP semantics:
80
+ *
81
+ * Exposure and conversion events are strictly POST to preserve:
82
+ * - Statistical validity
83
+ * - Unique participant counting
84
+ * - Accurate conversion attribution
85
+ * - Idempotent replay safety
86
+ *
87
+ * @lifecycle
88
+ * 1. Define experiment
89
+ * 2. Assign user to variant
90
+ * 3. Track exposure (when user actually sees variant)
91
+ * 4. Track conversions (primary/secondary metrics)
92
+ * 5. Query results & statistical analysis
93
+ *
94
+ * @statistical_model
95
+ * - Variant-level sample sizes tracked independently
96
+ * - Conversion rates calculated per variant
97
+ * - Confidence intervals computed server-side
98
+ * - Statistical significance evaluated against control
99
+ *
100
+ * @guarantees
101
+ * - Assignment is explicitly recorded
102
+ * - Exposure tracking is immutable
103
+ * - Conversion events are attributable to assigned variant
104
+ * - Results queries never mutate experiment state
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const experiment = new Experiment(webId, endpoint, apiKey);
109
+ *
110
+ * // 1. Define experiment
111
+ * await experiment.define({
112
+ * experimentId: 'checkout_v2',
113
+ * name: 'Simplified Checkout',
114
+ * variants: [
115
+ * { variantId: 'control', name: 'Current', weight: 50 },
116
+ * { variantId: 'treatment', name: 'Simplified', weight: 50 }
117
+ * ],
118
+ * primaryMetric: 'checkout_completed'
119
+ * });
120
+ *
121
+ * // 2. Assign user
122
+ * await experiment.assign({
123
+ * experimentId: 'checkout_v2',
124
+ * userId: 'user_123',
125
+ * variantId: 'treatment'
126
+ * });
127
+ *
128
+ * // 3. Track exposure
129
+ * await experiment.trackExposure('checkout_v2', 'user_123', 'treatment');
130
+ *
131
+ * // 4. Track conversion
132
+ * await experiment.trackConversion({
133
+ * experimentId: 'checkout_v2',
134
+ * userId: 'user_123',
135
+ * variantId: 'treatment',
136
+ * metricName: 'checkout_completed',
137
+ * value: 1
138
+ * });
139
+ *
140
+ * // 5. Fetch results
141
+ * const results = await experiment.getResults('checkout_v2');
142
+ * ```
143
+ */
144
+ export class Experiment extends FlowGridTransport {
145
+ constructor() {
146
+ // ---------------------------------------------------------------------------
147
+ // CLIENT-SIDE EXPERIMENT ENGINE STATE
148
+ // ---------------------------------------------------------------------------
149
+ super(...arguments);
150
+ /** Verified experiment configs: { [experiment_id]: { variants, properties, verified } } */
151
+ this.experiments = {};
152
+ /** Local variant assignments: { [experiment_id]: variant_id } */
153
+ this.assignments = {};
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // CLIENT-SIDE ENGINE — SSR-safe persistence (localStorage + cookie mirror)
157
+ // ---------------------------------------------------------------------------
158
+ /**
159
+ * Decode the cookie’s URL-encoded JSON payload into an assignment map.
160
+ * Returns `{}` on any parse failure so a stale cookie never throws.
161
+ */
162
+ parseAssignmentCookie(raw) {
163
+ if (!raw)
164
+ return {};
165
+ try {
166
+ const decoded = decodeURIComponent(raw);
167
+ const parsed = JSON.parse(decoded);
168
+ return parsed && typeof parsed === 'object' ? parsed : {};
169
+ }
170
+ catch {
171
+ return {};
172
+ }
173
+ }
174
+ readAssignmentCookie() {
175
+ return this.parseAssignmentCookie(getCookie(ASSIGNMENT_COOKIE_KEY));
176
+ }
177
+ /**
178
+ * Persist `this.assignments` to a first-party cookie so SSR requests on
179
+ * subsequent navigations see the same variant. Best-effort — no-op in
180
+ * non-browser contexts or when document.cookie is unavailable.
181
+ */
182
+ writeAssignmentCookie() {
183
+ try {
184
+ if (typeof document === 'undefined')
185
+ return;
186
+ const value = encodeURIComponent(JSON.stringify(this.assignments));
187
+ const maxAge = ASSIGNMENT_COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
188
+ const sameSite = 'Lax';
189
+ const secure = typeof location !== 'undefined' && location.protocol === 'https:'
190
+ ? '; Secure'
191
+ : '';
192
+ document.cookie = `${ASSIGNMENT_COOKIE_KEY}=${value}; Path=/; Max-Age=${maxAge}; SameSite=${sameSite}${secure}`;
193
+ }
194
+ catch { /* SSR or restricted env */ }
195
+ }
196
+ loadAssignments() {
197
+ // 1. localStorage — authoritative on the client
198
+ try {
199
+ if (typeof localStorage !== 'undefined') {
200
+ const stored = localStorage.getItem(ASSIGNMENT_STORAGE_KEY);
201
+ if (stored) {
202
+ const parsed = JSON.parse(stored);
203
+ if (parsed && typeof parsed === 'object')
204
+ return parsed;
205
+ }
206
+ }
207
+ }
208
+ catch { /* SSR or privacy mode */ }
209
+ // 2. Cookie fallback — lets SSR + first-paint resolve a variant
210
+ const cookieAssignments = this.readAssignmentCookie();
211
+ if (Object.keys(cookieAssignments).length > 0)
212
+ return cookieAssignments;
213
+ return {};
214
+ }
215
+ saveAssignments() {
216
+ try {
217
+ if (typeof localStorage !== 'undefined') {
218
+ localStorage.setItem(ASSIGNMENT_STORAGE_KEY, JSON.stringify(this.assignments));
219
+ }
220
+ }
221
+ catch { /* SSR or privacy mode */ }
222
+ // Mirror to cookie so the next SSR request can hydrate without flashing.
223
+ this.writeAssignmentCookie();
224
+ }
225
+ /**
226
+ * Inject assignments from server context (SSR/Node).
227
+ * Use this when assignments were resolved server-side and need to be
228
+ * available before localStorage is accessible.
229
+ */
230
+ injectAssignments(assignments) {
231
+ this.assignments = { ...this.assignments, ...assignments };
232
+ }
233
+ /**
234
+ * Returns all current assignments (for SSR hydration).
235
+ */
236
+ getAssignments() {
237
+ return { ...this.assignments };
238
+ }
239
+ /**
240
+ * Hydrate assignments from a server-side cookie reader
241
+ * (Next.js `cookies()`, Nuxt `useCookie()`, Hono ctx, etc.).
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * import { cookies } from "next/headers";
246
+ * fg.experiments.client.hydrateFromCookies(cookies());
247
+ * const variant = fg.experiments.variant("checkout_v2");
248
+ * ```
249
+ */
250
+ hydrateFromCookies(reader) {
251
+ const raw = reader.get(ASSIGNMENT_COOKIE_KEY);
252
+ const value = typeof raw === 'string' ? raw : raw?.value ?? null;
253
+ const parsed = this.parseAssignmentCookie(value ?? null);
254
+ if (Object.keys(parsed).length > 0)
255
+ this.injectAssignments(parsed);
256
+ }
257
+ /**
258
+ * Hydrate assignments from a raw `Cookie` request header string.
259
+ * Useful for runtimes without a structured cookie reader (Workers, Bun, etc.).
260
+ */
261
+ hydrateFromCookieHeader(cookieHeader) {
262
+ if (!cookieHeader)
263
+ return;
264
+ const parts = cookieHeader.split(/;\s*/);
265
+ const prefix = `${ASSIGNMENT_COOKIE_KEY}=`;
266
+ const match = parts.find(p => p.startsWith(prefix));
267
+ if (!match)
268
+ return;
269
+ const parsed = this.parseAssignmentCookie(match.slice(prefix.length));
270
+ if (Object.keys(parsed).length > 0)
271
+ this.injectAssignments(parsed);
272
+ }
273
+ /**
274
+ * Serialise current assignments as a `Set-Cookie` header value.
275
+ * Use server-side after a server-only assignment so the response can
276
+ * seed the visitor’s browser with the same variant.
277
+ */
278
+ serializeAssignmentCookie() {
279
+ const value = encodeURIComponent(JSON.stringify(this.assignments));
280
+ const maxAge = ASSIGNMENT_COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
281
+ return `${ASSIGNMENT_COOKIE_KEY}=${value}; Path=/; Max-Age=${maxAge}; SameSite=Lax`;
282
+ }
283
+ /**
284
+ * Clear all locally cached assignments (cookie + localStorage + memory).
285
+ * Useful for logout flows or test resets.
286
+ */
287
+ clearAssignments() {
288
+ this.assignments = {};
289
+ try {
290
+ if (typeof localStorage !== 'undefined')
291
+ localStorage.removeItem(ASSIGNMENT_STORAGE_KEY);
292
+ }
293
+ catch { /* noop */ }
294
+ try {
295
+ if (typeof document !== 'undefined') {
296
+ document.cookie = `${ASSIGNMENT_COOKIE_KEY}=; Path=/; Max-Age=0; SameSite=Lax`;
297
+ }
298
+ }
299
+ catch { /* noop */ }
300
+ }
301
+ /**
302
+ * Returns a stable anonymous ID. Generates a UUID on first call and
303
+ * persists it in localStorage so the same anonymous visitor always
304
+ * receives a consistent variant assignment.
305
+ */
306
+ getOrCreateAnonId() {
307
+ try {
308
+ if (typeof localStorage !== 'undefined') {
309
+ const existing = localStorage.getItem(Experiment.ANON_ID_KEY);
310
+ if (existing)
311
+ return existing;
312
+ const id = typeof crypto !== 'undefined' && crypto.randomUUID
313
+ ? crypto.randomUUID()
314
+ : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
315
+ const r = (Math.random() * 16) | 0;
316
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
317
+ });
318
+ localStorage.setItem(Experiment.ANON_ID_KEY, id);
319
+ return id;
320
+ }
321
+ }
322
+ catch { /* SSR or privacy mode */ }
323
+ // Non-browser fallback — still deterministic within process lifetime
324
+ return 'anon_' + fnv1a(String(Math.random())).toString(36);
325
+ }
326
+ // ---------------------------------------------------------------------------
327
+ // CLIENT-SIDE ENGINE — deterministic hash-based assignment
328
+ // ---------------------------------------------------------------------------
329
+ /**
330
+ * Deterministic variant assignment using FNV-1a hash.
331
+ * Same userId + experimentId always produces the same variant.
332
+ */
333
+ deterministicAssign(userId, experimentId, variants) {
334
+ const hashValue = fnv1a(userId + experimentId);
335
+ let totalWeight = 0;
336
+ for (const v of variants)
337
+ totalWeight += v.weight;
338
+ const bucket = (hashValue % 10000) / 10000 * totalWeight;
339
+ let cumulative = 0;
340
+ for (const v of variants) {
341
+ cumulative += v.weight;
342
+ if (bucket < cumulative)
343
+ return v.id;
344
+ }
345
+ return variants[variants.length - 1].id;
346
+ }
347
+ assignVariant(experimentId, variants) {
348
+ // Reload from localStorage to handle cross-tab race conditions
349
+ this.assignments = this.loadAssignments();
350
+ const existing = this.assignments[experimentId];
351
+ if (existing && variants.some(v => v.id === existing)) {
352
+ // Existing assignment still valid — keep it
353
+ return;
354
+ }
355
+ // Resolve a userId for the hash: prefer identityManager's visitorId, then fall back
356
+ const userId = this.identityManager?.getVisitorId?.()
357
+ || this.visitorId
358
+ || this.getOrCreateAnonId();
359
+ // Deterministic assignment via hash
360
+ const assignedVariantId = this.deterministicAssign(userId, experimentId, variants);
361
+ this.assignments[experimentId] = assignedVariantId;
362
+ this.saveAssignments();
363
+ // Fire-and-forget assignment event.
364
+ // The backend AssignmentHandlerNode requires `userId` in properties.
365
+ this.init('experiment_assignment', {
366
+ type: 'experiment',
367
+ subtype: 'assignment',
368
+ experimentId,
369
+ userId,
370
+ variantId: assignedVariantId,
371
+ source: 'random',
372
+ assignmentSource: 'sdk_init',
373
+ timestamp: new Date().toISOString(),
374
+ }).catch(() => { });
375
+ }
376
+ // ---------------------------------------------------------------------------
377
+ // CLIENT-SIDE ENGINE — server verification
378
+ // ---------------------------------------------------------------------------
379
+ async verifyExperiments(configs) {
380
+ const payload = {
381
+ web_id: this.webId,
382
+ experiments: configs.map(exp => ({
383
+ experiment_id: exp.id,
384
+ variants: exp.variants,
385
+ properties: exp.properties || {},
386
+ })),
387
+ };
388
+ try {
389
+ const res = await fetch(`${this.endpoint}/api/v1/experiments/verify`, {
390
+ method: 'POST',
391
+ headers: { 'Content-Type': 'application/json' },
392
+ body: JSON.stringify(payload),
393
+ });
394
+ const response = await res.json();
395
+ if (response.ok && Array.isArray(response.data)) {
396
+ for (const item of response.data) {
397
+ if (item.action !== 'error') {
398
+ this.experiments[item.experiment_id] = {
399
+ variants: item.variants,
400
+ properties: item.properties ?? {},
401
+ verified: true,
402
+ };
403
+ this.assignVariant(item.experiment_id, item.variants);
404
+ }
405
+ }
406
+ }
407
+ }
408
+ catch {
409
+ // Network failure — fall back to local config
410
+ for (const exp of configs) {
411
+ this.experiments[exp.id] = {
412
+ variants: exp.variants,
413
+ properties: exp.properties || {},
414
+ verified: false,
415
+ };
416
+ this.assignVariant(exp.id, exp.variants);
417
+ }
418
+ }
419
+ }
420
+ // ---------------------------------------------------------------------------
421
+ // CLIENT-SIDE ENGINE — public init
422
+ // ---------------------------------------------------------------------------
423
+ /**
424
+ * Initialise the client-side experiment engine.
425
+ *
426
+ * Verifies experiment configs against the API, assigns variants using
427
+ * weighted random allocation, and persists assignments in localStorage
428
+ * under `fg_exp_assignments`.
429
+ *
430
+ * @param experiments - Array of experiment configs to register
431
+ */
432
+ async initExperiments(experiments) {
433
+ this.assignments = this.loadAssignments();
434
+ if (experiments.length > 0) {
435
+ await this.verifyExperiments(experiments);
436
+ }
437
+ }
438
+ /**
439
+ * Returns the locally assigned variant for an experiment.
440
+ *
441
+ * Reads from the in-memory assignment map (backed by localStorage).
442
+ * Returns `null` if no assignment exists.
443
+ */
444
+ getLocalVariant(experimentId) {
445
+ // Re-read from storage to stay in sync with other tabs
446
+ this.assignments = this.loadAssignments();
447
+ return this.assignments[experimentId] ?? null;
448
+ }
449
+ /**
450
+ * Track a conversion against the locally assigned variant.
451
+ *
452
+ * No-op if the visitor has no assignment for this experiment.
453
+ */
454
+ async trackLocalConversion(experimentId, metricName = 'conversion', metricValue = 1) {
455
+ const variantId = this.getLocalVariant(experimentId);
456
+ if (!variantId)
457
+ return;
458
+ // Backend ConversionHandlerNode requires `userId` — resolve from
459
+ // identity manager or the persisted anonymous ID.
460
+ const userId = this.identityManager?.getVisitorId?.()
461
+ || this.visitorId
462
+ || this.getOrCreateAnonId();
463
+ await this.init('experiment_conversion', {
464
+ type: 'experiment',
465
+ subtype: 'conversion',
466
+ experimentId,
467
+ userId,
468
+ variantId,
469
+ metricName,
470
+ value: metricValue,
471
+ timestamp: new Date().toISOString(),
472
+ });
473
+ }
474
+ // ---------------------------------------------------------------------------
475
+ // SERVER-SIDE EXPERIMENT METHODS
476
+ //
477
+ // Backend supports write events only: definition, assignment, conversion,
478
+ // exposure, plus the deprecated generic custom shim below. Read endpoints
479
+ // (results / analytics / variant lookup) are not exposed via the SDK
480
+ // functions router.
481
+ // ---------------------------------------------------------------------------
482
+ /**
483
+ * @deprecated Prefer `assign()`, `trackExposure()`, or `trackConversion()`.
484
+ * Retained as a write-only compatibility shim for generic experiment events.
485
+ */
486
+ async track(config) {
487
+ return await this.init(config.eventName, {
488
+ ...config.properties,
489
+ type: "experiment",
490
+ subtype: "custom",
491
+ variant: config.variant,
492
+ timestamp: new Date().toISOString()
493
+ });
494
+ }
495
+ /**
496
+ * Defines a new experiment.
497
+ */
498
+ async define(definition) {
499
+ return await this.init("experiment_definition", {
500
+ type: "experiment",
501
+ subtype: "definition",
502
+ ...definition,
503
+ timestamp: new Date().toISOString()
504
+ });
505
+ }
506
+ /**
507
+ * Assigns a user to a variant.
508
+ */
509
+ async assign(config) {
510
+ return await this.init("experiment_assignment", {
511
+ type: "experiment",
512
+ subtype: "assignment",
513
+ experimentId: config.experimentId,
514
+ userId: config.userId,
515
+ variantId: config.variantId,
516
+ source: config.source || "random",
517
+ timestamp: new Date().toISOString()
518
+ });
519
+ }
520
+ /**
521
+ * Records a conversion event for an experiment.
522
+ */
523
+ async trackConversion(config) {
524
+ return await this.init("experiment_conversion", {
525
+ type: "experiment",
526
+ subtype: "conversion",
527
+ experimentId: config.experimentId,
528
+ userId: config.userId,
529
+ variantId: config.variantId,
530
+ metricName: config.metricName,
531
+ value: config.value,
532
+ timestamp: new Date().toISOString()
533
+ });
534
+ }
535
+ /**
536
+ * Records that a user was exposed to a variant.
537
+ */
538
+ async trackExposure(experimentId, userId, variantId) {
539
+ return await this.init("experiment_exposure", {
540
+ type: "experiment",
541
+ subtype: "exposure",
542
+ experimentId,
543
+ userId,
544
+ variantId,
545
+ timestamp: new Date().toISOString()
546
+ });
547
+ }
548
+ }
549
+ // ---------------------------------------------------------------------------
550
+ // CLIENT-SIDE ENGINE — stable anonymous ID
551
+ // ---------------------------------------------------------------------------
552
+ Experiment.ANON_ID_KEY = 'fg_anon_id';
553
+ export default Experiment;
554
+ //# sourceMappingURL=experiment.js.map