@rooguys/sdk 0.1.0 → 1.0.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
@@ -1,6 +1,6 @@
1
1
  # Rooguys Node.js SDK
2
2
 
3
- The official Node.js SDK for the Rooguys Gamification API.
3
+ The official Node.js SDK for the Rooguys Gamification API with full TypeScript support.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,207 +11,572 @@ npm install @rooguys/sdk
11
11
  ## Initialization
12
12
 
13
13
  ```typescript
14
- import { Rooguys } from '@rooguys/sdk';
14
+ import { Rooguys, RooguysOptions } from '@rooguys/sdk';
15
15
 
16
- const client = new Rooguys('YOUR_API_KEY', {
17
- baseUrl: 'https://api.rooguys.com/v1', // Optional, defaults to production
16
+ const options: RooguysOptions = {
17
+ baseUrl: 'https://api.rooguys.com/v1', // Optional
18
18
  timeout: 10000, // Optional, defaults to 10s
19
- });
19
+ // Rate limit handling
20
+ onRateLimitWarning: (info) => {
21
+ console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
22
+ },
23
+ autoRetry: true,
24
+ maxRetries: 3,
25
+ };
26
+
27
+ const client = new Rooguys('YOUR_API_KEY', options);
20
28
  ```
21
29
 
30
+ ## Migration Guide (v1.x to v2.x)
31
+
32
+ ### Breaking Changes
33
+
34
+ 1. **Event Tracking Endpoint**: The SDK now uses `/v1/events` instead of `/v1/event`
35
+ ```typescript
36
+ // Old (deprecated, still works with warning)
37
+ await client.events.trackLegacy('event_name', 'user_id', properties);
38
+
39
+ // New (recommended)
40
+ await client.events.track('event-name', 'user_id', properties);
41
+ ```
42
+
43
+ 2. **Global Leaderboard Endpoint**: Now uses `/v1/leaderboards/global` with `timeframe` query parameter
44
+ ```typescript
45
+ // Both signatures work
46
+ await client.leaderboards.getGlobal('weekly', 1, 10);
47
+ await client.leaderboards.getGlobal({ timeframe: 'weekly', page: 1, limit: 10 });
48
+ ```
49
+
50
+ 3. **Response Format**: All responses now follow standardized format `{ success: true, data: {...} }`
51
+
52
+ ### New Features
53
+
54
+ - Batch event tracking (`events.trackBatch`)
55
+ - User management (`users.create`, `users.update`, `users.createBatch`)
56
+ - User search (`users.search`)
57
+ - Field selection for user profiles
58
+ - Leaderboard filters (persona, level range, date range)
59
+ - "Around me" leaderboard view (`leaderboards.getAroundUser`)
60
+ - Health check endpoints
61
+ - Rate limit handling with auto-retry
62
+ - Full TypeScript type definitions
63
+
22
64
  ## Usage Examples
23
65
 
24
- ### 1. Track an Event
66
+ ### Events
25
67
 
26
- Track user actions to award points and unlock badges.
68
+ #### Track a Single Event
27
69
 
28
70
  ```typescript
29
- try {
30
- const response = await client.events.track(
31
- 'purchase_completed', // Event name
32
- 'user_123', // User ID
33
- { amount: 50.00 }, // Event properties
34
- { includeProfile: true } // Optional: Return updated profile
35
- );
36
-
37
- console.log(`Event status: ${response.status}`);
38
- if (response.profile) {
39
- console.log(`New points: ${response.profile.points}`);
40
- }
41
- } catch (error) {
42
- console.error('Tracking failed:', error.message);
71
+ import { TrackEventResponse, TrackOptions } from '@rooguys/sdk';
72
+
73
+ const options: TrackOptions = {
74
+ includeProfile: true,
75
+ idempotencyKey: 'unique-request-id'
76
+ };
77
+
78
+ const response: TrackEventResponse = await client.events.track(
79
+ 'level-completed',
80
+ 'user_123',
81
+ { difficulty: 'hard', score: 1500 },
82
+ options
83
+ );
84
+
85
+ console.log(`Event status: ${response.status}`);
86
+ if (response.profile) {
87
+ console.log(`Updated points: ${response.profile.points}`);
43
88
  }
44
89
  ```
