@apex-inc/sdk 0.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/esm/index.js +180 -0
- package/dist/esm/management.js +152 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +224 -0
- package/dist/index.js.map +1 -0
- package/dist/management.d.ts +50 -0
- package/dist/management.d.ts.map +1 -0
- package/dist/management.js +156 -0
- package/dist/management.js.map +1 -0
- package/package.json +32 -0
- package/skills/apex-communications/SKILL.md +135 -0
- package/skills/apex-experimentation/SKILL.md +70 -0
- package/skills/apex-growth-tracking/SKILL.md +41 -0
- package/skills/apex-integration-cookbook/SKILL.md +137 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Apex Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @apex-inc/sdk
|
|
2
|
+
|
|
3
|
+
Lightweight SDK for sending events and user data to Apex Growth Intelligence. Works in **Node.js** (18+) and **browser** environments. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @apex-inc/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { init, track, identify } from "@apex-inc/sdk";
|
|
15
|
+
|
|
16
|
+
// Initialize (once, at app startup)
|
|
17
|
+
init({
|
|
18
|
+
projectKey: "your-project-key",
|
|
19
|
+
apiUrl: "https://your-apex-instance.com",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Identify a user
|
|
23
|
+
identify("user_123", {
|
|
24
|
+
email: "jane@example.com",
|
|
25
|
+
plan: "starter",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Track events
|
|
29
|
+
track("page_viewed", { path: "/pricing" });
|
|
30
|
+
track("button_clicked", { label: "Start Trial", section: "hero" });
|
|
31
|
+
track("form_submitted", { formId: "signup", fields: 3 });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### `init(config)`
|
|
37
|
+
|
|
38
|
+
| Option | Type | Default | Description |
|
|
39
|
+
|-----------------|----------|--------------------------|-----------------------------------|
|
|
40
|
+
| `projectKey` | string | **required** | Your Apex project key |
|
|
41
|
+
| `apiUrl` | string | `window.location.origin` | Apex API URL |
|
|
42
|
+
| `flushInterval` | number | `5000` | Auto-flush interval (ms) |
|
|
43
|
+
| `flushAt` | number | `20` | Max queued events before flush |
|
|
44
|
+
| `debug` | boolean | `false` | Log SDK activity to console |
|
|
45
|
+
|
|
46
|
+
### `track(event, properties?)`
|
|
47
|
+
|
|
48
|
+
Send a named event with optional properties.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
track("page_viewed", { path: "/pricing", referrer: "google" });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `identify(userId, traits?)`
|
|
55
|
+
|
|
56
|
+
Associate subsequent events with a user. Also performs **identity stitching** — linking an anonymous visitor to a known email address.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
identify("jane@example.com", {
|
|
60
|
+
name: "Jane Smith",
|
|
61
|
+
plan: "growth",
|
|
62
|
+
company: "Acme Inc",
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Identity Stitching
|
|
67
|
+
|
|
68
|
+
When the `userId` contains `@` (or `traits.email` is set), the SDK automatically calls the `/api/identity/stitch` endpoint to link the email to a visitor profile. This connects anonymous browsing behavior (from the Apex snippet) to a known user.
|
|
69
|
+
|
|
70
|
+
For **server-side** identify calls (e.g., from a webhook handler), pass the browser's `apex_vid` cookie as `traits.visitorId` to ensure the stitch connects to the real browser session:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
identify("jane@example.com", {
|
|
74
|
+
visitorId: req.cookies.apex_vid, // From the browser cookie
|
|
75
|
+
plan: "growth",
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Without `visitorId`, a synthetic visitor ID is created that won't connect to marketing touchpoints.
|
|
80
|
+
|
|
81
|
+
### `trackForm(formData)`
|
|
82
|
+
|
|
83
|
+
Track a form submission and identify the user in one call. Combines `track("form_submit", ...)` with `identify(email, ...)`.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
trackForm({
|
|
87
|
+
email: "jane@example.com",
|
|
88
|
+
formId: "signup-form",
|
|
89
|
+
action: "/api/register",
|
|
90
|
+
fields: { name: "Jane", company: "Acme" },
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Option | Type | Required | Description |
|
|
95
|
+
|----------|--------|----------|----------------------------------|
|
|
96
|
+
| `email` | string | Yes | User's email address |
|
|
97
|
+
| `formId` | string | No | Form identifier |
|
|
98
|
+
| `action` | string | No | Form action URL |
|
|
99
|
+
| `fields` | object | No | Additional form field data |
|
|
100
|
+
|
|
101
|
+
### `flush()`
|
|
102
|
+
|
|
103
|
+
Immediately send all queued events.
|
|
104
|
+
|
|
105
|
+
### `shutdown()`
|
|
106
|
+
|
|
107
|
+
Flush remaining events and stop the background timer.
|
|
108
|
+
|
|
109
|
+
## Node.js Usage
|
|
110
|
+
|
|
111
|
+
The SDK works in Node.js 18+ (which includes global `fetch`). For older Node.js versions, install `node-fetch`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm install node-fetch
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { init, track, identify, shutdown } from "@apex-inc/sdk";
|
|
119
|
+
|
|
120
|
+
init({
|
|
121
|
+
projectKey: "your-project-key",
|
|
122
|
+
apiUrl: "https://your-apex-instance.com",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// In your webhook handler or API route:
|
|
126
|
+
identify("new-signup@example.com", { source: "stripe_webhook" });
|
|
127
|
+
track("subscription_created", { plan: "growth", mrr: 99 });
|
|
128
|
+
|
|
129
|
+
// On process exit:
|
|
130
|
+
shutdown();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Management API (API key)
|
|
134
|
+
|
|
135
|
+
For **server-side** access to experiments (same auth model as the Apex MCP server), use `createManagementClient` with your **`APEX_API_KEY`** and project key. The client sends **`x-api-key`** and **`x-apex-project`** so routes resolve the tenant without a browser session.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { createManagementClient, init, track } from "@apex-inc/sdk";
|
|
139
|
+
|
|
140
|
+
const apex = createManagementClient({
|
|
141
|
+
projectKey: "your-project-key",
|
|
142
|
+
apiKey: process.env.APEX_API_KEY!,
|
|
143
|
+
apiUrl: "https://your-apex-instance.com",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const experiments = await apex.listExperiments();
|
|
147
|
+
const { variant } = await apex.assignVariant("experiment-id-123", "stable-visitor-id-per-user");
|
|
148
|
+
|
|
149
|
+
init({ projectKey: "your-project-key", apiUrl: "https://your-apex-instance.com" });
|
|
150
|
+
track("notification_sent", { experimentId: "experiment-id-123", variant });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`assignVariant` uses a stable **visitor id** per end user (e.g. hashed internal user id) so variant assignment stays deterministic. Pass the same id on every call for that user and experiment.
|
|
154
|
+
|
|
155
|
+
## Shipped skills (AI assistants)
|
|
156
|
+
|
|
157
|
+
Cursor-style skills live in **`skills/apex-growth-tracking/SKILL.md`**. Copy that folder into `.cursor/skills/` so your AI assistant gets Apex tracking conventions in context.
|
|
158
|
+
|
|
159
|
+
## How It Works
|
|
160
|
+
|
|
161
|
+
Events are queued in memory and flushed in batches to `POST /api/events`. The SDK auto-flushes on a timer, when the queue hits `flushAt`, and on `beforeunload` / `visibilitychange` in browsers.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT — see [LICENSE](./LICENSE)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const isBrowser = typeof window !== "undefined";
|
|
2
|
+
const DEFAULT_API_URL = isBrowser ? window.location.origin : "http://localhost:3000";
|
|
3
|
+
let config = null;
|
|
4
|
+
let queue = [];
|
|
5
|
+
let flushTimer = null;
|
|
6
|
+
let userId = null;
|
|
7
|
+
function log(...args) {
|
|
8
|
+
if (config?.debug)
|
|
9
|
+
console.log("[apex-sdk]", ...args);
|
|
10
|
+
}
|
|
11
|
+
async function sendBatch(events) {
|
|
12
|
+
if (!config || events.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
const apiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
15
|
+
const url = `${apiUrl.replace(/\/$/, "")}/api/events`;
|
|
16
|
+
const body = JSON.stringify({
|
|
17
|
+
projectKey: config.projectKey,
|
|
18
|
+
userId,
|
|
19
|
+
events,
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
const fetchFn = typeof fetch !== "undefined" ? fetch : await loadNodeFetch();
|
|
23
|
+
const res = await fetchFn(url, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
body,
|
|
27
|
+
});
|
|
28
|
+
log(`Flushed ${events.length} events — ${res.status}`);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
log("Flush failed:", err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function loadNodeFetch() {
|
|
35
|
+
// Fallback for older Node.js environments without global fetch
|
|
36
|
+
try {
|
|
37
|
+
const mod = await import("node-fetch");
|
|
38
|
+
return mod.default;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error("apex-sdk: fetch is not available. Use Node.js 18+ or install node-fetch.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function startFlushTimer() {
|
|
45
|
+
if (flushTimer)
|
|
46
|
+
return;
|
|
47
|
+
const interval = config?.flushInterval ?? 5000;
|
|
48
|
+
flushTimer = setInterval(() => flush(), interval);
|
|
49
|
+
if (isBrowser) {
|
|
50
|
+
window.addEventListener("beforeunload", () => flush());
|
|
51
|
+
document.addEventListener("visibilitychange", () => {
|
|
52
|
+
if (document.visibilityState === "hidden")
|
|
53
|
+
flush();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function enqueue(event) {
|
|
58
|
+
queue.push(event);
|
|
59
|
+
const flushAt = config?.flushAt ?? 20;
|
|
60
|
+
if (queue.length >= flushAt)
|
|
61
|
+
flush();
|
|
62
|
+
}
|
|
63
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Initialize the Apex SDK. Must be called before track() or identify().
|
|
66
|
+
*/
|
|
67
|
+
export function init(options) {
|
|
68
|
+
config = options;
|
|
69
|
+
queue = [];
|
|
70
|
+
log("Initialized with project:", options.projectKey);
|
|
71
|
+
startFlushTimer();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Track a custom event.
|
|
75
|
+
*/
|
|
76
|
+
export function track(event, properties) {
|
|
77
|
+
if (!config) {
|
|
78
|
+
console.warn("[apex-sdk] Call init() before track()");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
log("Track:", event, properties);
|
|
82
|
+
enqueue({
|
|
83
|
+
type: "track",
|
|
84
|
+
payload: { event, properties, timestamp: new Date().toISOString() },
|
|
85
|
+
timestamp: new Date().toISOString(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Identify a user with traits (email, name, plan, etc.).
|
|
90
|
+
* Also performs identity stitching if the userId looks like an email.
|
|
91
|
+
*
|
|
92
|
+
* Pass `traits.visitorId` (the browser's `apex_vid` cookie value) to stitch
|
|
93
|
+
* server-side identify calls to the real browser session. Without it, the SDK
|
|
94
|
+
* creates a synthetic visitor ID that is disconnected from marketing touchpoints.
|
|
95
|
+
*
|
|
96
|
+
* PLG integration pattern:
|
|
97
|
+
* apex.identify('user@example.com', { visitorId: req.cookies.apex_vid })
|
|
98
|
+
*/
|
|
99
|
+
export function identify(userId_, traits) {
|
|
100
|
+
if (!config) {
|
|
101
|
+
console.warn("[apex-sdk] Call init() before identify()");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
userId = userId_;
|
|
105
|
+
log("Identify:", userId, traits);
|
|
106
|
+
enqueue({
|
|
107
|
+
type: "identify",
|
|
108
|
+
payload: { userId: userId_, traits },
|
|
109
|
+
timestamp: new Date().toISOString(),
|
|
110
|
+
});
|
|
111
|
+
const email = userId_.includes("@") ? userId_ : traits?.email;
|
|
112
|
+
if (email) {
|
|
113
|
+
const browserVisitorId = traits?.visitorId;
|
|
114
|
+
stitchIdentity(email, browserVisitorId, traits);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function stitchIdentity(email, browserVisitorId, metadata) {
|
|
118
|
+
if (!config)
|
|
119
|
+
return;
|
|
120
|
+
const apiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
121
|
+
const url = `${apiUrl.replace(/\/$/, "")}/api/identity/stitch`;
|
|
122
|
+
const effectiveVisitorId = browserVisitorId || `sdk-${config.projectKey}-${email}`;
|
|
123
|
+
try {
|
|
124
|
+
const fetchFn = typeof fetch !== "undefined" ? fetch : await loadNodeFetch();
|
|
125
|
+
await fetchFn(url, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { "Content-Type": "application/json" },
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
visitorId: effectiveVisitorId,
|
|
130
|
+
email,
|
|
131
|
+
projectKey: config.projectKey,
|
|
132
|
+
metadata: metadata ?? {},
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
log("Identity stitched for:", email, "visitor:", effectiveVisitorId);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
log("Identity stitch failed:", err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Track a form submission event with the email for identity stitching.
|
|
143
|
+
*/
|
|
144
|
+
export function trackForm(formData) {
|
|
145
|
+
if (!config) {
|
|
146
|
+
console.warn("[apex-sdk] Call init() before trackForm()");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const { email, formId, action, fields } = formData;
|
|
150
|
+
log("TrackForm:", email, formId);
|
|
151
|
+
track("form_submit", {
|
|
152
|
+
formId,
|
|
153
|
+
formAction: action,
|
|
154
|
+
email,
|
|
155
|
+
...(fields ?? {}),
|
|
156
|
+
});
|
|
157
|
+
identify(email, { source: "form", formId });
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Immediately flush all queued events to the server.
|
|
161
|
+
*/
|
|
162
|
+
export function flush() {
|
|
163
|
+
if (queue.length === 0)
|
|
164
|
+
return;
|
|
165
|
+
const batch = queue.splice(0, queue.length);
|
|
166
|
+
sendBatch(batch);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Shut down the SDK — flush remaining events and stop the timer.
|
|
170
|
+
*/
|
|
171
|
+
export function shutdown() {
|
|
172
|
+
flush();
|
|
173
|
+
if (flushTimer) {
|
|
174
|
+
clearInterval(flushTimer);
|
|
175
|
+
flushTimer = null;
|
|
176
|
+
}
|
|
177
|
+
config = null;
|
|
178
|
+
log("Shut down");
|
|
179
|
+
}
|
|
180
|
+
export { createManagementClient, } from "./management";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic access to Apex APIs using an API key (same as MCP server).
|
|
3
|
+
* Intended for Node.js backends; sends x-api-key and x-apex-project on each request.
|
|
4
|
+
*/
|
|
5
|
+
function baseUrl(cfg) {
|
|
6
|
+
return (cfg.apiUrl ?? "http://localhost:3000").replace(/\/$/, "");
|
|
7
|
+
}
|
|
8
|
+
function keyHeaders(cfg) {
|
|
9
|
+
return {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
"x-api-key": cfg.apiKey,
|
|
12
|
+
"x-apex-project": cfg.projectKey,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a minimal management client for server-side experiment reads and variant assignment.
|
|
17
|
+
* Assignment uses Cookie apex_vid + apex_project so GET /api/experiments/[id]/assign resolves consistently.
|
|
18
|
+
*/
|
|
19
|
+
export function createManagementClient(cfg) {
|
|
20
|
+
const root = baseUrl(cfg);
|
|
21
|
+
const headers = keyHeaders(cfg);
|
|
22
|
+
return {
|
|
23
|
+
async listExperiments() {
|
|
24
|
+
const res = await fetch(`${root}/api/experiments`, { headers });
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const text = await res.text().catch(() => "");
|
|
27
|
+
throw new Error(`Apex listExperiments failed (${res.status}): ${text}`);
|
|
28
|
+
}
|
|
29
|
+
return res.json();
|
|
30
|
+
},
|
|
31
|
+
async assignVariant(experimentId, visitorId) {
|
|
32
|
+
const cookie = [
|
|
33
|
+
`apex_vid=${encodeURIComponent(visitorId)}`,
|
|
34
|
+
`apex_project=${encodeURIComponent(cfg.projectKey)}`,
|
|
35
|
+
].join("; ");
|
|
36
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(experimentId)}/assign`, {
|
|
37
|
+
headers: { ...headers, Cookie: cookie },
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const text = await res.text().catch(() => "");
|
|
41
|
+
throw new Error(`Apex assignVariant failed (${res.status}): ${text}`);
|
|
42
|
+
}
|
|
43
|
+
return res.json();
|
|
44
|
+
},
|
|
45
|
+
// ─── Communications ───────────────────────────────────────────────
|
|
46
|
+
async recommendCommunications() {
|
|
47
|
+
const res = await fetch(`${root}/api/communications/recommend`, { method: "POST", headers });
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
throw new Error(`Apex recommendCommunications failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
50
|
+
return res.json();
|
|
51
|
+
},
|
|
52
|
+
async generateCommunications(catalogEntryIds) {
|
|
53
|
+
const res = await fetch(`${root}/api/communications/generate`, {
|
|
54
|
+
method: "POST", headers, body: JSON.stringify({ catalogEntryIds }),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
throw new Error(`Apex generateCommunications failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
58
|
+
return res.json();
|
|
59
|
+
},
|
|
60
|
+
async listCommunications() {
|
|
61
|
+
const res = await fetch(`${root}/api/communications`, { headers });
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
throw new Error(`Apex listCommunications failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
64
|
+
return res.json();
|
|
65
|
+
},
|
|
66
|
+
async getCommunication(id) {
|
|
67
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}`, { headers });
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`Apex getCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
70
|
+
return res.json();
|
|
71
|
+
},
|
|
72
|
+
async updateCommunication(id, updates) {
|
|
73
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}`, {
|
|
74
|
+
method: "PATCH", headers, body: JSON.stringify(updates),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
throw new Error(`Apex updateCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
78
|
+
return res.json();
|
|
79
|
+
},
|
|
80
|
+
async deleteCommunication(id) {
|
|
81
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}`, {
|
|
82
|
+
method: "DELETE", headers,
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok)
|
|
85
|
+
throw new Error(`Apex deleteCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
86
|
+
},
|
|
87
|
+
async previewCommunication(id) {
|
|
88
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}/preview`, {
|
|
89
|
+
method: "POST", headers,
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok)
|
|
92
|
+
throw new Error(`Apex previewCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
93
|
+
return res.json();
|
|
94
|
+
},
|
|
95
|
+
async sendTestCommunication(id) {
|
|
96
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}/test`, {
|
|
97
|
+
method: "POST", headers,
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok)
|
|
100
|
+
throw new Error(`Apex sendTestCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
101
|
+
},
|
|
102
|
+
async triggerCommunication(id, opts) {
|
|
103
|
+
const res = await fetch(`${root}/api/communications/${encodeURIComponent(id)}/send`, {
|
|
104
|
+
method: "POST", headers, body: JSON.stringify(opts),
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok)
|
|
107
|
+
throw new Error(`Apex triggerCommunication failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
108
|
+
},
|
|
109
|
+
// ─── Communication Experiments ────────────────────────────────────
|
|
110
|
+
async listCommExperiments() {
|
|
111
|
+
const res = await fetch(`${root}/api/communications/experiments`, { headers });
|
|
112
|
+
if (!res.ok)
|
|
113
|
+
throw new Error(`Apex listCommExperiments failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
114
|
+
return res.json();
|
|
115
|
+
},
|
|
116
|
+
async createCommExperiment(params) {
|
|
117
|
+
const res = await fetch(`${root}/api/communications/experiments`, {
|
|
118
|
+
method: "POST", headers, body: JSON.stringify(params),
|
|
119
|
+
});
|
|
120
|
+
if (!res.ok)
|
|
121
|
+
throw new Error(`Apex createCommExperiment failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
122
|
+
return res.json();
|
|
123
|
+
},
|
|
124
|
+
async startCommExperiment(id) {
|
|
125
|
+
const res = await fetch(`${root}/api/communications/experiments/${encodeURIComponent(id)}`, {
|
|
126
|
+
method: "PATCH", headers, body: JSON.stringify({ action: "start" }),
|
|
127
|
+
});
|
|
128
|
+
if (!res.ok)
|
|
129
|
+
throw new Error(`Apex startCommExperiment failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
130
|
+
},
|
|
131
|
+
async promoteCommExperimentWinner(id) {
|
|
132
|
+
const res = await fetch(`${root}/api/communications/experiments/${encodeURIComponent(id)}`, {
|
|
133
|
+
method: "PATCH", headers, body: JSON.stringify({ action: "promote" }),
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok)
|
|
136
|
+
throw new Error(`Apex promoteCommExperimentWinner failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
137
|
+
},
|
|
138
|
+
// ─── Event Taxonomy ───────────────────────────────────────────────
|
|
139
|
+
async getEventTaxonomy() {
|
|
140
|
+
const res = await fetch(`${root}/api/communications/events`, { headers });
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
throw new Error(`Apex getEventTaxonomy failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
143
|
+
return res.json();
|
|
144
|
+
},
|
|
145
|
+
async getEventIntegrationStatus() {
|
|
146
|
+
const res = await fetch(`${root}/api/communications/events/status`, { headers });
|
|
147
|
+
if (!res.ok)
|
|
148
|
+
throw new Error(`Apex getEventIntegrationStatus failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
149
|
+
return res.json();
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface ApexConfig {
|
|
2
|
+
projectKey: string;
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
/** Flush interval in ms (default: 5000) */
|
|
5
|
+
flushInterval?: number;
|
|
6
|
+
/** Max events before auto-flush (default: 20) */
|
|
7
|
+
flushAt?: number;
|
|
8
|
+
/** Enable debug logging (default: false) */
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface TrackEvent {
|
|
12
|
+
event: string;
|
|
13
|
+
properties?: Record<string, unknown>;
|
|
14
|
+
timestamp?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface IdentifyPayload {
|
|
17
|
+
userId: string;
|
|
18
|
+
traits?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the Apex SDK. Must be called before track() or identify().
|
|
22
|
+
*/
|
|
23
|
+
export declare function init(options: ApexConfig): void;
|
|
24
|
+
/**
|
|
25
|
+
* Track a custom event.
|
|
26
|
+
*/
|
|
27
|
+
export declare function track(event: string, properties?: Record<string, unknown>): void;
|
|
28
|
+
/**
|
|
29
|
+
* Identify a user with traits (email, name, plan, etc.).
|
|
30
|
+
* Also performs identity stitching if the userId looks like an email.
|
|
31
|
+
*
|
|
32
|
+
* Pass `traits.visitorId` (the browser's `apex_vid` cookie value) to stitch
|
|
33
|
+
* server-side identify calls to the real browser session. Without it, the SDK
|
|
34
|
+
* creates a synthetic visitor ID that is disconnected from marketing touchpoints.
|
|
35
|
+
*
|
|
36
|
+
* PLG integration pattern:
|
|
37
|
+
* apex.identify('user@example.com', { visitorId: req.cookies.apex_vid })
|
|
38
|
+
*/
|
|
39
|
+
export declare function identify(userId_: string, traits?: Record<string, unknown>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Track a form submission event with the email for identity stitching.
|
|
42
|
+
*/
|
|
43
|
+
export declare function trackForm(formData: {
|
|
44
|
+
email: string;
|
|
45
|
+
formId?: string;
|
|
46
|
+
action?: string;
|
|
47
|
+
fields?: Record<string, unknown>;
|
|
48
|
+
}): void;
|
|
49
|
+
/**
|
|
50
|
+
* Immediately flush all queued events to the server.
|
|
51
|
+
*/
|
|
52
|
+
export declare function flush(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Shut down the SDK — flush remaining events and stop the timer.
|
|
55
|
+
*/
|
|
56
|
+
export declare function shutdown(): void;
|
|
57
|
+
export { createManagementClient, type ManagementConfig, type AssignVariantResult, } from "./management";
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AA4ED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAK9C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAW/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAkBhF;AA+BD;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,GAAG,IAAI,CAgBP;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,IAAI,CAI5B;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,IAAI,CAQ/B;AAED,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC"}
|