@se-studio/ab-testing 0.0.0-next16-20260822103555

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 (96) hide show
  1. package/CHANGELOG.md +1125 -0
  2. package/README.md +412 -0
  3. package/dist/codegen/cli.d.ts +3 -0
  4. package/dist/codegen/cli.d.ts.map +1 -0
  5. package/dist/codegen/cli.js +121 -0
  6. package/dist/codegen/cli.js.map +1 -0
  7. package/dist/codegen/generator.d.ts +42 -0
  8. package/dist/codegen/generator.d.ts.map +1 -0
  9. package/dist/codegen/generator.js +56 -0
  10. package/dist/codegen/generator.js.map +1 -0
  11. package/dist/codegen/index.d.ts +9 -0
  12. package/dist/codegen/index.d.ts.map +1 -0
  13. package/dist/codegen/index.js +8 -0
  14. package/dist/codegen/index.js.map +1 -0
  15. package/dist/components/AbTestReporter.d.ts +2 -0
  16. package/dist/components/AbTestReporter.d.ts.map +1 -0
  17. package/dist/components/AbTestReporter.js +19 -0
  18. package/dist/components/AbTestReporter.js.map +1 -0
  19. package/dist/components/AbTestUtmScript.d.ts +17 -0
  20. package/dist/components/AbTestUtmScript.d.ts.map +1 -0
  21. package/dist/components/AbTestUtmScript.js +43 -0
  22. package/dist/components/AbTestUtmScript.js.map +1 -0
  23. package/dist/components/index.d.ts +3 -0
  24. package/dist/components/index.d.ts.map +1 -0
  25. package/dist/components/index.js +3 -0
  26. package/dist/components/index.js.map +1 -0
  27. package/dist/hooks/index.d.ts +8 -0
  28. package/dist/hooks/index.d.ts.map +1 -0
  29. package/dist/hooks/index.js +7 -0
  30. package/dist/hooks/index.js.map +1 -0
  31. package/dist/hooks/useAbTestAssignments.d.ts +56 -0
  32. package/dist/hooks/useAbTestAssignments.d.ts.map +1 -0
  33. package/dist/hooks/useAbTestAssignments.js +87 -0
  34. package/dist/hooks/useAbTestAssignments.js.map +1 -0
  35. package/dist/index.d.ts +17 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +17 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/middleware/assignment.d.ts +31 -0
  40. package/dist/middleware/assignment.d.ts.map +1 -0
  41. package/dist/middleware/assignment.js +125 -0
  42. package/dist/middleware/assignment.js.map +1 -0
  43. package/dist/middleware/cache.d.ts +30 -0
  44. package/dist/middleware/cache.d.ts.map +1 -0
  45. package/dist/middleware/cache.js +140 -0
  46. package/dist/middleware/cache.js.map +1 -0
  47. package/dist/middleware/cookies.d.ts +44 -0
  48. package/dist/middleware/cookies.d.ts.map +1 -0
  49. package/dist/middleware/cookies.js +66 -0
  50. package/dist/middleware/cookies.js.map +1 -0
  51. package/dist/middleware/handler.d.ts +53 -0
  52. package/dist/middleware/handler.d.ts.map +1 -0
  53. package/dist/middleware/handler.js +201 -0
  54. package/dist/middleware/handler.js.map +1 -0
  55. package/dist/middleware/index.d.ts +15 -0
  56. package/dist/middleware/index.d.ts.map +1 -0
  57. package/dist/middleware/index.js +13 -0
  58. package/dist/middleware/index.js.map +1 -0
  59. package/dist/middleware/prune.d.ts +13 -0
  60. package/dist/middleware/prune.d.ts.map +1 -0
  61. package/dist/middleware/prune.js +29 -0
  62. package/dist/middleware/prune.js.map +1 -0
  63. package/dist/middleware/types.d.ts +103 -0
  64. package/dist/middleware/types.d.ts.map +1 -0
  65. package/dist/middleware/types.js +2 -0
  66. package/dist/middleware/types.js.map +1 -0
  67. package/dist/middleware/urlParams.d.ts +7 -0
  68. package/dist/middleware/urlParams.d.ts.map +1 -0
  69. package/dist/middleware/urlParams.js +20 -0
  70. package/dist/middleware/urlParams.js.map +1 -0
  71. package/dist/middleware/variantId.d.ts +9 -0
  72. package/dist/middleware/variantId.d.ts.map +1 -0
  73. package/dist/middleware/variantId.js +12 -0
  74. package/dist/middleware/variantId.js.map +1 -0
  75. package/dist/reporting/buildExperimentImpression.d.ts +6 -0
  76. package/dist/reporting/buildExperimentImpression.d.ts.map +1 -0
  77. package/dist/reporting/buildExperimentImpression.js +22 -0
  78. package/dist/reporting/buildExperimentImpression.js.map +1 -0
  79. package/dist/types.d.ts +144 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +7 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/utils.d.ts +12 -0
  84. package/dist/utils.d.ts.map +1 -0
  85. package/dist/utils.js +20 -0
  86. package/dist/utils.js.map +1 -0
  87. package/dist/webhook/handler.d.ts +109 -0
  88. package/dist/webhook/handler.d.ts.map +1 -0
  89. package/dist/webhook/handler.js +128 -0
  90. package/dist/webhook/handler.js.map +1 -0
  91. package/dist/webhook/index.d.ts +8 -0
  92. package/dist/webhook/index.d.ts.map +1 -0
  93. package/dist/webhook/index.js +7 -0
  94. package/dist/webhook/index.js.map +1 -0
  95. package/docs/llms.md +106 -0
  96. package/package.json +91 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @se-studio/ab-testing/codegen