45
90
 
46
- ### 2. Get User Profile
91
+ #### Track Events with Custom Timestamp
47
92
 
48
- Retrieve a user's current level, points, and badges.
93
+ ```typescript
94
+ // Track historical events (up to 7 days in the past)
95
+ const response = await client.events.track(
96
+ 'purchase',
97
+ 'user_123',
98
+ { amount: 99.99 },
99
+ { timestamp: new Date('2024-01-15T10:30:00Z') }
100
+ );
101
+ ```
102
+
103
+ #### Batch Event Tracking
49
104
 
50
105
  ```typescript
51
- const user = await client.users.get('user_123');
106
+ import { BatchEvent, BatchTrackResponse, BatchOptions } from '@rooguys/sdk';
107
+
108
+ const events: BatchEvent[] = [
109
+ { eventName: 'page-view', userId: 'user_123', properties: { page: '/home' } },
110
+ { eventName: 'button-click', userId: 'user_123', properties: { button: 'signup' } },
111
+ { eventName: 'purchase', userId: 'user_456', properties: { amount: 50 }, timestamp: new Date() }
112
+ ];
113
+
114
+ const options: BatchOptions = { idempotencyKey: 'batch-123' };
115
+ const response: BatchTrackResponse = await client.events.trackBatch(events, options);
116
+
117
+ // Check individual results
118
+ response.results.forEach((result, index) => {
119
+ if (result.status === 'queued') {
120
+ console.log(`Event ${index} queued successfully`);
121
+ } else {
122
+ console.error(`Event ${index} failed:`, result.error);
123
+ }
124
+ });
125
+ ```
126
+
127
+ ### Users
128
+
129
+ #### Create a New User
52
130
 
53
- console.log(`User: ${user.user_id}`);
54
- console.log(`Points: ${user.points}`);
55
- console.log(`Level: ${user.level?.name}`);
56
- console.log('Badges:', user.badges.map(b => b.name).join(', '));
131
+ ```typescript
132
+ import { CreateUserData, UserProfile } from '@rooguys/sdk';
133
+
134
+ const userData: CreateUserData = {
135
+ userId: 'user_123',
136
+ displayName: 'John Doe',
137
+ email: 'john@example.com',
138
+ firstName: 'John',
139
+ lastName: 'Doe',
140
+ metadata: { plan: 'premium' }
141
+ };
142
+
143
+ const user: UserProfile = await client.users.create(userData);
57
144
  ```
58
145
 
59
- ### 3. Global Leaderboard
146
+ #### Update User Profile
147
+
148
+ ```typescript
149
+ import { UpdateUserData, UserProfile } from '@rooguys/sdk';
150
+
151
+ // Partial update - only sends provided fields
152
+ const updateData: UpdateUserData = {
153
+ displayName: 'Johnny Doe',
154
+ metadata: { plan: 'enterprise' }
155
+ };
156
+
157
+ const updated: UserProfile = await client.users.update('user_123', updateData);
158
+ ```
60
159
 
61
- Fetch the top players.
160
+ #### Batch User Creation
62
161
 
