@datalyr/api 1.2.1 → 1.2.2

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
@@ -2,183 +2,123 @@
2
2
 
3
3
  Server-side analytics and attribution SDK for Node.js. Track events, identify users, and preserve attribution data from your backend.
4
4
 
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Quick Start](#quick-start)
9
- - [How It Works](#how-it-works)
10
- - [Configuration](#configuration)
11
- - [Event Tracking](#event-tracking)
12
- - [Custom Events](#custom-events)
13
- - [Page Views](#page-views)
14
- - [User Identity](#user-identity)
15
- - [Anonymous ID](#anonymous-id)
16
- - [Identifying Users](#identifying-users)
17
- - [Groups](#groups)
18
- - [Attribution Preservation](#attribution-preservation)
19
- - [Event Queue](#event-queue)
20
- - [Framework Examples](#framework-examples)
21
- - [Express.js](#expressjs)
22
- - [Stripe Webhooks](#stripe-webhooks)
23
- - [TypeScript](#typescript)
24
- - [Troubleshooting](#troubleshooting)
25
- - [License](#license)
26
-
27
- ---
28
-
29
5
  ## Installation
30
6
 
31
7
  ```bash
32
8
  npm install @datalyr/api
33
9
  ```
34
10
 
35
- ---
36
-
37
11
  ## Quick Start
38
12
 
39
13
  ```javascript
40
14
  import { Datalyr } from '@datalyr/api';
41
15
 
42
- // Initialize
16
+ // String shorthand
43
17
  const datalyr = new Datalyr('dk_your_api_key');
44
18
 
45
- // Track events
46
- await datalyr.track('user_123', 'button_clicked', { button: 'signup' });
19
+ // Or with config object
20
+ const datalyr = new Datalyr({
21
+ apiKey: 'dk_your_api_key',
22
+ debug: true,
23
+ });
24
+
25
+ // Track an event
26
+ await datalyr.track('user_123', 'signup_completed', { plan: 'pro' });
47
27
 
48
- // Identify users
28
+ // Identify a user
49
29
  await datalyr.identify('user_123', { email: 'user@example.com' });
50
30
 
51
- // Clean up on shutdown
31
+ // Flush and shut down
52
32
  await datalyr.close();
53
33
  ```
54
34
 
55
- ---
56
-
57
- ## How It Works
58
-
59
- The SDK collects events and sends them to the Datalyr backend for analytics and attribution.
60
-
61
- ### Data Flow
62
-
63
- 1. Events are created with `track()`, `identify()`, `page()`, or `group()`
64
- 2. Events are queued locally and sent in batches
65
- 3. Batches are sent when queue reaches 20 events or every 10 seconds
66
- 4. Failed requests are retried with exponential backoff
67
- 5. Events are processed server-side for analytics and attribution reporting
68
-
69
- ### Event Payload
70
-
71
- Every event includes:
72
-
73
- ```javascript
74
- {
75
- event: 'purchase', // Event name
76
- properties: { ... }, // Custom properties
77
-
78
- // Identity
79
- anonymous_id: 'uuid', // Persistent ID
80
- user_id: 'user_123', // Set after identify()
81
-
82
- // Timestamps
83
- timestamp: '2024-01-15T10:30:00Z',
84
- }
85
- ```
86
-
87
- ---
88
-
89
35
  ## Configuration
90
36
 
37
+ The constructor accepts a `DatalyrConfig` object or an API key string.
38
+
91
39
  ```javascript
40
+ // Config object
92
41
  const datalyr = new Datalyr({
93
- // Required
94
- apiKey: string,
95
-
96
- // Optional
97
- host?: string, // Custom endpoint (default: https://ingest.datalyr.com)
98
- flushAt?: number, // Batch size (default: 20)
99
- flushInterval?: number, // Send interval ms (default: 10000)
100
- timeout?: number, // Request timeout ms (default: 10000)
101
- retryLimit?: number, // Max retries (default: 3)
102
- maxQueueSize?: number, // Max queued events (default: 1000)
103
- debug?: boolean, // Console logging (default: false)
42
+ apiKey: 'dk_...', // Required. Must start with "dk_".
43
+ host: 'https://ingest.datalyr.com/track', // API endpoint (default: 'https://ingest.datalyr.com/track')
44
+ flushAt: 20, // Flush when queue reaches this size (default: 20, range: 1-100)
45
+ flushInterval: 10000, // Flush timer interval in ms (default: 10000)
46
+ debug: false, // Log events and errors to console (default: false)
47
+ timeout: 10000, // HTTP request timeout in ms (default: 10000, range: 1000-60000)
48
+ retryLimit: 3, // Max retries for failed requests (default: 3)
49
+ maxQueueSize: 1000, // Max queued events before dropping oldest (default: 1000, range: 100-10000)
104
50
  });
105
- ```
106
51
 
107
- ---
52
+ // String shorthand — uses all defaults
53
+ const datalyr = new Datalyr('dk_your_api_key');
54
+ ```
108
55
 
109
- ## Event Tracking
56
+ ## Methods
110
57
 
111
- ### Custom Events
58
+ ### track()
112
59
 
113
- Track any action in your application:
60
+ Two call signatures:
114
61
 
115
62
  ```javascript
116
- // Simple event
117
- await datalyr.track('user_123', 'signup_started');
118
-
119
- // Event with properties
120
- await datalyr.track('user_123', 'product_viewed', {
121
- product_id: 'SKU123',
122
- product_name: 'Blue Shirt',
123
- price: 29.99,
124
- currency: 'USD',
63
+ // Object form (TrackOptions)
64
+ await datalyr.track({
65
+ event: 'Purchase Completed', // Required
66
+ userId: 'user_123', // Optional
67
+ anonymousId: 'anon_from_browser', // Optional — override the auto-generated anonymous ID
68
+ properties: { // Optional
69
+ amount: 99.99,
70
+ currency: 'USD',
71
+ },
125
72
  });
126
73
 
127
- // Purchase event
74
+ // Legacy form
128
75
  await datalyr.track('user_123', 'Purchase Completed', {
129
- order_id: 'ORD-456',
130
- total: 99.99,
76
+ amount: 99.99,
131
77
  currency: 'USD',
132
- items: ['SKU123', 'SKU456'],
133
78
  });
134
- ```
135
79
 
136
- ### Page Views
80
+ // Pass null as userId for anonymous events
81
+ await datalyr.track(null, 'page_loaded', { url: '/pricing' });
82
+ ```
137
83
 
138
- Track server-rendered page views:
84
+ ### identify()
139
85
 
140
86
  ```javascript
141
- await datalyr.page('user_123', 'Homepage', {
142
- url: 'https://example.com',
143
- referrer: 'https://google.com',
144
- });
145
-
146
- await datalyr.page('user_123', 'Product Details', {
147
- url: 'https://example.com/products/123',
148
- product_id: 'SKU123',
149
- });
87
+ await datalyr.identify(userId: string, traits?: any);
150
88
  ```
151
89
 
152
- ---
153
-
154
- ## User Identity
155
-
156
- ### Anonymous ID
157
-
158
- The SDK generates a persistent anonymous ID:
90
+ Links a user ID to traits. Internally sends a `$identify` event.
159
91
 
160
92
  ```javascript
161
- const anonymousId = datalyr.getAnonymousId();
162
- // 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
93
+ await datalyr.identify('user_123', {
94
+ email: 'user@example.com',
95
+ name: 'Jane Doe',
96
+ plan: 'premium',
97
+ });
163
98
  ```
164
99
 
165
- For attribution preservation, pass the anonymous ID from your browser/mobile SDK instead.
100
+ ### page()
166
101
 
167
- ### Identifying Users
102
+ ```javascript
103
+ await datalyr.page(userId: string, name?: string, properties?: any);
104
+ ```
168
105
 
169
- Link events to a known user:
106
+ Track a page view. Internally sends a `$pageview` event.
170
107
 
171
108
  ```javascript
172
- await datalyr.identify('user_123', {
173
- email: 'user@example.com',
174
- name: 'John Doe',
175
- plan: 'premium',
109
+ await datalyr.page('user_123', 'Pricing', {
110
+ url: 'https://example.com/pricing',
111
+ referrer: 'https://google.com',
176
112
  });
177
113
  ```
178
114
 
179
- ### Groups
115
+ ### group()
116
+
117
+ ```javascript
118
+ await datalyr.group(userId: string, groupId: string, traits?: any);
119
+ ```
180
120
 
181
- Associate users with companies or teams:
121
+ Associate a user with a group (company, team, etc.). Internally sends a `$group` event.
182
122
 
183
123
  ```javascript
184
124
  await datalyr.group('user_123', 'company_456', {
@@ -188,71 +128,90 @@ await datalyr.group('user_123', 'company_456', {
188
128
  });
189
129
  ```
190
130
 
191
- ---
131
+ ### flush()
192
132
 
193
- ## Attribution Preservation
194
-
195
- Pass the anonymous ID from browser/mobile SDKs to preserve attribution data:
133
+ Send all queued events immediately.
196
134
 
197
135
  ```javascript
198
- // Object signature with anonymousId
199
- await datalyr.track({
200
- event: 'Purchase Completed',
201
- userId: 'user_123',
202
- anonymousId: req.body.anonymous_id, // From browser SDK
203
- properties: {
204
- amount: 99.99,
205
- currency: 'USD',
206
- },
207
- });
136
+ await datalyr.flush();
208
137
  ```
209
138
 
210
- This links server-side events to the user's browser session, preserving:
211
- - UTM parameters (utm_source, utm_medium, utm_campaign)
212
- - Click IDs (fbclid, gclid, ttclid)
213
- - Referrer and landing page
214
- - Customer journey touchpoints
139
+ ### close()
215
140
 
216
- ---
141
+ Stops the flush timer, then attempts a final flush with a **5-second timeout**. Any events still queued after the timeout are dropped. New events tracked after `close()` is called are silently ignored.
217
142
 
218
- ## Event Queue
143
+ ```javascript
144
+ process.on('SIGTERM', async () => {
145
+ await datalyr.close();
146
+ process.exit(0);
147
+ });
148
+ ```
219
149
 
220
- Events are batched for efficiency.
150
+ ### getAnonymousId()
221
151
 
222
- ### Configuration
152
+ Returns the SDK instance's persistent anonymous ID. The ID is generated lazily on first use and has the format `anon_<random><timestamp>` (e.g., `anon_k7x2m9f1lxyzabc`).
223
153
 
224
154
  ```javascript
225
- const datalyr = new Datalyr({
226
- apiKey: 'dk_your_api_key',
227
- flushAt: 20, // Send when 20 events queued
228
- flushInterval: 10000, // Or every 10 seconds
229
- });
155
+ const anonId = datalyr.getAnonymousId();
230
156
  ```
231
157
 
232
- ### Manual Flush
158
+ ## Event Payload
233
159
 
234
- Send all queued events immediately:
160
+ Every event sent to the API has this structure:
235
161
 
236
162
  ```javascript
237
- await datalyr.flush();
163
+ {
164
+ event: 'Purchase Completed',
165
+ userId: 'user_123', // undefined if not provided
166
+ anonymousId: 'anon_k7x2m9f...', // Always present
167
+ properties: {
168
+ amount: 99.99,
169
+ anonymous_id: 'anon_k7x2m9f...', // Automatically added
170
+ },
171
+ context: {
172
+ library: '@datalyr/api',
173
+ version: '1.2.1',
174
+ source: 'api',
175
+ },
176
+ timestamp: '2025-01-15T10:30:00.000Z',
177
+ }
238
178
  ```
239
179
 
240
- ### Graceful Shutdown
180
+ Notes:
181
+ - `anonymous_id` is automatically added to `properties` on every event for attribution.
182
+ - The `context` object identifies the SDK and version.
183
+ - `timestamp` is set to the ISO 8601 time when the event was created.
184
+
185
+ ## Batching and Retry Behavior
186
+
187
+ Events are queued locally and sent in batches, not one at a time.
188
+
189
+ - **Auto-flush triggers:** when the queue reaches `flushAt` events, or every `flushInterval` ms.
190
+ - **Batch size:** events are sent in parallel batches of 10 within a single flush.
191
+ - **Queue overflow:** when the queue reaches `maxQueueSize`, the oldest event is dropped to make room.
192
+ - **Retry:** 5xx (server) errors are retried up to `retryLimit` times with exponential backoff (1s, 2s, 4s, ... capped at 10s). 4xx (client) errors are permanent failures and are not retried.
193
+ - **Failed events:** events that fail after all retries are re-queued at the front.
194
+
195
+ ## Attribution Preservation
241
196
 
242
- Always close the client on application shutdown:
197
+ Pass the anonymous ID from your browser or mobile SDK to link server-side events to a client-side session:
243
198
 
244
199
  ```javascript
245
- process.on('SIGTERM', async () => {
246
- await datalyr.close(); // Flushes remaining events
247
- process.exit(0);
200
+ await datalyr.track({
201
+ event: 'Purchase Completed',
202
+ userId: 'user_123',
203
+ anonymousId: req.body.anonymous_id, // From browser SDK
204
+ properties: {
205
+ amount: 99.99,
206
+ },
248
207
  });
249
208
  ```
250
209
 
251
- ---
210
+ This preserves UTM parameters, click IDs (gclid, fbclid, ttclid), referrer, landing page, and the full customer journey.
252
211
 
253
212
  ## Framework Examples
254
213
 
255
- ### Express.js
214
+ ### Express.js Middleware
256
215
 
257
216
  ```javascript
258
217
  import express from 'express';
@@ -264,7 +223,6 @@ const datalyr = new Datalyr('dk_your_api_key');
264
223
  app.post('/api/purchase', async (req, res) => {
265
224
  const { items, anonymous_id } = req.body;
266
225
 
267
- // Track with anonymous_id to preserve attribution
268
226
  await datalyr.track({
269
227
  event: 'Purchase Completed',
270
228
  userId: req.user?.id,
@@ -278,7 +236,6 @@ app.post('/api/purchase', async (req, res) => {
278
236
  res.json({ success: true });
279
237
  });
280
238
 
281
- // Graceful shutdown
282
239
  process.on('SIGTERM', async () => {
283
240
  await datalyr.close();
284
241
  process.exit(0);
@@ -299,93 +256,91 @@ app.post('/webhooks/stripe', async (req, res) => {
299
256
  const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
300
257
 
301
258
  switch (event.type) {
302
- case 'checkout.session.completed':
259
+ case 'checkout.session.completed': {
303
260
  const session = event.data.object;
304
- await datalyr.track(
305
- session.client_reference_id,
306
- 'Purchase Completed',
307
- {
308
- amount: session.amount_total / 100,
309
- currency: session.currency,
310
- stripe_session_id: session.id,
311
- }
312
- );
261
+ await datalyr.track(session.client_reference_id, 'Purchase Completed', {
262
+ amount: session.amount_total / 100,
263
+ currency: session.currency,
264
+ stripe_session_id: session.id,
265
+ });
313
266
  break;
267
+ }
314
268
 
315
- case 'customer.subscription.created':
269
+ case 'customer.subscription.created': {
316
270
  const subscription = event.data.object;
317
- await datalyr.track(
318
- subscription.metadata.userId,
319
- 'Subscription Started',
320
- {
321
- plan: subscription.items.data[0].price.nickname,
322
- mrr: subscription.items.data[0].price.unit_amount / 100,
323
- interval: subscription.items.data[0].price.recurring.interval,
324
- }
325
- );
271
+ await datalyr.track(subscription.metadata.userId, 'Subscription Started', {
272
+ plan: subscription.items.data[0].price.nickname,
273
+ mrr: subscription.items.data[0].price.unit_amount / 100,
274
+ interval: subscription.items.data[0].price.recurring.interval,
275
+ });
326
276
  break;
277
+ }
327
278
  }
328
279
 
329
280
  res.json({ received: true });
330
281
  });
331
282
  ```
332
283
 
333
- ---
334
-
335
284
  ## TypeScript
336
285
 
286
+ Full type definitions are included. Exported types:
287
+
337
288
  ```typescript
338
- import { Datalyr, TrackOptions, IdentifyOptions } from '@datalyr/api';
289
+ import { Datalyr, DatalyrConfig, TrackOptions, TrackEvent } from '@datalyr/api';
339
290
 
340
- const datalyr = new Datalyr('dk_your_api_key');
291
+ const config: DatalyrConfig = {
292
+ apiKey: 'dk_your_api_key',
293
+ debug: true,
294
+ };
295
+
296
+ const datalyr = new Datalyr(config);
341
297
 
342
- // Type-safe tracking
343
- const trackOptions: TrackOptions = {
298
+ const options: TrackOptions = {
344
299
  event: 'Purchase Completed',
345
300
  userId: 'user_123',
346
- anonymousId: 'anon_456',
301
+ anonymousId: 'anon_from_browser',
347
302
  properties: {
348
303
  amount: 99.99,
349
304
  currency: 'USD',
350
305
  },
351
306
  };
352
307
 
353
- await datalyr.track(trackOptions);
308
+ await datalyr.track(options);
354
309
  ```
355
310
 
356
- ---
357
-
358
311
  ## Troubleshooting
359
312
 
360
- ### Events not appearing
313
+ **Events not appearing**
361
314
 
362
- 1. Check API key starts with `dk_`
363
- 2. Enable `debug: true`
364
- 3. Call `flush()` to force send
365
- 4. Check server logs for errors
315
+ 1. Verify your API key starts with `dk_`.
316
+ 2. Enable `debug: true` to see console output.
317
+ 3. Call `await datalyr.flush()` to force-send queued events.
318
+ 4. Check for 4xx errors in debug output -- these indicate a client-side issue (bad API key, malformed payload).
366
319
 
367
- ### Request timeouts
320
+ **Request timeouts**
321
+
322
+ Increase `timeout` and `retryLimit`:
368
323
 
369
324
  ```javascript
370
325
  const datalyr = new Datalyr({
371
326
  apiKey: 'dk_your_api_key',
372
- timeout: 30000, // Increase timeout
373
- retryLimit: 5, // More retries
327
+ timeout: 30000,
328
+ retryLimit: 5,
374
329
  });
375
330
  ```
376
331
 
377
- ### Queue full
332
+ **Queue full (oldest events dropped)**
333
+
334
+ Increase `maxQueueSize` or flush more aggressively:
378
335
 
379
336
  ```javascript
380
337
  const datalyr = new Datalyr({
381
338
  apiKey: 'dk_your_api_key',
382
- maxQueueSize: 5000, // Increase queue size
383
- flushAt: 50, // Larger batches
339
+ maxQueueSize: 5000,
340
+ flushAt: 50,
384
341
  });
385
342
  ```
386
343
 
387
- ---
388
-
389
344
  ## License
390
345
 
391
346
  MIT
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ var Datalyr = class {
32
32
  this.isClosing = false;
33
33
  if (typeof config === "string") {
34
34
  this.apiKey = config;
35
- this.host = "https://api.datalyr.com";
35
+ this.host = "https://ingest.datalyr.com/track";
36
36
  this.debug = false;
37
37
  this.flushAt = 20;
38
38
  this.flushInterval = 1e4;
@@ -41,7 +41,7 @@ var Datalyr = class {
41
41
  this.maxQueueSize = 1e3;
42
42
  } else {
43
43
  this.apiKey = config.apiKey;
44
- this.host = config.host || "https://api.datalyr.com";
44
+ this.host = config.host || "https://ingest.datalyr.com/track";
45
45
  this.debug = config.debug || false;
46
46
  this.flushAt = config.flushAt || 20;
47
47
  this.flushInterval = config.flushInterval || 1e4;
@@ -100,7 +100,7 @@ var Datalyr = class {
100
100
  properties: enrichedProperties,
101
101
  context: {
102
102
  library: "@datalyr/api",
103
- version: "1.1.0",
103
+ version: "1.2.1",
104
104
  source: "api"
105
105
  // Explicitly set source for server-side API
106
106
  },
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ var Datalyr = class {
7
7
  this.isClosing = false;
8
8
  if (typeof config === "string") {
9
9
  this.apiKey = config;
10
- this.host = "https://api.datalyr.com";
10
+ this.host = "https://ingest.datalyr.com/track";
11
11
  this.debug = false;
12
12
  this.flushAt = 20;
13
13
  this.flushInterval = 1e4;
@@ -16,7 +16,7 @@ var Datalyr = class {
16
16
  this.maxQueueSize = 1e3;
17
17
  } else {
18
18
  this.apiKey = config.apiKey;
19
- this.host = config.host || "https://api.datalyr.com";
19
+ this.host = config.host || "https://ingest.datalyr.com/track";
20
20
  this.debug = config.debug || false;
21
21
  this.flushAt = config.flushAt || 20;
22
22
  this.flushInterval = config.flushInterval || 1e4;
@@ -75,7 +75,7 @@ var Datalyr = class {
75
75
  properties: enrichedProperties,
76
76
  context: {
77
77
  library: "@datalyr/api",
78
- version: "1.1.0",
78
+ version: "1.2.1",
79
79
  source: "api"
80
80
  // Explicitly set source for server-side API
81
81
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/api",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Datalyr API SDK for server-side tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -43,4 +43,4 @@
43
43
  "engines": {
44
44
  "node": ">=14.0.0"
45
45
  }
46
- }
46
+ }