@variantlab/core 0.1.0 → 0.1.2
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 +180 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,7 +1,184 @@
|
|
|
1
1
|
# @variantlab/core
|
|
2
2
|
|
|
3
|
-
The framework-agnostic
|
|
3
|
+
> The framework-agnostic A/B testing and feature-flag engine. Zero runtime dependencies, runs anywhere.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @variantlab/core@alpha
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
### 1. Define experiments
|
|
18
|
+
|
|
19
|
+
Create an `experiments.json` file:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"version": 1,
|
|
24
|
+
"experiments": [
|
|
25
|
+
{
|
|
26
|
+
"id": "cta-copy",
|
|
27
|
+
"type": "value",
|
|
28
|
+
"default": "buy-now",
|
|
29
|
+
"variants": [
|
|
30
|
+
{ "id": "buy-now", "value": "Buy now" },
|
|
31
|
+
{ "id": "get-started", "value": "Get started" }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "hero-layout",
|
|
36
|
+
"type": "render",
|
|
37
|
+
"default": "centered",
|
|
38
|
+
"variants": [
|
|
39
|
+
{ "id": "centered" },
|
|
40
|
+
{ "id": "split" }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Create the engine
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createEngine } from "@variantlab/core";
|
|
51
|
+
import experiments from "./experiments.json";
|
|
52
|
+
|
|
53
|
+
const engine = createEngine(experiments);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Get variants
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// Get assigned variant ID
|
|
60
|
+
const variant = engine.getVariant("hero-layout"); // "centered" | "split"
|
|
61
|
+
|
|
62
|
+
// Get a value experiment's value
|
|
63
|
+
const cta = engine.getVariantValue("cta-copy"); // "Buy now" | "Get started"
|
|
64
|
+
|
|
65
|
+
// Override for testing
|
|
66
|
+
engine.setVariant("hero-layout", "split");
|
|
67
|
+
|
|
68
|
+
// Clear override
|
|
69
|
+
engine.clearVariant("hero-layout");
|
|
70
|
+
|
|
71
|
+
// Reset all overrides
|
|
72
|
+
engine.resetAll();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Subscribe to changes
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
engine.subscribe((event) => {
|
|
79
|
+
console.log(event.experimentId, event.variantId);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. Targeting context
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const engine = createEngine(experiments, {
|
|
87
|
+
context: {
|
|
88
|
+
userId: "user-123",
|
|
89
|
+
platform: "web",
|
|
90
|
+
locale: "en",
|
|
91
|
+
screenSize: "large",
|
|
92
|
+
appVersion: "2.1.0",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Update context at runtime
|
|
97
|
+
engine.updateContext({ locale: "bn" });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Config validation
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { validateConfig } from "@variantlab/core";
|
|
104
|
+
|
|
105
|
+
const result = validateConfig(experiments);
|
|
106
|
+
if (!result.ok) {
|
|
107
|
+
console.error(result.issues);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Explain targeting (debug)
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { explain } from "@variantlab/core";
|
|
115
|
+
|
|
116
|
+
const trace = explain(experiments, "hero-layout", {
|
|
117
|
+
platform: "ios",
|
|
118
|
+
screenSize: "small",
|
|
119
|
+
});
|
|
120
|
+
// Returns step-by-step targeting trace with pass/fail per field
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Assignment strategies
|
|
124
|
+
|
|
125
|
+
The engine supports multiple assignment strategies per experiment:
|
|
126
|
+
|
|
127
|
+
| Strategy | Description |
|
|
128
|
+
|----------|-------------|
|
|
129
|
+
| `default` | Always assigns the default variant |
|
|
130
|
+
| `random` | Random assignment on each evaluation |
|
|
131
|
+
| `sticky-hash` | Deterministic hash-based assignment (requires `userId`) |
|
|
132
|
+
| `weighted` | Weighted random distribution |
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"id": "pricing",
|
|
137
|
+
"type": "value",
|
|
138
|
+
"default": "low",
|
|
139
|
+
"assignment": { "strategy": "sticky-hash" },
|
|
140
|
+
"variants": [
|
|
141
|
+
{ "id": "low", "value": 9.99, "weight": 50 },
|
|
142
|
+
{ "id": "high", "value": 14.99, "weight": 50 }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Targeting operators
|
|
148
|
+
|
|
149
|
+
Built-in targeting predicates:
|
|
150
|
+
|
|
151
|
+
- `platform` — `ios`, `android`, `web`
|
|
152
|
+
- `appVersion` — semver ranges (`>=1.2.0`, `^2.0.0`)
|
|
153
|
+
- `locale` — locale codes (`en`, `bn`, `fr`)
|
|
154
|
+
- `screenSize` — `small`, `medium`, `large`
|
|
155
|
+
- `routes` — glob patterns (`/settings/*`, `/dashboard`)
|
|
156
|
+
- `userId` — exact match or list
|
|
157
|
+
- `attributes` — custom key-value matching
|
|
158
|
+
- `predicate` — compound `and`/`or`/`not` logic
|
|
159
|
+
|
|
160
|
+
## Key features
|
|
161
|
+
|
|
162
|
+
- Zero runtime dependencies
|
|
163
|
+
- < 3 KB gzipped
|
|
164
|
+
- O(1) hot-path `getVariant()` calls
|
|
165
|
+
- Kill switch and time-gated experiments
|
|
166
|
+
- Mutex groups (mutually exclusive experiments)
|
|
167
|
+
- Crash counter for auto-rollback
|
|
168
|
+
- History ring buffer
|
|
169
|
+
- `Object.freeze` on loaded config
|
|
170
|
+
- Prototype pollution guards
|
|
171
|
+
- CSP-strict compatible (no `eval`, no `Function()`)
|
|
172
|
+
- Works in Node, Deno, Bun, Cloudflare Workers, browsers, and React Native
|
|
173
|
+
|
|
174
|
+
## Framework adapters
|
|
175
|
+
|
|
176
|
+
Use `@variantlab/core` directly for vanilla JS/TS, or pair it with a framework adapter:
|
|
177
|
+
|
|
178
|
+
- [`@variantlab/react`](https://www.npmjs.com/package/@variantlab/react) — React 18/19 hooks and components
|
|
179
|
+
- [`@variantlab/react-native`](https://www.npmjs.com/package/@variantlab/react-native) — React Native + Expo with debug overlay
|
|
180
|
+
- [`@variantlab/next`](https://www.npmjs.com/package/@variantlab/next) — Next.js 14/15 SSR + Edge
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
[MIT](./LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variantlab/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The framework-agnostic variantlab engine. Zero dependencies, runs anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
],
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
41
|
-
"url": "git+https://github.com/
|
|
41
|
+
"url": "git+https://github.com/Minhaj-Rabby/variantlab.git",
|
|
42
42
|
"directory": "packages/core"
|
|
43
43
|
},
|
|
44
44
|
"bugs": {
|
|
45
|
-
"url": "https://github.com/
|
|
45
|
+
"url": "https://github.com/Minhaj-Rabby/variantlab/issues"
|
|
46
46
|
},
|
|
47
|
-
"homepage": "https://github.com/
|
|
47
|
+
"homepage": "https://github.com/Minhaj-Rabby/variantlab#readme",
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18.17"
|
|
50
50
|
},
|