@elevateab/sdk 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.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @elevateab/sdk
2
+
3
+ Elevate AB Testing SDK for Hydrogen and Remix frameworks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @elevateab/sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Example
14
+
15
+ ```typescript
16
+ import { useExperiment, ExperimentConfig } from '@elevateab/sdk';
17
+
18
+ const config: ExperimentConfig = {
19
+ id: 'price-test-001',
20
+ name: 'Pricing Experiment',
21
+ enabled: true,
22
+ variants: [
23
+ { id: 'control', name: 'Control', weight: 50 },
24
+ { id: 'variant-a', name: 'Variant A', weight: 50 },
25
+ ],
26
+ };
27
+
28
+ function MyComponent() {
29
+ const { variant, isLoading } = useExperiment(config, 'user-123');
30
+
31
+ if (isLoading) return <div>Loading...</div>;
32
+
33
+ return (
34
+ <div>
35
+ {variant?.id === 'control' ? (
36
+ <Price amount={99.99} />
37
+ ) : (
38
+ <Price amount={89.99} />
39
+ )}
40
+ </div>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### Using the Experiment Component
46
+
47
+ ```typescript
48
+ import { Experiment, VariantDisplay } from '@elevateab/sdk';
49
+
50
+ function ProductPage() {
51
+ return (
52
+ <Experiment
53
+ config={config}
54
+ userId="user-123"
55
+ onVariantAssigned={(variant) => console.log('Assigned:', variant)}
56
+ >
57
+ <VariantDisplay variantId="control">
58
+ <h1>Original Headline</h1>
59
+ </VariantDisplay>
60
+ <VariantDisplay variantId="variant-a">
61
+ <h1>New Headline</h1>
62
+ </VariantDisplay>
63
+ </Experiment>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ### Utility Functions
69
+
70
+ ```typescript
71
+ import {
72
+ assignVariant,
73
+ validateConfig,
74
+ generateExperimentId,
75
+ calculateRevenueLift,
76
+ } from '@elevateab/sdk';
77
+
78
+ // Assign a variant based on weighted distribution
79
+ const variant = assignVariant(config.variants, 'user-123');
80
+
81
+ // Validate experiment configuration
82
+ const isValid = validateConfig(config);
83
+
84
+ // Generate unique experiment ID
85
+ const expId = generateExperimentId('My Test');
86
+
87
+ // Calculate revenue lift
88
+ const { lift, confidence } = calculateRevenueLift(
89
+ 10000, // control revenue
90
+ 12000, // variant revenue
91
+ 500, // control sample size
92
+ 500 // variant sample size
93
+ );
94
+ ```
95
+
96
+ ## Features
97
+
98
+ - ✅ Zero-config TypeScript support
99
+ - ✅ React Server Components compatible
100
+ - ✅ ESM and CJS dual format
101
+ - ✅ Tree-shakeable
102
+ - ✅ Minified and optimized
103
+ - ✅ Full type definitions
104
+ - ✅ < 1KB gzipped
105
+
106
+ ## Compatibility
107
+
108
+ - **Shopify Hydrogen**: ✅ Full support
109
+ - **Remix**: ✅ Full support (ESM + CJS)
110
+ - **React**: 18.0.0+
111
+ - **TypeScript**: 5.0.0+
112
+ - **Node.js**: 18.0.0+
113
+
114
+ ## API Reference
115
+
116
+ ### Types
117
+
118
+ - `ExperimentConfig`: Configuration for an experiment
119
+ - `Variant`: Definition of a test variant
120
+ - `TrackingEvent`: Event tracking data structure
121
+ - `ExperimentStatus`: Experiment lifecycle status
122
+
123
+ ### Components
124
+
125
+ - `Experiment`: Main experiment wrapper component
126
+ - `VariantDisplay`: Conditional rendering based on variant
127
+ - `useExperiment`: React hook for experiment logic
128
+
129
+ ### Utilities
130
+
131
+ - `assignVariant()`: Assign variant based on distribution
132
+ - `hashString()`: Hash function for consistent assignment
133
+ - `validateConfig()`: Validate experiment configuration
134
+ - `generateExperimentId()`: Generate unique experiment IDs
135
+ - `calculateRevenueLift()`: Calculate A/B test metrics
136
+
137
+ ## License
138
+
139
+ MIT
140
+
141
+ ## Support
142
+
143
+ For issues and questions, please visit [GitHub Issues](https://github.com/elevateab/sdk/issues).
144
+
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var y=Object.create;var p=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var M=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var N=(t,e)=>{for(var n in e)p(t,n,{get:e[n],enumerable:!0})},l=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of D(e))!L.call(t,r)&&r!==n&&p(t,r,{get:()=>e[r],enumerable:!(a=C(e,r))||a.enumerable});return t};var w=(t,e,n)=>(n=t!=null?y(M(t)):{},l(e||!t||!t.__esModule?p(n,"default",{value:t,enumerable:!0}):n,t)),I=t=>l(p({},"__esModule",{value:!0}),t);var k={};N(k,{DEFAULT_CONFIG:()=>R,Experiment:()=>h,VERSION:()=>P,VariantDisplay:()=>x,assignVariant:()=>c,calculateRevenueLift:()=>g,generateExperimentId:()=>f,hashString:()=>u,useExperiment:()=>E,validateConfig:()=>m});module.exports=I(k);function c(t,e){let n=t.reduce((i,d)=>i+d.weight,0),r=u(e)%n,s=0;for(let i of t)if(s+=i.weight,r<s)return i;return t[0]}function u(t){let e=0;for(let n=0;n<t.length;n++){let a=t.charCodeAt(n);e=(e<<5)-e+a,e=e&e}return Math.abs(e)}function m(t){return!t.id||!t.name||!t.variants||t.variants.length===0?!1:t.variants.reduce((n,a)=>n+a.weight,0)===100}function f(t){let e=Date.now(),n=Math.random().toString(36).substring(2,9);return`exp-${t.toLowerCase().replace(/[^a-z0-9]/g,"-")}-${e}-${n}`}function g(t,e,n,a){let r=t/n,s=e/a,i=(s-r)/r*100,v=Math.sqrt((t+e)/(n+a))*Math.sqrt(1/n+1/a),V=Math.abs(s-r)/v,b=Math.min(99.9,V*34);return{lift:i,confidence:b}}var o=w(require("react"),1);function h({config:t,userId:e,onVariantAssigned:n,children:a}){let[r,s]=o.default.useState(null);return o.default.useEffect(()=>{if(t.enabled&&t.variants.length>0){let i=c(t.variants,e);s(i),n?.(i)}},[t,e,n]),!t.enabled||!r?null:o.default.createElement("div",{"data-experiment-id":t.id,"data-variant-id":r.id},a)}function x({variantId:t,children:e}){return o.default.createElement("div",{"data-variant":t,style:{display:"contents"}},e)}function E(t,e){let[n,a]=o.default.useState(null),[r,s]=o.default.useState(!0);return o.default.useEffect(()=>{if(!t.enabled){s(!1);return}let i=c(t.variants,e);a(i),s(!1)},[t,e]),{variant:n,isLoading:r}}var P="1.0.0",R={enabled:!0,trackingEndpoint:"https://analytics.elevateab.com/track",cacheDuration:3600};0&&(module.exports={DEFAULT_CONFIG,Experiment,VERSION,VariantDisplay,assignVariant,calculateRevenueLift,generateExperimentId,hashString,useExperiment,validateConfig});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/components/Experiment.tsx"],"sourcesContent":["// Main entry point for the Elevate AB Testing NPM package\nexport type {\n ExperimentConfig,\n Variant,\n TrackingEvent,\n ExperimentStatus,\n} from './types';\n\nexport {\n assignVariant,\n hashString,\n validateConfig,\n generateExperimentId,\n calculateRevenueLift,\n} from './utils';\n\nexport {\n Experiment,\n VariantDisplay,\n useExperiment,\n} from './components/Experiment';\n\n// Package version\nexport const VERSION = '1.0.0';\n\n// Default configuration\nexport const DEFAULT_CONFIG = {\n enabled: true,\n trackingEndpoint: 'https://analytics.elevateab.com/track',\n cacheDuration: 3600,\n};\n\n","import type { Variant, ExperimentConfig } from './types';\n\n/**\n * Assigns a variant based on weighted distribution\n */\nexport function assignVariant(variants: Variant[], userId: string): Variant {\n const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);\n const hash = hashString(userId);\n const normalized = hash % totalWeight;\n \n let cumulative = 0;\n for (const variant of variants) {\n cumulative += variant.weight;\n if (normalized < cumulative) {\n return variant;\n }\n }\n \n return variants[0];\n}\n\n/**\n * Simple hash function for user ID\n */\nexport function hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash);\n}\n\n/**\n * Validates experiment configuration\n */\nexport function validateConfig(config: ExperimentConfig): boolean {\n if (!config.id || !config.name) return false;\n if (!config.variants || config.variants.length === 0) return false;\n \n const totalWeight = config.variants.reduce((sum, v) => sum + v.weight, 0);\n return totalWeight === 100;\n}\n\n/**\n * Generates a unique experiment ID\n */\nexport function generateExperimentId(name: string): string {\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 9);\n const safeName = name.toLowerCase().replace(/[^a-z0-9]/g, '-');\n return `exp-${safeName}-${timestamp}-${random}`;\n}\n\n/**\n * Sensitive business logic that should be minified/obfuscated\n */\nexport function calculateRevenueLift(\n controlRevenue: number,\n variantRevenue: number,\n controlSampleSize: number,\n variantSampleSize: number\n): { lift: number; confidence: number } {\n const controlMean = controlRevenue / controlSampleSize;\n const variantMean = variantRevenue / variantSampleSize;\n \n const lift = ((variantMean - controlMean) / controlMean) * 100;\n \n // Simplified confidence calculation\n const pooledStdDev = Math.sqrt(\n (controlRevenue + variantRevenue) / (controlSampleSize + variantSampleSize)\n );\n const standardError = pooledStdDev * Math.sqrt(\n 1 / controlSampleSize + 1 / variantSampleSize\n );\n const zScore = Math.abs(variantMean - controlMean) / standardError;\n const confidence = Math.min(99.9, zScore * 34); // Simplified\n \n return { lift, confidence };\n}\n\n","import React from 'react';\nimport type { ExperimentConfig, Variant } from '../types';\nimport { assignVariant } from '../utils';\n\ninterface ExperimentProps {\n config: ExperimentConfig;\n userId: string;\n onVariantAssigned?: (variant: Variant) => void;\n children?: React.ReactNode;\n}\n\n/**\n * React component for A/B test experiments\n * Tests React/JSX compatibility with bundlers\n */\nexport function Experiment({ config, userId, onVariantAssigned, children }: ExperimentProps) {\n const [assignedVariant, setAssignedVariant] = React.useState<Variant | null>(null);\n \n React.useEffect(() => {\n if (config.enabled && config.variants.length > 0) {\n const variant = assignVariant(config.variants, userId);\n setAssignedVariant(variant);\n onVariantAssigned?.(variant);\n }\n }, [config, userId, onVariantAssigned]);\n \n if (!config.enabled || !assignedVariant) {\n return null;\n }\n \n return (\n <div data-experiment-id={config.id} data-variant-id={assignedVariant.id}>\n {children}\n </div>\n );\n}\n\ninterface VariantDisplayProps {\n variantId: string;\n children: React.ReactNode;\n}\n\n/**\n * Conditionally renders content based on variant\n */\nexport function VariantDisplay({ variantId, children }: VariantDisplayProps) {\n return (\n <div data-variant={variantId} style={{ display: 'contents' }}>\n {children}\n </div>\n );\n}\n\n/**\n * Hook for using experiments in functional components\n */\nexport function useExperiment(config: ExperimentConfig, userId: string) {\n const [variant, setVariant] = React.useState<Variant | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n \n React.useEffect(() => {\n if (!config.enabled) {\n setIsLoading(false);\n return;\n }\n \n const assigned = assignVariant(config.variants, userId);\n setVariant(assigned);\n setIsLoading(false);\n }, [config, userId]);\n \n return { variant, isLoading };\n}\n\n"],"mappings":"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","names":["index_exports","__export","DEFAULT_CONFIG","Experiment","VERSION","VariantDisplay","assignVariant","calculateRevenueLift","generateExperimentId","hashString","useExperiment","validateConfig","__toCommonJS","assignVariant","variants","userId","totalWeight","sum","v","normalized","hashString","cumulative","variant","str","hash","i","char","validateConfig","config","generateExperimentId","name","timestamp","random","calculateRevenueLift","controlRevenue","variantRevenue","controlSampleSize","variantSampleSize","controlMean","variantMean","lift","standardError","zScore","confidence","import_react","Experiment","config","userId","onVariantAssigned","children","assignedVariant","setAssignedVariant","React","variant","assignVariant","VariantDisplay","variantId","useExperiment","setVariant","isLoading","setIsLoading","assigned","VERSION","DEFAULT_CONFIG"]}
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ interface ExperimentConfig {
4
+ id: string;
5
+ name: string;
6
+ variants: Variant[];
7
+ enabled: boolean;
8
+ }
9
+ interface Variant {
10
+ id: string;
11
+ name: string;
12
+ weight: number;
13
+ }
14
+ interface TrackingEvent {
15
+ experimentId: string;
16
+ variantId: string;
17
+ userId: string;
18
+ timestamp: number;
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+ type ExperimentStatus = 'draft' | 'running' | 'paused' | 'completed';
22
+
23
+ /**
24
+ * Assigns a variant based on weighted distribution
25
+ */
26
+ declare function assignVariant(variants: Variant[], userId: string): Variant;
27
+ /**
28
+ * Simple hash function for user ID
29
+ */
30
+ declare function hashString(str: string): number;
31
+ /**
32
+ * Validates experiment configuration
33
+ */
34
+ declare function validateConfig(config: ExperimentConfig): boolean;
35
+ /**
36
+ * Generates a unique experiment ID
37
+ */
38
+ declare function generateExperimentId(name: string): string;
39
+ /**
40
+ * Sensitive business logic that should be minified/obfuscated
41
+ */
42
+ declare function calculateRevenueLift(controlRevenue: number, variantRevenue: number, controlSampleSize: number, variantSampleSize: number): {
43
+ lift: number;
44
+ confidence: number;
45
+ };
46
+
47
+ interface ExperimentProps {
48
+ config: ExperimentConfig;
49
+ userId: string;
50
+ onVariantAssigned?: (variant: Variant) => void;
51
+ children?: React.ReactNode;
52
+ }
53
+ /**
54
+ * React component for A/B test experiments
55
+ * Tests React/JSX compatibility with bundlers
56
+ */
57
+ declare function Experiment({ config, userId, onVariantAssigned, children }: ExperimentProps): React.JSX.Element | null;
58
+ interface VariantDisplayProps {
59
+ variantId: string;
60
+ children: React.ReactNode;
61
+ }
62
+ /**
63
+ * Conditionally renders content based on variant
64
+ */
65
+ declare function VariantDisplay({ variantId, children }: VariantDisplayProps): React.JSX.Element;
66
+ /**
67
+ * Hook for using experiments in functional components
68
+ */
69
+ declare function useExperiment(config: ExperimentConfig, userId: string): {
70
+ variant: Variant | null;
71
+ isLoading: boolean;
72
+ };
73
+
74
+ declare const VERSION = "1.0.0";
75
+ declare const DEFAULT_CONFIG: {
76
+ enabled: boolean;
77
+ trackingEndpoint: string;
78
+ cacheDuration: number;
79
+ };
80
+
81
+ export { DEFAULT_CONFIG, Experiment, type ExperimentConfig, type ExperimentStatus, type TrackingEvent, VERSION, type Variant, VariantDisplay, assignVariant, calculateRevenueLift, generateExperimentId, hashString, useExperiment, validateConfig };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ interface ExperimentConfig {
4
+ id: string;
5
+ name: string;
6
+ variants: Variant[];
7
+ enabled: boolean;
8
+ }
9
+ interface Variant {
10
+ id: string;
11
+ name: string;
12
+ weight: number;
13
+ }
14
+ interface TrackingEvent {
15
+ experimentId: string;
16
+ variantId: string;
17
+ userId: string;
18
+ timestamp: number;
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+ type ExperimentStatus = 'draft' | 'running' | 'paused' | 'completed';
22
+
23
+ /**
24
+ * Assigns a variant based on weighted distribution
25
+ */
26
+ declare function assignVariant(variants: Variant[], userId: string): Variant;
27
+ /**
28
+ * Simple hash function for user ID
29
+ */
30
+ declare function hashString(str: string): number;
31
+ /**
32
+ * Validates experiment configuration
33
+ */
34
+ declare function validateConfig(config: ExperimentConfig): boolean;
35
+ /**
36
+ * Generates a unique experiment ID
37
+ */
38
+ declare function generateExperimentId(name: string): string;
39
+ /**
40
+ * Sensitive business logic that should be minified/obfuscated
41
+ */
42
+ declare function calculateRevenueLift(controlRevenue: number, variantRevenue: number, controlSampleSize: number, variantSampleSize: number): {
43
+ lift: number;
44
+ confidence: number;
45
+ };
46
+
47
+ interface ExperimentProps {
48
+ config: ExperimentConfig;
49
+ userId: string;
50
+ onVariantAssigned?: (variant: Variant) => void;
51
+ children?: React.ReactNode;
52
+ }
53
+ /**
54
+ * React component for A/B test experiments
55
+ * Tests React/JSX compatibility with bundlers
56
+ */
57
+ declare function Experiment({ config, userId, onVariantAssigned, children }: ExperimentProps): React.JSX.Element | null;
58
+ interface VariantDisplayProps {
59
+ variantId: string;
60
+ children: React.ReactNode;
61
+ }
62
+ /**
63
+ * Conditionally renders content based on variant
64
+ */
65
+ declare function VariantDisplay({ variantId, children }: VariantDisplayProps): React.JSX.Element;
66
+ /**
67
+ * Hook for using experiments in functional components
68
+ */
69
+ declare function useExperiment(config: ExperimentConfig, userId: string): {
70
+ variant: Variant | null;
71
+ isLoading: boolean;
72
+ };
73
+
74
+ declare const VERSION = "1.0.0";
75
+ declare const DEFAULT_CONFIG: {
76
+ enabled: boolean;
77
+ trackingEndpoint: string;
78
+ cacheDuration: number;
79
+ };
80
+
81
+ export { DEFAULT_CONFIG, Experiment, type ExperimentConfig, type ExperimentStatus, type TrackingEvent, VERSION, type Variant, VariantDisplay, assignVariant, calculateRevenueLift, generateExperimentId, hashString, useExperiment, validateConfig };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ function c(t,e){let n=t.reduce((r,p)=>r+p.weight,0),i=u(e)%n,s=0;for(let r of t)if(s+=r.weight,i<s)return r;return t[0]}function u(t){let e=0;for(let n=0;n<t.length;n++){let a=t.charCodeAt(n);e=(e<<5)-e+a,e=e&e}return Math.abs(e)}function f(t){return!t.id||!t.name||!t.variants||t.variants.length===0?!1:t.variants.reduce((n,a)=>n+a.weight,0)===100}function g(t){let e=Date.now(),n=Math.random().toString(36).substring(2,9);return`exp-${t.toLowerCase().replace(/[^a-z0-9]/g,"-")}-${e}-${n}`}function h(t,e,n,a){let i=t/n,s=e/a,r=(s-i)/i*100,d=Math.sqrt((t+e)/(n+a))*Math.sqrt(1/n+1/a),l=Math.abs(s-i)/d,m=Math.min(99.9,l*34);return{lift:r,confidence:m}}import o from"react";function x({config:t,userId:e,onVariantAssigned:n,children:a}){let[i,s]=o.useState(null);return o.useEffect(()=>{if(t.enabled&&t.variants.length>0){let r=c(t.variants,e);s(r),n?.(r)}},[t,e,n]),!t.enabled||!i?null:o.createElement("div",{"data-experiment-id":t.id,"data-variant-id":i.id},a)}function E({variantId:t,children:e}){return o.createElement("div",{"data-variant":t,style:{display:"contents"}},e)}function v(t,e){let[n,a]=o.useState(null),[i,s]=o.useState(!0);return o.useEffect(()=>{if(!t.enabled){s(!1);return}let r=c(t.variants,e);a(r),s(!1)},[t,e]),{variant:n,isLoading:i}}var D="1.0.0",M={enabled:!0,trackingEndpoint:"https://analytics.elevateab.com/track",cacheDuration:3600};export{M as DEFAULT_CONFIG,x as Experiment,D as VERSION,E as VariantDisplay,c as assignVariant,h as calculateRevenueLift,g as generateExperimentId,u as hashString,v as useExperiment,f as validateConfig};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/components/Experiment.tsx","../src/index.ts"],"sourcesContent":["import type { Variant, ExperimentConfig } from './types';\n\n/**\n * Assigns a variant based on weighted distribution\n */\nexport function assignVariant(variants: Variant[], userId: string): Variant {\n const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);\n const hash = hashString(userId);\n const normalized = hash % totalWeight;\n \n let cumulative = 0;\n for (const variant of variants) {\n cumulative += variant.weight;\n if (normalized < cumulative) {\n return variant;\n }\n }\n \n return variants[0];\n}\n\n/**\n * Simple hash function for user ID\n */\nexport function hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash);\n}\n\n/**\n * Validates experiment configuration\n */\nexport function validateConfig(config: ExperimentConfig): boolean {\n if (!config.id || !config.name) return false;\n if (!config.variants || config.variants.length === 0) return false;\n \n const totalWeight = config.variants.reduce((sum, v) => sum + v.weight, 0);\n return totalWeight === 100;\n}\n\n/**\n * Generates a unique experiment ID\n */\nexport function generateExperimentId(name: string): string {\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 9);\n const safeName = name.toLowerCase().replace(/[^a-z0-9]/g, '-');\n return `exp-${safeName}-${timestamp}-${random}`;\n}\n\n/**\n * Sensitive business logic that should be minified/obfuscated\n */\nexport function calculateRevenueLift(\n controlRevenue: number,\n variantRevenue: number,\n controlSampleSize: number,\n variantSampleSize: number\n): { lift: number; confidence: number } {\n const controlMean = controlRevenue / controlSampleSize;\n const variantMean = variantRevenue / variantSampleSize;\n \n const lift = ((variantMean - controlMean) / controlMean) * 100;\n \n // Simplified confidence calculation\n const pooledStdDev = Math.sqrt(\n (controlRevenue + variantRevenue) / (controlSampleSize + variantSampleSize)\n );\n const standardError = pooledStdDev * Math.sqrt(\n 1 / controlSampleSize + 1 / variantSampleSize\n );\n const zScore = Math.abs(variantMean - controlMean) / standardError;\n const confidence = Math.min(99.9, zScore * 34); // Simplified\n \n return { lift, confidence };\n}\n\n","import React from 'react';\nimport type { ExperimentConfig, Variant } from '../types';\nimport { assignVariant } from '../utils';\n\ninterface ExperimentProps {\n config: ExperimentConfig;\n userId: string;\n onVariantAssigned?: (variant: Variant) => void;\n children?: React.ReactNode;\n}\n\n/**\n * React component for A/B test experiments\n * Tests React/JSX compatibility with bundlers\n */\nexport function Experiment({ config, userId, onVariantAssigned, children }: ExperimentProps) {\n const [assignedVariant, setAssignedVariant] = React.useState<Variant | null>(null);\n \n React.useEffect(() => {\n if (config.enabled && config.variants.length > 0) {\n const variant = assignVariant(config.variants, userId);\n setAssignedVariant(variant);\n onVariantAssigned?.(variant);\n }\n }, [config, userId, onVariantAssigned]);\n \n if (!config.enabled || !assignedVariant) {\n return null;\n }\n \n return (\n <div data-experiment-id={config.id} data-variant-id={assignedVariant.id}>\n {children}\n </div>\n );\n}\n\ninterface VariantDisplayProps {\n variantId: string;\n children: React.ReactNode;\n}\n\n/**\n * Conditionally renders content based on variant\n */\nexport function VariantDisplay({ variantId, children }: VariantDisplayProps) {\n return (\n <div data-variant={variantId} style={{ display: 'contents' }}>\n {children}\n </div>\n );\n}\n\n/**\n * Hook for using experiments in functional components\n */\nexport function useExperiment(config: ExperimentConfig, userId: string) {\n const [variant, setVariant] = React.useState<Variant | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n \n React.useEffect(() => {\n if (!config.enabled) {\n setIsLoading(false);\n return;\n }\n \n const assigned = assignVariant(config.variants, userId);\n setVariant(assigned);\n setIsLoading(false);\n }, [config, userId]);\n \n return { variant, isLoading };\n}\n\n","// Main entry point for the Elevate AB Testing NPM package\nexport type {\n ExperimentConfig,\n Variant,\n TrackingEvent,\n ExperimentStatus,\n} from './types';\n\nexport {\n assignVariant,\n hashString,\n validateConfig,\n generateExperimentId,\n calculateRevenueLift,\n} from './utils';\n\nexport {\n Experiment,\n VariantDisplay,\n useExperiment,\n} from './components/Experiment';\n\n// Package version\nexport const VERSION = '1.0.0';\n\n// Default configuration\nexport const DEFAULT_CONFIG = {\n enabled: true,\n trackingEndpoint: 'https://analytics.elevateab.com/track',\n cacheDuration: 3600,\n};\n\n"],"mappings":"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","names":["assignVariant","variants","userId","totalWeight","sum","v","normalized","hashString","cumulative","variant","str","hash","i","char","validateConfig","config","generateExperimentId","name","timestamp","random","calculateRevenueLift","controlRevenue","variantRevenue","controlSampleSize","variantSampleSize","controlMean","variantMean","lift","standardError","zScore","confidence","React","Experiment","config","userId","onVariantAssigned","children","assignedVariant","setAssignedVariant","React","variant","assignVariant","VariantDisplay","variantId","useExperiment","setVariant","isLoading","setIsLoading","assigned","VERSION","DEFAULT_CONFIG"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@elevateab/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Elevate AB Testing SDK for Hydrogen and Remix frameworks",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format esm,cjs --dts --minify --sourcemap",
21
+ "build:watch": "tsup src/index.ts --format esm,cjs --dts --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "ab-testing",
26
+ "experiments",
27
+ "hydrogen",
28
+ "remix",
29
+ "shopify",
30
+ "react"
31
+ ],
32
+ "author": "Elevate AB",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/abshop/headless-npm.git"
37
+ },
38
+ "homepage": "https://github.com/abshop/headless-npm#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/abshop/headless-npm/issues"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/react": "^18.3.27",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }