@klime/node 1.0.3 → 1.2.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
@@ -11,7 +11,11 @@ npm install @klime/node
11
11
  ## Quick Start
12
12
 
13
13
  ```javascript
14
- const { KlimeClient } = require("@klime/node");
14
+ // ESM
15
+ import { KlimeClient } from "@klime/node";
16
+
17
+ // CommonJS
18
+ // const { KlimeClient } = require("@klime/node");
15
19
 
16
20
  const client = new KlimeClient({
17
21
  writeKey: "your-write-key",
@@ -50,6 +54,87 @@ process.on("SIGTERM", async () => {
50
54
  });
51
55
  ```
52
56
 
57
+ ## Installation Prompt
58
+
59
+ Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:
60
+
61
+ ```
62
+ Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.
63
+
64
+ ANALYTICS MODES (determine which applies):
65
+ - Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
66
+ → Use identify() + group() + track()
67
+ - Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
68
+ → Use identify() + track() only (no group() needed)
69
+
70
+ KEY CONCEPTS:
71
+ - Every track() call requires either userId OR groupId (no anonymous events)
72
+ - Use groupId alone for org-level events (webhooks, cron jobs, system metrics)
73
+ - group() links a user to a company AND sets company traits (only for Companies & Teams mode)
74
+ - Order doesn't matter - events before identify/group still get attributed correctly
75
+
76
+ BEST PRACTICES:
77
+ - Initialize client ONCE at app startup (singleton pattern)
78
+ - Store write key in KLIME_WRITE_KEY environment variable
79
+ - Call shutdown() on SIGTERM/SIGINT to flush remaining events
80
+
81
+ Install: npm install @klime/node
82
+
83
+ const { KlimeClient } = require("@klime/node");
84
+
85
+ // Initialize with your KLIME_WRITE_KEY environment variable
86
+ const client = new KlimeClient({ writeKey: "YOUR_WRITE_KEY" });
87
+
88
+ // Identify users at signup/login:
89
+ client.identify("usr_abc123", { email: "jane@acme.com", name: "Jane Smith" });
90
+
91
+ // Track key activities:
92
+ client.track("Report Generated", { report_type: "revenue" }, { userId: "usr_abc123" });
93
+ client.track("Feature Used", { feature: "export", format: "csv" }, { userId: "usr_abc123" });
94
+ client.track("Teammate Invited", { role: "member" }, { userId: "usr_abc123" });
95
+
96
+ // If Companies & Teams mode: link user to their company and set company traits
97
+ client.group("org_456", { name: "Acme Inc", plan: "enterprise" }, { userId: "usr_abc123" });
98
+
99
+ // Graceful shutdown:
100
+ process.on("SIGTERM", async () => { await client.shutdown(); process.exit(0); });
101
+
102
+ INTEGRATION WORKFLOW:
103
+
104
+ Phase 1: Discover
105
+ Explore the codebase to understand:
106
+ 1. What framework is used? (Express, Hono, Fastify, Koa, Next.js, NestJS, etc.)
107
+ 2. Where is user identity available? (e.g., req.user.id, ctx.state.user, session.userId, JWT payload)
108
+ 3. Is this Companies & Teams or Individual Customers?
109
+ - Look for: organization, workspace, tenant, team, account models → Companies & Teams (use group())
110
+ - No company/org concept, just individual users → Individual Customers (skip group())
111
+ 4. Where do core user actions happen? (route handlers, services, controllers)
112
+ 5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, .track)
113
+ Match your integration style to the framework's conventions.
114
+
115
+ Phase 2: Instrument
116
+ Add these calls using idiomatic patterns for the framework:
117
+ - Initialize client once at startup (Express: app.js, Next.js: instrumentation.ts, NestJS: module provider)
118
+ - identify() in auth/login success handler
119
+ - group() when user-org association is established (Companies & Teams mode only)
120
+ - track() for key user actions (see below)
121
+
122
+ WHAT TO TRACK:
123
+ Active engagement (primary): feature usage, resource creation, collaboration, completing flows
124
+ Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
125
+ Do NOT track: every page view, every API request, health checks, background jobs
126
+
127
+ Phase 3: Verify
128
+ Confirm: client initialized, shutdown handled, identify/group/track calls added
129
+
130
+ Phase 4: Summarize
131
+ Report what you added:
132
+ - Files modified and what was added to each
133
+ - Events being tracked (list event names and what triggers them)
134
+ - How userId is obtained (and groupId if Companies & Teams mode)
135
+ - Any assumptions made or questions
136
+ ```
137
+
53
138
  ## API Reference
54
139
 
55
140
  ### Constructor
@@ -64,16 +149,23 @@ new KlimeClient(config: {
64
149
  retryMaxAttempts?: number; // Optional: Max retry attempts (default: 5)
65
150
  retryInitialDelay?: number; // Optional: Initial retry delay in ms (default: 1000)
66
151
  flushOnShutdown?: boolean; // Optional: Auto-flush on SIGTERM/SIGINT (default: true)
152
+ logger?: Logger; // Optional: Custom logger (default: console with [Klime] prefix)
153
+ onError?: (error, events) => void; // Optional: Callback for batch failures
154
+ onSuccess?: (response) => void; // Optional: Callback for successful sends
67
155
  })
68
156
  ```
69
157
 
70
158
  ### Methods
71
159
 
72
- #### `track(event: string, properties?: object, options?: { userId?, groupId?, ip? })`
160
+ #### `track(event: string, properties?: object, options?: { userId?, groupId? })`
161
+
162
+ Track an event. Events can be attributed in two ways:
73
163
 
74
- Track a user event. A `userId` is required for events to be useful in Klime.
164
+ - **User events**: Provide `userId` to track user activity (most common)
165
+ - **Group events**: Provide `groupId` without `userId` for organization-level events
75
166
 
76
167
  ```javascript
168
+ // User event (most common)
77
169
  client.track(
78
170
  "Button Clicked",
79
171
  {
@@ -83,40 +175,31 @@ client.track(
83
175
  { userId: "user_123" }
84
176
  );
85
177
 
86
- // With IP address (for geolocation)
178
+ // Group event (for webhooks, cron jobs, system events)
87
179
  client.track(
88
- "Button Clicked",
180
+ "Events Received",
89
181
  {
90
- buttonName: "Sign up",
91
- plan: "pro",
182
+ count: 100,
183
+ source: "webhook",
92
184
  },
93
- {
94
- userId: "user_123",
95
- ip: "192.168.1.1",
96
- }
185
+ { groupId: "org_456" }
97
186
  );
98
187
  ```
99
188
 
100
- > **Advanced**: The `groupId` option is available for multi-tenant scenarios where a user belongs to multiple organizations and you need to specify which organization context the event occurred in.
189
+ > **Note**: The `groupId` option can also be combined with `userId` for multi-tenant scenarios where you need to specify which organization context a user event occurred in.
101
190
 
102
- #### `identify(userId: string, traits?: object, options?: { ip? })`
191
+ #### `identify(userId: string, traits?: object)`
103
192
 
104
193
  Identify a user with traits.
105
194
 
106
195
  ```javascript
107
- client.identify(
108
- "user_123",
109
- {
110
- email: "user@example.com",
111
- name: "Stefan",
112
- },
113
- {
114
- ip: "192.168.1.1",
115
- }
116
- );
196
+ client.identify("user_123", {
197
+ email: "user@example.com",
198
+ name: "Stefan",
199
+ });
117
200
  ```
118
201
 
119
- #### `group(groupId: string, traits?: object, options?: { userId?, ip? })`
202
+ #### `group(groupId: string, traits?: object, options?: { userId? })`
120
203
 
121
204
  Associate a user with a group and/or set group traits.
122
205
 
@@ -151,12 +234,92 @@ Gracefully shutdown the client, flushing remaining events.
151
234
  await client.shutdown();
152
235
  ```
153
236
 
237
+ #### `getQueueSize(): number`
238
+
239
+ Return the number of events currently in the queue.
240
+
241
+ ```javascript
242
+ const pending = client.getQueueSize();
243
+ console.log(`${pending} events waiting to be sent`);
244
+ ```
245
+
246
+ ### Synchronous Methods
247
+
248
+ For cases where you need confirmation that events were sent (e.g., before process exit, in tests), use the synchronous variants:
249
+
250
+ #### `trackSync(event, properties, options): Promise<BatchResponse>`
251
+
252
+ Track an event synchronously. Returns `BatchResponse` or throws `SendError`.
253
+
254
+ ```javascript
255
+ const { SendError } = require("@klime/node");
256
+
257
+ try {
258
+ const response = await client.trackSync(
259
+ "Critical Action",
260
+ { key: "value" },
261
+ { userId: "user_123" }
262
+ );
263
+ console.log(`Sent! Accepted: ${response.accepted}`);
264
+ } catch (error) {
265
+ if (error instanceof SendError) {
266
+ console.error(`Failed to send: ${error.message}`);
267
+ }
268
+ }
269
+ ```
270
+
271
+ #### `identifySync(userId, traits): Promise<BatchResponse>`
272
+
273
+ Identify a user synchronously. Returns `BatchResponse` or throws `SendError`.
274
+
275
+ #### `groupSync(groupId, traits, options): Promise<BatchResponse>`
276
+
277
+ Associate a user with a group synchronously. Returns `BatchResponse` or throws `SendError`.
278
+
154
279
  ## Features
155
280
 
156
281
  - **Automatic Batching**: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
157
282
  - **Automatic Retries**: Failed requests are automatically retried with exponential backoff
158
283
  - **Process Exit Handling**: Automatically flushes events on SIGTERM/SIGINT
159
284
  - **Zero Dependencies**: Uses only Node.js standard library (fetch for Node 18+, https/http for older versions)
285
+ - **Universal Module Support**: Works with ESM and CommonJS out of the box
286
+
287
+ ## Module Compatibility
288
+
289
+ This package ships dual ESM/CommonJS builds:
290
+
291
+ ```javascript
292
+ // ESM (recommended for modern projects)
293
+ import { KlimeClient } from "@klime/node";
294
+
295
+ // CommonJS
296
+ const { KlimeClient } = require("@klime/node");
297
+ ```
298
+
299
+ Works with all major Node.js frameworks including Express, Fastify, Hono, Next.js, and NestJS.
300
+
301
+ ## Performance
302
+
303
+ When you call `track()`, `identify()`, or `group()`, the SDK:
304
+
305
+ 1. Adds the event to an in-memory queue (microseconds)
306
+ 2. Returns immediately without waiting for network I/O
307
+
308
+ Events are sent to Klime's servers asynchronously via Node.js's event loop. This means:
309
+
310
+ - **No network blocking**: HTTP requests happen asynchronously without blocking the event loop
311
+ - **No latency impact**: Tracking calls add < 1ms to your request handling time
312
+ - **Automatic batching**: Events are queued and sent in batches (default: every 2 seconds or 20 events)
313
+
314
+ ```javascript
315
+ // This returns immediately - no HTTP request is made here
316
+ client.track("Button Clicked", { button: "signup" }, { userId: "user_123" });
317
+
318
+ // Your code continues without waiting
319
+ res.json({ success: true });
320
+ ```
321
+
322
+ The only blocking operation is `await flush()`, which waits for all queued events to be sent. This is typically only called during graceful shutdown.
160
323
 
161
324
  ## Configuration
162
325
 
@@ -169,6 +332,38 @@ await client.shutdown();
169
332
  - `retryInitialDelay`: 1000ms
170
333
  - `flushOnShutdown`: true
171
334
 
335
+ ### Logging
336
+
337
+ The SDK uses a console wrapper with `[Klime]` prefix by default. You can provide a custom logger:
338
+
339
+ ```javascript
340
+ const client = new KlimeClient({
341
+ writeKey: "your-write-key",
342
+ logger: {
343
+ debug: (msg, ...args) => myLogger.debug(msg, ...args),
344
+ info: (msg, ...args) => myLogger.info(msg, ...args),
345
+ warn: (msg, ...args) => myLogger.warn(msg, ...args),
346
+ error: (msg, ...args) => myLogger.error(msg, ...args),
347
+ },
348
+ });
349
+ ```
350
+
351
+ ### Callbacks
352
+
353
+ ```javascript
354
+ const client = new KlimeClient({
355
+ writeKey: "your-write-key",
356
+ onError: (error, events) => {
357
+ // Report to your error tracking service
358
+ Sentry.captureException(error);
359
+ console.error(`Failed to send ${events.length} events: ${error.message}`);
360
+ },
361
+ onSuccess: (response) => {
362
+ console.log(`Sent ${response.accepted} events`);
363
+ },
364
+ });
365
+ ```
366
+
172
367
  ## Error Handling
173
368
 
174
369
  The SDK automatically handles:
@@ -177,6 +372,20 @@ The SDK automatically handles:
177
372
  - **Permanent errors** (400, 401): Logs error and drops event
178
373
  - **Rate limiting**: Respects `Retry-After` header
179
374
 
375
+ For synchronous operations, use `*Sync()` methods which throw `SendError` on failure:
376
+
377
+ ```javascript
378
+ const { KlimeClient, SendError } = require("@klime/node");
379
+
380
+ try {
381
+ const response = await client.trackSync("Event", {}, { userId: "user_123" });
382
+ } catch (error) {
383
+ if (error instanceof SendError) {
384
+ console.error(`Failed: ${error.message}, events: ${error.events.length}`);
385
+ }
386
+ }
387
+ ```
388
+
180
389
  ## Size Limits
181
390
 
182
391
  - Maximum event size: 200KB
@@ -204,7 +413,6 @@ app.post("/api/button-clicked", (req, res) => {
204
413
  },
205
414
  {
206
415
  userId: req.user.id,
207
- ip: req.ip,
208
416
  }
209
417
  );
210
418