@se-studio/ab-testing 1.0.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 (50) hide show
  1. package/README.md +393 -0
  2. package/dist/hooks/index.d.ts +8 -0
  3. package/dist/hooks/index.d.ts.map +1 -0
  4. package/dist/hooks/index.js +7 -0
  5. package/dist/hooks/index.js.map +1 -0
  6. package/dist/hooks/useAbTestAssignments.d.ts +55 -0
  7. package/dist/hooks/useAbTestAssignments.d.ts.map +1 -0
  8. package/dist/hooks/useAbTestAssignments.js +87 -0
  9. package/dist/hooks/useAbTestAssignments.js.map +1 -0
  10. package/dist/index.d.ts +15 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +14 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/middleware/assignment.d.ts +36 -0
  15. package/dist/middleware/assignment.d.ts.map +1 -0
  16. package/dist/middleware/assignment.js +91 -0
  17. package/dist/middleware/assignment.js.map +1 -0
  18. package/dist/middleware/cache.d.ts +26 -0
  19. package/dist/middleware/cache.d.ts.map +1 -0
  20. package/dist/middleware/cache.js +128 -0
  21. package/dist/middleware/cache.js.map +1 -0
  22. package/dist/middleware/cookies.d.ts +44 -0
  23. package/dist/middleware/cookies.d.ts.map +1 -0
  24. package/dist/middleware/cookies.js +66 -0
  25. package/dist/middleware/cookies.js.map +1 -0
  26. package/dist/middleware/handler.d.ts +45 -0
  27. package/dist/middleware/handler.d.ts.map +1 -0
  28. package/dist/middleware/handler.js +189 -0
  29. package/dist/middleware/handler.js.map +1 -0
  30. package/dist/middleware/index.d.ts +11 -0
  31. package/dist/middleware/index.d.ts.map +1 -0
  32. package/dist/middleware/index.js +10 -0
  33. package/dist/middleware/index.js.map +1 -0
  34. package/dist/middleware/types.d.ts +78 -0
  35. package/dist/middleware/types.d.ts.map +1 -0
  36. package/dist/middleware/types.js +2 -0
  37. package/dist/middleware/types.js.map +1 -0
  38. package/dist/types.d.ts +125 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +7 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/webhook/handler.d.ts +116 -0
  43. package/dist/webhook/handler.d.ts.map +1 -0
  44. package/dist/webhook/handler.js +123 -0
  45. package/dist/webhook/handler.js.map +1 -0
  46. package/dist/webhook/index.d.ts +8 -0
  47. package/dist/webhook/index.d.ts.map +1 -0
  48. package/dist/webhook/index.js +7 -0
  49. package/dist/webhook/index.js.map +1 -0
  50. package/package.json +74 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Perform weighted random selection to assign a variant.
