@runbandit/sdk 2.0.1 → 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/dist/index.js CHANGED
@@ -51,7 +51,6 @@ var BanditsClient = class {
51
51
  batchSize: 100,
52
52
  flushInterval: 1e4,
53
53
  sessionTtl: 864e5,
54
- // 24 hours in ms
55
54
  ...config
56
55
  };
57
56
  this.sessionId = this.getOrCreateSessionId();
@@ -86,11 +85,6 @@ var BanditsClient = class {
86
85
  config: assignment.treatment?.config || {}
87
86
  };
88
87
  }
89
- /**
90
- * Track a named event for session-based attribution (recommended)
91
- * Events are matched to bandits by sessionId — no assignmentId needed
92
- * Set immediate: true to fire synchronously (survives navigation)
93
- */
94
88
  trackNamedEvent(event, opts) {
95
89
  const request = {
96
90
  name: event.name,
@@ -139,21 +133,6 @@ var BanditsClient = class {
139
133
  throw error;
140
134
  }
141
135
  }
142
- /**
143
- * Link an anonymous user ID to a durable identity (e.g. email, account ID).
144
- * All historical assignments and events for the anonymous ID are re-attributed
145
- * to the identified user. Future calls to getAssignment() with either ID will
146
- * resolve to the same assignment.
147
- *
148
- * Requires admin authentication — the client must be configured with a
149
- * personal access token (PAT) instead of an API key, and companyId must
150
- * be provided. This is a management operation, not an SDK client operation.
151
- *
152
- * @param anonymousId - The temporary/anonymous user ID (e.g. crypto.randomUUID())
153
- * @param identifiedUserId - The permanent user ID (e.g. email, database ID)
154
- * @param companyId - The company ID (required for PAT auth)
155
- * @param mergeReason - Why the identification is happening
156
- */
157
136
  async identify(anonymousId, identifiedUserId, companyId, mergeReason) {
158
137
  const response = await this.makeRequest(
159
138
  "/identify",
@@ -169,14 +148,12 @@ var BanditsClient = class {
169
148
  throw new Error(response.error || "Failed to identify user");
170
149
  }
171
150
  }
172
- /**
173
- * Record a funnel payout event.
174
- * Call this when the terminal conversion happens (e.g., purchase).
175
- *
176
- * @param funnelId - The funnel ID
177
- * @param userId - The user who converted
178
- * @param value - The payout value (e.g., purchase amount in dollars)
179
- */
151
+ async label(userId, labels) {
152
+ const response = await this.makeRequest("/labels", "POST", { userId, labels });
153
+ if (!response.success) {
154
+ throw new Error(response.error || "Failed to record labels");
155
+ }
156
+ }
180
157
  async trackFunnelReward(funnelId, userId, value) {
181
158
  const response = await this.makeRequest(
182
159
  `/funnels/${funnelId}/reward`,
@@ -221,10 +198,6 @@ var BanditsClient = class {
221
198
  generateId() {
222
199
  return generateId();
223
200
  }
224
- /**
225
- * Normalize the apiUrl by adding /api prefix if not present.
226
- * Supports both 'https://runbandit.com' and 'https://runbandit.com/api'.
227
- */
228
201
  normalizeApiUrl() {
229
202
  let baseUrl = this.config.apiUrl.replace(/\/$/, "");
230
203
  if (!baseUrl.endsWith("/api")) {
@@ -780,16 +753,6 @@ var SnapshotClient = class {
780
753
  this.snapshot = this.readSnapshot();
781
754
  }
782
755
  }
783
- /**
784
- * Decide an arm with no network call, or return null.
785
- *
786
- * Null means "this caller must fall back" - a cold cache, an expired one, an
787
- * unknown experiment, or one whose state cannot be served from a static
788
- * document. Returning null rather than the control is deliberate: the caller
789
- * renders control itself, and no impression is reported for it. Reporting a
790
- * forced-control impression would inflate the control arm's trials with
791
- * decisions the bandit never made.
792
- */
793
756
  getAssignmentSync(experimentId, userId) {
794
757
  const snapshot = this.freshSnapshot();
795
758
  if (!snapshot) return null;
@@ -816,7 +779,6 @@ var SnapshotClient = class {
816
779
  decisions[key] = {
817
780
  clientAssignmentId,
818
781
  treatmentId: treatment.id,
819
- // expiresAt is milliseconds here; assignmentTtl arrives in seconds.
820
782
  expiresAt: this.now() + ttlSeconds * 1e3
821
783
  };
822
784
  this.writeDecisions(decisions);
@@ -835,7 +797,6 @@ var SnapshotClient = class {
835
797
  config: treatment.config
836
798
  };
837
799
  }
838
- /** Fetch a fresh snapshot and cache it. Safe to call on every pageview. */
839
800
  async refresh() {
840
801
  const response = await this.fetchImpl(`${this.config.apiUrl}/snapshot`, {
841
802
  method: "GET",
@@ -846,14 +807,6 @@ var SnapshotClient = class {
846
807
  this.snapshot = snapshot;
847
808
  this.persistSnapshot(snapshot);
848
809
  }
849
- /**
850
- * Post queued impressions.
851
- *
852
- * The queue is only cleared on success. A failed flush leaves the reports in
853
- * storage, so a pageview that happened offline is still counted when the
854
- * next one succeeds - the trial really did occur, and dropping it would bias
855
- * the arm's denominator downward.
856
- */
857
810
  async flush() {
858
811
  if (this.inFlight) return this.inFlight;
859
812
  this.inFlight = this.doFlush().finally(() => {
@@ -881,14 +834,12 @@ var SnapshotClient = class {
881
834
  this.queue = this.queue.slice(reports.length);
882
835
  this.persistQueue();
883
836
  }
884
- /** The session id these reports are attributed under. */
885
837
  getSessionId() {
886
838
  return this.sessionId;
887
839
  }
888
840
  pendingReportCount() {
889
841
  return this.queue.length;
890
842
  }
891
- /** The cached snapshot, or null when absent or past its ttl. */
892
843
  freshSnapshot() {
893
844
  if (!this.snapshot) return null;
894
845
  if (this.snapshot.ttl === 0) return null;
@@ -921,7 +872,6 @@ var SnapshotClient = class {
921
872
  return [];
922
873
  }
923
874
  }
924
- /** Stored decisions, pruned of expired entries. */
925
875
  readDecisions() {
926
876
  if (!this.storage) return {};
927
877
  try {
@@ -1130,9 +1080,6 @@ async function reportPlanImpressions(plan, recipientIds, options) {
1130
1080
  headers: { "Content-Type": "application/json", "x-api-key": options.apiKey },
1131
1081
  body: JSON.stringify({
1132
1082
  campaignId: plan.campaignId,
1133
- // Derived from the recipients this call covers, so a retry of the same
1134
- // chunk collapses onto the same key without the caller needing to
1135
- // invent and persist an idempotency token of its own.
1136
1083
  batchKey: options.batchKey ?? await batchKeyFor(plan.campaignId, recipientIds),
1137
1084
  counts
1138
1085
  })