@runbandit/sdk 2.0.0 → 2.1.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 CHANGED
@@ -14,7 +14,7 @@ npm install @runbandit/sdk
14
14
  import { BanditsClient } from '@runbandit/sdk'
15
15
 
16
16
  const client = new BanditsClient({
17
- apiUrl: 'https://app.runbandit.com',
17
+ apiUrl: 'https://runbandit.com',
18
18
  apiKey: 'your-api-key',
19
19
  })
20
20
 
@@ -29,4 +29,6 @@ client.trackNamedEvent({
29
29
  })
30
30
  ```
31
31
 
32
+ **Note:** The SDK automatically adds `/api` to your `apiUrl`, so use the base domain (e.g., `https://runbandit.com`) without the `/api` path. If your `apiUrl` already ends with `/api`, the SDK won't double-prefix it.
33
+
32
34
  See the [SDK docs](https://runbandit.com/docs/sdk) for the full API.
package/dist/index.d.ts CHANGED
@@ -63,7 +63,10 @@ interface Experiment {
63
63
  createdAt: Date;
64
64
  updatedAt: Date;
65
65
  endAt?: Date;
66
+ placement?: Placement;
66
67
  }
68
+ declare const PLACEMENTS: readonly ["hero_headline", "hero_subhead", "cta_primary", "banner_text", "badge_text"];
69
+ type Placement = (typeof PLACEMENTS)[number];
67
70
  interface Treatment {
68
71
  id: string;
69
72
  experimentId: string;
@@ -77,11 +80,13 @@ interface Treatment {
77
80
  interface AlgorithmResult {
78
81
  treatmentId: string;
79
82
  confidence: number;
83
+ probabilityBest?: number;
80
84
  }
81
85
  interface BanditResult {
82
86
  assignmentId: string;
83
87
  treatmentId: string;
84
88
  confidence: number;
89
+ probabilityBest?: number;
85
90
  metadata?: Record<string, any>;
86
91
  }
87
92
  interface ContextualFeatures {
@@ -108,33 +113,15 @@ interface VariantConfig {
108
113
  content: string;
109
114
  metadata?: TreatmentMetadata & Record<string, any>;
110
115
  }
111
- /**
112
- * Bandit algorithm families.
113
- *
114
- * Every family is Thompson-style: a Bayesian posterior per arm, sampled at
115
- * selection time. How much of the past that posterior remembers is controlled
116
- * separately, by `ForgettingMode`.
117
- */
118
116
  declare enum BanditAlgorithm {
119
117
  THOMPSON_SAMPLING = "thompson_sampling",
120
118
  CONTEXTUAL_LINEAR = "contextual_linear"
121
119
  }
122
- /**
123
- * How an algorithm forgets old observations.
124
- *
125
- * Standard bandits assume the arms are stationary: a conversion from six months
126
- * ago counts exactly as much as one from this morning. Real treatments are
127
- * restless - creative fatigues, seasons turn, traffic mix shifts - so the two
128
- * non-stationary modes below down-weight or drop stale evidence.
129
- *
130
- * - NONE stationary; every observation counts forever
131
- * - DISCOUNTED observation weight decays as 0.5^(age / halfLifeMs)
132
- * - SLIDING_WINDOW observations outside the last `windowMs` are dropped entirely
133
- *
134
- * Both non-stationary modes measure age in wall-clock time rather than in
135
- * observation count, so a quiet experiment forgets at the same rate as a busy
136
- * one.
137
- */
120
+ declare enum Posterior {
121
+ BETA = "beta",
122
+ NORMAL_GAMMA = "normal_gamma"
123
+ }
124
+ declare function normalizePosterior(posterior: string | null | undefined): Posterior;
138
125
  declare enum ForgettingMode {
139
126
  NONE = "none",
140
127
  DISCOUNTED = "discounted",
@@ -142,31 +129,13 @@ declare enum ForgettingMode {
142
129
  }
143
130
  interface ForgettingConfig {
144
131
  mode: ForgettingMode;
145
- /** Half-life in milliseconds. DISCOUNTED only. */
146
132
  halfLifeMs?: number;
147
- /** Window length in milliseconds. SLIDING_WINDOW only. */
148
133
  windowMs?: number;
149
134
  }
150
- /** Default half-life for DISCOUNTED: one week. */
151
135
  declare const DEFAULT_HALF_LIFE_MS: number;
152
- /** Default window for SLIDING_WINDOW: two weeks. */
153
136
  declare const DEFAULT_WINDOW_MS: number;
154
- /** The stationary default applied when an experiment has no forgetting config. */
155
137
  declare const STATIONARY: ForgettingConfig;
156
- /**
157
- * Fill in the defaults for a partially-specified forgetting config.
158
- * An unrecognised or missing mode degrades to NONE rather than throwing, so a
159
- * stale row written by an older deploy still selects treatments.
160
- */
161
138
  declare function resolveForgetting(config?: Partial<ForgettingConfig> | null): ForgettingConfig;
162
- /**
163
- * Map a stored algorithm string onto a supported algorithm.
164
- *
165
- * Retired names (epsilon_greedy, ucb1) map onto standard Thompson Sampling,
166
- * which reads the same successes/trials counters they did, so a running
167
- * experiment keeps its learned history. contextual_neural maps onto
168
- * contextual_linear, which is what it already fell back to at runtime.
169
- */
170
139
  declare function normalizeAlgorithm(algorithm: string | null | undefined): BanditAlgorithm;
171
140
  declare enum ContentType {
172
141
  PLAIN_TEXT = "plain_text",
@@ -217,6 +186,7 @@ interface AssignmentRequest {
217
186
  userId: string;
218
187
  context?: ContextualFeatures;
219
188
  sessionId?: string;
189
+ pageUrl?: string;
220
190
  }
221
191
  interface TrackNamedEventRequest {
222
192
  name: string;
@@ -300,6 +270,7 @@ interface BundleAssignmentRequest {
300
270
  userId: string;
301
271
  context?: ContextualFeatures;
302
272
  sessionId?: string;
273
+ pageUrl?: string;
303
274
  }
304
275
  interface BundleSlotAssignment {
305
276
  assignmentId: string;
@@ -311,33 +282,14 @@ interface BundleAssignmentResponse {
311
282
  bundleAssignmentId: string;
312
283
  slots: Record<string, BundleSlotAssignment>;
313
284
  }
314
- /**
315
- * One treatment's published allocation within a snapshot experiment.
316
- *
317
- * This is the wire contract between the snapshot publisher (dashboard API)
318
- * and its only consumer (the SDK). Declared once here so both sides share
319
- * the same shape instead of drifting apart.
320
- */
321
285
  interface SnapshotTreatment {
322
286
  id: string;
323
287
  name?: string;
324
288
  config: unknown;
325
- /** Probability the next visitor is assigned this arm. Sums to ~1 per experiment. */
326
289
  pct: number;
327
290
  }
328
291
  interface SnapshotExperiment {
329
- /**
330
- * 'snapshot' means the client may decide locally from `pct`. 'network' means
331
- * it must call /api/assignments, because the experiment's state cannot be
332
- * reduced to a static distribution.
333
- */
334
292
  mode: 'snapshot' | 'network';
335
- /**
336
- * Assignment lifetime in SECONDS (the unit `assignments.expiresAt` uses).
337
- * The client holds a decision this long so that repeated pageviews in one
338
- * session report one impression, matching the server path's one assignment
339
- * per (experiment, user).
340
- */
341
293
  assignmentTtl: number;
342
294
  treatments: SnapshotTreatment[];
343
295
  }
@@ -356,35 +308,124 @@ declare function calculateConfidenceInterval(successes: number, trials: number,
356
308
  };
357
309
  declare function isValidEmail(email: string): boolean;
358
310
  declare function sanitizeMetadata(metadata: Record<string, any>): Record<string, any>;
359
- /**
360
- * Generates a URL-safe subdomain from a company name
361
- * @param name Company name
362
- * @returns URL-safe subdomain
363
- */
364
311
  declare function generateSubdomain(name: string): string;
365
- /**
366
- * Validates if a subdomain is properly formatted
367
- * @param subdomain Subdomain to validate
368
- * @returns true if valid, false otherwise
369
- */
370
312
  declare function isValidSubdomain(subdomain: string): boolean;
371
- /**
372
- * Extracts subdomain from hostname
373
- * @param hostname Full hostname (e.g., 'acme.example.com')
374
- * @param baseDomain Base domain (e.g., 'example.com')
375
- * @returns Subdomain or null if not found
376
- */
377
313
  declare function extractSubdomain(hostname: string, baseDomain: string): string | null;
378
- /**
379
- * Builds full URL with subdomain
380
- * @param subdomain Subdomain
381
- * @param baseDomain Base domain
382
- * @param path Optional path
383
- * @param protocol Protocol (default: https)
384
- * @returns Full URL
385
- */
386
314
  declare function buildSubdomainUrl(subdomain: string, baseDomain: string, path?: string, protocol?: string): string;
387
315
 
316
+ declare const EMAIL_TOKEN_PARAM = "bt";
317
+ interface SigningKey {
318
+ version: number;
319
+ secret: string;
320
+ }
321
+ interface EmailTokenPayload {
322
+ c: string;
323
+ r: string;
324
+ k: number[];
325
+ l: number;
326
+ v?: number;
327
+ }
328
+ type VerifiedToken = {
329
+ valid: true;
330
+ payload: Required<EmailTokenPayload>;
331
+ } | {
332
+ valid: false;
333
+ reason: 'malformed' | 'unknown_key' | 'bad_signature';
334
+ };
335
+ declare function hashRecipient(recipientId: string, companySecret: string): Promise<string>;
336
+ declare function mintEmailToken(payload: EmailTokenPayload, key: SigningKey): Promise<string>;
337
+ declare function verifyEmailToken(token: string, keys: SigningKey[]): Promise<VerifiedToken>;
338
+ declare function appendEmailToken(url: string, token: string): string;
339
+
340
+ interface PlanArm {
341
+ treatmentId: string;
342
+ armIndex: number;
343
+ }
344
+ interface PlanSlot {
345
+ experimentId: string;
346
+ name: string;
347
+ arms: PlanArm[];
348
+ probabilities: number[];
349
+ configs: unknown[];
350
+ }
351
+ interface EmailPlan {
352
+ campaignId: string;
353
+ slots: PlanSlot[];
354
+ linkTable: string[];
355
+ salt: string;
356
+ signingKey: {
357
+ version: number;
358
+ secret: string;
359
+ };
360
+ refreshEvery: number;
361
+ expiresAt: number;
362
+ }
363
+ declare function uniformFor(salt: string, recipientId: string, slotIndex: number): Promise<number>;
364
+ declare function pickArm(probabilities: number[], uniform: number): number;
365
+ declare function sampleCombination(plan: EmailPlan, recipientId: string): Promise<number[]>;
366
+ declare function impressionManifest(plan: EmailPlan, recipientIds: string[]): Promise<Array<{
367
+ experimentId: string;
368
+ treatmentId: string;
369
+ count: number;
370
+ }>>;
371
+
372
+ interface SlotBinding {
373
+ $slot: string;
374
+ }
375
+ type Bound<T> = T | SlotBinding;
376
+ declare function isSlotBinding(value: unknown): value is SlotBinding;
377
+ interface HeadingBlock {
378
+ type: 'heading';
379
+ text: Bound<string>;
380
+ level?: 1 | 2 | 3;
381
+ align?: 'left' | 'center' | 'right';
382
+ color?: Bound<string>;
383
+ }
384
+ interface TextBlock {
385
+ type: 'text';
386
+ text: Bound<string>;
387
+ align?: 'left' | 'center' | 'right';
388
+ color?: Bound<string>;
389
+ }
390
+ interface ButtonBlock {
391
+ type: 'button';
392
+ text: Bound<string>;
393
+ href: Bound<string>;
394
+ backgroundColor?: Bound<string>;
395
+ textColor?: Bound<string>;
396
+ align?: 'left' | 'center' | 'right';
397
+ }
398
+ interface ImageBlock {
399
+ type: 'image';
400
+ src: Bound<string>;
401
+ alt: Bound<string>;
402
+ href?: Bound<string>;
403
+ width?: number;
404
+ }
405
+ interface DividerBlock {
406
+ type: 'divider';
407
+ color?: string;
408
+ }
409
+ interface SpacerBlock {
410
+ type: 'spacer';
411
+ height?: number;
412
+ }
413
+ type EmailBlock = HeadingBlock | TextBlock | ButtonBlock | ImageBlock | DividerBlock | SpacerBlock;
414
+ interface EmailDocument {
415
+ subject: Bound<string>;
416
+ preheader?: Bound<string>;
417
+ blocks: EmailBlock[];
418
+ backgroundColor?: string;
419
+ contentWidth?: number;
420
+ }
421
+ declare function extractDocumentSlots(document: EmailDocument): string[];
422
+ interface RenderedDocument {
423
+ subject: string;
424
+ html: string;
425
+ missingSlots: string[];
426
+ }
427
+ declare function renderEmailDocument(document: EmailDocument, slots: Record<string, string>): RenderedDocument;
428
+
388
429
  interface BanditsClientConfig {
389
430
  apiUrl: string;
390
431
  apiKey: string;
@@ -407,11 +448,6 @@ declare class BanditsClient {
407
448
  private flushTimer?;
408
449
  constructor(config: BanditsClientConfig);
409
450
  getAssignment(experimentId: string, userId: string, context?: ContextualFeatures): Promise<Assignment>;
410
- /**
411
- * Track a named event for session-based attribution (recommended)
412
- * Events are matched to bandits by sessionId — no assignmentId needed
413
- * Set immediate: true to fire synchronously (survives navigation)
414
- */
415
451
  trackNamedEvent(event: {
416
452
  name: string;
417
453
  category?: string;
@@ -421,34 +457,13 @@ declare class BanditsClient {
421
457
  immediate?: boolean;
422
458
  }): void;
423
459
  flush(): Promise<void>;
424
- /**
425
- * Link an anonymous user ID to a durable identity (e.g. email, account ID).
426
- * All historical assignments and events for the anonymous ID are re-attributed
427
- * to the identified user. Future calls to getAssignment() with either ID will
428
- * resolve to the same assignment.
429
- *
430
- * Requires admin authentication — the client must be configured with a
431
- * personal access token (PAT) instead of an API key, and companyId must
432
- * be provided. This is a management operation, not an SDK client operation.
433
- *
434
- * @param anonymousId - The temporary/anonymous user ID (e.g. crypto.randomUUID())
435
- * @param identifiedUserId - The permanent user ID (e.g. email, database ID)
436
- * @param companyId - The company ID (required for PAT auth)
437
- * @param mergeReason - Why the identification is happening
438
- */
439
460
  identify(anonymousId: string, identifiedUserId: string, companyId: string, mergeReason?: 'user_signup' | 'email_verification' | 'social_login' | 'account_recovery' | 'manual'): Promise<void>;
440
- /**
441
- * Record a funnel payout event.
442
- * Call this when the terminal conversion happens (e.g., purchase).
443
- *
444
- * @param funnelId - The funnel ID
445
- * @param userId - The user who converted
446
- * @param value - The payout value (e.g., purchase amount in dollars)
447
- */
461
+ label(userId: string, labels: Record<string, string | boolean>): Promise<void>;
448
462
  trackFunnelReward(funnelId: string, userId: string, value: number): Promise<void>;
449
463
  getBundle(bundleId: string, userId: string, context?: ContextualFeatures): Promise<BundleAssignmentResponse>;
450
464
  trackBundleReward(bundleId: string, userId: string, value: number): Promise<void>;
451
465
  private generateId;
466
+ private normalizeApiUrl;
452
467
  private getOrCreateSessionId;
453
468
  private makeRequest;
454
469
  private startBatchFlush;
@@ -464,28 +479,12 @@ interface SyncAssignment {
464
479
  interface SnapshotClientConfig {
465
480
  apiUrl: string;
466
481
  publishableKey: string;
467
- /** Defaults to window.localStorage. Injectable for tests and SSR. */
468
482
  storage?: Storage;
469
- /** Injectable clock. Defaults to Date.now. */
470
483
  now?: () => number;
471
- /** Session lifetime in ms. Must match the BanditsClient using the same storage. */
472
484
  sessionTtl?: number;
473
- /**
474
- * A snapshot embedded in the page at render time. When present there is no
475
- * cold start at all - the decision happens with zero network calls, which is
476
- * the whole point of the design.
477
- */
478
485
  inlineSnapshot?: CompanySnapshot;
479
486
  fetchImpl?: typeof fetch;
480
487
  }
481
- /**
482
- * Pick an arm from a published allocation.
483
- *
484
- * One uniform draw against the cumulative distribution. This is equivalent to
485
- * Thompson sampling because `pct` *is* the distribution Thompson sampling
486
- * draws from - the Monte Carlo ran server-side, once, instead of here on every
487
- * pageview.
488
- */
489
488
  declare function pickTreatment(treatments: SnapshotTreatment[], draw: number): SnapshotTreatment | null;
490
489
  declare class SnapshotClient {
491
490
  private config;
@@ -497,66 +496,70 @@ declare class SnapshotClient {
497
496
  private sessionId;
498
497
  private inFlight;
499
498
  constructor(config: SnapshotClientConfig);
500
- /**
501
- * Decide an arm with no network call, or return null.
502
- *
503
- * Null means "this caller must fall back" - a cold cache, an expired one, an
504
- * unknown experiment, or one whose state cannot be served from a static
505
- * document. Returning null rather than the control is deliberate: the caller
506
- * renders control itself, and no impression is reported for it. Reporting a
507
- * forced-control impression would inflate the control arm's trials with
508
- * decisions the bandit never made.
509
- */
510
499
  getAssignmentSync(experimentId: string, userId: string): SyncAssignment | null;
511
- /** Fetch a fresh snapshot and cache it. Safe to call on every pageview. */
512
500
  refresh(): Promise<void>;
513
- /**
514
- * Post queued impressions.
515
- *
516
- * The queue is only cleared on success. A failed flush leaves the reports in
517
- * storage, so a pageview that happened offline is still counted when the
518
- * next one succeeds - the trial really did occur, and dropping it would bias
519
- * the arm's denominator downward.
520
- */
521
501
  flush(): Promise<void>;
522
502
  private doFlush;
523
- /** The session id these reports are attributed under. */
524
503
  getSessionId(): string;
525
504
  pendingReportCount(): number;
526
- /** The cached snapshot, or null when absent or past its ttl. */
527
505
  private freshSnapshot;
528
506
  private readSnapshot;
529
507
  private persistSnapshot;
530
508
  private readQueue;
531
- /** Stored decisions, pruned of expired entries. */
532
509
  private readDecisions;
533
510
  private writeDecisions;
534
511
  private persistQueue;
535
512
  }
536
513
 
537
- /**
538
- * Session identity, shared by every client in this package.
539
- *
540
- * Reward attribution joins `assignments` by `sessionId`, so a decision made by
541
- * `SnapshotClient` and an event tracked by `BanditsClient` must agree on the
542
- * id or the conversion never lands on the impression. One implementation,
543
- * used by both, is the only way that rule stays true.
544
- */
545
514
  declare const SESSION_ID_KEY = "bandits_session_id";
546
515
  declare const SESSION_CREATED_AT_KEY = "bandits_session_created_at";
547
- /** 24 hours, in milliseconds. */
548
516
  declare const DEFAULT_SESSION_TTL_MS = 86400000;
549
517
  interface SessionOptions {
550
- /** Where the session lives. Null (no browser storage) means ephemeral. */
551
518
  storage?: Storage | null;
552
- /** Injectable clock, in milliseconds. */
553
519
  now?: () => number;
554
520
  ttlMs?: number;
555
521
  }
556
- /**
557
- * Return the current session id, minting and persisting a new one when there
558
- * is none or the stored one has aged past `ttlMs`.
559
- */
560
522
  declare function getOrCreateSessionId(options?: SessionOptions): string;
561
523
 
562
- export { type APIResponse, type Account, type AlgorithmResult, type ApiKey, type Assignment, type AssignmentRequest, BanditAlgorithm, BanditsClient as BanditClient, type BanditConfig, type BanditResult, BanditsClient, type BanditsClientConfig, type Bundle, type BundleAssignmentRequest, type BundleAssignmentResponse, type BundleMode, type BundleSlotAssignment, type Company, type CompanySnapshot, ContentType, type ContextualFeatures, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateExperimentRequest, DEFAULT_HALF_LIFE_MS, DEFAULT_SESSION_TTL_MS, DEFAULT_WINDOW_MS, type Experiment, type ExperimentStats, ExperimentStatus, type ForgettingConfig, ForgettingMode, type Funnel, type FunnelRewardRequest, FunnelStatus, type IdentifyMergeReason, type IdentifyUserRequest, type IdentifyUserResponse, type ResolvedIdentity, SESSION_CREATED_AT_KEY, SESSION_ID_KEY, STATIONARY, type Session, type SessionOptions, SnapshotClient, type SnapshotClientConfig, type SnapshotExperiment, type SnapshotTreatment, type SyncAssignment, type TrackNamedEventRequest, type Treatment, type TreatmentMetadata, type TreatmentStats, type User, type VariantConfig, buildSubdomainUrl, calculateConfidenceInterval, extractSubdomain, generateId, generateSubdomain, getContentTypeCategory, getOrCreateSessionId, isValidEmail, isValidSubdomain, normalizeAlgorithm, normalizeTreatmentWeights, pickTreatment, resolveForgetting, sanitizeMetadata, validateTreatmentWeights };
524
+ interface RewriteOptions {
525
+ linkTable: string[];
526
+ tokens: string[];
527
+ }
528
+ interface RewriteResult {
529
+ html: string;
530
+ rewritten: number;
531
+ untracked: string[];
532
+ }
533
+ declare function extractEmailLinks(html: string): string[];
534
+ declare function rewriteEmailLinks(html: string, options: RewriteOptions): RewriteResult;
535
+
536
+ interface RenderEmailOptions {
537
+ slots: Record<string, string>;
538
+ linkTable: string[];
539
+ tokens: string[];
540
+ }
541
+ interface RenderedEmail {
542
+ html: string;
543
+ rewritten: number;
544
+ untracked: string[];
545
+ missingSlots: string[];
546
+ }
547
+ declare function renderEmail(templateHtml: string, options: RenderEmailOptions): RenderedEmail;
548
+ declare function extractSlotNames(templateHtml: string): string[];
549
+
550
+ interface PlanRenderResult extends RenderedEmail {
551
+ combination: number[];
552
+ tokens: string[];
553
+ }
554
+ declare function renderFromPlan(plan: EmailPlan, recipientId: string, templateHtml: string, companySecret: string): Promise<PlanRenderResult>;
555
+ declare function shouldRefreshPlan(plan: EmailPlan, recipientsRenderedSincePlan: number, now?: number): boolean;
556
+ declare function reportPlanImpressions(plan: EmailPlan, recipientIds: string[], options: {
557
+ apiUrl: string;
558
+ apiKey: string;
559
+ batchKey?: string;
560
+ }): Promise<{
561
+ recorded: boolean;
562
+ alreadyRecorded: boolean;
563
+ }>;
564
+
565
+ export { type APIResponse, type Account, type AlgorithmResult, type ApiKey, type Assignment, type AssignmentRequest, BanditAlgorithm, BanditsClient as BanditClient, type BanditConfig, type BanditResult, BanditsClient, type BanditsClientConfig, type Bound, type Bundle, type BundleAssignmentRequest, type BundleAssignmentResponse, type BundleMode, type BundleSlotAssignment, type ButtonBlock, type Company, type CompanySnapshot, ContentType, type ContextualFeatures, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateExperimentRequest, DEFAULT_HALF_LIFE_MS, DEFAULT_SESSION_TTL_MS, DEFAULT_WINDOW_MS, type DividerBlock, EMAIL_TOKEN_PARAM, type EmailBlock, type EmailDocument, type EmailPlan, type EmailTokenPayload, type Experiment, type ExperimentStats, ExperimentStatus, type ForgettingConfig, ForgettingMode, type Funnel, type FunnelRewardRequest, FunnelStatus, type HeadingBlock, type IdentifyMergeReason, type IdentifyUserRequest, type IdentifyUserResponse, type ImageBlock, PLACEMENTS, type Placement, type PlanArm, type PlanRenderResult, type PlanSlot, Posterior, type RenderEmailOptions, type RenderedDocument, type RenderedEmail, type ResolvedIdentity, type RewriteOptions, type RewriteResult, SESSION_CREATED_AT_KEY, SESSION_ID_KEY, STATIONARY, type Session, type SessionOptions, type SigningKey, type SlotBinding, SnapshotClient, type SnapshotClientConfig, type SnapshotExperiment, type SnapshotTreatment, type SpacerBlock, type SyncAssignment, type TextBlock, type TrackNamedEventRequest, type Treatment, type TreatmentMetadata, type TreatmentStats, type User, type VariantConfig, type VerifiedToken, appendEmailToken, buildSubdomainUrl, calculateConfidenceInterval, extractDocumentSlots, extractEmailLinks, extractSlotNames, extractSubdomain, generateId, generateSubdomain, getContentTypeCategory, getOrCreateSessionId, hashRecipient, impressionManifest, isSlotBinding, isValidEmail, isValidSubdomain, mintEmailToken, normalizeAlgorithm, normalizePosterior, normalizeTreatmentWeights, pickArm, pickTreatment, renderEmail, renderEmailDocument, renderFromPlan, reportPlanImpressions, resolveForgetting, rewriteEmailLinks, sampleCombination, sanitizeMetadata, shouldRefreshPlan, uniformFor, validateTreatmentWeights, verifyEmailToken };