@rooguys/js 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 +342 -141
- package/package.json +1 -1
- package/src/__tests__/fixtures/responses.js +249 -0
- package/src/__tests__/property/batch-event-validation.property.test.js +225 -0
- package/src/__tests__/property/email-validation.property.test.js +272 -0
- package/src/__tests__/property/error-mapping.property.test.js +506 -0
- package/src/__tests__/property/field-selection.property.test.js +297 -0
- package/src/__tests__/property/idempotency-key.property.test.js +350 -0
- package/src/__tests__/property/leaderboard-filter.property.test.js +585 -0
- package/src/__tests__/property/partial-update.property.test.js +251 -0
- package/src/__tests__/property/rate-limit-error.property.test.js +276 -0
- package/src/__tests__/property/rate-limit-extraction.property.test.js +193 -0
- package/src/__tests__/property/request-construction.property.test.js +20 -28
- package/src/__tests__/property/response-format.property.test.js +418 -0
- package/src/__tests__/property/response-parsing.property.test.js +16 -21
- package/src/__tests__/property/timestamp-validation.property.test.js +345 -0
- package/src/__tests__/unit/aha.test.js +57 -26
- package/src/__tests__/unit/config.test.js +7 -1
- package/src/__tests__/unit/errors.test.js +6 -8
- package/src/__tests__/unit/events.test.js +253 -14
- package/src/__tests__/unit/leaderboards.test.js +249 -0
- package/src/__tests__/unit/questionnaires.test.js +6 -6
- package/src/__tests__/unit/users.test.js +275 -12
- package/src/__tests__/utils/generators.js +87 -0
- package/src/__tests__/utils/mockClient.js +71 -5
- package/src/errors.js +156 -0
- package/src/http-client.js +276 -0
- package/src/index.js +856 -66
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rooguys
|
|
1
|
+
# Rooguys JavaScript SDK
|
|
2
2
|
|
|
3
3
|
The official Browser SDK for the Rooguys Gamification API. Lightweight and dependency-free (uses native `fetch`).
|
|
4
4
|
|
|
@@ -21,257 +21,458 @@ import Rooguys from '@rooguys/js';
|
|
|
21
21
|
|
|
22
22
|
const client = new Rooguys('YOUR_API_KEY', {
|
|
23
23
|
baseUrl: 'https://api.rooguys.com/v1',
|
|
24
|
+
timeout: 10000,
|
|
25
|
+
// Rate limit handling
|
|
26
|
+
onRateLimitWarning: (info) => {
|
|
27
|
+
console.warn(`Rate limit warning: ${info.remaining}/${info.limit} remaining`);
|
|
28
|
+
},
|
|
29
|
+
autoRetry: true,
|
|
30
|
+
maxRetries: 3,
|
|
24
31
|
});
|
|
25
32
|
```
|
|
26
33
|
|
|
27
|
-
##
|
|
34
|
+
## Migration Guide (v1.x to v2.x)
|
|
28
35
|
|
|
29
|
-
###
|
|
36
|
+
### Breaking Changes
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
1. **Event Tracking Endpoint**: The SDK now uses `/v1/events` instead of `/v1/event`
|
|
39
|
+
```javascript
|
|
40
|
+
// Old (deprecated, still works with warning)
|
|
41
|
+
client.events.trackLegacy('event_name', 'user_id', properties);
|
|
42
|
+
|
|
43
|
+
// New (recommended)
|
|
44
|
+
client.events.track('event-name', 'user_id', properties);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Global Leaderboard Endpoint**: Now uses `/v1/leaderboards/global` with `timeframe` query parameter
|
|
48
|
+
```javascript
|
|
49
|
+
// Both signatures work
|
|
50
|
+
client.leaderboards.getGlobal('weekly', 1, 10);
|
|
51
|
+
client.leaderboards.getGlobal({ timeframe: 'weekly', page: 1, limit: 10 });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
3. **Response Format**: All responses now follow standardized format `{ success: true, data: {...} }`
|
|
55
|
+
|
|
56
|
+
### New Features
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
- Batch event tracking (`events.trackBatch`)
|
|
59
|
+
- User management (`users.create`, `users.update`, `users.createBatch`)
|
|
60
|
+
- User search (`users.search`)
|
|
61
|
+
- Field selection for user profiles
|
|
62
|
+
- Leaderboard filters (persona, level range, date range)
|
|
63
|
+
- "Around me" leaderboard view (`leaderboards.getAroundUser`)
|
|
64
|
+
- Health check endpoints
|
|
65
|
+
- Rate limit handling with auto-retry
|
|
66
|
+
- Typed error classes
|
|
67
|
+
|
|
68
|
+
## Usage Examples
|
|
69
|
+
|
|
70
|
+
### Events
|
|
71
|
+
|
|
72
|
+
#### Track a Single Event
|
|
40
73
|
|
|
41
74
|
```javascript
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
const response = await client.events.track('level-completed', 'user_123', {
|
|
76
|
+
difficulty: 'hard',
|
|
77
|
+
score: 1500
|
|
78
|
+
}, {
|
|
79
|
+
includeProfile: true,
|
|
80
|
+
idempotencyKey: 'unique-request-id'
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log('Event tracked:', response.status);
|
|
84
|
+
if (response.profile) {
|
|
85
|
+
console.log('Updated points:', response.profile.points);
|
|
50
86
|
}
|
|
51
87
|
```
|
|
52
88
|
|
|
53
|
-
|
|
89
|
+
#### Track Events with Custom Timestamp
|
|
54
90
|
|
|
55
91
|
```javascript
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.join('');
|
|
63
|
-
}
|
|
92
|
+
// Track historical events (up to 7 days in the past)
|
|
93
|
+
const response = await client.events.track('purchase', 'user_123', {
|
|
94
|
+
amount: 99.99
|
|
95
|
+
}, {
|
|
96
|
+
timestamp: new Date('2024-01-15T10:30:00Z')
|
|
97
|
+
});
|
|
64
98
|
```
|
|
65
99
|
|
|
66
|
-
|
|
100
|
+
#### Batch Event Tracking
|
|
67
101
|
|
|
68
102
|
```javascript
|
|
69
|
-
//
|
|
70
|
-
const
|
|
103
|
+
// Track up to 100 events in a single request
|
|
104
|
+
const response = await client.events.trackBatch([
|
|
105
|
+
{ eventName: 'page-view', userId: 'user_123', properties: { page: '/home' } },
|
|
106
|
+
{ eventName: 'button-click', userId: 'user_123', properties: { button: 'signup' } },
|
|
107
|
+
{ eventName: 'purchase', userId: 'user_456', properties: { amount: 50 }, timestamp: new Date() }
|
|
108
|
+
], {
|
|
109
|
+
idempotencyKey: 'batch-123'
|
|
110
|
+
});
|
|
71
111
|
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
// Check individual results
|
|
113
|
+
response.results.forEach((result, index) => {
|
|
114
|
+
if (result.status === 'queued') {
|
|
115
|
+
console.log(`Event ${index} queued successfully`);
|
|
116
|
+
} else {
|
|
117
|
+
console.error(`Event ${index} failed:`, result.error);
|
|
118
|
+
}
|
|
74
119
|
});
|
|
75
120
|
```
|
|
76
121
|
|
|
77
|
-
###
|
|
122
|
+
### Users
|
|
78
123
|
|
|
79
|
-
|
|
80
|
-
// Get rankings for a specific leaderboard
|
|
81
|
-
const customLb = await client.leaderboards.getCustom('leaderboard_id', 1, 10);
|
|
124
|
+
#### Create a New User
|
|
82
125
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
```javascript
|
|
127
|
+
const user = await client.users.create({
|
|
128
|
+
userId: 'user_123',
|
|
129
|
+
displayName: 'John Doe',
|
|
130
|
+
email: 'john@example.com',
|
|
131
|
+
firstName: 'John',
|
|
132
|
+
lastName: 'Doe',
|
|
133
|
+
metadata: { plan: 'premium' }
|
|
86
134
|
});
|
|
87
135
|
```
|
|
88
136
|
|
|
89
|
-
|
|
137
|
+
#### Update User Profile
|
|
90
138
|
|
|
91
139
|
```javascript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
// Partial update - only sends provided fields
|
|
141
|
+
const updated = await client.users.update('user_123', {
|
|
142
|
+
displayName: 'Johnny Doe',
|
|
143
|
+
metadata: { plan: 'enterprise' }
|
|
144
|
+
});
|
|
95
145
|
```
|
|
96
146
|
|
|
97
|
-
|
|
147
|
+
#### Batch User Creation
|
|
98
148
|
|
|
99
149
|
```javascript
|
|
100
|
-
|
|
101
|
-
|
|
150
|
+
const response = await client.users.createBatch([
|
|
151
|
+
{ userId: 'user_1', displayName: 'User One', email: 'one@example.com' },
|
|
152
|
+
{ userId: 'user_2', displayName: 'User Two', email: 'two@example.com' },
|
|
153
|
+
// ... up to 100 users
|
|
154
|
+
]);
|
|
155
|
+
```
|
|
102
156
|
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
#### Get User Profile with Field Selection
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// Only fetch specific fields
|
|
161
|
+
const user = await client.users.get('user_123', {
|
|
162
|
+
fields: ['points', 'level', 'badges']
|
|
105
163
|
});
|
|
106
164
|
```
|
|
107
165
|
|
|
108
|
-
|
|
166
|
+
#### Search Users
|
|
109
167
|
|
|
110
168
|
```javascript
|
|
111
|
-
const
|
|
169
|
+
const results = await client.users.search('john', {
|
|
170
|
+
page: 1,
|
|
171
|
+
limit: 20,
|
|
172
|
+
fields: ['userId', 'displayName', 'points']
|
|
173
|
+
});
|
|
112
174
|
|
|
113
|
-
|
|
114
|
-
console.log(
|
|
175
|
+
results.users.forEach(user => {
|
|
176
|
+
console.log(`${user.displayName}: ${user.points} points`);
|
|
115
177
|
});
|
|
116
178
|
```
|
|
117
179
|
|
|
118
|
-
|
|
180
|
+
#### Access Enhanced Profile Data
|
|
119
181
|
|
|
120
182
|
```javascript
|
|
121
|
-
|
|
122
|
-
const questionnaire = await client.questionnaires.get('onboarding-survey');
|
|
183
|
+
const user = await client.users.get('user_123');
|
|
123
184
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.log(`
|
|
127
|
-
});
|
|
185
|
+
// Activity summary
|
|
186
|
+
if (user.activitySummary) {
|
|
187
|
+
console.log(`Last active: ${user.activitySummary.lastEventAt}`);
|
|
188
|
+
console.log(`Total events: ${user.activitySummary.eventCount}`);
|
|
189
|
+
console.log(`Days active: ${user.activitySummary.daysActive}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Streak information
|
|
193
|
+
if (user.streak) {
|
|
194
|
+
console.log(`Current streak: ${user.streak.currentStreak} days`);
|
|
195
|
+
console.log(`Longest streak: ${user.streak.longestStreak} days`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Inventory summary
|
|
199
|
+
if (user.inventory) {
|
|
200
|
+
console.log(`Items owned: ${user.inventory.itemCount}`);
|
|
201
|
+
console.log(`Active effects: ${user.inventory.activeEffects.join(', ')}`);
|
|
202
|
+
}
|
|
128
203
|
```
|
|
129
204
|
|
|
130
|
-
###
|
|
205
|
+
### Leaderboards
|
|
206
|
+
|
|
207
|
+
#### Global Leaderboard with Filters
|
|
131
208
|
|
|
132
209
|
```javascript
|
|
133
|
-
|
|
210
|
+
// Using options object (recommended)
|
|
211
|
+
const leaderboard = await client.leaderboards.getGlobal({
|
|
212
|
+
timeframe: 'weekly',
|
|
213
|
+
page: 1,
|
|
214
|
+
limit: 10,
|
|
215
|
+
persona: 'competitor',
|
|
216
|
+
minLevel: 5,
|
|
217
|
+
maxLevel: 20,
|
|
218
|
+
startDate: new Date('2024-01-01'),
|
|
219
|
+
endDate: new Date('2024-01-31')
|
|
220
|
+
});
|
|
134
221
|
|
|
135
|
-
|
|
136
|
-
|
|
222
|
+
// Access cache metadata
|
|
223
|
+
if (leaderboard.cacheMetadata) {
|
|
224
|
+
console.log(`Cached at: ${leaderboard.cacheMetadata.cachedAt}`);
|
|
225
|
+
console.log(`TTL: ${leaderboard.cacheMetadata.ttl}s`);
|
|
137
226
|
}
|
|
227
|
+
|
|
228
|
+
// Rankings include percentile
|
|
229
|
+
leaderboard.rankings.forEach(entry => {
|
|
230
|
+
console.log(`#${entry.rank} ${entry.userId}: ${entry.score} pts (top ${entry.percentile}%)`);
|
|
231
|
+
});
|
|
138
232
|
```
|
|
139
233
|
|
|
140
|
-
|
|
234
|
+
#### Custom Leaderboard with Filters
|
|
141
235
|
|
|
142
|
-
|
|
236
|
+
```javascript
|
|
237
|
+
const customLb = await client.leaderboards.getCustom('leaderboard_id', {
|
|
238
|
+
page: 1,
|
|
239
|
+
limit: 10,
|
|
240
|
+
persona: 'achiever',
|
|
241
|
+
minLevel: 10
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### "Around Me" View
|
|
143
246
|
|
|
144
247
|
```javascript
|
|
145
|
-
//
|
|
146
|
-
const
|
|
248
|
+
// Get entries around a specific user
|
|
249
|
+
const aroundMe = await client.leaderboards.getAroundUser(
|
|
250
|
+
'leaderboard_id',
|
|
251
|
+
'user_123',
|
|
252
|
+
5 // 5 entries above and below
|
|
253
|
+
);
|
|
147
254
|
|
|
148
|
-
|
|
255
|
+
aroundMe.rankings.forEach(entry => {
|
|
256
|
+
const marker = entry.userId === 'user_123' ? '→' : ' ';
|
|
257
|
+
console.log(`${marker} #${entry.rank} ${entry.userId}: ${entry.score}`);
|
|
258
|
+
});
|
|
149
259
|
```
|
|
150
260
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Retrieve a user's Aha Score, including declarative and inferred scores.
|
|
261
|
+
#### Get User Rank with Percentile
|
|
154
262
|
|
|
155
263
|
```javascript
|
|
156
|
-
const
|
|
264
|
+
const rank = await client.leaderboards.getUserRank('leaderboard_id', 'user_123');
|
|
265
|
+
|
|
266
|
+
console.log(`Rank: #${rank.rank}`);
|
|
267
|
+
console.log(`Score: ${rank.score}`);
|
|
268
|
+
console.log(`Percentile: top ${rank.percentile}%`);
|
|
269
|
+
```
|
|
157
270
|
|
|
158
|
-
|
|
159
|
-
console.log(`Status: ${ahaScore.data.status}`); // 'not_started', 'progressing', or 'activated'
|
|
160
|
-
console.log(`Declarative Score: ${ahaScore.data.declarative_score}`);
|
|
161
|
-
console.log(`Inferred Score: ${ahaScore.data.inferred_score}`);
|
|
271
|
+
### Health Checks
|
|
162
272
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
273
|
+
```javascript
|
|
274
|
+
// Full health check
|
|
275
|
+
const health = await client.health.check();
|
|
276
|
+
console.log(`Status: ${health.status}`);
|
|
277
|
+
console.log(`Version: ${health.version}`);
|
|
278
|
+
|
|
279
|
+
// Quick availability check
|
|
280
|
+
const isReady = await client.health.isReady();
|
|
281
|
+
if (isReady) {
|
|
282
|
+
console.log('API is ready');
|
|
167
283
|
}
|
|
168
284
|
```
|
|
169
285
|
|
|
286
|
+
### Aha Score
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// Declare user activation milestone (1-5)
|
|
290
|
+
const result = await client.aha.declare('user_123', 4);
|
|
291
|
+
console.log(result.message);
|
|
292
|
+
|
|
293
|
+
// Get user's aha score
|
|
294
|
+
const score = await client.aha.getUserScore('user_123');
|
|
295
|
+
console.log(`Current Score: ${score.data.current_score}`);
|
|
296
|
+
console.log(`Status: ${score.data.status}`);
|
|
297
|
+
```
|
|
298
|
+
|
|
170
299
|
## API Reference
|
|
171
300
|
|
|
172
301
|
### Events
|
|
173
302
|
|
|
174
|
-
|
|
303
|
+
| Method | Description |
|
|
304
|
+
|--------|-------------|
|
|
305
|
+
| `track(eventName, userId, properties?, options?)` | Track a single event |
|
|
306
|
+
| `trackBatch(events, options?)` | Track multiple events (max 100) |
|
|
307
|
+
| `trackLegacy(eventName, userId, properties?, options?)` | **Deprecated** - Use `track()` |
|
|
175
308
|
|
|
176
309
|
### Users
|
|
177
310
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
311
|
+
| Method | Description |
|
|
312
|
+
|--------|-------------|
|
|
313
|
+
| `create(userData)` | Create a new user |
|
|
314
|
+
| `update(userId, userData)` | Update user profile (partial update) |
|
|
315
|
+
| `createBatch(users)` | Create multiple users (max 100) |
|
|
316
|
+
| `get(userId, options?)` | Get user profile with optional field selection |
|
|
317
|
+
| `search(query, options?)` | Search users with pagination |
|
|
318
|
+
| `getBulk(userIds)` | Get multiple user profiles |
|
|
319
|
+
| `getBadges(userId)` | Get user's badges |
|
|
320
|
+
| `getRank(userId, timeframe?)` | Get user's global rank |
|
|
321
|
+
| `submitAnswers(userId, questionnaireId, answers)` | Submit questionnaire answers |
|
|
183
322
|
|
|
184
323
|
### Leaderboards
|
|
185
324
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
325
|
+
| Method | Description |
|
|
326
|
+
|--------|-------------|
|
|
327
|
+
| `getGlobal(timeframeOrOptions?, page?, limit?, options?)` | Get global leaderboard with filters |
|
|
328
|
+
| `list(pageOrOptions?, limit?, search?)` | List all leaderboards |
|
|
329
|
+
| `getCustom(leaderboardId, pageOrOptions?, limit?, search?, options?)` | Get custom leaderboard with filters |
|
|
330
|
+
| `getUserRank(leaderboardId, userId)` | Get user's rank in leaderboard |
|
|
331
|
+
| `getAroundUser(leaderboardId, userId, range?)` | Get entries around a user |
|
|
190
332
|
|
|
191
333
|
### Badges
|
|
192
334
|
|
|
193
|
-
|
|
335
|
+
| Method | Description |
|
|
336
|
+
|--------|-------------|
|
|
337
|
+
| `list(page?, limit?, activeOnly?)` | List all badges |
|
|
194
338
|
|
|
195
339
|
### Levels
|
|
196
340
|
|
|
197
|
-
|
|
341
|
+
| Method | Description |
|
|
342
|
+
|--------|-------------|
|
|
343
|
+
| `list(page?, limit?)` | List all levels |
|
|
198
344
|
|
|
199
345
|
### Questionnaires
|
|
200
346
|
|
|
201
|
-
|
|
202
|
-
|
|
347
|
+
| Method | Description |
|
|
348
|
+
|--------|-------------|
|
|
349
|
+
| `get(slug)` | Get questionnaire by slug |
|
|
350
|
+
| `getActive()` | Get active questionnaire |
|
|
203
351
|
|
|
204
352
|
### Aha Score
|
|
205
353
|
|
|
206
|
-
|
|
207
|
-
|
|
354
|
+
| Method | Description |
|
|
355
|
+
|--------|-------------|
|
|
356
|
+
| `declare(userId, value)` | Declare aha score (1-5) |
|
|
357
|
+
| `getUserScore(userId)` | Get user's aha score |
|
|
358
|
+
|
|
359
|
+
### Health
|
|
360
|
+
|
|
361
|
+
| Method | Description |
|
|
362
|
+
|--------|-------------|
|
|
363
|
+
| `check()` | Get full health status |
|
|
364
|
+
| `isReady()` | Quick availability check |
|
|
208
365
|
|
|
209
366
|
## Error Handling
|
|
210
367
|
|
|
368
|
+
The SDK provides typed error classes for different error scenarios:
|
|
369
|
+
|
|
211
370
|
```javascript
|
|
371
|
+
import Rooguys, {
|
|
372
|
+
ValidationError,
|
|
373
|
+
AuthenticationError,
|
|
374
|
+
NotFoundError,
|
|
375
|
+
ConflictError,
|
|
376
|
+
RateLimitError,
|
|
377
|
+
ServerError
|
|
378
|
+
} from '@rooguys/js';
|
|
379
|
+
|
|
212
380
|
try {
|
|
213
|
-
await client.users.
|
|
381
|
+
await client.users.create({ userId: 'user_123', email: 'invalid-email' });
|
|
214
382
|
} catch (error) {
|
|
215
|
-
|
|
383
|
+
if (error instanceof ValidationError) {
|
|
384
|
+
console.error('Validation failed:', error.message);
|
|
385
|
+
console.error('Field errors:', error.fieldErrors);
|
|
386
|
+
console.error('Error code:', error.code);
|
|
387
|
+
} else if (error instanceof AuthenticationError) {
|
|
388
|
+
console.error('Invalid API key');
|
|
389
|
+
} else if (error instanceof NotFoundError) {
|
|
390
|
+
console.error('Resource not found');
|
|
391
|
+
} else if (error instanceof ConflictError) {
|
|
392
|
+
console.error('Resource already exists');
|
|
393
|
+
} else if (error instanceof RateLimitError) {
|
|
394
|
+
console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
|
|
395
|
+
} else if (error instanceof ServerError) {
|
|
396
|
+
console.error('Server error:', error.message);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// All errors include requestId for debugging
|
|
400
|
+
console.error('Request ID:', error.requestId);
|
|
216
401
|
}
|
|
217
402
|
```
|
|
218
403
|
|
|
219
|
-
###
|
|
404
|
+
### Error Types
|
|
220
405
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
406
|
+
| Error Class | HTTP Status | Description |
|
|
407
|
+
|-------------|-------------|-------------|
|
|
408
|
+
| `ValidationError` | 400 | Invalid input data |
|
|
409
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
410
|
+
| `ForbiddenError` | 403 | Insufficient permissions |
|
|
411
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
412
|
+
| `ConflictError` | 409 | Resource already exists |
|
|
413
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
414
|
+
| `ServerError` | 500+ | Server-side error |
|
|
229
415
|
|
|
230
|
-
|
|
416
|
+
### Error Properties
|
|
231
417
|
|
|
232
|
-
|
|
418
|
+
All errors include:
|
|
419
|
+
- `message` - Human-readable error message
|
|
420
|
+
- `code` - Machine-readable error code (e.g., `INVALID_EMAIL`, `USER_NOT_FOUND`)
|
|
421
|
+
- `requestId` - Unique request identifier for debugging
|
|
422
|
+
- `statusCode` - HTTP status code
|
|
233
423
|
|
|
234
|
-
|
|
424
|
+
`ValidationError` also includes:
|
|
425
|
+
- `fieldErrors` - Array of `{ field, message }` for field-level errors
|
|
235
426
|
|
|
236
|
-
|
|
237
|
-
|
|
427
|
+
`RateLimitError` also includes:
|
|
428
|
+
- `retryAfter` - Seconds until rate limit resets
|
|
429
|
+
|
|
430
|
+
## Rate Limiting
|
|
431
|
+
|
|
432
|
+
The SDK provides built-in rate limit handling:
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
const client = new Rooguys('YOUR_API_KEY', {
|
|
436
|
+
// Get notified when 80% of rate limit is consumed
|
|
437
|
+
onRateLimitWarning: (info) => {
|
|
438
|
+
console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
|
|
439
|
+
console.warn(`Resets at: ${new Date(info.reset * 1000)}`);
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// Automatically retry rate-limited requests
|
|
443
|
+
autoRetry: true,
|
|
444
|
+
maxRetries: 3
|
|
445
|
+
});
|
|
238
446
|
```
|
|
239
447
|
|
|
240
|
-
|
|
448
|
+
Rate limit info is available in response metadata:
|
|
449
|
+
```javascript
|
|
450
|
+
const response = await client._httpClient.get('/users/user_123');
|
|
451
|
+
console.log('Rate limit:', response.rateLimit);
|
|
452
|
+
// { limit: 1000, remaining: 950, reset: 1704067200 }
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Testing
|
|
241
456
|
|
|
242
457
|
```bash
|
|
243
|
-
npm
|
|
458
|
+
npm test # Run all tests
|
|
459
|
+
npm run test:coverage # Run with coverage report
|
|
244
460
|
```
|
|
245
461
|
|
|
246
|
-
The SDK maintains >90% test coverage
|
|
462
|
+
The SDK maintains >90% test coverage with:
|
|
247
463
|
- Unit tests for all API methods
|
|
248
464
|
- Property-based tests using fast-check
|
|
249
|
-
- Error handling
|
|
250
|
-
-
|
|
251
|
-
|
|
252
|
-
### Property-Based Testing
|
|
253
|
-
|
|
254
|
-
The SDK uses [fast-check](https://github.com/dubzzz/fast-check) for property-based testing to verify correctness across a wide range of inputs:
|
|
255
|
-
|
|
256
|
-
```javascript
|
|
257
|
-
// Example: Verifying response parsing preservation
|
|
258
|
-
fc.assert(
|
|
259
|
-
fc.property(
|
|
260
|
-
fc.jsonValue(),
|
|
261
|
-
async (responseData) => {
|
|
262
|
-
// Test that any valid JSON response is parsed correctly
|
|
263
|
-
// and data structure is preserved
|
|
264
|
-
}
|
|
265
|
-
),
|
|
266
|
-
{ numRuns: 100 }
|
|
267
|
-
);
|
|
268
|
-
```
|
|
465
|
+
- Error handling validation
|
|
466
|
+
- Rate limit handling tests
|
|
269
467
|
|
|
270
468
|
## Requirements
|
|
271
469
|
|
|
272
|
-
|
|
273
|
-
- `fetch` API
|
|
274
|
-
- `Promise`
|
|
275
|
-
- `AbortController` (for timeouts)
|
|
470
|
+
- Browser with support for:
|
|
471
|
+
- `fetch` API
|
|
472
|
+
- `Promise`
|
|
473
|
+
- `AbortController` (for timeouts)
|
|
474
|
+
- Most modern browsers support these features
|
|
475
|
+
|
|
476
|
+
## License
|
|
276
477
|
|
|
277
|
-
|
|
478
|
+
MIT
|