63
162
  ```typescript
64
- // Get top 10 all-time
65
- const leaderboard = await client.leaderboards.getGlobal('all-time', 1, 10);
163
+ import { CreateUserData, BatchCreateResponse } from '@rooguys/sdk';
66
164
 
67
- leaderboard.rankings.forEach(entry => {
68
- console.log(`#${entry.rank} - ${entry.user_id} (${entry.points} pts)`);
165
+ const users: CreateUserData[] = [
166
+ { userId: 'user_1', displayName: 'User One', email: 'one@example.com' },
167
+ { userId: 'user_2', displayName: 'User Two', email: 'two@example.com' },
168
+ // ... up to 100 users
169
+ ];
170
+
171
+ const response: BatchCreateResponse = await client.users.createBatch(users);
172
+ ```
173
+
174
+ #### Get User Profile with Field Selection
175
+
176
+ ```typescript
177
+ import { GetUserOptions, UserProfile } from '@rooguys/sdk';
178
+
179
+ const options: GetUserOptions = {
180
+ fields: ['points', 'level', 'badges']
181
+ };
182
+
183
+ const user: UserProfile = await client.users.get('user_123', options);
184
+ ```
185
+
186
+ #### Search Users
187
+
188
+ ```typescript
189
+ import { SearchOptions, PaginatedResponse, UserProfile } from '@rooguys/sdk';
190
+
191
+ const options: SearchOptions = {
192
+ page: 1,
193
+ limit: 20,
194
+ fields: ['userId', 'displayName', 'points']
195
+ };
196
+
197
+ const results: PaginatedResponse<UserProfile> = await client.users.search('john', options);
198
+
199
+ results.users?.forEach(user => {
200
+ console.log(`${user.display_name}: ${user.points} points`);
69
201
  });
70
202
  ```
71
203
 
72
- ### 4. Submit Questionnaire Answers
204
+ #### Access Enhanced Profile Data
73
205
 
74
- Submit answers for a user.
206
+ ```typescript
207
+ const user: UserProfile = await client.users.get('user_123');
208
+
209
+ // Activity summary
210
+ if (user.activitySummary) {
211
+ console.log(`Last active: ${user.activitySummary.lastEventAt}`);
212
+ console.log(`Total events: ${user.activitySummary.eventCount}`);
213
+ console.log(`Days active: ${user.activitySummary.daysActive}`);
214
+ }
215
+
216
+ // Streak information
217
+ if (user.streak) {
218
+ console.log(`Current streak: ${user.streak.currentStreak} days`);
219
+ console.log(`Longest streak: ${user.streak.longestStreak} days`);
220
+ }
221
+
222
+ // Inventory summary
223
+ if (user.inventory) {
224
+ console.log(`Items owned: ${user.inventory.itemCount}`);
225
+ console.log(`Active effects: ${user.inventory.activeEffects.join(', ')}`);
226
+ }
227
+ ```
228
+
229
+ ### Leaderboards
230
+
231
+ #### Global Leaderboard with Filters
75
232
 
76
233
  ```typescript
77
- await client.users.submitAnswers('user_123', 'questionnaire_id', [
78
- { question_id: 'q1', answer_option_id: 'opt_a' },
79
- { question_id: 'q2', answer_option_id: 'opt_b' }
80
- ]);
234
+ import { LeaderboardFilterOptions, LeaderboardResult } from '@rooguys/sdk';
235
+
236
+ const options: LeaderboardFilterOptions = {
237
+ timeframe: 'weekly',
238
+ page: 1,
239
+ limit: 10,
240
+ persona: 'competitor',
241
+ minLevel: 5,
242
+ maxLevel: 20,
243
+ startDate: new Date('2024-01-01'),
244
+ endDate: new Date('2024-01-31')
245
+ };
246
+
247
+ const leaderboard: LeaderboardResult = await client.leaderboards.getGlobal(options);
248
+
249
+ // Access cache metadata
250
+ if (leaderboard.cacheMetadata) {
251
+ console.log(`Cached at: ${leaderboard.cacheMetadata.cachedAt}`);
252
+ console.log(`TTL: ${leaderboard.cacheMetadata.ttl}s`);
253
+ }
254
+
255
+ // Rankings include percentile
256
+ leaderboard.rankings.forEach(entry => {
257
+ console.log(`#${entry.rank} ${entry.user_id}: ${entry.points} pts (top ${entry.percentile}%)`);
258
+ });
81
259
  ```
82
260
 
83
- ### 5. Aha Score - Declare User Activation
261
+ #### Custom Leaderboard with Filters
84
262
 
85
- Track when users reach their "Aha Moment" with declarative scores (1-5).
263
+ ```typescript
264
+ const customLb: LeaderboardResult = await client.leaderboards.getCustom('leaderboard_id', {
265
+ page: 1,
266
+ limit: 10,
267
+ persona: 'achiever',
268
+ minLevel: 10
269
+ });
270
+ ```
271
+
272
+ #### "Around Me" View
86
273
 
87
274
  ```typescript