3
+ *
4
+ * @param test - The cached test with pre-computed weights
5
+ * @returns Object with selectedKey (variant ID or "control") and variant slug
6
+ */
7
+ export function selectVariant(test) {
8
+ const rand = Math.random();
9
+ let sum = 0;
10
+ let selectedKey = 'control';
11
+ for (const [key, weight] of Object.entries(test.readyWeights)) {
12
+ sum += weight;
13
+ if (rand < sum) {
14
+ selectedKey = key;
15
+ break;
16
+ }
17
+ }
18
+ const variantSlug = test.variantLookup[selectedKey] || 'control';
19
+ return { selectedKey, variantSlug };
20
+ }
21
+ /**
22
+ * Create an assignment object for a newly selected variant.
23
+ *
24
+ * @param test - The cached test
25
+ * @param selectedKey - The selected variant ID or "control"
26
+ * @param variantSlug - The slug of the selected variant
27
+ * @param originalPath - The original URL path where the test was assigned
28
+ * @returns Assignment object to store in cookie
29
+ */
30
+ export function createAssignment(test, selectedKey, variantSlug, originalPath) {
31
+ return {
32
+ test_label: test.trackingLabel || test.cmsLabel,
33
+ test_path: variantSlug === 'control' ? 'control' : variantSlug,
34
+ hubspot_event: test.hubspotEventLookup[selectedKey],
35
+ original_path: originalPath,
36
+ };
37
+ }
38
+ /**
39
+ * Validate an existing cookie assignment against current CMS config.
40
+ * Checks that:
41
+ * - original_path is present
42
+ * - test_label matches current config
43
+ * - test_path (variant) exists and has weight > 0
44
+ * - hubspot_event matches current config
45
+ *
46
+ * @param test - The cached test with current config
47
+ * @param assignment - The assignment from the cookie
48
+ * @returns true if assignment is still valid, false if it should be re-assigned
49
+ */
50
+ export function isValidAssignment(test, assignment) {
51
+ const { test_label, test_path, hubspot_event, original_path } = assignment;
52
+ // 1. Validate original_path is present (required for path-based reporting)
53
+ if (!original_path) {
54
+ return false;
55
+ }
56
+ // 2. Validate test_label matches current config
57
+ const expectedLabel = test.trackingLabel || test.cmsLabel;
58
+ if (test_label !== expectedLabel) {
59
+ return false;
60
+ }
61
+ // 3. Validate test_path and get the variant key for hubspot lookup
62
+ let variantKey;
63
+ if (test_path === 'control') {
64
+ if ((test.readyWeights['control'] ?? 0) <= 0) {
65
+ return false;
66
+ }
67
+ variantKey = 'control';
68
+ }
69
+ else {
70
+ // Find variant ID by slug
71
+ for (const [id, slug] of Object.entries(test.variantLookup)) {
72
+ if (slug === test_path && id !== 'control') {
73
+ if ((test.readyWeights[id] ?? 0) <= 0) {
74
+ return false;
75
+ }
76
+ variantKey = id;
77
+ break;
78
+ }
79
+ }
80
+ if (!variantKey) {
81
+ return false; // Slug not found
82
+ }
83
+ }
84
+ // 4. Validate hubspot_event matches current config
85
+ const expectedHubspotEvent = test.hubspotEventLookup[variantKey];
86
+ if (hubspot_event !== expectedHubspotEvent) {
87
+ return false;
88
+ }
89
+ return true;
90
+ }
91
+ //# sourceMappingURL=assignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment.js","sourceRoot":"","sources":["../../src/middleware/assignment.ts"],"names":[],"mappings":"AAGA;;;;;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,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,QAAQ;QAC/C,SAAS,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9D,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC;QACnD,aAAa,EAAE,YAAY;KAC5B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAkB,EAAE,UAA4B;IAChF,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,UAAU,CAAC;IAE3E,2EAA2E;IAC3E,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gDAAgD;IAChD,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,QAAQ,CAAC;IAC1D,IAAI,UAAU,KAAK,aAAa,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mEAAmE;IACnE,IAAI,UAA8B,CAAC;IAEnC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,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,0BAA0B;QAC1B,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5D,IAAI,IAAI,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBAC3C,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,CAAC,iBAAiB;QACjC,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,MAAM,oBAAoB,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACjE,IAAI,aAAa,KAAK,oBAAoB,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { AbTest, IBlobStore } from '../types';
2
+ import type { CachedAbTest, TestsCache } from './types';
3
+ /**
4
+ * Normalize a URL path for consistent matching.
5
+ * - Removes trailing slashes (except for root)
6
+ * - Ensures leading slash
7
+ */
8
+ export declare function normalizePath(path: string): string;
9
+ /**
10
+ * Get cached tests, refreshing from store if needed.
11
+ *
12
+ * @param store - The blob store to fetch tests from
13
+ * @param cacheTtlMs - Cache time-to-live in milliseconds
14
+ * @param devTestData - Optional test data for development mode
15
+ * @returns Map of tests indexed by normalized control path
16
+ */
17
+ export declare function getCachedTests(store: IBlobStore<AbTest>, cacheTtlMs: number, devTestData?: AbTest[]): Promise<Map<string, CachedAbTest[]>>;
18
+ /**
19
+ * Clear the test cache. Useful for testing or forcing a refresh.
20
+ */
21
+ export declare function clearTestCache(): void;
22
+ /**
23
+ * Get the current cache state. Useful for debugging.
24
+ */
25
+ export declare function getTestCacheState(): TestsCache | null;
26
+ //# 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;AAmDxD;;;;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"}
@@ -0,0 +1,128 @@
1
+ /** Global cache instance */
2
+ let globalTestsCache = null;
3
+ /**
4
+ * Pre-compute weights and lookups for a test.
5
+ */
6
+ function computeCachedTest(test) {
7
+ const readyWeights = {};
8
+ const hubspotEventLookup = {};
9
+ const config = test.configuration;
10
+ if (config && config.length > 0) {
11
+ // Control weight (index 0)
12
+ readyWeights['control'] = config[0]?.weight ?? 0;
13
+ hubspotEventLookup['control'] = config[0]?.hubspot_event_name;
14
+ // Variant weights (index 1+)
15
+ for (let i = 0; i < test.variants.length; i++) {
16
+ const variantConfig = config[i + 1];
17
+ const variant = test.variants[i];
18
+ if (variant) {
19
+ readyWeights[variant.id] = variantConfig?.weight ?? 0;
20
+ hubspotEventLookup[variant.id] = variantConfig?.hubspot_event_name;
21
+ }
22
+ }
23
+ }
24
+ else {
25
+ // Fallback: equal weights if no configuration
26
+ const totalOptions = test.variants.length + 1;
27
+ const equalWeight = 1 / totalOptions;
28
+ readyWeights['control'] = equalWeight;
29
+ for (const variant of test.variants) {
30
+ readyWeights[variant.id] = equalWeight;
31
+ }
32
+ }
33
+ // Pre-compute variant lookup (ID -> Slug)
34
+ const variantLookup = { control: 'control' };
35
+ for (const variant of test.variants) {
36
+ variantLookup[variant.id] = variant.slug;
37
+ }
38
+ return {
39
+ ...test,
40
+ readyWeights,
41
+ variantLookup,
42
+ hubspotEventLookup,
43
+ };
44
+ }
45
+ /**
46
+ * Normalize a URL path for consistent matching.
47
+ * - Removes trailing slashes (except for root)
48
+ * - Ensures leading slash
49
+ */
50
+ export function normalizePath(path) {
51
+ // Handle "index" as root
52
+ if (path === 'index') {
53
+ return '/';
54
+ }
55
+ // Ensure leading slash
56
+ let normalized = path.startsWith('/') ? path : `/${path}`;
57
+ // Remove trailing slash (except for root)
58
+ if (normalized.endsWith('/') && normalized.length > 1) {
59
+ normalized = normalized.slice(0, -1);
60
+ }
61
+ return normalized;
62
+ }
63
+ /**
64
+ * Build cached tests map from raw tests.
65
+ */
66
+ function buildTestsByPath(tests) {
67
+ const testsByPath = new Map();
68
+ for (const test of tests) {
69
+ if (!test.enabled)
70
+ continue;
71
+ const normalizedControlPath = normalizePath(test.controlSlug);
72
+ const cachedTest = computeCachedTest(test);
73
+ const existing = testsByPath.get(normalizedControlPath) || [];
74
+ existing.push(cachedTest);
75
+ testsByPath.set(normalizedControlPath, existing);
76
+ }
77
+ return testsByPath;
78
+ }
79
+ /**
80
+ * Get cached tests, refreshing from store if needed.
81
+ *
82
+ * @param store - The blob store to fetch tests from
83
+ * @param cacheTtlMs - Cache time-to-live in milliseconds
84
+ * @param devTestData - Optional test data for development mode
85
+ * @returns Map of tests indexed by normalized control path
86
+ */
87
+ export async function getCachedTests(store, cacheTtlMs, devTestData) {
88
+ const now = Date.now();
89
+ // Return cached data if still valid
90
+ if (globalTestsCache && now - globalTestsCache.timestamp < cacheTtlMs) {
91
+ return globalTestsCache.testsByPath;
92
+ }
93
+ try {
94
+ let tests;
95
+ // Use dev test data if provided
96
+ if (devTestData && process.env.NODE_ENV === 'development') {
97
+ tests = devTestData;
98
+ }
99
+ else {
100
+ tests = await store.values();
101
+ }
102
+ const testsByPath = buildTestsByPath(tests);
103
+ globalTestsCache = {
104
+ timestamp: now,
105
+ testsByPath,
106
+ };
107
+ return testsByPath;
108
+ }
109
+ catch (error) {
110
+ // biome-ignore lint/suspicious/noConsole: Error logging in middleware
111
+ console.error('Error fetching A/B tests:', error);
112
+ // Return stale cache if available, otherwise empty map
113
+ return globalTestsCache?.testsByPath || new Map();
114
+ }
115
+ }
116
+ /**
117
+ * Clear the test cache. Useful for testing or forcing a refresh.
118
+ */
119
+ export function clearTestCache() {
120
+ globalTestsCache = null;
121
+ }
122
+ /**
123
+ * Get the current cache state. Useful for debugging.
124
+ */
125
+ export function getTestCacheState() {
126
+ return globalTestsCache;
127
+ }
128
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { AbTestAssignment, AbTestCookie } from '../types';
2
+ /** Default cookie name for A/B test assignments */
3
+ export declare const DEFAULT_COOKIE_NAME = "ab-test-info";
4
+ /** Default cookie max age: 30 days in seconds */
5
+ export declare const DEFAULT_COOKIE_MAX_AGE: number;
6
+ /**
7
+ * Parse the A/B test cookie value into an assignment map.
8
+ *
9
+ * @param cookieValue - The raw cookie value string
10
+ * @returns Parsed assignment map, or empty object if invalid
11
+ */
12
+ export declare function parseCookie(cookieValue: string | undefined): AbTestCookie;
13
+ /**
14
+ * Serialize an assignment map to a cookie value string.
15
+ *
16
+ * @param assignments - The assignment map to serialize
17
+ * @returns Serialized cookie value
18
+ */
19
+ export declare function serializeCookie(assignments: AbTestCookie): string;
20
+ /**
21
+ * Get a specific test assignment from the cookie.
22
+ *
23
+ * @param cookie - Parsed cookie object
24
+ * @param testId - The test ID to look up
25
+ * @returns The assignment for this test, or undefined
26
+ */
27
+ export declare function getAssignment(cookie: AbTestCookie, testId: string): AbTestAssignment | undefined;
28
+ /**
29
+ * Set a test assignment in the cookie object.
30
+ *
31
+ * @param cookie - Parsed cookie object (will be mutated)
32
+ * @param testId - The test ID
33
+ * @param assignment - The assignment to store
34
+ */
35
+ export declare function setAssignment(cookie: AbTestCookie, testId: string, assignment: AbTestAssignment): void;
36
+ /**
37
+ * Read an A/B test cookie from a document.cookie string (client-side).
38
+ *
39
+ * @param documentCookie - The document.cookie string
40
+ * @param cookieName - The cookie name to look for
41
+ * @returns Parsed assignment map, or empty object if not found
42
+ */
43
+ export declare function readCookieFromDocument(documentCookie: string, cookieName?: string): AbTestCookie;
44
+ //# sourceMappingURL=cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/middleware/cookies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/D,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,QAAoB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAWzE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,GAC3B,IAAI,CAEN;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,UAAU,GAAE,MAA4B,GACvC,YAAY,CAMd"}
@@ -0,0 +1,66 @@
1
+ /** Default cookie name for A/B test assignments */
2
+ export const DEFAULT_COOKIE_NAME = 'ab-test-info';
3
+ /** Default cookie max age: 30 days in seconds */
4
+ export const DEFAULT_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
5
+ /**
6
+ * Parse the A/B test cookie value into an assignment map.
7
+ *
8
+ * @param cookieValue - The raw cookie value string
9
+ * @returns Parsed assignment map, or empty object if invalid
10
+ */
11
+ export function parseCookie(cookieValue) {
12
+ if (!cookieValue) {
13
+ return {};
14
+ }
15
+ try {
16
+ const decoded = decodeURIComponent(cookieValue);
17
+ return JSON.parse(decoded);
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ /**
24
+ * Serialize an assignment map to a cookie value string.
25
+ *
26
+ * @param assignments - The assignment map to serialize
27
+ * @returns Serialized cookie value
28
+ */
29
+ export function serializeCookie(assignments) {
30
+ return JSON.stringify(assignments);
31
+ }
32
+ /**
33
+ * Get a specific test assignment from the cookie.
34
+ *
35
+ * @param cookie - Parsed cookie object
36
+ * @param testId - The test ID to look up
37
+ * @returns The assignment for this test, or undefined
38
+ */
39
+ export function getAssignment(cookie, testId) {
40
+ return cookie[testId];
41
+ }
42
+ /**
43
+ * Set a test assignment in the cookie object.
44
+ *
45
+ * @param cookie - Parsed cookie object (will be mutated)
46
+ * @param testId - The test ID
47
+ * @param assignment - The assignment to store
48
+ */
49
+ export function setAssignment(cookie, testId, assignment) {
50
+ cookie[testId] = assignment;
51
+ }
52
+ /**
53
+ * Read an A/B test cookie from a document.cookie string (client-side).
54
+ *
55
+ * @param documentCookie - The document.cookie string
56
+ * @param cookieName - The cookie name to look for
57
+ * @returns Parsed assignment map, or empty object if not found
58
+ */
59
+ export function readCookieFromDocument(documentCookie, cookieName = DEFAULT_COOKIE_NAME) {
60
+ const match = documentCookie.match(new RegExp(`(^| )${cookieName}=([^;]+)`));
61
+ if (!match || !match[2]) {
62
+ return {};
63
+ }
64
+ return parseCookie(match[2]);
65
+ }
66
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/middleware/cookies.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,MAAM,CAAC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAElD,iDAAiD;AACjD,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,WAA+B;IACzD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,WAAyB;IACvD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAoB,EAAE,MAAc;IAChE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAoB,EACpB,MAAc,EACd,UAA4B;IAE5B,MAAM,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,cAAsB,EACtB,aAAqB,mBAAmB;IAExC,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,UAAU,UAAU,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { NextRequest } from 'next/server';
2
+ import { NextResponse } from 'next/server';
3
+ import type { AbTestMiddlewareConfig, AbTestResult } from './types';
4
+ /**
5
+ * Create an A/B test middleware handler.
6
+ *
7
+ * This factory function returns a middleware handler that can be called
8
+ * from your Next.js middleware to process A/B test assignments.
9
+ *
10
+ * @param config - Middleware configuration
11
+ * @returns Async function that processes requests and returns NextResponse or null
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // middleware.ts
16
+ * import { createAbTestMiddleware } from '@se-studio/ab-testing/middleware';
17
+ * import { getAbTestStore } from './server/abTestStore';
18
+ *
19
+ * const abTestHandler = createAbTestMiddleware({
20
+ * getStore: getAbTestStore,
21
+ * cacheTtlMs: 60000,
22
+ * });
23
+ *
24
+ * export async function middleware(request: NextRequest) {
25
+ * const abResponse = await abTestHandler(request);
26
+ * if (abResponse) return abResponse;
27
+ * return NextResponse.next();
28
+ * }
29
+ * ```
30
+ */
31
+ export declare function createAbTestMiddleware(config: AbTestMiddlewareConfig): (request: NextRequest) => Promise<NextResponse | null>;
32
+ /**
33
+ * Low-level function to process an A/B test request.
34
+ * Use this if you need more control over the response handling.
35
+ *
36
+ * @param pathname - The request pathname
37
+ * @param searchParams - The request search params
38
+ * @param cookieValue - The current A/B test cookie value
39
+ * @param store - The blob store to fetch tests from
40
+ * @param cacheTtlMs - Cache TTL in milliseconds
41
+ * @param devTestData - Optional dev test data
42
+ * @returns Result object with test matching info and new cookie value
43
+ */
44
+ export declare function processAbTestRequest(pathname: string, searchParams: URLSearchParams, cookieValue: string | undefined, store: ReturnType<AbTestMiddlewareConfig['getStore']>, cacheTtlMs: number, devTestData?: AbTestMiddlewareConfig['devTestData']): Promise<AbTestResult>;
45
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/middleware/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,YAAY,EAAgB,MAAM,SAAS,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,IAmE5B,SAAS,WAAW,KAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA0D3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,eAAe,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,KAAK,EAAE,UAAU,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,EACrD,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,sBAAsB,CAAC,aAAa,CAAC,GAClD,OAAO,CAAC,YAAY,CAAC,CAwDvB"}
@@ -0,0 +1,189 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { createAssignment, isValidAssignment, selectVariant } from './assignment';
3
+ import { getCachedTests, normalizePath } from './cache';
4
+ import { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, serializeCookie, setAssignment, } from './cookies';
5
+ /**
6
+ * Create an A/B test middleware handler.
7
+ *
8
+ * This factory function returns a middleware handler that can be called
9
+ * from your Next.js middleware to process A/B test assignments.
10
+ *
11
+ * @param config - Middleware configuration
12
+ * @returns Async function that processes requests and returns NextResponse or null
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // middleware.ts
17
+ * import { createAbTestMiddleware } from '@se-studio/ab-testing/middleware';
18
+ * import { getAbTestStore } from './server/abTestStore';
19
+ *
20
+ * const abTestHandler = createAbTestMiddleware({
21
+ * getStore: getAbTestStore,
22
+ * cacheTtlMs: 60000,
23
+ * });
24
+ *
25
+ * export async function middleware(request: NextRequest) {
26
+ * const abResponse = await abTestHandler(request);
27
+ * if (abResponse) return abResponse;
28
+ * return NextResponse.next();
29
+ * }
30
+ * ```
31
+ */
32
+ export function createAbTestMiddleware(config) {
33
+ const { getStore, cacheTtlMs = 60000, cookieName = DEFAULT_COOKIE_NAME, cookieMaxAge = DEFAULT_COOKIE_MAX_AGE, shouldProcess, devTestData, onValidationFailure, } = config;
34
+ /**
35
+ * Process a single test against the request.
36
+ */
37
+ function processTest(test, normalizedPathname, searchParams, cookie) {
38
+ // Check search parameters matching
39
+ if (test.searchParameters) {
40
+ const testSearchParams = new URLSearchParams(test.searchParameters);
41
+ for (const [key, value] of testSearchParams.entries()) {
42
+ if (searchParams.get(key) !== value) {
43
+ return null; // Params don't match
44
+ }
45
+ }
46
+ }
47
+ // Get existing assignment from cookie
48
+ let assignmentData = getAssignment(cookie, test.id);
49
+ // Validate existing assignment against current CMS config
50
+ if (assignmentData && !isValidAssignment(test, assignmentData)) {
51
+ onValidationFailure?.(test.id, assignmentData);
52
+ assignmentData = undefined; // Force re-assignment
53
+ }
54
+ // Create new assignment if needed
55
+ if (!assignmentData) {
56
+ const { selectedKey, variantSlug } = selectVariant(test);
57
+ assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
58
+ setAssignment(cookie, test.id, assignmentData);
59
+ }
60
+ // Determine if we need to rewrite
61
+ const variantSlug = assignmentData.test_path;
62
+ const rewriteUrl = variantSlug !== 'control' && variantSlug
63
+ ? variantSlug.startsWith('/')
64
+ ? variantSlug
65
+ : `/${variantSlug}`
66
+ : undefined;
67
+ return {
68
+ matched: true,
69
+ test,
70
+ assignment: assignmentData,
71
+ rewriteUrl,
72
+ cookieValue: serializeCookie(cookie),
73
+ };
74
+ }
75
+ /**
76
+ * Main middleware handler.
77
+ */
78
+ return async function abTestMiddleware(request) {
79
+ const pathname = request.nextUrl.pathname;
80
+ // Check if we should process this request
81
+ if (shouldProcess && !shouldProcess(pathname)) {
82
+ return null;
83
+ }
84
+ try {
85
+ const normalizedPathname = normalizePath(pathname);
86
+ const store = await Promise.resolve(getStore());
87
+ const testsByPath = await getCachedTests(store, cacheTtlMs, devTestData);
88
+ const tests = testsByPath.get(normalizedPathname);
89
+ if (!tests || tests.length === 0) {
90
+ return null;
91
+ }
92
+ // Parse existing cookie
93
+ const cookieValue = request.cookies.get(cookieName)?.value;
94
+ const cookie = parseCookie(cookieValue);
95
+ // Process each test for this path
96
+ for (const test of tests) {
97
+ const result = processTest(test, normalizedPathname, request.nextUrl.searchParams, cookie);
98
+ if (result?.matched) {
99
+ // Create response
100
+ let response;
101
+ if (result.rewriteUrl) {
102
+ const url = request.nextUrl.clone();
103
+ url.pathname = result.rewriteUrl;
104
+ response = NextResponse.rewrite(url);
105
+ }
106
+ else {
107
+ response = NextResponse.next();
108
+ }
109
+ // Set cookie with assignment
110
+ if (result.cookieValue) {
111
+ response.cookies.set(cookieName, result.cookieValue, {
112
+ path: '/',
113
+ maxAge: cookieMaxAge,
114
+ sameSite: 'lax',
115
+ });
116
+ }
117
+ return response;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ catch (error) {
123
+ // biome-ignore lint/suspicious/noConsole: Error logging in middleware
124
+ console.error('Middleware A/B test error:', error);
125
+ return null;
126
+ }
127
+ };
128
+ }
129
+ /**
130
+ * Low-level function to process an A/B test request.
131
+ * Use this if you need more control over the response handling.
132
+ *
133
+ * @param pathname - The request pathname
134
+ * @param searchParams - The request search params
135
+ * @param cookieValue - The current A/B test cookie value
136
+ * @param store - The blob store to fetch tests from
137
+ * @param cacheTtlMs - Cache TTL in milliseconds
138
+ * @param devTestData - Optional dev test data
139
+ * @returns Result object with test matching info and new cookie value
140
+ */
141
+ export async function processAbTestRequest(pathname, searchParams, cookieValue, store, cacheTtlMs, devTestData) {
142
+ const normalizedPathname = normalizePath(pathname);
143
+ const resolvedStore = await Promise.resolve(store);
144
+ const testsByPath = await getCachedTests(resolvedStore, cacheTtlMs, devTestData);
145
+ const tests = testsByPath.get(normalizedPathname);
146
+ if (!tests || tests.length === 0) {
147
+ return { matched: false };
148
+ }
149
+ const cookie = parseCookie(cookieValue);
150
+ for (const test of tests) {
151
+ // Check search parameters
152
+ if (test.searchParameters) {
153
+ const testSearchParams = new URLSearchParams(test.searchParameters);
154
+ let paramsMatch = true;
155
+ for (const [key, value] of testSearchParams.entries()) {
156
+ if (searchParams.get(key) !== value) {
157
+ paramsMatch = false;
158
+ break;
159
+ }
160
+ }
161
+ if (!paramsMatch)
162
+ continue;
163
+ }
164
+ let assignmentData = getAssignment(cookie, test.id);
165
+ if (assignmentData && !isValidAssignment(test, assignmentData)) {
166
+ assignmentData = undefined;
167
+ }
168
+ if (!assignmentData) {
169
+ const { selectedKey, variantSlug } = selectVariant(test);
170
+ assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
171
+ setAssignment(cookie, test.id, assignmentData);
172
+ }
173
+ const variantSlug = assignmentData.test_path;
174
+ const rewriteUrl = variantSlug !== 'control' && variantSlug
175
+ ? variantSlug.startsWith('/')
176
+ ? variantSlug
177
+ : `/${variantSlug}`
178
+ : undefined;
179
+ return {
180
+ matched: true,
181
+ test,
182
+ assignment: assignmentData,
183
+ rewriteUrl,
184
+ cookieValue: serializeCookie(cookie),
185
+ };
186
+ }
187
+ return { matched: false };
188
+ }
189
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/middleware/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A/B Testing Middleware
3
+ *
4
+ * Helpers for implementing server-side A/B testing in Next.js middleware.
5
+ */
6
+ export { createAssignment, isValidAssignment, selectVariant } from './assignment';
7
+ export { clearTestCache, getCachedTests, getTestCacheState, normalizePath } from './cache';
8
+ export { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, readCookieFromDocument, serializeCookie, setAssignment, } from './cookies';
9
+ export { createAbTestMiddleware, processAbTestRequest } from './handler';
10
+ export type { AbTestMiddlewareConfig, AbTestResult, CachedAbTest, TestsCache, } from './types';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACzE,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A/B Testing Middleware
3
+ *
4
+ * Helpers for implementing server-side A/B testing in Next.js middleware.
5
+ */
6
+ export { createAssignment, isValidAssignment, selectVariant } from './assignment';
7
+ export { clearTestCache, getCachedTests, getTestCacheState, normalizePath } from './cache';
8
+ export { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, readCookieFromDocument, serializeCookie, setAssignment, } from './cookies';
9
+ export { createAbTestMiddleware, processAbTestRequest } from './handler';
10
+ //# sourceMappingURL=index.js.map