@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 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
+ };