3
+ *
4
+ * Build-time utilities for generating a static A/B test configuration file.
5
+ * Import these in a Node.js management script, not in edge/browser code.
6
+ */
7
+ export { buildStaticTestsByPath, generateAbTestsFileContent, } from './generator';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/codegen/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function AbTestReporter(): null;
2
+ //# sourceMappingURL=AbTestReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbTestReporter.d.ts","sourceRoot":"","sources":["../../src/components/AbTestReporter.tsx"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,SAe7B"}
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { useAnalytics } from '@se-studio/core-ui';
3
+ import { useEffect } from 'react';
4
+ import { useAbTestAssignments } from '../hooks/useAbTestAssignments';
5
+ import { buildExperimentImpression } from '../reporting/buildExperimentImpression';
6
+ export function AbTestReporter() {
7
+ const assignments = useAbTestAssignments();
8
+ const { trackEvent } = useAnalytics();
9
+ useEffect(() => {
10
+ if (assignments === null || assignments.length === 0) {
11
+ return;
12
+ }
13
+ for (const assignment of assignments) {
14
+ trackEvent('experiment_impression', buildExperimentImpression(assignment.testId, assignment));
15
+ }
16
+ }, [assignments, trackEvent]);
17
+ return null;
18
+ }
19
+ //# sourceMappingURL=AbTestReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbTestReporter.js","sourceRoot":"","sources":["../../src/components/AbTestReporter.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AAEnF,MAAM,UAAU,cAAc;IAC5B,MAAM,WAAW,GAAG,oBAAoB,EAAE,CAAC;IAC3C,MAAM,EAAE,UAAU,EAAE,GAAG,YAAY,EAAE,CAAC;IAEtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrD,OAAO;QACT,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,UAAU,CAAC,uBAAuB,EAAE,yBAAyB,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;QAChG,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9B,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,17 @@
1
+ interface AbTestUtmScriptProps {
2
+ cookieName?: string;
3
+ }
4
+ /**
5
+ * Inline script that applies url_params from the A/B test cookie to the browser URL
6
+ * as early as possible (before React hydration) so GTM scripts can read them on load.
7
+ *
8
+ * Only runs when the assignment has **injectUrlParams === true** (aligned with middleware).
9
+ * Legacy cookies without the flag do not inject. Also requires original_path match and
10
+ * non-empty url_params. Skips query keys already present on the URL.
11
+ *
12
+ * Must be rendered in the root layout only — Next.js requires
13
+ * `strategy="beforeInteractive"` scripts to live in the root layout.
14
+ */
15
+ export declare function AbTestUtmScript({ cookieName }: AbTestUtmScriptProps): import("react").JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=AbTestUtmScript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbTestUtmScript.d.ts","sourceRoot":"","sources":["../../src/components/AbTestUtmScript.tsx"],"names":[],"mappings":"AAEA,UAAU,oBAAoB;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,EAAE,UAA2B,EAAE,EAAE,oBAAoB,+BA8BpF"}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Script from 'next/script';
3
+ /**
4
+ * Inline script that applies url_params from the A/B test cookie to the browser URL
5
+ * as early as possible (before React hydration) so GTM scripts can read them on load.
6
+ *
7
+ * Only runs when the assignment has **injectUrlParams === true** (aligned with middleware).
8
+ * Legacy cookies without the flag do not inject. Also requires original_path match and
9
+ * non-empty url_params. Skips query keys already present on the URL.
10
+ *
11
+ * Must be rendered in the root layout only — Next.js requires
12
+ * `strategy="beforeInteractive"` scripts to live in the root layout.
13
+ */
14
+ export function AbTestUtmScript({ cookieName = 'ab-test-info' }) {
15
+ return (_jsx(Script, { id: "ab-test-utm", strategy: "beforeInteractive", children: `
16
+ (function() {
17
+ try {
18
+ var m = document.cookie.match(new RegExp('(^| )${cookieName}=([^;]+)'));
19
+ if (!m) return;
20
+ var a = JSON.parse(decodeURIComponent(m[2]));
21
+ var p = window.location.pathname.replace(/\\/$/, '') || '/';
22
+ var url = new URL(window.location.href);
23
+ var changed = false;
24
+ for (var id in a) {
25
+ var t = a[id];
26
+ if (t.injectUrlParams !== true) continue;
27
+ if (!t.url_params) continue;
28
+ var op = (t.original_path || '').replace(/\\/$/, '') || '/';
29
+ if (op !== p) continue;
30
+ for (var key in t.url_params) {
31
+ if (url.searchParams.has(key)) continue;
32
+ url.searchParams.set(key, t.url_params[key]);
33
+ changed = true;
34
+ }
35
+ }
36
+ if (changed) {
37
+ window.history.replaceState(window.history.state, '', url.toString());
38
+ }
39
+ } catch(e) {}
40
+ })();
41
+ ` }));
42
+ }
43
+ //# sourceMappingURL=AbTestUtmScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbTestUtmScript.js","sourceRoot":"","sources":["../../src/components/AbTestUtmScript.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAMjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,UAAU,GAAG,cAAc,EAAwB;IACnF,OAAO,CACL,KAAC,MAAM,IAAC,EAAE,EAAC,aAAa,EAAC,QAAQ,EAAC,mBAAmB,YAAE;;;qDAGN,UAAU;;;;;;;;;;;;;;;;;;;;;;;KAuB1D,GAAU,CACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { AbTestReporter } from './AbTestReporter';
2
+ export { AbTestUtmScript } from './AbTestUtmScript';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { AbTestReporter } from './AbTestReporter';
2
+ export { AbTestUtmScript } from './AbTestUtmScript';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A/B Testing Hooks
3
+ *
4
+ * React hooks for client-side A/B test reporting.
5
+ */
6
+ export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions } from './useAbTestAssignments';
7
+ export { useAbTestAssignments } from './useAbTestAssignments';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A/B Testing Hooks
3
+ *
4
+ * React hooks for client-side A/B test reporting.
5
+ */
6
+ export { useAbTestAssignments } from './useAbTestAssignments';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,56 @@
1
+ import type { AbTestAssignment } from '../types';
2
+ /**
3
+ * An A/B test assignment with its test ID included.
4
+ */
5
+ export interface ActiveAbTestAssignment extends AbTestAssignment {
6
+ /** Contentful entry ID of the PageTest */
7
+ testId: string;
8
+ }
9
+ /**
10
+ * Options for the useAbTestAssignments hook.
11
+ */
12
+ export interface UseAbTestAssignmentsOptions {
13
+ /**
14
+ * Cookie name to read assignments from.
15
+ * @default "ab-test-info"
16
+ */
17
+ cookieName?: string;
18
+ }
19
+ /**
20
+ * Hook that returns A/B test assignments for the current page.
21
+ *
22
+ * This hook reads the A/B test cookie and returns only the assignments
23
+ * that match the current page path. Projects can use this to build
24
+ * their own analytics reporting with project-specific integrations.
25
+ *
26
+ * @param options - Hook configuration options
27
+ * @returns Array of active test assignments for the current page
28
+ *
29
+ * @example Basic usage
30
+ * ```tsx
31
+ * 'use client';
32
+ *
33
+ * import { useEffect } from 'react';
34
+ * import { useAbTestAssignments } from '@se-studio/ab-testing';
35
+ *
36
+ * export function AbTestReporter() {
37
+ * const assignments = useAbTestAssignments();
38
+ *
39
+ * useEffect(() => {
40
+ * for (const assignment of assignments) {
41
+ * // Send to your analytics
42
+ * sendEvent('experiment_impression', {
43
+ * experiment_id: assignment.testId,
44
+ * experiment_name: assignment.experiment_name,
45
+ * variant_id: assignment.variant_id,
46
+ * variant_slug: assignment.variant_slug,
47
+ * });
48
+ * }
49
+ * }, [assignments]);
50
+ *
51
+ * return null;
52
+ * }
53
+ * ```
54
+ */
55
+ export declare function useAbTestAssignments(options?: UseAbTestAssignmentsOptions): ActiveAbTestAssignment[] | null;
56
+ //# sourceMappingURL=useAbTestAssignments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAbTestAssignments.d.ts","sourceRoot":"","sources":["../../src/hooks/useAbTestAssignments.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,2BAA2B,GACpC,sBAAsB,EAAE,GAAG,IAAI,CA6CjC"}
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import { DEFAULT_COOKIE_NAME, readCookieFromDocument } from '../middleware/cookies';
4
+ /**
5
+ * Normalize a path for comparison (remove trailing slash except for root).
6
+ */
7
+ function normalizePath(path) {
8
+ if (path.endsWith('/') && path.length > 1) {
9
+ return path.slice(0, -1);
10
+ }
11
+ return path;
12
+ }
13
+ /**
14
+ * Hook that returns A/B test assignments for the current page.
15
+ *
16
+ * This hook reads the A/B test cookie and returns only the assignments
17
+ * that match the current page path. Projects can use this to build
18
+ * their own analytics reporting with project-specific integrations.
19
+ *
20
+ * @param options - Hook configuration options
21
+ * @returns Array of active test assignments for the current page
22
+ *
23
+ * @example Basic usage
24
+ * ```tsx
25
+ * 'use client';
26
+ *
27
+ * import { useEffect } from 'react';
28
+ * import { useAbTestAssignments } from '@se-studio/ab-testing';
29
+ *
30
+ * export function AbTestReporter() {
31
+ * const assignments = useAbTestAssignments();
32
+ *
33
+ * useEffect(() => {
34
+ * for (const assignment of assignments) {
35
+ * // Send to your analytics
36
+ * sendEvent('experiment_impression', {
37
+ * experiment_id: assignment.testId,
38
+ * experiment_name: assignment.experiment_name,
39
+ * variant_id: assignment.variant_id,
40
+ * variant_slug: assignment.variant_slug,
41
+ * });
42
+ * }
43
+ * }, [assignments]);
44
+ *
45
+ * return null;
46
+ * }
47
+ * ```
48
+ */
49
+ export function useAbTestAssignments(options) {
50
+ const cookieName = options?.cookieName ?? DEFAULT_COOKIE_NAME;
51
+ const [assignments, setAssignments] = useState(null);
52
+ useEffect(() => {
53
+ try {
54
+ const allAssignments = readCookieFromDocument(document.cookie, cookieName);
55
+ if (Object.keys(allAssignments).length === 0) {
56
+ setAssignments([]);
57
+ return;
58
+ }
59
+ // Get and normalize current browser path
60
+ const currentPath = window.location.pathname;
61
+ const normalizedCurrentPath = normalizePath(currentPath);
62
+ // Filter to only assignments for the current page
63
+ const activeAssignments = [];
64
+ for (const [testId, assignment] of Object.entries(allAssignments)) {
65
+ // Skip if original_path is not present
66
+ if (!assignment.original_path) {
67
+ continue;
68
+ }
69
+ // Skip if not on the original path where the test was assigned
70
+ if (assignment.original_path !== normalizedCurrentPath) {
71
+ continue;
72
+ }
73
+ activeAssignments.push({
74
+ ...assignment,
75
+ testId,
76
+ });
77
+ }
78
+ setAssignments(activeAssignments);
79
+ }
80
+ catch (e) {
81
+ // biome-ignore lint/suspicious/noConsole: Error logging
82
+ console.error('[useAbTestAssignments] Error reading A/B test cookie', e);
83
+ }
84
+ }, [cookieName]);
85
+ return assignments;
86
+ }
87
+ //# sourceMappingURL=useAbTestAssignments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAbTestAssignments.js","sourceRoot":"","sources":["../../src/hooks/useAbTestAssignments.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAsBpF;;GAEG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAqC;IAErC,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,mBAAmB,CAAC;IAC9D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAkC,IAAI,CAAC,CAAC;IAEtF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,sBAAsB,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAE3E,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7C,cAAc,CAAC,EAAE,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YAED,yCAAyC;YACzC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC7C,MAAM,qBAAqB,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;YAEzD,kDAAkD;YAClD,MAAM,iBAAiB,GAA6B,EAAE,CAAC;YAEvD,KAAK,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;gBAClE,uCAAuC;gBACvC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;oBAC9B,SAAS;gBACX,CAAC;gBAED,+DAA+D;gBAC/D,IAAI,UAAU,CAAC,aAAa,KAAK,qBAAqB,EAAE,CAAC;oBACvD,SAAS;gBACX,CAAC;gBAED,iBAAiB,CAAC,IAAI,CAAC;oBACrB,GAAG,UAAU;oBACb,MAAM;iBACP,CAAC,CAAC;YACL,CAAC;YAED,cAAc,CAAC,iBAAiB,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,wDAAwD;YACxD,OAAO,CAAC,KAAK,CAAC,sDAAsD,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @se-studio/ab-testing
3
+ *
4
+ * Server-side A/B testing framework for Next.js applications with Contentful CMS.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export type { ActiveAbTestAssignment, UseAbTestAssignmentsOptions } from './hooks/index.js';
9
+ export { useAbTestAssignments } from './hooks/index.js';
10
+ export type { AbTestMiddlewareConfig, AbTestResult, CachedAbTest, StaticAbTestMiddlewareOptions, TestsCache, } from './middleware/index.js';
11
+ export { applyUrlParams, buildVariantId, clearTestCache, computeCachedTest, createAbTestMiddleware, createAssignment, createStaticAbTestMiddleware, DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getActiveTestIds, getAssignment, getCachedTests, getTestCacheState, isValidAssignment, normalizePath, parseCookie, processAbTestRequest, pruneStaleAssignments, readCookieFromDocument, selectVariant, serializeCookie, setAssignment, } from './middleware/index.js';
12
+ export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
13
+ export type { AbTest, AbTestAssignment, AbTestCookie, AbTestVariant, AbTestVariantConfig, IBlobStore, } from './types.js';
14
+ export { findCanonicalPath } from './utils.js';
15
+ export type { RawPageTest, WebhookHandlerConfig, WebhookResult, } from './webhook/index.js';
16
+ export { createWebhookHandler, normalizeVariantConfig, processAbTestWebhook, transformPageTest, } from './webhook/index.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,6BAA6B,EAC7B,UAAU,GACX,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAErF,YAAY,EACV,MAAM,EACN,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @se-studio/ab-testing
3
+ *
4
+ * Server-side A/B testing framework for Next.js applications with Contentful CMS.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ // Re-export hooks
9
+ export { useAbTestAssignments } from './hooks/index.js';
10
+ // Re-export middleware utilities
11
+ export { applyUrlParams, buildVariantId, clearTestCache, computeCachedTest, createAbTestMiddleware, createAssignment, createStaticAbTestMiddleware, DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getActiveTestIds, getAssignment, getCachedTests, getTestCacheState, isValidAssignment, normalizePath, parseCookie, processAbTestRequest, pruneStaleAssignments, readCookieFromDocument, selectVariant, serializeCookie, setAssignment, } from './middleware/index.js';
12
+ export { buildExperimentImpression } from './reporting/buildExperimentImpression.js';
13
+ // Utilities
14
+ export { findCanonicalPath } from './utils.js';
15
+ // Re-export webhook utilities
16
+ export { createWebhookHandler, normalizeVariantConfig, processAbTestWebhook, transformPageTest, } from './webhook/index.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,kBAAkB;AAClB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAQxD,iCAAiC;AACjC,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAUrF,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAM/C,8BAA8B;AAC9B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { AbTestAssignment } from '../types';
2
+ import type { CachedAbTest } from './types';
3
+ /**
4
+ * Perform weighted random selection to assign a variant.
5
+ *
6
+ * @param test - The cached test with pre-computed weights
7
+ * @returns Object with selectedKey (variant ID or "control") and variant slug
8
+ */
9
+ export declare function selectVariant(test: CachedAbTest): {
10
+ selectedKey: string;
11
+ variantSlug: string;
12
+ };
13
+ /**
14
+ * Create an assignment object for a newly selected variant.
15
+ *
16
+ * @param test - The cached test
17
+ * @param selectedKey - The selected variant ID or "control"
18
+ * @param variantSlug - The slug of the selected variant
19
+ * @param originalPath - The original URL path where the test was assigned
20
+ * @returns Assignment object to store in cookie
21
+ */
22
+ export declare function createAssignment(test: CachedAbTest, selectedKey: string, variantSlug: string, originalPath: string): AbTestAssignment;
23
+ /**
24
+ * Validate an existing cookie assignment against current CMS config.
25
+ *
26
+ * @param test - The cached test with current config
27
+ * @param assignment - The assignment from the cookie
28
+ * @returns true if assignment is still valid, false if it should be re-assigned
29
+ */
30
+ export declare function isValidAssignment(test: CachedAbTest, assignment: AbTestAssignment): boolean;
31
+ //# sourceMappingURL=assignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment.d.ts","sourceRoot":"","sources":["../../src/middleware/assignment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAe5C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAe9F;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,gBAAgB,CA2BlB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAyE3F"}
@@ -0,0 +1,125 @@
1
+ import { buildVariantId } from './variantId';
2
+ function recordsEqual(a, b) {
3
+ if (!a && !b)
4
+ return true;
5
+ if (!a || !b)
6
+ return false;
7
+ const aKeys = Object.keys(a);
8
+ const bKeys = Object.keys(b);
9
+ if (aKeys.length !== bKeys.length)
10
+ return false;
11
+ return aKeys.every((key) => a[key] === b[key]);
12
+ }
13
+ /**
14
+ * Perform weighted random selection to assign a variant.
15
+ *
16
+ * @param test - The cached test with pre-computed weights
17
+ * @returns Object with selectedKey (variant ID or "control") and variant slug
18
+ */
19
+ export function selectVariant(test) {
20
+ const rand = Math.random();
21
+ let sum = 0;
22
+ let selectedKey = 'control';
23
+ for (const [key, weight] of Object.entries(test.readyWeights)) {
24
+ sum += weight;
25
+ if (rand < sum) {
26
+ selectedKey = key;
27
+ break;
28
+ }
29
+ }
30
+ const variantSlug = test.variantLookup[selectedKey] || 'control';
31
+ return { selectedKey, variantSlug };
32
+ }
33
+ /**
34
+ * Create an assignment object for a newly selected variant.
35
+ *
36
+ * @param test - The cached test
37
+ * @param selectedKey - The selected variant ID or "control"
38
+ * @param variantSlug - The slug of the selected variant
39
+ * @param originalPath - The original URL path where the test was assigned
40
+ * @returns Assignment object to store in cookie
41
+ */
42
+ export function createAssignment(test, selectedKey, variantSlug, originalPath) {
43
+ const isControl = selectedKey === 'control';
44
+ const slug = variantSlug === 'control' ? 'control' : variantSlug;
45
+ const variantLabel = test.variantLabelLookup[selectedKey];
46
+ const fallbackLabel = isControl ? 'control' : slug;
47
+ const assignment = {
48
+ experiment_name: test.trackingLabel || test.cmsLabel,
49
+ variant_id: buildVariantId(isControl, variantLabel, fallbackLabel),
50
+ variant_slug: slug,
51
+ original_path: originalPath,
52
+ reporting_event: test.reportingEventLookup[selectedKey],
53
+ // Strict true only — missing/false means AbTestUtmScript must not inject
54
+ injectUrlParams: test.injectUrlParams === true,
55
+ };
56
+ const urlParams = test.urlParamsLookup[selectedKey];
57
+ if (urlParams && Object.keys(urlParams).length > 0) {
58
+ assignment.url_params = urlParams;
59
+ }
60
+ const reportingParams = test.reportingParamsLookup[selectedKey];
61
+ if (reportingParams && Object.keys(reportingParams).length > 0) {
62
+ assignment.reporting_params = reportingParams;
63
+ }
64
+ return assignment;
65
+ }
66
+ /**
67
+ * Validate an existing cookie assignment against current CMS config.
68
+ *
69
+ * @param test - The cached test with current config
70
+ * @param assignment - The assignment from the cookie
71
+ * @returns true if assignment is still valid, false if it should be re-assigned
72
+ */
73
+ export function isValidAssignment(test, assignment) {
74
+ const { experiment_name, variant_id, variant_slug, reporting_event, original_path, injectUrlParams, url_params, reporting_params, } = assignment;
75
+ if (!original_path) {
76
+ return false;
77
+ }
78
+ const expectedName = test.trackingLabel || test.cmsLabel;
79
+ if (experiment_name !== expectedName) {
80
+ return false;
81
+ }
82
+ // Reassign when CMS toggles injectUrlParams (legacy cookies lack the field → refresh)
83
+ const expectedInject = test.injectUrlParams === true;
84
+ if ((injectUrlParams === true) !== expectedInject) {
85
+ return false;
86
+ }
87
+ let variantKey;
88
+ if (variant_slug === 'control') {
89
+ if ((test.readyWeights['control'] ?? 0) <= 0) {
90
+ return false;
91
+ }
92
+ variantKey = 'control';
93
+ }
94
+ else {
95
+ for (const [id, slug] of Object.entries(test.variantLookup)) {
96
+ if (slug === variant_slug && id !== 'control') {
97
+ if ((test.readyWeights[id] ?? 0) <= 0) {
98
+ return false;
99
+ }
100
+ variantKey = id;
101
+ break;
102
+ }
103
+ }
104
+ if (!variantKey) {
105
+ return false;
106
+ }
107
+ }
108
+ const isControl = variantKey === 'control';
109
+ const expectedVariantId = buildVariantId(isControl, test.variantLabelLookup[variantKey], isControl ? 'control' : variant_slug);
110
+ if (variant_id !== expectedVariantId) {
111
+ return false;
112
+ }
113
+ const expectedReportingEvent = test.reportingEventLookup[variantKey];
114
+ if (reporting_event !== expectedReportingEvent) {
115
+ return false;
116
+ }
117
+ if (!recordsEqual(url_params, test.urlParamsLookup[variantKey])) {
118
+ return false;
119
+ }
120
+ if (!recordsEqual(reporting_params, test.reportingParamsLookup[variantKey])) {
121
+ return false;
122
+ }
123
+ return true;
124
+ }
125
+ //# sourceMappingURL=assignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment.js","sourceRoot":"","sources":["../../src/middleware/assignment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,SAAS,YAAY,CACnB,CAAqC,EACrC,CAAqC;IAErC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAChD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAkB;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC3B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,WAAW,GAAG,SAAS,CAAC;IAE5B,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9D,GAAG,IAAI,MAAM,CAAC;QACd,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC;YACf,WAAW,GAAG,GAAG,CAAC;YAClB,MAAM;QACR,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,SAAS,CAAC;IACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAkB,EAClB,WAAmB,EACnB,WAAmB,EACnB,YAAoB;IAEpB,MAAM,SAAS,GAAG,WAAW,KAAK,SAAS,CAAC;IAC5C,MAAM,IAAI,GAAG,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACjE,MAAM,YAAY,GAAG,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IAEnD,MAAM,UAAU,GAAqB;QACnC,eAAe,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,QAAQ;QACpD,UAAU,EAAE,cAAc,CAAC,SAAS,EAAE,YAAY,EAAE,aAAa,CAAC;QAClE,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,YAAY;QAC3B,eAAe,EAAE,IAAI,CAAC,oBAAoB,CAAC,WAAW,CAAC;QACvD,yEAAyE;QACzE,eAAe,EAAE,IAAI,CAAC,eAAe,KAAK,IAAI;KAC/C,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,UAAU,CAAC,UAAU,GAAG,SAAS,CAAC;IACpC,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAChE,IAAI,eAAe,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,UAAU,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAChD,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAkB,EAAE,UAA4B;IAChF,MAAM,EACJ,eAAe,EACf,UAAU,EACV,YAAY,EACZ,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,EACV,gBAAgB,GACjB,GAAG,UAAU,CAAC;IAEf,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,QAAQ,CAAC;IACzD,IAAI,eAAe,KAAK,YAAY,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sFAAsF;IACtF,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,KAAK,IAAI,CAAC;IACrD,IAAI,CAAC,eAAe,KAAK,IAAI,CAAC,KAAK,cAAc,EAAE,CAAC;QAClD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,UAA8B,CAAC;IAEnC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAC;QACf,CAAC;QACD,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5D,IAAI,IAAI,KAAK,YAAY,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBAC9C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtC,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,UAAU,GAAG,EAAE,CAAC;gBAChB,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,KAAK,SAAS,CAAC;IAC3C,MAAM,iBAAiB,GAAG,cAAc,CACtC,SAAS,EACT,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,EACnC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CACrC,CAAC;IACF,IAAI,UAAU,KAAK,iBAAiB,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,sBAAsB,GAAG,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACrE,IAAI,eAAe,KAAK,sBAAsB,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAC5E,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { AbTest, IBlobStore } from '../types';
2
+ import type { CachedAbTest, TestsCache } from './types';
3
+ /**
4
+ * Pre-compute weights and lookups for a test.
5
+ */
6
+ export declare function computeCachedTest(test: AbTest): CachedAbTest;
7
+ /**
8
+ * Normalize a URL path for consistent matching.
9
+ * - Removes trailing slashes (except for root)
10
+ * - Ensures leading slash
11
+ */
12
+ export declare function normalizePath(path: string): string;
13
+ /**
14
+ * Get cached tests, refreshing from store if needed.
15
+ *
16
+ * @param store - The blob store to fetch tests from
17
+ * @param cacheTtlMs - Cache time-to-live in milliseconds
18
+ * @param devTestData - Optional test data for development mode
19
+ * @returns Map of tests indexed by normalized control path
20
+ */
21
+ export declare function getCachedTests(store: IBlobStore<AbTest>, cacheTtlMs: number, devTestData?: AbTest[]): Promise<Map<string, CachedAbTest[]>>;
22
+ /**
23
+ * Clear the test cache. Useful for testing or forcing a refresh.
24
+ */
25
+ export declare function clearTestCache(): void;
26
+ /**
27
+ * Get the current cache state. Useful for debugging.
28
+ */
29
+ export declare function getTestCacheState(): TestsCache | null;
30
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAKxD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAqD5D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAelD;AAsBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAgCtC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,GAAG,IAAI,CAErD"}