@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,128 @@
1
+ /**
2
+ * Normalize a single bucket config, accepting legacy field names.
3
+ */
4
+ export function normalizeVariantConfig(raw) {
5
+ const reporting_event = raw.reporting_event ?? raw.hubspot_event_name;
6
+ const url_params = raw.url_params ??
7
+ (raw.utm_content !== undefined ? { utm_content: raw.utm_content } : undefined);
8
+ const config = {
9
+ weight: raw.weight,
10
+ };
11
+ if (raw.variant_label) {
12
+ config.variant_label = raw.variant_label;
13
+ }
14
+ if (reporting_event) {
15
+ config.reporting_event = reporting_event;
16
+ }
17
+ if (url_params && Object.keys(url_params).length > 0) {
18
+ config.url_params = url_params;
19
+ }
20
+ if (raw.reporting_params && Object.keys(raw.reporting_params).length > 0) {
21
+ config.reporting_params = raw.reporting_params;
22
+ }
23
+ return config;
24
+ }
25
+ /**
26
+ * Transform raw Contentful PageTest data into AbTest format.
27
+ */
28
+ export function transformPageTest(raw, onSkipped) {
29
+ const controlSlug = raw.control?.slug;
30
+ if (!controlSlug) {
31
+ onSkipped?.(raw.sys.id, 'No control slug');
32
+ return null;
33
+ }
34
+ const variants = (raw.variantsCollection?.items ?? [])
35
+ .filter((item) => item !== null && item.slug !== null)
36
+ .map((item) => ({
37
+ id: item.sys.id,
38
+ slug: item.slug,
39
+ }));
40
+ const configuration = (raw.configuration ?? []).map(normalizeVariantConfig);
41
+ return {
42
+ id: raw.sys.id,
43
+ cmsLabel: raw.cmsLabel || '',
44
+ controlSlug,
45
+ searchParameters: raw.searchParameters ?? undefined,
46
+ trackingLabel: raw.trackingLabel ?? undefined,
47
+ enabled: raw.enabled ?? false,
48
+ injectUrlParams: raw.injectUrlParams ?? false,
49
+ configuration,
50
+ variants,
51
+ };
52
+ }
53
+ /**
54
+ * Process all PageTest entries and store them in the blob store.
55
+ *
56
+ * @param config - Webhook handler configuration
57
+ * @returns Result with success status and count
58
+ */
59
+ export async function processAbTestWebhook(config) {
60
+ const { fetchPageTests, getStore, onSkippedTest, revalidate } = config;
61
+ try {
62
+ // Revalidate cache tags if function provided
63
+ if (revalidate) {
64
+ await Promise.resolve(revalidate());
65
+ }
66
+ // Fetch all active tests from Contentful
67
+ const rawTests = await fetchPageTests();
68
+ // Transform and index by control slug
69
+ const abTests = {};
70
+ for (const raw of rawTests) {
71
+ const test = transformPageTest(raw, onSkippedTest);
72
+ if (test) {
73
+ abTests[test.controlSlug] = test;
74
+ }
75
+ }
76
+ // Store in blob store
77
+ const store = await Promise.resolve(getStore());
78
+ await store.bulkWrite(Object.entries(abTests));
79
+ return {
80
+ success: true,
81
+ count: Object.keys(abTests).length,
82
+ };
83
+ }
84
+ catch (error) {
85
+ // biome-ignore lint/suspicious/noConsole: Error logging in webhook
86
+ console.error('Error processing ab-test webhook:', error);
87
+ return {
88
+ success: false,
89
+ count: 0,
90
+ error: error instanceof Error ? error.message : 'Unknown error',
91
+ };
92
+ }
93
+ }
94
+ /**
95
+ * Create a Next.js API route handler for the A/B test webhook.
96
+ */
97
+ export function createWebhookHandler(config) {
98
+ return async function handler(request) {
99
+ // Validate webhook secret if configured
100
+ if (config.webhookSecret) {
101
+ const secret = request.headers.get('x-contentful-webhook-secret');
102
+ if (secret !== config.webhookSecret) {
103
+ return new Response(JSON.stringify({ message: 'Invalid secret' }), {
104
+ status: 401,
105
+ headers: { 'Content-Type': 'application/json' },
106
+ });
107
+ }
108
+ }
109
+ const result = await processAbTestWebhook(config);
110
+ if (result.success) {
111
+ return new Response(JSON.stringify({
112
+ message: 'Success',
113
+ count: result.count,
114
+ }), {
115
+ status: 200,
116
+ headers: { 'Content-Type': 'application/json' },
117
+ });
118
+ }
119
+ return new Response(JSON.stringify({
120
+ message: 'Error processing webhook',
121
+ error: result.error,
122
+ }), {
123
+ status: 500,
124
+ headers: { 'Content-Type': 'application/json' },
125
+ });
126
+ };
127
+ }
128
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/webhook/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A/B Testing Webhook Handlers
3
+ *
4
+ * Utilities for processing Contentful webhooks to sync PageTest configurations.
5
+ */
6
+ export type { RawPageTest, WebhookHandlerConfig, WebhookResult, } from './handler';
7
+ export { createWebhookHandler, normalizeVariantConfig, processAbTestWebhook, transformPageTest, } from './handler';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A/B Testing Webhook Handlers
3
+ *
4
+ * Utilities for processing Contentful webhooks to sync PageTest configurations.
5
+ */
6
+ export { createWebhookHandler, normalizeVariantConfig, processAbTestWebhook, transformPageTest, } from './handler';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
package/docs/llms.md ADDED
@@ -0,0 +1,106 @@
1
+ # @se-studio/ab-testing — LLM Reference
2
+
3
+ Server-side A/B testing framework for Next.js App Router with Contentful CMS. Variants are assigned via cookies; decisions happen in Next.js `proxy` (Next 16) or `middleware` (Next 15). Call `createAbTestMiddleware` / `createStaticAbTestMiddleware` from either file — the factory API is unchanged.
4
+
5
+ ## Core Concepts
6
+
7
+ - **Test**: a named experiment with 2+ variants, each mapped to a URL path
8
+ - **Assignment**: a cookie that records which variant a user is seeing
9
+ - **Middleware**: intercepts requests and rewrites URLs to the assigned variant path
10
+
11
+ ## Key Types
12
+
13
+ ```ts
14
+ import type {
15
+ AbTest, // { id, name, variants: AbTestVariant[] }
16
+ AbTestVariant, // { id, name, path: string, weight: number }
17
+ AbTestAssignment, // { testId, variantId }
18
+ AbTestCookie, // serialised cookie value
19
+ AbTestVariantConfig, // { variantId, contentPath }
20
+ IBlobStore, // storage interface for test configs (Vercel Blob)
21
+ } from '@se-studio/ab-testing';
22
+ ```
23
+
24
+ ## Middleware Setup
25
+
26
+ ```ts
27
+ // src/proxy.ts (Next 16) or src/middleware.ts (Next 15)
28
+ import { createAbTestMiddleware } from '@se-studio/ab-testing';
29
+
30
+ const abTestMiddleware = createAbTestMiddleware({
31
+ blobStore, // IBlobStore — fetches test configs from Vercel Blob
32
+ cookieName: 'ab_assignment', // optional, default: DEFAULT_COOKIE_NAME
33
+ cookieMaxAge: 60 * 60 * 24 * 30, // optional, default: DEFAULT_COOKIE_MAX_AGE
34
+ });
35
+
36
+ export async function proxy(request: NextRequest) {
37
+ return abTestMiddleware(request);
38
+ }
39
+ ```
40
+
41
+ For static (no blob store) tests:
42
+
43
+ ```ts
44
+ import { createStaticAbTestMiddleware } from '@se-studio/ab-testing';
45
+
46
+ const abTestMiddleware = createStaticAbTestMiddleware({
47
+ tests: [
48
+ {
49
+ id: 'hero-test',
50
+ variants: [
51
+ { id: 'control', path: '/home', weight: 50 },
52
+ { id: 'variant-a', path: '/home-v2', weight: 50 },
53
+ ],
54
+ },
55
+ ],
56
+ });
57
+ ```
58
+
59
+ ## Reading the Assignment (Client / Server)
60
+
61
+ ```ts
62
+ import { useAbTestAssignments } from '@se-studio/ab-testing';
63
+
64
+ // In a React component:
65
+ const { assignments, isLoading } = useAbTestAssignments({ testIds: ['hero-test'] });
66
+ const variant = assignments['hero-test']; // AbTestAssignment | undefined
67
+ ```
68
+
69
+ ## Webhook Handler (Contentful → update test cache)
70
+
71
+ ```ts
72
+ import { createWebhookHandler } from '@se-studio/ab-testing';
73
+
74
+ const handler = createWebhookHandler({
75
+ blobStore,
76
+ secret: process.env.WEBHOOK_SECRET!,
77
+ });
78
+
79
+ // In a Next.js route handler:
80
+ export const POST = handler.handle;
81
+ ```
82
+
83
+ ## Cookie Utilities
84
+
85
+ ```ts
86
+ import {
87
+ parseCookie,
88
+ serializeCookie,
89
+ readCookieFromDocument, // client-side
90
+ DEFAULT_COOKIE_NAME,
91
+ DEFAULT_COOKIE_MAX_AGE,
92
+ } from '@se-studio/ab-testing';
93
+ ```
94
+
95
+ ## Low-Level Utilities
96
+
97
+ ```ts
98
+ import {
99
+ selectVariant, // weighted random variant selection
100
+ getAssignment, // read assignment from cookies
101
+ setAssignment, // write assignment to response cookies
102
+ isValidAssignment, // validate an assignment object
103
+ normalizePath, // normalise URL paths for comparison
104
+ findCanonicalPath, // resolve variant path back to canonical
105
+ } from '@se-studio/ab-testing';
106
+ ```
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@se-studio/ab-testing",
3
+ "version": "0.0.0-next16-20260822103555",
4
+ "description": "Server-side A/B testing framework for Next.js applications with Contentful CMS",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Something-Else-Studio/se-core-product",
8
+ "directory": "packages/ab-testing"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./middleware": {
20
+ "types": "./dist/middleware/index.d.ts",
21
+ "import": "./dist/middleware/index.js"
22
+ },
23
+ "./webhook": {
24
+ "types": "./dist/webhook/index.d.ts",
25
+ "import": "./dist/webhook/index.js"
26
+ },
27
+ "./hooks": {
28
+ "types": "./dist/hooks/index.d.ts",
29
+ "import": "./dist/hooks/index.js"
30
+ },
31
+ "./codegen": {
32
+ "types": "./dist/codegen/index.d.ts",
33
+ "import": "./dist/codegen/index.js"
34
+ },
35
+ "./components": {
36
+ "types": "./dist/components/index.d.ts",
37
+ "import": "./dist/components/index.js"
38
+ },
39
+ "./reporting": {
40
+ "types": "./dist/reporting/buildExperimentImpression.d.ts",
41
+ "import": "./dist/reporting/buildExperimentImpression.js"
42
+ }
43
+ },
44
+ "bin": {
45
+ "generate-ab-tests": "./dist/codegen/cli.js"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "*.md",
50
+ "docs"
51
+ ],
52
+ "keywords": [
53
+ "ab-testing",
54
+ "a/b-testing",
55
+ "experiment",
56
+ "nextjs",
57
+ "next.js",
58
+ "middleware",
59
+ "contentful",
60
+ "cms",
61
+ "typescript",
62
+ "react",
63
+ "analytics"
64
+ ],
65
+ "homepage": "https://github.com/Something-Else-Studio/se-core-product#readme",
66
+ "bugs": {
67
+ "url": "https://github.com/Something-Else-Studio/se-core-product/issues"
68
+ },
69
+ "peerDependencies": {
70
+ "next": ">=15.5.0 <17",
71
+ "react": "^19.0.0",
72
+ "@se-studio/core-ui": "0.0.0-next16-20260822103555"
73
+ },
74
+ "devDependencies": {
75
+ "@biomejs/biome": "^2.5.5",
76
+ "@types/node": "^24.13.3",
77
+ "@types/react": "^19.2.17",
78
+ "next": "^16.3.1",
79
+ "typescript": "^6.0.3",
80
+ "vitest": "^4.1.10",
81
+ "@se-studio/core-ui": "0.0.0-next16-20260822103555"
82
+ },
83
+ "scripts": {
84
+ "build": "tsc --project tsconfig.build.json",
85
+ "dev": "tsc --project tsconfig.build.json --watch",
86
+ "type-check": "tsc --noEmit",
87
+ "test": "vitest run",
88
+ "lint": "biome lint .",
89
+ "clean": "rm -rf dist .turbo *.tsbuildinfo"
90
+ }
91
+ }