ai-experiments 0.1.0 → 2.0.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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +15 -0
- package/README.md +306 -91
- package/dist/cartesian.d.ts +140 -0
- package/dist/cartesian.d.ts.map +1 -0
- package/dist/cartesian.js +216 -0
- package/dist/cartesian.js.map +1 -0
- package/dist/decide.d.ts +152 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +329 -0
- package/dist/decide.js.map +1 -0
- package/dist/experiment.d.ts +53 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +292 -0
- package/dist/experiment.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/tracking.d.ts +159 -0
- package/dist/tracking.d.ts.map +1 -0
- package/dist/tracking.js +310 -0
- package/dist/tracking.js.map +1 -0
- package/dist/types.d.ts +198 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/examples.ts +261 -0
- package/package.json +21 -39
- package/src/cartesian.ts +259 -0
- package/src/decide.ts +398 -0
- package/src/experiment.ts +358 -0
- package/src/index.ts +44 -0
- package/src/tracking.ts +339 -0
- package/src/types.ts +215 -0
- package/tsconfig.json +9 -0
package/src/tracking.ts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event tracking for experiments
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TrackingEvent, TrackingBackend, TrackingOptions } from './types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default tracking configuration
|
|
9
|
+
*/
|
|
10
|
+
let trackingConfig: Required<TrackingOptions> = {
|
|
11
|
+
backend: createConsoleBackend(),
|
|
12
|
+
enabled: true,
|
|
13
|
+
metadata: {},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configure tracking
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { configureTracking } from 'ai-experiments'
|
|
22
|
+
*
|
|
23
|
+
* // Use console backend (default)
|
|
24
|
+
* configureTracking({
|
|
25
|
+
* enabled: true,
|
|
26
|
+
* metadata: { projectId: 'my-project' },
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
* // Use custom backend
|
|
30
|
+
* configureTracking({
|
|
31
|
+
* backend: {
|
|
32
|
+
* track: async (event) => {
|
|
33
|
+
* await fetch('/api/analytics', {
|
|
34
|
+
* method: 'POST',
|
|
35
|
+
* body: JSON.stringify(event),
|
|
36
|
+
* })
|
|
37
|
+
* },
|
|
38
|
+
* },
|
|
39
|
+
* })
|
|
40
|
+
*
|
|
41
|
+
* // Disable tracking
|
|
42
|
+
* configureTracking({ enabled: false })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function configureTracking(options: TrackingOptions): void {
|
|
46
|
+
trackingConfig = {
|
|
47
|
+
backend: options.backend ?? trackingConfig.backend,
|
|
48
|
+
enabled: options.enabled ?? trackingConfig.enabled,
|
|
49
|
+
metadata: options.metadata ?? trackingConfig.metadata,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Track an event
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { track } from 'ai-experiments'
|
|
59
|
+
*
|
|
60
|
+
* track({
|
|
61
|
+
* type: 'experiment.start',
|
|
62
|
+
* timestamp: new Date(),
|
|
63
|
+
* data: {
|
|
64
|
+
* experimentId: 'my-experiment',
|
|
65
|
+
* variantCount: 3,
|
|
66
|
+
* },
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function track(event: TrackingEvent): void {
|
|
71
|
+
if (!trackingConfig.enabled) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Merge global metadata
|
|
76
|
+
const enrichedEvent: TrackingEvent = {
|
|
77
|
+
...event,
|
|
78
|
+
data: {
|
|
79
|
+
...event.data,
|
|
80
|
+
...trackingConfig.metadata,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Track via backend (handle both sync and async)
|
|
85
|
+
const result = trackingConfig.backend.track(enrichedEvent)
|
|
86
|
+
if (result instanceof Promise) {
|
|
87
|
+
// Don't await - fire and forget
|
|
88
|
+
result.catch((error) => {
|
|
89
|
+
console.error('Error tracking event:', error)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Flush pending events
|
|
96
|
+
*
|
|
97
|
+
* Call this before the process exits to ensure all events are sent.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { flush } from 'ai-experiments'
|
|
102
|
+
*
|
|
103
|
+
* process.on('SIGINT', async () => {
|
|
104
|
+
* await flush()
|
|
105
|
+
* process.exit(0)
|
|
106
|
+
* })
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export async function flush(): Promise<void> {
|
|
110
|
+
if (trackingConfig.backend.flush) {
|
|
111
|
+
await trackingConfig.backend.flush()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a console-based tracking backend
|
|
117
|
+
*
|
|
118
|
+
* Logs events to console.log in a human-readable format.
|
|
119
|
+
*/
|
|
120
|
+
export function createConsoleBackend(options?: {
|
|
121
|
+
/** Whether to include full event data (default: false) */
|
|
122
|
+
verbose?: boolean
|
|
123
|
+
}): TrackingBackend {
|
|
124
|
+
const { verbose = false } = options ?? {}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
track: (event: TrackingEvent) => {
|
|
128
|
+
const timestamp = event.timestamp.toISOString()
|
|
129
|
+
|
|
130
|
+
if (verbose) {
|
|
131
|
+
console.log(`[${timestamp}] ${event.type}`, event.data)
|
|
132
|
+
} else {
|
|
133
|
+
// Condensed format
|
|
134
|
+
const key = extractKey(event)
|
|
135
|
+
console.log(`[${timestamp}] ${event.type} ${key}`)
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create an in-memory tracking backend that stores events
|
|
143
|
+
*
|
|
144
|
+
* Useful for testing or collecting events for batch processing.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { createMemoryBackend } from 'ai-experiments'
|
|
149
|
+
*
|
|
150
|
+
* const backend = createMemoryBackend()
|
|
151
|
+
* configureTracking({ backend })
|
|
152
|
+
*
|
|
153
|
+
* // Run experiments...
|
|
154
|
+
*
|
|
155
|
+
* // Get all events
|
|
156
|
+
* const events = backend.getEvents()
|
|
157
|
+
* console.log(`Tracked ${events.length} events`)
|
|
158
|
+
*
|
|
159
|
+
* // Clear events
|
|
160
|
+
* backend.clear()
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function createMemoryBackend(): TrackingBackend & {
|
|
164
|
+
getEvents: () => TrackingEvent[]
|
|
165
|
+
clear: () => void
|
|
166
|
+
} {
|
|
167
|
+
const events: TrackingEvent[] = []
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
track: (event: TrackingEvent) => {
|
|
171
|
+
events.push(event)
|
|
172
|
+
},
|
|
173
|
+
getEvents: () => [...events],
|
|
174
|
+
clear: () => {
|
|
175
|
+
events.length = 0
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a batching tracking backend
|
|
182
|
+
*
|
|
183
|
+
* Batches events and sends them in groups to reduce network overhead.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* import { createBatchBackend } from 'ai-experiments'
|
|
188
|
+
*
|
|
189
|
+
* const backend = createBatchBackend({
|
|
190
|
+
* batchSize: 10,
|
|
191
|
+
* flushInterval: 5000, // 5 seconds
|
|
192
|
+
* send: async (events) => {
|
|
193
|
+
* await fetch('/api/analytics/batch', {
|
|
194
|
+
* method: 'POST',
|
|
195
|
+
* body: JSON.stringify({ events }),
|
|
196
|
+
* })
|
|
197
|
+
* },
|
|
198
|
+
* })
|
|
199
|
+
*
|
|
200
|
+
* configureTracking({ backend })
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function createBatchBackend(options: {
|
|
204
|
+
/** Maximum batch size before auto-flush */
|
|
205
|
+
batchSize: number
|
|
206
|
+
/** Interval in ms to auto-flush (default: no auto-flush) */
|
|
207
|
+
flushInterval?: number
|
|
208
|
+
/** Function to send batched events */
|
|
209
|
+
send: (events: TrackingEvent[]) => Promise<void>
|
|
210
|
+
}): TrackingBackend {
|
|
211
|
+
const { batchSize, flushInterval, send } = options
|
|
212
|
+
const batch: TrackingEvent[] = []
|
|
213
|
+
let flushTimer: NodeJS.Timeout | null = null
|
|
214
|
+
|
|
215
|
+
const flush = async () => {
|
|
216
|
+
if (batch.length === 0) return
|
|
217
|
+
|
|
218
|
+
const eventsToSend = [...batch]
|
|
219
|
+
batch.length = 0
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
await send(eventsToSend)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Error sending batch:', error)
|
|
225
|
+
// Re-add failed events to batch (simple retry strategy)
|
|
226
|
+
batch.unshift(...eventsToSend)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const scheduleFlush = () => {
|
|
231
|
+
if (flushTimer) {
|
|
232
|
+
clearTimeout(flushTimer)
|
|
233
|
+
}
|
|
234
|
+
if (flushInterval) {
|
|
235
|
+
flushTimer = setTimeout(() => {
|
|
236
|
+
flush().catch(console.error)
|
|
237
|
+
}, flushInterval)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
track: (event: TrackingEvent) => {
|
|
243
|
+
batch.push(event)
|
|
244
|
+
|
|
245
|
+
// Auto-flush if batch is full
|
|
246
|
+
if (batch.length >= batchSize) {
|
|
247
|
+
flush().catch(console.error)
|
|
248
|
+
} else {
|
|
249
|
+
scheduleFlush()
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
flush: async () => {
|
|
253
|
+
if (flushTimer) {
|
|
254
|
+
clearTimeout(flushTimer)
|
|
255
|
+
flushTimer = null
|
|
256
|
+
}
|
|
257
|
+
await flush()
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a file-based tracking backend
|
|
264
|
+
*
|
|
265
|
+
* Writes events to a file (JSONL format).
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* import { createFileBackend } from 'ai-experiments'
|
|
270
|
+
*
|
|
271
|
+
* const backend = createFileBackend({
|
|
272
|
+
* path: './experiments.jsonl',
|
|
273
|
+
* })
|
|
274
|
+
*
|
|
275
|
+
* configureTracking({ backend })
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export function createFileBackend(options: {
|
|
279
|
+
/** Path to the file */
|
|
280
|
+
path: string
|
|
281
|
+
}): TrackingBackend {
|
|
282
|
+
// Note: This requires Node.js fs module
|
|
283
|
+
// Import dynamically to avoid breaking in non-Node environments
|
|
284
|
+
let fs: typeof import('fs') | null = null
|
|
285
|
+
let writeStream: ReturnType<typeof import('fs').createWriteStream> | null = null
|
|
286
|
+
|
|
287
|
+
const ensureStream = async () => {
|
|
288
|
+
if (!writeStream) {
|
|
289
|
+
try {
|
|
290
|
+
fs = await import('fs')
|
|
291
|
+
writeStream = fs.createWriteStream(options.path, { flags: 'a' })
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Failed to create file stream:', error)
|
|
294
|
+
throw error
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return writeStream
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
track: async (event: TrackingEvent) => {
|
|
302
|
+
try {
|
|
303
|
+
const stream = await ensureStream()
|
|
304
|
+
const line = JSON.stringify(event) + '\n'
|
|
305
|
+
stream.write(line)
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('Failed to write event to file:', error)
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
flush: async () => {
|
|
311
|
+
if (writeStream) {
|
|
312
|
+
return new Promise<void>((resolve, reject) => {
|
|
313
|
+
writeStream!.end((error?: Error) => {
|
|
314
|
+
if (error) reject(error)
|
|
315
|
+
else resolve()
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Extract a key identifier from event data for logging
|
|
325
|
+
*/
|
|
326
|
+
function extractKey(event: TrackingEvent): string {
|
|
327
|
+
const data = event.data
|
|
328
|
+
if ('experimentId' in data) return `exp=${data.experimentId}`
|
|
329
|
+
if ('variantId' in data) return `variant=${data.variantId}`
|
|
330
|
+
if ('runId' in data) return `run=${data.runId}`
|
|
331
|
+
return ''
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get current tracking configuration
|
|
336
|
+
*/
|
|
337
|
+
export function getTrackingConfig(): Readonly<Required<TrackingOptions>> {
|
|
338
|
+
return { ...trackingConfig }
|
|
339
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for AI experimentation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A variant within an experiment
|
|
7
|
+
*/
|
|
8
|
+
export interface ExperimentVariant<TConfig = unknown> {
|
|
9
|
+
/** Unique identifier for the variant */
|
|
10
|
+
id: string
|
|
11
|
+
/** Human-readable name */
|
|
12
|
+
name: string
|
|
13
|
+
/** Variant configuration */
|
|
14
|
+
config: TConfig
|
|
15
|
+
/** Weight for weighted random selection (default: 1) */
|
|
16
|
+
weight?: number
|
|
17
|
+
/** Optional description */
|
|
18
|
+
description?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for an experiment
|
|
23
|
+
*/
|
|
24
|
+
export interface ExperimentConfig<TConfig = unknown, TResult = unknown> {
|
|
25
|
+
/** Unique experiment identifier */
|
|
26
|
+
id: string
|
|
27
|
+
/** Human-readable name */
|
|
28
|
+
name: string
|
|
29
|
+
/** Experiment description */
|
|
30
|
+
description?: string
|
|
31
|
+
/** List of variants to test */
|
|
32
|
+
variants: ExperimentVariant<TConfig>[]
|
|
33
|
+
/** Function to execute for each variant */
|
|
34
|
+
execute: (config: TConfig, context?: ExperimentContext) => Promise<TResult> | TResult
|
|
35
|
+
/** Optional success metric function */
|
|
36
|
+
metric?: (result: TResult) => number | Promise<number>
|
|
37
|
+
/** Metadata for the experiment */
|
|
38
|
+
metadata?: Record<string, unknown>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Context passed to experiment execution
|
|
43
|
+
*/
|
|
44
|
+
export interface ExperimentContext {
|
|
45
|
+
/** Experiment ID */
|
|
46
|
+
experimentId: string
|
|
47
|
+
/** Variant ID */
|
|
48
|
+
variantId: string
|
|
49
|
+
/** Run ID (unique per execution) */
|
|
50
|
+
runId: string
|
|
51
|
+
/** Timestamp when execution started */
|
|
52
|
+
startedAt: Date
|
|
53
|
+
/** Additional context data */
|
|
54
|
+
data?: Record<string, unknown>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Result of executing an experiment variant
|
|
59
|
+
*/
|
|
60
|
+
export interface ExperimentResult<TResult = unknown> {
|
|
61
|
+
/** Experiment ID */
|
|
62
|
+
experimentId: string
|
|
63
|
+
/** Variant ID */
|
|
64
|
+
variantId: string
|
|
65
|
+
/** Variant name */
|
|
66
|
+
variantName: string
|
|
67
|
+
/** Run ID */
|
|
68
|
+
runId: string
|
|
69
|
+
/** Execution result */
|
|
70
|
+
result: TResult
|
|
71
|
+
/** Computed metric value (if metric function provided) */
|
|
72
|
+
metricValue?: number
|
|
73
|
+
/** Execution duration in milliseconds */
|
|
74
|
+
duration: number
|
|
75
|
+
/** Timestamp when execution started */
|
|
76
|
+
startedAt: Date
|
|
77
|
+
/** Timestamp when execution completed */
|
|
78
|
+
completedAt: Date
|
|
79
|
+
/** Error if execution failed */
|
|
80
|
+
error?: Error
|
|
81
|
+
/** Success flag */
|
|
82
|
+
success: boolean
|
|
83
|
+
/** Additional metadata */
|
|
84
|
+
metadata?: Record<string, unknown>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Summary of experiment results across all variants
|
|
89
|
+
*/
|
|
90
|
+
export interface ExperimentSummary<TResult = unknown> {
|
|
91
|
+
/** Experiment ID */
|
|
92
|
+
experimentId: string
|
|
93
|
+
/** Experiment name */
|
|
94
|
+
experimentName: string
|
|
95
|
+
/** Results for each variant */
|
|
96
|
+
results: ExperimentResult<TResult>[]
|
|
97
|
+
/** Best performing variant (by metric) */
|
|
98
|
+
bestVariant?: {
|
|
99
|
+
variantId: string
|
|
100
|
+
variantName: string
|
|
101
|
+
metricValue: number
|
|
102
|
+
}
|
|
103
|
+
/** Total execution duration */
|
|
104
|
+
totalDuration: number
|
|
105
|
+
/** Number of successful runs */
|
|
106
|
+
successCount: number
|
|
107
|
+
/** Number of failed runs */
|
|
108
|
+
failureCount: number
|
|
109
|
+
/** Timestamp when experiment started */
|
|
110
|
+
startedAt: Date
|
|
111
|
+
/** Timestamp when experiment completed */
|
|
112
|
+
completedAt: Date
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Options for running an experiment
|
|
117
|
+
*/
|
|
118
|
+
export interface RunExperimentOptions {
|
|
119
|
+
/** Run variants in parallel (default: true) */
|
|
120
|
+
parallel?: boolean
|
|
121
|
+
/** Maximum concurrent executions (default: unlimited) */
|
|
122
|
+
maxConcurrency?: number
|
|
123
|
+
/** Stop on first error (default: false) */
|
|
124
|
+
stopOnError?: boolean
|
|
125
|
+
/** Custom context data */
|
|
126
|
+
context?: Record<string, unknown>
|
|
127
|
+
/** Event callbacks */
|
|
128
|
+
onVariantStart?: (variantId: string, variantName: string) => void
|
|
129
|
+
onVariantComplete?: (result: ExperimentResult) => void
|
|
130
|
+
onVariantError?: (variantId: string, error: Error) => void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parameters for cartesian product generation
|
|
135
|
+
*/
|
|
136
|
+
export type CartesianParams = Record<string, unknown[]>
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Result of cartesian product - array of parameter combinations
|
|
140
|
+
*/
|
|
141
|
+
export type CartesianResult<T extends CartesianParams> = Array<{
|
|
142
|
+
[K in keyof T]: T[K][number]
|
|
143
|
+
}>
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Decision options
|
|
147
|
+
*/
|
|
148
|
+
export interface DecideOptions<T> {
|
|
149
|
+
/** Options to choose from */
|
|
150
|
+
options: T[]
|
|
151
|
+
/** Scoring function for each option */
|
|
152
|
+
score: (option: T) => number | Promise<number>
|
|
153
|
+
/** Context or prompt for decision making */
|
|
154
|
+
context?: string
|
|
155
|
+
/** Whether to return all options sorted by score (default: false) */
|
|
156
|
+
returnAll?: boolean
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Result of a decision
|
|
161
|
+
*/
|
|
162
|
+
export interface DecisionResult<T> {
|
|
163
|
+
/** The selected option */
|
|
164
|
+
selected: T
|
|
165
|
+
/** Score of the selected option */
|
|
166
|
+
score: number
|
|
167
|
+
/** All options with their scores (if returnAll was true) */
|
|
168
|
+
allOptions?: Array<{ option: T; score: number }>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Tracking event types
|
|
173
|
+
*/
|
|
174
|
+
export type TrackingEventType =
|
|
175
|
+
| 'experiment.start'
|
|
176
|
+
| 'experiment.complete'
|
|
177
|
+
| 'variant.start'
|
|
178
|
+
| 'variant.complete'
|
|
179
|
+
| 'variant.error'
|
|
180
|
+
| 'metric.computed'
|
|
181
|
+
| 'decision.made'
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Tracking event
|
|
185
|
+
*/
|
|
186
|
+
export interface TrackingEvent {
|
|
187
|
+
/** Event type */
|
|
188
|
+
type: TrackingEventType
|
|
189
|
+
/** Timestamp */
|
|
190
|
+
timestamp: Date
|
|
191
|
+
/** Event data */
|
|
192
|
+
data: Record<string, unknown>
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Tracking backend interface
|
|
197
|
+
*/
|
|
198
|
+
export interface TrackingBackend {
|
|
199
|
+
/** Track an event */
|
|
200
|
+
track(event: TrackingEvent): void | Promise<void>
|
|
201
|
+
/** Flush pending events */
|
|
202
|
+
flush?(): void | Promise<void>
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Options for tracking configuration
|
|
207
|
+
*/
|
|
208
|
+
export interface TrackingOptions {
|
|
209
|
+
/** Custom tracking backend */
|
|
210
|
+
backend?: TrackingBackend
|
|
211
|
+
/** Whether tracking is enabled (default: true) */
|
|
212
|
+
enabled?: boolean
|
|
213
|
+
/** Additional metadata to include with all events */
|
|
214
|
+
metadata?: Record<string, unknown>
|
|
215
|
+
}
|