@rooguys/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/README.md +217 -0
- package/dist/__tests__/fixtures/responses.d.ts +272 -0
- package/dist/__tests__/fixtures/responses.js +311 -0
- package/dist/__tests__/utils/generators.d.ts +28 -0
- package/dist/__tests__/utils/generators.js +52 -0
- package/dist/__tests__/utils/mockClient.d.ts +22 -0
- package/dist/__tests__/utils/mockClient.js +35 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +222 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +2 -0
- package/jest.config.js +31 -0
- package/package.json +41 -0
- package/src/__tests__/fixtures/responses.ts +330 -0
- package/src/__tests__/property/request-construction.property.test.ts +203 -0
- package/src/__tests__/property/response-parsing.property.test.ts +200 -0
- package/src/__tests__/unit/aha.test.ts +164 -0
- package/src/__tests__/unit/badges.test.ts +112 -0
- package/src/__tests__/unit/config.test.ts +187 -0
- package/src/__tests__/unit/errors.test.ts +220 -0
- package/src/__tests__/unit/events.test.ts +282 -0
- package/src/__tests__/unit/leaderboards.test.ts +124 -0
- package/src/__tests__/unit/levels.test.ts +138 -0
- package/src/__tests__/unit/questionnaires.test.ts +138 -0
- package/src/__tests__/unit/users.test.ts +273 -0
- package/src/__tests__/utils/generators.ts +80 -0
- package/src/__tests__/utils/mockClient.ts +48 -0
- package/src/index.test.ts +69 -0
- package/src/index.ts +236 -0
- package/src/types.ts +165 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Rooguys Node.js SDK
|
|
2
|
+
|
|
3
|
+
The official Node.js SDK for the Rooguys Gamification API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rooguys/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Initialization
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Rooguys } from '@rooguys/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new Rooguys('YOUR_API_KEY', {
|
|
17
|
+
baseUrl: 'https://api.rooguys.com/v1', // Optional, defaults to production
|
|
18
|
+
timeout: 10000, // Optional, defaults to 10s
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage Examples
|
|
23
|
+
|
|
24
|
+
### 1. Track an Event
|
|
25
|
+
|
|
26
|
+
Track user actions to award points and unlock badges.
|
|
27
|
+
|
|
28
|
+
```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);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Get User Profile
|
|
47
|
+
|
|
48
|
+
Retrieve a user's current level, points, and badges.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const user = await client.users.get('user_123');
|
|
52
|
+
|
|
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(', '));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Global Leaderboard
|
|
60
|
+
|
|
61
|
+
Fetch the top players.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Get top 10 all-time
|
|
65
|
+
const leaderboard = await client.leaderboards.getGlobal('all-time', 1, 10);
|
|
66
|
+
|
|
67
|
+
leaderboard.rankings.forEach(entry => {
|
|
68
|
+
console.log(`#${entry.rank} - ${entry.user_id} (${entry.points} pts)`);
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. Submit Questionnaire Answers
|
|
73
|
+
|
|
74
|
+
Submit answers for a user.
|
|
75
|
+
|
|
76
|
+
```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
|
+
]);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. Aha Score - Declare User Activation
|
|
84
|
+
|
|
85
|
+
Track when users reach their "Aha Moment" with declarative scores (1-5).
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Declare that a user has reached an activation milestone
|
|
89
|
+
const result = await client.aha.declare('user_123', 4);
|
|
90
|
+
|
|
91
|
+
console.log(result.message); // "Aha score declared successfully"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 6. Aha Score - Get User Score
|
|
95
|
+
|
|
96
|
+
Retrieve a user's Aha Score, including declarative and inferred scores.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const ahaScore = await client.aha.getUserScore('user_123');
|
|
100
|
+
|
|
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}`);
|
|
105
|
+
|
|
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}`);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### Events
|
|
116
|
+
|
|
117
|
+
- `track(eventName: string, userId: string, properties?: object, options?: { includeProfile?: boolean }): Promise<TrackEventResponse>`
|
|
118
|
+
|
|
119
|
+
### Users
|
|
120
|
+
|
|
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>`
|
|
126
|
+
|
|
127
|
+
### Leaderboards
|
|
128
|
+
|
|
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>`
|
|
133
|
+
|
|
134
|
+
### Badges
|
|
135
|
+
|
|
136
|
+
- `list(page?: number, limit?: number, activeOnly?: boolean): Promise<BadgeListResponse>`
|
|
137
|
+
|
|
138
|
+
### Levels
|
|
139
|
+
|
|
140
|
+
- `list(page?: number, limit?: number): Promise<LevelListResponse>`
|
|
141
|
+
|
|
142
|
+
### Questionnaires
|
|
143
|
+
|
|
144
|
+
- `get(slug: string): Promise<Questionnaire>`
|
|
145
|
+
- `getActive(): Promise<Questionnaire>`
|
|
146
|
+
|
|
147
|
+
### Aha Score
|
|
148
|
+
|
|
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
|
|
153
|
+
|
|
154
|
+
## Error Handling
|
|
155
|
+
|
|
156
|
+
The SDK throws errors for non-2xx responses.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
try {
|
|
160
|
+
await client.users.get('non_existent_user');
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(error.message); // "Rooguys API Error: User not found"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Validation Errors
|
|
167
|
+
|
|
168
|
+
```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"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Testing
|
|
178
|
+
|
|
179
|
+
The SDK includes comprehensive test coverage with both unit tests and property-based tests.
|
|
180
|
+
|
|
181
|
+
### Running Tests
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm test
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Test Coverage
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm run test:coverage
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The SDK maintains >90% test coverage across all modules, including:
|
|
194
|
+
- Unit tests for all API methods
|
|
195
|
+
- 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
|
+
```
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock API response fixtures for testing
|
|
3
|
+
*/
|
|
4
|
+
export declare const mockResponses: {
|
|
5
|
+
userProfile: {
|
|
6
|
+
user_id: string;
|
|
7
|
+
points: number;
|
|
8
|
+
persona: string;
|
|
9
|
+
level: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
level_number: number;
|
|
13
|
+
points_required: number;
|
|
14
|
+
};
|
|
15
|
+
next_level: {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
level_number: number;
|
|
19
|
+
points_required: number;
|
|
20
|
+
points_needed: number;
|
|
21
|
+
};
|
|
22
|
+
metrics: {
|
|
23
|
+
logins: number;
|
|
24
|
+
purchases: number;
|
|
25
|
+
};
|
|
26
|
+
badges: {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
icon_url: string;
|
|
31
|
+
earned_at: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
trackEventResponse: {
|
|
35
|
+
status: string;
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
trackEventWithProfileResponse: {
|
|
39
|
+
status: string;
|
|
40
|
+
message: string;
|
|
41
|
+
profile: {
|
|
42
|
+
user_id: string;
|
|
43
|
+
points: number;
|
|
44
|
+
persona: string;
|
|
45
|
+
level: {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
level_number: number;
|
|
49
|
+
points_required: number;
|
|
50
|
+
};
|
|
51
|
+
next_level: {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
level_number: number;
|
|
55
|
+
points_required: number;
|
|
56
|
+
points_needed: number;
|
|
57
|
+
};
|
|
58
|
+
metrics: {};
|
|
59
|
+
badges: never[];
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
leaderboardResponse: {
|
|
63
|
+
timeframe: string;
|
|
64
|
+
page: number;
|
|
65
|
+
limit: number;
|
|
66
|
+
total: number;
|
|
67
|
+
rankings: {
|
|
68
|
+
rank: number;
|
|
69
|
+
user_id: string;
|
|
70
|
+
points: number;
|
|
71
|
+
level: {
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
level_number: number;
|
|
75
|
+
};
|
|
76
|
+
}[];
|
|
77
|
+
};
|
|
78
|
+
userRankResponse: {
|
|
79
|
+
user_id: string;
|
|
80
|
+
rank: number;
|
|
81
|
+
points: number;
|
|
82
|
+
total_users: number;
|
|
83
|
+
};
|
|
84
|
+
badgesListResponse: {
|
|
85
|
+
badges: {
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
description: string;
|
|
89
|
+
icon_url: string;
|
|
90
|
+
is_active: boolean;
|
|
91
|
+
unlock_criteria: {
|
|
92
|
+
metric: string;
|
|
93
|
+
value: number;
|
|
94
|
+
};
|
|
95
|
+
}[];
|
|
96
|
+
pagination: {
|
|
97
|
+
page: number;
|
|
98
|
+
limit: number;
|
|
99
|
+
total: number;
|
|
100
|
+
totalPages: number;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
levelsListResponse: {
|
|
104
|
+
levels: {
|
|
105
|
+
id: string;
|
|
106
|
+
name: string;
|
|
107
|
+
level_number: number;
|
|
108
|
+
points_required: number;
|
|
109
|
+
description: string;
|
|
110
|
+
icon_url: string;
|
|
111
|
+
}[];
|
|
112
|
+
pagination: {
|
|
113
|
+
page: number;
|
|
114
|
+
limit: number;
|
|
115
|
+
total: number;
|
|
116
|
+
totalPages: number;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
questionnaireResponse: {
|
|
120
|
+
id: string;
|
|
121
|
+
slug: string;
|
|
122
|
+
title: string;
|
|
123
|
+
description: string;
|
|
124
|
+
is_active: boolean;
|
|
125
|
+
questions: {
|
|
126
|
+
id: string;
|
|
127
|
+
text: string;
|
|
128
|
+
order: number;
|
|
129
|
+
answer_options: ({
|
|
130
|
+
id: string;
|
|
131
|
+
text: string;
|
|
132
|
+
persona_weights: {
|
|
133
|
+
Competitor: number;
|
|
134
|
+
Explorer?: undefined;
|
|
135
|
+
};
|
|
136
|
+
} | {
|
|
137
|
+
id: string;
|
|
138
|
+
text: string;
|
|
139
|
+
persona_weights: {
|
|
140
|
+
Explorer: number;
|
|
141
|
+
Competitor?: undefined;
|
|
142
|
+
};
|
|
143
|
+
})[];
|
|
144
|
+
}[];
|
|
145
|
+
};
|
|
146
|
+
bulkUsersResponse: {
|
|
147
|
+
users: ({
|
|
148
|
+
user_id: string;
|
|
149
|
+
points: number;
|
|
150
|
+
persona: null;
|
|
151
|
+
level: null;
|
|
152
|
+
next_level: null;
|
|
153
|
+
metrics: {};
|
|
154
|
+
badges: never[];
|
|
155
|
+
} | {
|
|
156
|
+
user_id: string;
|
|
157
|
+
points: number;
|
|
158
|
+
persona: string;
|
|
159
|
+
level: {
|
|
160
|
+
id: string;
|
|
161
|
+
name: string;
|
|
162
|
+
level_number: number;
|
|
163
|
+
points_required: number;
|
|
164
|
+
};
|
|
165
|
+
next_level: null;
|
|
166
|
+
metrics: {};
|
|
167
|
+
badges: never[];
|
|
168
|
+
})[];
|
|
169
|
+
};
|
|
170
|
+
ahaScoreResponse: {
|
|
171
|
+
success: boolean;
|
|
172
|
+
data: {
|
|
173
|
+
user_id: string;
|
|
174
|
+
current_score: number;
|
|
175
|
+
declarative_score: number;
|
|
176
|
+
inferred_score: number;
|
|
177
|
+
status: string;
|
|
178
|
+
history: {
|
|
179
|
+
initial: number;
|
|
180
|
+
initial_date: string;
|
|
181
|
+
previous: number;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
ahaDeclarationResponse: {
|
|
186
|
+
success: boolean;
|
|
187
|
+
message: string;
|
|
188
|
+
};
|
|
189
|
+
answerSubmissionResponse: {
|
|
190
|
+
status: string;
|
|
191
|
+
message: string;
|
|
192
|
+
};
|
|
193
|
+
leaderboardsListResponse: {
|
|
194
|
+
leaderboards: {
|
|
195
|
+
id: string;
|
|
196
|
+
name: string;
|
|
197
|
+
description: string;
|
|
198
|
+
type: string;
|
|
199
|
+
timeframe: string;
|
|
200
|
+
is_active: boolean;
|
|
201
|
+
created_at: string;
|
|
202
|
+
}[];
|
|
203
|
+
pagination: {
|
|
204
|
+
page: number;
|
|
205
|
+
limit: number;
|
|
206
|
+
total: number;
|
|
207
|
+
totalPages: number;
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
customLeaderboardResponse: {
|
|
211
|
+
leaderboard: {
|
|
212
|
+
id: string;
|
|
213
|
+
name: string;
|
|
214
|
+
description: string;
|
|
215
|
+
type: string;
|
|
216
|
+
timeframe: string;
|
|
217
|
+
is_active: boolean;
|
|
218
|
+
created_at: string;
|
|
219
|
+
};
|
|
220
|
+
rankings: {
|
|
221
|
+
rank: number;
|
|
222
|
+
user_id: string;
|
|
223
|
+
points: number;
|
|
224
|
+
level: null;
|
|
225
|
+
}[];
|
|
226
|
+
pagination: {
|
|
227
|
+
page: number;
|
|
228
|
+
limit: number;
|
|
229
|
+
total: number;
|
|
230
|
+
totalPages: number;
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
export declare const mockErrors: {
|
|
235
|
+
validationError: {
|
|
236
|
+
error: string;
|
|
237
|
+
details: {
|
|
238
|
+
field: string;
|
|
239
|
+
message: string;
|
|
240
|
+
}[];
|
|
241
|
+
};
|
|
242
|
+
notFoundError: {
|
|
243
|
+
error: string;
|
|
244
|
+
message: string;
|
|
245
|
+
};
|
|
246
|
+
serverError: {
|
|
247
|
+
error: string;
|
|
248
|
+
};
|
|
249
|
+
queueFullError: {
|
|
250
|
+
error: string;
|
|
251
|
+
message: string;
|
|
252
|
+
};
|
|
253
|
+
unauthorizedError: {
|
|
254
|
+
error: string;
|
|
255
|
+
message: string;
|
|
256
|
+
};
|
|
257
|
+
invalidTimeframeError: {
|
|
258
|
+
error: string;
|
|
259
|
+
message: string;
|
|
260
|
+
};
|
|
261
|
+
invalidPaginationError: {
|
|
262
|
+
error: string;
|
|
263
|
+
message: string;
|
|
264
|
+
};
|
|
265
|
+
ahaValueError: {
|
|
266
|
+
error: string;
|
|
267
|
+
details: {
|
|
268
|
+
field: string;
|
|
269
|
+
message: string;
|
|
270
|
+
}[];
|
|
271
|
+
};
|
|
272
|
+
};
|