88
- // Declare that a user has reached an activation milestone
89
- const result = await client.aha.declare('user_123', 4);
275
+ import { AroundUserResponse } from '@rooguys/sdk';
276
+
277
+ const aroundMe: AroundUserResponse = await client.leaderboards.getAroundUser(
278
+ 'leaderboard_id',
279
+ 'user_123',
280
+ 5 // 5 entries above and below
281
+ );
90
282
 
91
- console.log(result.message); // "Aha score declared successfully"
283
+ aroundMe.rankings.forEach(entry => {
284
+ const marker = entry.user_id === 'user_123' ? '→' : ' ';
285
+ console.log(`${marker} #${entry.rank} ${entry.user_id}: ${entry.points}`);
286
+ });
92
287
  ```
93
288
 
94
- ### 6. Aha Score - Get User Score
289
+ #### Get User Rank with Percentile
290
+
291
+ ```typescript
292
+ import { UserRank } from '@rooguys/sdk';
293
+
294
+ const rank: UserRank = await client.leaderboards.getUserRank('leaderboard_id', 'user_123');
295
+
296
+ console.log(`Rank: #${rank.rank}`);
297
+ console.log(`Score: ${rank.points}`);
298
+ console.log(`Percentile: top ${rank.percentile}%`);
299
+ ```
95
300
 
96
- Retrieve a user's Aha Score, including declarative and inferred scores.
301
+ ### Health Checks
97
302
 
98
303
  ```typescript
99
- const ahaScore = await client.aha.getUserScore('user_123');
304
+ import { HealthCheckResponse } from '@rooguys/sdk';
100
305
 
101
- console.log(`Current Score: ${ahaScore.data.current_score}`);
102
- console.log(`Status: ${ahaScore.data.status}`); // 'not_started', 'progressing', or 'activated'
103
- console.log(`Declarative Score: ${ahaScore.data.declarative_score}`);
104
- console.log(`Inferred Score: ${ahaScore.data.inferred_score}`);
306
+ // Full health check
307
+ const health: HealthCheckResponse = await client.health.check();
308
+ console.log(`Status: ${health.status}`);
309
+ console.log(`Version: ${health.version}`);
105
310
 
