@apex-inc/sdk 0.7.3 → 0.9.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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * GENERATED FILE — DO NOT EDIT BY HAND.
3
+ *
4
+ * Typed Apex event payloads, generated from the Apex Spec registries by
5
+ * `app/scripts/generate-spec-types.ts`. Regenerate with:
6
+ *
7
+ * npx tsx app/scripts/generate-spec-types.ts
8
+ *
9
+ * The `codegen.test.ts` stale gate fails CI if this drifts from the spec.
10
+ */
11
+ export {};
package/dist/esm/index.js CHANGED
@@ -4,6 +4,9 @@ let config = null;
4
4
  let queue = [];
5
5
  let flushTimer = null;
6
6
  let userId = null;
7
+ // Consent gate. Defaults to granted (collect immediately); flipped to false
8
+ // by `init({ requireConsent: true })` until `setConsent(true)` is called.
9
+ let consentGranted = true;
7
10
  function log(...args) {
8
11
  if (config?.debug)
9
12
  console.log("[apex-sdk]", ...args);
@@ -66,6 +69,11 @@ function startFlushTimer() {
66
69
  }
67
70
  }
68
71
  function enqueue(event) {
72
+ // Consent gate — drop (don't even buffer) until consent is granted.
73
+ if (!consentGranted) {
74
+ log("Consent not granted; dropping event", event.type);
75
+ return;
76
+ }
69
77
  queue.push(event);
70
78
  const flushAt = config?.flushAt ?? 20;
71
79
  if (queue.length >= flushAt)
@@ -83,9 +91,33 @@ export function init(options) {
83
91
  }
84
92
  config = { ...options, workspaceKey };
85
93
  queue = [];
86
- log("Initialized with workspace:", workspaceKey);
94
+ consentGranted = options.requireConsent ? false : true;
95
+ log("Initialized with workspace:", workspaceKey, "consent:", consentGranted);
87
96
  startFlushTimer();
88
97
  }
98
+ /**
99
+ * Grant or revoke data-collection consent at runtime. Wire this to your
100
+ * cookie-banner / privacy-settings UI.
101
+ *
102
+ * - `setConsent(true)` → resume collection and flush anything queued
103
+ * while waiting (there won't be any if `requireConsent` was set).
104
+ * - `setConsent(false)` → stop collecting and drop anything queued but
105
+ * not yet sent.
106
+ */
107
+ export function setConsent(granted) {
108
+ consentGranted = granted;
109
+ log("Consent set:", granted);
110
+ if (granted) {
111
+ flush();
112
+ }
113
+ else {
114
+ queue = [];
115
+ }
116
+ }
117
+ /** Current consent state. */
118
+ export function getConsent() {
119
+ return consentGranted;
120
+ }
89
121
  /**
90
122
  * Track a custom event.
91
123
  */
@@ -142,6 +174,8 @@ export function identify(userId_, traits) {
142
174
  async function stitchIdentity(email, browserVisitorId, metadata, stableUserId) {
143
175
  if (!config)
144
176
  return;
177
+ if (!consentGranted)
178
+ return;
145
179
  const apiUrl = config.apiUrl ?? DEFAULT_API_URL;
146
180
  const url = `${apiUrl.replace(/\/$/, "")}/api/identity/stitch`;
147
181
  const effectiveVisitorId = browserVisitorId || `sdk-${config.workspaceKey}-${email}`;
@@ -236,6 +270,8 @@ export function trackForm(formData) {
236
270
  * Immediately flush all queued events to the server.
237
271
  */
238
272
  export function flush() {
273
+ if (!consentGranted)
274
+ return;
239
275
  if (queue.length === 0)
240
276
  return;
241
277
  const batch = queue.splice(0, queue.length);
@@ -371,6 +407,10 @@ function fireExperimentExposure(experimentId, variant, apiBase, workspaceKey) {
371
407
  export async function getVariant(experimentId, opts = {}) {
372
408
  if (!isBrowser)
373
409
  return "control";
410
+ // Consent gate — without consent we neither persist a visitor id nor fire
411
+ // an exposure, so everyone deterministically sees control until they opt in.
412
+ if (!consentGranted)
413
+ return "control";
374
414
  const apiBase = (opts.apiUrl ?? config?.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
375
415
  const workspaceKey = opts.workspaceKey ?? config?.workspaceKey;
376
416
  // 1. Preview mode — author previewing, not a real exposure.
@@ -28,6 +28,20 @@ export function createManagementClient(cfg) {
28
28
  }
29
29
  return res.json();
30
30
  },
31
+ /**
32
+ * "Is our growth real?" — per-goal cumulative lift (journeys + experiments)
33
+ * vs the do-nothing global holdout, with 95% CIs. Forward-only; goals still
34
+ * collecting are flagged `isDecisive: false`. (Is Our Growth Real, Phase 4.)
35
+ */
36
+ async getGrowthReality(opts) {
37
+ const days = opts?.days ?? "all";
38
+ const res = await fetch(`${root}/api/growth/reality?days=${days}`, { headers });
39
+ if (!res.ok) {
40
+ const text = await res.text().catch(() => "");
41
+ throw new Error(`Apex getGrowthReality failed (${res.status}): ${text}`);
42
+ }
43
+ return res.json();
44
+ },
31
45
  async assignVariant(experimentId, visitorId) {
32
46
  const cookie = [
33
47
  `apex_vid=${encodeURIComponent(visitorId)}`,