@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.
- package/CHANGELOG.md +1125 -0
- package/README.md +412 -0
- package/dist/codegen/cli.d.ts +3 -0
- package/dist/codegen/cli.d.ts.map +1 -0
- package/dist/codegen/cli.js +121 -0
- package/dist/codegen/cli.js.map +1 -0
- package/dist/codegen/generator.d.ts +42 -0
- package/dist/codegen/generator.d.ts.map +1 -0
- package/dist/codegen/generator.js +56 -0
- package/dist/codegen/generator.js.map +1 -0
- package/dist/codegen/index.d.ts +9 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +8 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/components/AbTestReporter.d.ts +2 -0
- package/dist/components/AbTestReporter.d.ts.map +1 -0
- package/dist/components/AbTestReporter.js +19 -0
- package/dist/components/AbTestReporter.js.map +1 -0
- package/dist/components/AbTestUtmScript.d.ts +17 -0
- package/dist/components/AbTestUtmScript.d.ts.map +1 -0
- package/dist/components/AbTestUtmScript.js +43 -0
- package/dist/components/AbTestUtmScript.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useAbTestAssignments.d.ts +56 -0
- package/dist/hooks/useAbTestAssignments.d.ts.map +1 -0
- package/dist/hooks/useAbTestAssignments.js +87 -0
- package/dist/hooks/useAbTestAssignments.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/assignment.d.ts +31 -0
- package/dist/middleware/assignment.d.ts.map +1 -0
- package/dist/middleware/assignment.js +125 -0
- package/dist/middleware/assignment.js.map +1 -0
- package/dist/middleware/cache.d.ts +30 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +140 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/cookies.d.ts +44 -0
- package/dist/middleware/cookies.d.ts.map +1 -0
- package/dist/middleware/cookies.js +66 -0
- package/dist/middleware/cookies.js.map +1 -0
- package/dist/middleware/handler.d.ts +53 -0
- package/dist/middleware/handler.d.ts.map +1 -0
- package/dist/middleware/handler.js +201 -0
- package/dist/middleware/handler.js.map +1 -0
- package/dist/middleware/index.d.ts +15 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +13 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/prune.d.ts +13 -0
- package/dist/middleware/prune.d.ts.map +1 -0
- package/dist/middleware/prune.js +29 -0
- package/dist/middleware/prune.js.map +1 -0
- package/dist/middleware/types.d.ts +103 -0
- package/dist/middleware/types.d.ts.map +1 -0
- package/dist/middleware/types.js +2 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/middleware/urlParams.d.ts +7 -0
- package/dist/middleware/urlParams.d.ts.map +1 -0
- package/dist/middleware/urlParams.js +20 -0
- package/dist/middleware/urlParams.js.map +1 -0
- package/dist/middleware/variantId.d.ts +9 -0
- package/dist/middleware/variantId.d.ts.map +1 -0
- package/dist/middleware/variantId.js +12 -0
- package/dist/middleware/variantId.js.map +1 -0
- package/dist/reporting/buildExperimentImpression.d.ts +6 -0
- package/dist/reporting/buildExperimentImpression.d.ts.map +1 -0
- package/dist/reporting/buildExperimentImpression.js +22 -0
- package/dist/reporting/buildExperimentImpression.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +20 -0
- package/dist/utils.js.map +1 -0
- package/dist/webhook/handler.d.ts +109 -0
- package/dist/webhook/handler.d.ts.map +1 -0
- package/dist/webhook/handler.js +128 -0
- package/dist/webhook/handler.js.map +1 -0
- package/dist/webhook/index.d.ts +8 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +7 -0
- package/dist/webhook/index.js.map +1 -0
- package/docs/llms.md +106 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
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
|
+
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":"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"}
|
|
@@ -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"}
|