106
- // Access history
107
- if (ahaScore.data.history.initial) {
108
- console.log(`Initial Score: ${ahaScore.data.history.initial}`);
109
- console.log(`Initial Date: ${ahaScore.data.history.initial_date}`);
311
+ // Quick availability check
312
+ const isReady: boolean = await client.health.isReady();
313
+ if (isReady) {
314
+ console.log('API is ready');
110
315
  }
111
316
  ```
112
317
 
318
+ ### Aha Score
319
+
320
+ ```typescript
321
+ import { AhaDeclarationResult, AhaScoreResult } from '@rooguys/sdk';
322
+
323
+ // Declare user activation milestone (1-5)
324
+ const result: AhaDeclarationResult = await client.aha.declare('user_123', 4);
325
+ console.log(result.message);
326
+
327
+ // Get user's aha score
328
+ const score: AhaScoreResult = await client.aha.getUserScore('user_123');
329
+ console.log(`Current Score: ${score.data.current_score}`);
330
+ console.log(`Status: ${score.data.status}`);
331
+ ```
332
+
333
+ ## TypeScript Types
334
+
335
+ The SDK exports comprehensive TypeScript types for all operations:
336
+
337
+ ### Core Types
338
+
339
+ ```typescript
340
+ import {
341
+ // SDK Options
342
+ RooguysOptions,
343
+
344
+ // User Types
345
+ UserProfile,
346
+ CreateUserData,
347
+ UpdateUserData,
348
+ GetUserOptions,
349
+ SearchOptions,
350
+ ActivitySummary,
351
+ StreakInfo,
352
+ InventorySummary,
353
+
354
+ // Event Types
355
+ TrackEventResponse,
356
+ TrackOptions,
357
+ BatchEvent,
358
+ BatchTrackResponse,
359
+ BatchOptions,
360
+
361
+ // Leaderboard Types
362
+ LeaderboardResult,
363
+ LeaderboardFilterOptions,
364
+ LeaderboardEntry,
365
+ AroundUserResponse,
366
+ UserRank,
367
+ Timeframe,
368
+
369
+ // Batch Types
370
+ BatchCreateResponse,
371
+ PaginatedResponse,
372
+
373
+ // Other Types
374
+ Badge,
375
+ Level,
376
+ Questionnaire,
377
+ HealthCheckResponse,
378
+ AhaDeclarationResult,
379
+ AhaScoreResult,
380
+ } from '@rooguys/sdk';
381
+ ```
382
+
383
+ ### HTTP Client Types
384
+
385
+ ```typescript
386
+ import {
387
+ RateLimitInfo,
388
+ CacheMetadata,
389
+ Pagination,
390
+ ApiResponse,
391
+ RequestConfig,
392
+ HttpClientOptions,
393
+ } from '@rooguys/sdk';
394
+ ```
395
+
113
396
  ## API Reference
114
397
 
115
398
  ### Events
116
399
 
117
- - `track(eventName: string, userId: string, properties?: object, options?: { includeProfile?: boolean }): Promise<TrackEventResponse>`
400
+ | Method | Return Type | Description |
401
+ |--------|-------------|-------------|
402
+ | `track(eventName, userId, properties?, options?)` | `Promise<TrackEventResponse>` | Track a single event |
403
+ | `trackBatch(events, options?)` | `Promise<BatchTrackResponse>` | Track multiple events (max 100) |
404
+ | `trackLegacy(eventName, userId, properties?, options?)` | `Promise<TrackEventResponse>` | **Deprecated** |
118
405
 
119
406
  ### Users
120
407
 
121
- - `get(userId: string): Promise<UserProfile>`
122
- - `getBulk(userIds: string[]): Promise<UserProfile[]>`
123
- - `getBadges(userId: string): Promise<Badge[]>`
124
- - `getRank(userId: string, timeframe?: 'all-time' | 'weekly' | 'monthly'): Promise<RankInfo>`
125
- - `submitAnswers(userId: string, questionnaireId: string, answers: Answer[]): Promise<void>`
408
+ | Method | Return Type | Description |
409
+ |--------|-------------|-------------|
410
+ | `create(userData)` | `Promise<UserProfile>` | Create a new user |
411
+ | `update(userId, userData)` | `Promise<UserProfile>` | Update user profile |
412
+ | `createBatch(users)` | `Promise<BatchCreateResponse>` | Create multiple users (max 100) |
413
+ | `get(userId, options?)` | `Promise<UserProfile>` | Get user profile |
414
+ | `search(query, options?)` | `Promise<PaginatedResponse<UserProfile>>` | Search users |
415
+ | `getBulk(userIds)` | `Promise<{ users: UserProfile[] }>` | Get multiple profiles |
416
+ | `getBadges(userId)` | `Promise<{ badges: UserBadge[] }>` | Get user's badges |
417
+ | `getRank(userId, timeframe?)` | `Promise<UserRank>` | Get user's global rank |
418
+ | `submitAnswers(userId, questionnaireId, answers)` | `Promise<{ status, message }>` | Submit answers |
126
419
 
127
420
  ### Leaderboards
128
421
 
129
- - `getGlobal(timeframe?: 'all-time' | 'weekly' | 'monthly', page?: number, limit?: number): Promise<LeaderboardResponse>`
130
- - `list(page?: number, limit?: number, search?: string): Promise<LeaderboardListResponse>`
131
- - `getCustom(leaderboardId: string, page?: number, limit?: number, search?: string): Promise<LeaderboardResponse>`
132
- - `getUserRank(leaderboardId: string, userId: string): Promise<RankInfo>`
422
+ | Method | Return Type | Description |
423
+ |--------|-------------|-------------|
424
+ | `getGlobal(timeframeOrOptions?, page?, limit?, options?)` | `Promise<LeaderboardResult>` | Get global leaderboard |
425
+ | `list(pageOrOptions?, limit?, search?)` | `Promise<LeaderboardListResult>` | List all leaderboards |
426
+ | `getCustom(leaderboardId, pageOrOptions?, ...)` | `Promise<LeaderboardResult>` | Get custom leaderboard |
427
+ | `getUserRank(leaderboardId, userId)` | `Promise<UserRank>` | Get user's rank |
428
+ | `getAroundUser(leaderboardId, userId, range?)` | `Promise<AroundUserResponse>` | Get entries around user |
133
429
 
134
430
  ### Badges
135
431
 
136
- - `list(page?: number, limit?: number, activeOnly?: boolean): Promise<BadgeListResponse>`
432
+ | Method | Return Type | Description |
433
+ |--------|-------------|-------------|
434
+ | `list(page?, limit?, activeOnly?)` | `Promise<BadgeListResult>` | List all badges |
137
435
 
138
436
  ### Levels
139
437
 
140
- - `list(page?: number, limit?: number): Promise<LevelListResponse>`
438
+ | Method | Return Type | Description |
439
+ |--------|-------------|-------------|
440
+ | `list(page?, limit?)` | `Promise<LevelListResult>` | List all levels |
141
441
 
142
442
  ### Questionnaires
143
443
 
144
- - `get(slug: string): Promise<Questionnaire>`
145
- - `getActive(): Promise<Questionnaire>`
444
+ | Method | Return Type | Description |
445
+ |--------|-------------|-------------|
446
+ | `get(slug)` | `Promise<Questionnaire>` | Get questionnaire by slug |
447
+ | `getActive()` | `Promise<Questionnaire>` | Get active questionnaire |
146
448
 
147
449
  ### Aha Score
148
450
 
149
- - `declare(userId: string, value: number): Promise<AhaDeclarationResult>`
150
- - Declare a user's Aha Moment score (value must be between 1-5)
151
- - `getUserScore(userId: string): Promise<AhaScoreResult>`
152
- - Retrieve a user's current Aha Score and history
451
+ | Method | Return Type | Description |
452
+ |--------|-------------|-------------|
453
+ | `declare(userId, value)` | `Promise<AhaDeclarationResult>` | Declare aha score (1-5) |
454
+ | `getUserScore(userId)` | `Promise<AhaScoreResult>` | Get user's aha score |
455
+
456
+ ### Health
457
+
458
+ | Method | Return Type | Description |
459
+ |--------|-------------|-------------|
460
+ | `check()` | `Promise<HealthCheckResponse>` | Get full health status |
461
+ | `isReady()` | `Promise<boolean>` | Quick availability check |
153
462
 
154
463
  ## Error Handling
155
464
 
156
- The SDK throws errors for non-2xx responses.
465
+ The SDK provides typed error classes for different error scenarios:
157
466
 
158
467
  ```typescript
468
+ import {
469
+ Rooguys,
470
+ ValidationError,
471
+ AuthenticationError,
472
+ NotFoundError,
473
+ ConflictError,
474
+ RateLimitError,
475
+ ServerError,
476
+ FieldError
477
+ } from '@rooguys/sdk';
478
+
159
479
  try {
160
- await client.users.get('non_existent_user');
480
+ await client.users.create({ userId: 'user_123', email: 'invalid-email' });
161
481
  } catch (error) {
162
- console.error(error.message); // "Rooguys API Error: User not found"
482
+ if (error instanceof ValidationError) {
483
+ console.error('Validation failed:', error.message);
484
+ console.error('Field errors:', error.fieldErrors);
485
+ console.error('Error code:', error.code);
486
+ } else if (error instanceof AuthenticationError) {
487
+ console.error('Invalid API key');
488
+ } else if (error instanceof NotFoundError) {
489
+ console.error('Resource not found');
490
+ } else if (error instanceof ConflictError) {
491
+ console.error('Resource already exists');
492
+ } else if (error instanceof RateLimitError) {
493
+ console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
494
+ } else if (error instanceof ServerError) {
495
+ console.error('Server error:', error.message);
496
+ }
497
+
498
+ // All errors include requestId for debugging
499
+ if (error instanceof RooguysError) {
500
+ console.error('Request ID:', error.requestId);
501
+ }
163
502
  }
164
503
  ```
165
504
 
166
- ### Validation Errors
505
+ ### Error Types
506
+
507
+ | Error Class | HTTP Status | Properties |
508
+ |-------------|-------------|------------|
509
+ | `ValidationError` | 400 | `fieldErrors?: FieldError[]` |
510
+ | `AuthenticationError` | 401 | - |
511
+ | `ForbiddenError` | 403 | - |
512
+ | `NotFoundError` | 404 | - |
513
+ | `ConflictError` | 409 | - |
514
+ | `RateLimitError` | 429 | `retryAfter: number` |
515
+ | `ServerError` | 500+ | - |
516
+
517
+ ### Common Error Properties
518
+
519
+ All errors extend `RooguysError` and include:
520
+ - `message: string` - Human-readable error message
521
+ - `code: string` - Machine-readable error code
522
+ - `requestId?: string` - Unique request identifier
523
+ - `statusCode: number` - HTTP status code
524
+
525
+ ### FieldError Type
167
526
 
168
527
  ```typescript
169
- try {
170
- // Invalid Aha Score value (must be 1-5)
171
- await client.aha.declare('user_123', 10);
172
- } catch (error) {
173
- console.error(error.message); // "Aha score value must be between 1 and 5"
528
+ interface FieldError {
529
+ field: string;
530
+ message: string;
174
531
  }
175
532
  ```
176
533
 
177
- ## Testing
534
+ ## Rate Limiting
178
535
 
179
- The SDK includes comprehensive test coverage with both unit tests and property-based tests.
536
+ The SDK provides built-in rate limit handling:
180
537
 
181
- ### Running Tests
538
+ ```typescript
539
+ const client = new Rooguys('YOUR_API_KEY', {
540
+ // Get notified when 80% of rate limit is consumed
541
+ onRateLimitWarning: (info: RateLimitInfo) => {
542
+ console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
543
+ console.warn(`Resets at: ${new Date(info.reset * 1000)}`);
544
+ },
545
+
546
+ // Automatically retry rate-limited requests
547
+ autoRetry: true,
548
+ maxRetries: 3
549
+ });
550
+ ```
182
551
 
183
- ```bash
184
- npm test
552
+ ### RateLimitInfo Type
553
+
554
+ ```typescript
555
+ interface RateLimitInfo {
556
+ limit: number; // Total requests allowed
557
+ remaining: number; // Requests remaining
558
+ reset: number; // Unix timestamp when limit resets
559
+ }
185
560
  ```
186
561
 
187
- ### Test Coverage
562
+ ## Testing
188
563
 
189
564
  ```bash
190
- npm run test:coverage
565
+ npm test # Run all tests
566
+ npm run test:coverage # Run with coverage report
191
567
  ```
192
568
 
193
- The SDK maintains >90% test coverage across all modules, including:
569
+ The SDK maintains >90% test coverage with:
194
570
  - Unit tests for all API methods
195
571
  - Property-based tests using fast-check
196
- - Error handling and edge case validation
197
- - Concurrent request handling
198
-
199
- ### Property-Based Testing
200
-
201
- The SDK uses [fast-check](https://github.com/dubzzz/fast-check) for property-based testing to verify correctness across a wide range of inputs:
202
-
203
- ```typescript
204
- // Example: Verifying HTTP request construction
205
- fc.assert(
206
- fc.property(
207
- arbitraries.eventName(),
208
- arbitraries.userId(),
209
- async (eventName, userId) => {
210
- // Test that any valid event name and user ID constructs a valid request
211
- await client.events.track(eventName, userId);
212
- // Assertions verify correct HTTP method, URL, headers, and body
213
- }
214
- ),
215
- { numRuns: 100 }
216
- );
217
- ```
572
+ - TypeScript type validation
573
+ - Error handling tests
574
+
575
+ ## Requirements
576
+
577
+ - Node.js >= 18.x
578
+ - TypeScript >= 4.7 (for TypeScript users)
579
+
580
+ ## License
581
+
582
+ MIT