@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/dist/index.js CHANGED
@@ -1,222 +1,661 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ /**
3
+ * Rooguys Node.js SDK
4
+ * Official TypeScript SDK for the Rooguys Gamification Platform
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
4
19
  };
5
20
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Rooguys = void 0;
7
- const axios_1 = __importDefault(require("axios"));
21
+ exports.parseResponseBody = exports.extractRequestId = exports.extractRateLimitInfo = exports.HttpClient = exports.mapStatusToError = exports.ServerError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.AuthenticationError = exports.ValidationError = exports.RooguysError = exports.Rooguys = void 0;
22
+ const http_client_1 = require("./http-client");
23
+ const errors_1 = require("./errors");
24
+ /**
25
+ * Main Rooguys SDK class
26
+ */
8
27
  class Rooguys {
28
+ /**
29
+ * Create a new Rooguys SDK instance
30
+ * @param apiKey - API key for authentication
31
+ * @param options - Configuration options
32
+ */
9
33
  constructor(apiKey, options = {}) {
10
- this.apiKey = apiKey;
34
+ /**
35
+ * Events module for tracking user events
36
+ */
11
37
  this.events = {
38
+ /**
39
+ * Track a single event
40
+ */
12
41
  track: async (eventName, userId, properties = {}, options = {}) => {
13
- try {
14
- const response = await this.client.post('/event', {
15
- event_name: eventName,
16
- user_id: userId,
17
- properties,
18
- }, {
19
- params: {
20
- include_profile: options.includeProfile,
21
- },
42
+ const body = {
43
+ event_name: eventName,
44
+ user_id: userId,
45
+ properties,
46
+ };
47
+ // Add custom timestamp if provided
48
+ if (options.timestamp) {
49
+ const timestamp = options.timestamp instanceof Date ? options.timestamp : new Date(options.timestamp);
50
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
51
+ if (timestamp < sevenDaysAgo) {
52
+ throw new errors_1.ValidationError('Custom timestamp cannot be more than 7 days in the past', {
53
+ code: 'TIMESTAMP_TOO_OLD',
54
+ fieldErrors: [{ field: 'timestamp', message: 'Timestamp must be within the last 7 days' }],
55
+ });
56
+ }
57
+ body.timestamp = timestamp.toISOString();
58
+ }
59
+ const response = await this._httpClient.post('/events', body, {
60
+ params: { include_profile: options.includeProfile },
61
+ idempotencyKey: options.idempotencyKey,
62
+ });
63
+ return response.data;
64
+ },
65
+ /**
66
+ * Track multiple events in a single request (batch operation)
67
+ */
68
+ trackBatch: async (events, options = {}) => {
69
+ // Validate array
70
+ if (!Array.isArray(events)) {
71
+ throw new errors_1.ValidationError('Events must be an array', {
72
+ code: 'INVALID_EVENTS',
73
+ fieldErrors: [{ field: 'events', message: 'Events must be an array' }],
22
74
  });
23
- return response.data;
24
75
  }
25
- catch (error) {
26
- throw this.handleError(error);
76
+ // Validate array length
77
+ if (events.length === 0) {
78
+ throw new errors_1.ValidationError('Events array cannot be empty', {
79
+ code: 'EMPTY_EVENTS',
80
+ fieldErrors: [{ field: 'events', message: 'At least one event is required' }],
81
+ });
82
+ }
83
+ if (events.length > 100) {
84
+ throw new errors_1.ValidationError('Batch size exceeds maximum of 100 events', {
85
+ code: 'BATCH_TOO_LARGE',
86
+ fieldErrors: [{ field: 'events', message: 'Maximum batch size is 100 events' }],
87
+ });
27
88
  }
89
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
90
+ // Transform and validate events
91
+ const transformedEvents = events.map((event, index) => {
92
+ const transformed = {
93
+ event_name: event.eventName,
94
+ user_id: event.userId,
95
+ properties: event.properties || {},
96
+ };
97
+ // Validate and add custom timestamp if provided
98
+ if (event.timestamp) {
99
+ const timestamp = event.timestamp instanceof Date ? event.timestamp : new Date(event.timestamp);
100
+ if (timestamp < sevenDaysAgo) {
101
+ throw new errors_1.ValidationError(`Event at index ${index}: Custom timestamp cannot be more than 7 days in the past`, {
102
+ code: 'TIMESTAMP_TOO_OLD',
103
+ fieldErrors: [{ field: `events[${index}].timestamp`, message: 'Timestamp must be within the last 7 days' }],
104
+ });
105
+ }
106
+ transformed.timestamp = timestamp.toISOString();
107
+ }
108
+ return transformed;
109
+ });
110
+ const response = await this._httpClient.post('/events/batch', {
111
+ events: transformedEvents,
112
+ }, {
113
+ idempotencyKey: options.idempotencyKey,
114
+ });
115
+ return response.data;
116
+ },
117
+ /**
118
+ * @deprecated Use track() instead. The /v1/event endpoint is deprecated.
119
+ */
120
+ trackLegacy: async (eventName, userId, properties = {}, options = {}) => {
121
+ console.warn('DEPRECATION WARNING: events.trackLegacy() uses the deprecated /v1/event endpoint. Please use events.track() instead which uses /v1/events.');
122
+ const response = await this._httpClient.post('/event', {
123
+ event_name: eventName,
124
+ user_id: userId,
125
+ properties,
126
+ }, {
127
+ params: { include_profile: options.includeProfile },
128
+ });
129
+ return response.data;
28
130
  },
29
131
  };
132
+ /**
133
+ * Users module for user management and queries
134
+ */
30
135
  this.users = {
31
- get: async (userId) => {
32
- try {
33
- const response = await this.client.get(`/user/${encodeURIComponent(userId)}`);
34
- return response.data;
136
+ /**
137
+ * Create a new user
138
+ */
139
+ create: async (userData) => {
140
+ // Validate required fields
141
+ if (!userData || !userData.userId) {
142
+ throw new errors_1.ValidationError('User ID is required', {
143
+ code: 'MISSING_USER_ID',
144
+ fieldErrors: [{ field: 'userId', message: 'User ID is required' }],
145
+ });
35
146
  }
36
- catch (error) {
37
- throw this.handleError(error);
147
+ // Validate email format if provided
148
+ if (userData.email && !this.isValidEmail(userData.email)) {
149
+ throw new errors_1.ValidationError('Invalid email format', {
150
+ code: 'INVALID_EMAIL',
151
+ fieldErrors: [{ field: 'email', message: 'Email must be a valid email address' }],
152
+ });
38
153
  }
154
+ const body = this.buildUserRequestBody(userData);
155
+ const response = await this._httpClient.post('/users', body);
156
+ return this.parseUserProfile(response.data);
39
157
  },
40
- getBulk: async (userIds) => {
41
- try {
42
- const response = await this.client.post('/users/bulk', { user_ids: userIds });
43
- return response.data;
158
+ /**
159
+ * Update an existing user
160
+ */
161
+ update: async (userId, userData) => {
162
+ // Validate user ID
163
+ if (!userId) {
164
+ throw new errors_1.ValidationError('User ID is required', {
165
+ code: 'MISSING_USER_ID',
166
+ fieldErrors: [{ field: 'userId', message: 'User ID is required' }],
167
+ });
44
168
  }
45
- catch (error) {
46
- throw this.handleError(error);
169
+ // Validate email format if provided
170
+ if (userData.email && !this.isValidEmail(userData.email)) {
171
+ throw new errors_1.ValidationError('Invalid email format', {
172
+ code: 'INVALID_EMAIL',
173
+ fieldErrors: [{ field: 'email', message: 'Email must be a valid email address' }],
174
+ });
47
175
  }
176
+ const body = this.buildUserRequestBody(userData);
177
+ const response = await this._httpClient.patch(`/users/${encodeURIComponent(userId)}`, body);
178
+ return this.parseUserProfile(response.data);
48
179
  },
49
- getBadges: async (userId) => {
50
- try {
51
- const response = await this.client.get(`/user/${encodeURIComponent(userId)}/badges`);
52
- return response.data;
53
- }
54
- catch (error) {
55
- throw this.handleError(error);
180
+ /**
181
+ * Create multiple users in a single request (batch operation)
182
+ */
183
+ createBatch: async (users) => {
184
+ // Validate array
185
+ if (!Array.isArray(users)) {
186
+ throw new errors_1.ValidationError('Users must be an array', {
187
+ code: 'INVALID_USERS',
188
+ fieldErrors: [{ field: 'users', message: 'Users must be an array' }],
189
+ });
56
190
  }
57
- },
58
- getRank: async (userId, timeframe = 'all-time') => {
59
- try {
60
- const response = await this.client.get(`/user/${encodeURIComponent(userId)}/rank`, {
61
- params: { timeframe },
191
+ // Validate array length
192
+ if (users.length === 0) {
193
+ throw new errors_1.ValidationError('Users array cannot be empty', {
194
+ code: 'EMPTY_USERS',
195
+ fieldErrors: [{ field: 'users', message: 'At least one user is required' }],
62
196
  });
63
- return response.data;
64
197
  }
65
- catch (error) {
66
- throw this.handleError(error);
198
+ if (users.length > 100) {
199
+ throw new errors_1.ValidationError('Batch size exceeds maximum of 100 users', {
200
+ code: 'BATCH_TOO_LARGE',
201
+ fieldErrors: [{ field: 'users', message: 'Maximum batch size is 100 users' }],
202
+ });
67
203
  }
204
+ // Validate and transform each user
205
+ const transformedUsers = users.map((user, index) => {
206
+ if (!user.userId) {
207
+ throw new errors_1.ValidationError(`User at index ${index}: User ID is required`, {
208
+ code: 'MISSING_USER_ID',
209
+ fieldErrors: [{ field: `users[${index}].userId`, message: 'User ID is required' }],
210
+ });
211
+ }
212
+ if (user.email && !this.isValidEmail(user.email)) {
213
+ throw new errors_1.ValidationError(`User at index ${index}: Invalid email format`, {
214
+ code: 'INVALID_EMAIL',
215
+ fieldErrors: [{ field: `users[${index}].email`, message: 'Email must be a valid email address' }],
216
+ });
217
+ }
218
+ return this.buildUserRequestBody(user);
219
+ });
220
+ const response = await this._httpClient.post('/users/batch', { users: transformedUsers });
221
+ return response.data;
222
+ },
223
+ /**
224
+ * Get user profile with optional field selection
225
+ */
226
+ get: async (userId, options = {}) => {
227
+ const params = {};
228
+ // Add field selection if provided
229
+ if (options.fields && Array.isArray(options.fields) && options.fields.length > 0) {
230
+ params.fields = options.fields.join(',');
231
+ }
232
+ const response = await this._httpClient.get(`/users/${encodeURIComponent(userId)}`, params);
233
+ return this.parseUserProfile(response.data);
234
+ },
235
+ /**
236
+ * Search users with pagination
237
+ */
238
+ search: async (query, options = {}) => {
239
+ const params = {
240
+ q: query,
241
+ page: options.page || 1,
242
+ limit: options.limit || 50,
243
+ };
244
+ // Add field selection if provided
245
+ if (options.fields && Array.isArray(options.fields) && options.fields.length > 0) {
246
+ params.fields = options.fields.join(',');
247
+ }
248
+ const response = await this._httpClient.get('/users/search', params);
249
+ return {
250
+ ...response.data,
251
+ users: (response.data.users || []).map(user => this.parseUserProfile(user)),
252
+ };
253
+ },
254
+ /**
255
+ * Get multiple user profiles
256
+ */
257
+ getBulk: async (userIds) => {
258
+ const response = await this._httpClient.post('/users/bulk', { user_ids: userIds });
259
+ return {
260
+ ...response.data,
261
+ users: (response.data.users || []).map(user => this.parseUserProfile(user)),
262
+ };
263
+ },
264
+ /**
265
+ * Get user badges
266
+ */
267
+ getBadges: async (userId) => {
268
+ const response = await this._httpClient.get(`/users/${encodeURIComponent(userId)}/badges`);
269
+ return response.data;
270
+ },
271
+ /**
272
+ * Get user rank
273
+ */
274
+ getRank: async (userId, timeframe = 'all-time') => {
275
+ const response = await this._httpClient.get(`/users/${encodeURIComponent(userId)}/rank`, { timeframe });
276
+ return {
277
+ ...response.data,
278
+ percentile: response.data.percentile !== undefined ? response.data.percentile : null,
279
+ };
68
280
  },
281
+ /**
282
+ * Submit questionnaire answers
283
+ */
69
284
  submitAnswers: async (userId, questionnaireId, answers) => {
70
- try {
71
- const response = await this.client.post(`/user/${encodeURIComponent(userId)}/answers`, {
72
- questionnaire_id: questionnaireId,
73
- answers,
74
- });
75
- return response.data;
76
- }
77
- catch (error) {
78
- throw this.handleError(error);
79
- }
285
+ const response = await this._httpClient.post(`/users/${encodeURIComponent(userId)}/answers`, {
286
+ questionnaire_id: questionnaireId,
287
+ answers,
288
+ });
289
+ return response.data;
80
290
  },
81
291
  };
292
+ /**
293
+ * Leaderboards module for ranking queries
294
+ */
82
295
  this.leaderboards = {
83
- getGlobal: async (timeframe = 'all-time', page = 1, limit = 50) => {
84
- try {
85
- const response = await this.client.get('/leaderboard', {
86
- params: { timeframe, page, limit },
296
+ /**
297
+ * Get global leaderboard with optional filters
298
+ */
299
+ getGlobal: async (timeframeOrOptions = 'all-time', page = 1, limit = 50, options = {}) => {
300
+ let params;
301
+ // Support both legacy signature and new options object
302
+ if (typeof timeframeOrOptions === 'object') {
303
+ params = this.buildFilterParams({
304
+ timeframe: 'all-time',
305
+ page: 1,
306
+ limit: 50,
307
+ ...timeframeOrOptions,
87
308
  });
88
- return response.data;
89
309
  }
90
- catch (error) {
91
- throw this.handleError(error);
310
+ else {
311
+ params = this.buildFilterParams({
312
+ timeframe: timeframeOrOptions,
313
+ page,
314
+ limit,
315
+ ...options,
316
+ });
92
317
  }
318
+ const response = await this._httpClient.get('/leaderboards/global', params);
319
+ return this.parseLeaderboardResponse(response.data);
93
320
  },
94
- list: async (page = 1, limit = 50, search) => {
95
- try {
96
- const params = { page, limit };
97
- if (search) {
98
- params.search = search;
321
+ /**
322
+ * List all leaderboards
323
+ */
324
+ list: async (pageOrOptions = 1, limit = 50, search = null) => {
325
+ let params;
326
+ if (typeof pageOrOptions === 'object') {
327
+ params = {
328
+ page: pageOrOptions.page || 1,
329
+ limit: pageOrOptions.limit || 50,
330
+ };
331
+ if (pageOrOptions.search !== undefined && pageOrOptions.search !== null) {
332
+ params.search = pageOrOptions.search;
99
333
  }
100
- const response = await this.client.get('/leaderboards', { params });
101
- return response.data;
102
334
  }
103
- catch (error) {
104
- throw this.handleError(error);
335
+ else {
336
+ params = { page: pageOrOptions, limit };
337
+ if (search !== null)
338
+ params.search = search;
105
339
  }
340
+ const response = await this._httpClient.get('/leaderboards', params);
341
+ return response.data;
106
342
  },
107
- getCustom: async (leaderboardId, page = 1, limit = 50, search) => {
108
- try {
109
- const params = { page, limit };
110
- if (search) {
111
- params.search = search;
112
- }
113
- const response = await this.client.get(`/leaderboard/${encodeURIComponent(leaderboardId)}`, { params });
114
- return response.data;
343
+ /**
344
+ * Get custom leaderboard with optional filters
345
+ */
346
+ getCustom: async (leaderboardId, pageOrOptions = 1, limit = 50, search = null, options = {}) => {
347
+ let params;
348
+ if (typeof pageOrOptions === 'object') {
349
+ params = this.buildFilterParams({
350
+ page: 1,
351
+ limit: 50,
352
+ ...pageOrOptions,
353
+ });
115
354
  }
116
- catch (error) {
117
- throw this.handleError(error);
355
+ else {
356
+ params = this.buildFilterParams({
357
+ page: pageOrOptions,
358
+ limit,
359
+ search: search || undefined,
360
+ ...options,
361
+ });
118
362
  }
363
+ const response = await this._httpClient.get(`/leaderboards/${encodeURIComponent(leaderboardId)}`, params);
364
+ return this.parseLeaderboardResponse(response.data);
119
365
  },
366
+ /**
367
+ * Get user rank in leaderboard
368
+ */
120
369
  getUserRank: async (leaderboardId, userId) => {
121
- try {
122
- const response = await this.client.get(`/leaderboard/${encodeURIComponent(leaderboardId)}/user/${encodeURIComponent(userId)}/rank`);
123
- return response.data;
124
- }
125
- catch (error) {
126
- throw this.handleError(error);
127
- }
370
+ const response = await this._httpClient.get(`/leaderboards/${encodeURIComponent(leaderboardId)}/users/${encodeURIComponent(userId)}/rank`);
371
+ return {
372
+ ...response.data,
373
+ percentile: response.data.percentile !== undefined ? response.data.percentile : null,
374
+ };
375
+ },
376
+ /**
377
+ * Get leaderboard entries around a specific user ("around me" view)
378
+ */
379
+ getAroundUser: async (leaderboardId, userId, range = 5) => {
380
+ const response = await this._httpClient.get(`/leaderboards/${encodeURIComponent(leaderboardId)}/users/${encodeURIComponent(userId)}/around`, { range });
381
+ return this.parseLeaderboardResponse(response.data);
128
382
  },
129
383
  };
384
+ /**
385
+ * Badges module for badge queries
386
+ */
130
387
  this.badges = {
388
+ /**
389
+ * List all badges
390
+ */
131
391
  list: async (page = 1, limit = 50, activeOnly = false) => {
132
- try {
133
- const response = await this.client.get('/badges', {
134
- params: { page, limit, active_only: activeOnly },
135
- });
136
- return response.data;
137
- }
138
- catch (error) {
139
- throw this.handleError(error);
140
- }
392
+ const response = await this._httpClient.get('/badges', {
393
+ page,
394
+ limit,
395
+ active_only: activeOnly,
396
+ });
397
+ return response.data;
141
398
  },
142
399
  };
400
+ /**
401
+ * Levels module for level queries
402
+ */
143
403
  this.levels = {
404
+ /**
405
+ * List all levels
406
+ */
144
407
  list: async (page = 1, limit = 50) => {
145
- try {
146
- const response = await this.client.get('/levels', {
147
- params: { page, limit },
148
- });
149
- return response.data;
150
- }
151
- catch (error) {
152
- throw this.handleError(error);
153
- }
408
+ const response = await this._httpClient.get('/levels', { page, limit });
409
+ return response.data;
154
410
  },
155
411
  };
412
+ /**
413
+ * Questionnaires module for questionnaire queries
414
+ */
156
415
  this.questionnaires = {
416
+ /**
417
+ * Get questionnaire by slug
418
+ */
157
419
  get: async (slug) => {
158
- try {
159
- const response = await this.client.get(`/questionnaire/${slug}`);
160
- return response.data;
161
- }
162
- catch (error) {
163
- throw this.handleError(error);
164
- }
420
+ const response = await this._httpClient.get(`/questionnaires/${slug}`);
421
+ return response.data;
165
422
  },
423
+ /**
424
+ * Get active questionnaire
425
+ */
166
426
  getActive: async () => {
167
- try {
168
- const response = await this.client.get('/questionnaire/active');
169
- return response.data;
170
- }
171
- catch (error) {
172
- throw this.handleError(error);
173
- }
427
+ const response = await this._httpClient.get('/questionnaires/active');
428
+ return response.data;
174
429
  },
175
430
  };
431
+ /**
432
+ * Aha moment module for engagement tracking
433
+ */
176
434
  this.aha = {
435
+ /**
436
+ * Declare aha moment score
437
+ */
177
438
  declare: async (userId, value) => {
178
439
  // Validate value is between 1 and 5
179
440
  if (!Number.isInteger(value) || value < 1 || value > 5) {
180
- throw new Error('Aha score value must be an integer between 1 and 5');
181
- }
182
- try {
183
- const response = await this.client.post('/aha/declare', {
184
- user_id: userId,
185
- value,
441
+ throw new errors_1.ValidationError('Aha score value must be an integer between 1 and 5', {
442
+ code: 'INVALID_AHA_VALUE',
443
+ fieldErrors: [{ field: 'value', message: 'value must be an integer between 1 and 5' }],
186
444
  });
187
- return response.data;
188
- }
189
- catch (error) {
190
- throw this.handleError(error);
191
445
  }
446
+ const response = await this._httpClient.post('/aha/declare', {
447
+ user_id: userId,
448
+ value,
449
+ });
450
+ return response.data;
192
451
  },
452
+ /**
453
+ * Get user aha score
454
+ */
193
455
  getUserScore: async (userId) => {
456
+ const response = await this._httpClient.get(`/users/${encodeURIComponent(userId)}/aha`);
457
+ return response.data;
458
+ },
459
+ };
460
+ /**
461
+ * Health module for API health checks
462
+ */
463
+ this.health = {
464
+ /**
465
+ * Check API health status
466
+ */
467
+ check: async () => {
468
+ const response = await this._httpClient.get('/health');
469
+ return response.data;
470
+ },
471
+ /**
472
+ * Quick availability check
473
+ */
474
+ isReady: async () => {
194
475
  try {
195
- const response = await this.client.get(`/users/${encodeURIComponent(userId)}/aha`);
196
- return response.data;
476
+ await this._httpClient.get('/health');
477
+ return true;
197
478
  }
198
- catch (error) {
199
- throw this.handleError(error);
479
+ catch (_a) {
480
+ return false;
200
481
  }
201
482
  },
202
483
  };
203
- this.client = axios_1.default.create({
204
- baseURL: options.baseUrl || 'https://api.rooguys.com/v1',
205
- timeout: options.timeout || 10000,
206
- headers: {
207
- 'x-api-key': this.apiKey,
208
- 'Content-Type': 'application/json',
209
- },
484
+ if (!apiKey) {
485
+ throw new errors_1.ValidationError('API key is required', { code: 'MISSING_API_KEY' });
486
+ }
487
+ this.apiKey = apiKey;
488
+ this.baseUrl = options.baseUrl || 'https://api.rooguys.com/v1';
489
+ this.timeout = options.timeout || 10000;
490
+ // Initialize HTTP client
491
+ this._httpClient = new http_client_1.HttpClient(apiKey, {
492
+ baseUrl: this.baseUrl,
493
+ timeout: this.timeout,
494
+ onRateLimitWarning: options.onRateLimitWarning,
495
+ autoRetry: options.autoRetry,
496
+ maxRetries: options.maxRetries,
210
497
  });
211
498
  }
212
- handleError(error) {
213
- var _a, _b;
214
- if (axios_1.default.isAxiosError(error)) {
215
- const message = ((_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || error.message;
216
- return new Error(message);
499
+ /**
500
+ * Validate email format client-side
501
+ */
502
+ isValidEmail(email) {
503
+ if (!email || typeof email !== 'string')
504
+ return true; // Optional field
505
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
506
+ return emailRegex.test(email);
507
+ }
508
+ /**
509
+ * Build request body with only provided fields (partial update support)
510
+ */
511
+ buildUserRequestBody(userData) {
512
+ const body = {};
513
+ if ('userId' in userData && userData.userId !== undefined) {
514
+ body.user_id = userData.userId;
515
+ }
516
+ if (userData.displayName !== undefined) {
517
+ body.display_name = userData.displayName;
518
+ }
519
+ if (userData.email !== undefined) {
520
+ body.email = userData.email;
521
+ }
522
+ if (userData.firstName !== undefined) {
523
+ body.first_name = userData.firstName;
524
+ }
525
+ if (userData.lastName !== undefined) {
526
+ body.last_name = userData.lastName;
527
+ }
528
+ if (userData.metadata !== undefined) {
529
+ body.metadata = userData.metadata;
530
+ }
531
+ return body;
532
+ }
533
+ /**
534
+ * Parse user profile to include activity summary, streak, and inventory
535
+ */
536
+ parseUserProfile(profile) {
537
+ if (!profile)
538
+ return profile;
539
+ const parsed = { ...profile };
540
+ // Parse activity summary if present
541
+ const activitySummary = profile.activity_summary;
542
+ if (activitySummary) {
543
+ parsed.activitySummary = {
544
+ lastEventAt: activitySummary.last_event_at
545
+ ? new Date(activitySummary.last_event_at)
546
+ : null,
547
+ eventCount: activitySummary.event_count || 0,
548
+ daysActive: activitySummary.days_active || 0,
549
+ };
550
+ }
551
+ // Parse streak info if present
552
+ const streak = profile.streak;
553
+ if (streak) {
554
+ parsed.streak = {
555
+ currentStreak: streak.current_streak || 0,
556
+ longestStreak: streak.longest_streak || 0,
557
+ lastActivityAt: streak.last_activity_at
558
+ ? new Date(streak.last_activity_at)
559
+ : null,
560
+ streakStartedAt: streak.streak_started_at
561
+ ? new Date(streak.streak_started_at)
562
+ : null,
563
+ };
564
+ }
565
+ // Parse inventory summary if present
566
+ const inventory = profile.inventory;
567
+ if (inventory) {
568
+ parsed.inventory = {
569
+ itemCount: inventory.item_count || 0,
570
+ activeEffects: inventory.active_effects || [],
571
+ };
572
+ }
573
+ return parsed;
574
+ }
575
+ /**
576
+ * Build filter query parameters from options
577
+ */
578
+ buildFilterParams(options = {}) {
579
+ const params = {};
580
+ // Pagination
581
+ if (options.page !== undefined)
582
+ params.page = options.page;
583
+ if (options.limit !== undefined)
584
+ params.limit = options.limit;
585
+ if (options.search !== undefined && options.search !== null)
586
+ params.search = options.search;
587
+ // Persona filter
588
+ if (options.persona !== undefined && options.persona !== null) {
589
+ params.persona = options.persona;
590
+ }
591
+ // Level range filters
592
+ if (options.minLevel !== undefined && options.minLevel !== null) {
593
+ params.min_level = options.minLevel;
594
+ }
595
+ if (options.maxLevel !== undefined && options.maxLevel !== null) {
596
+ params.max_level = options.maxLevel;
597
+ }
598
+ // Date range filters (convert to ISO 8601)
599
+ if (options.startDate !== undefined && options.startDate !== null) {
600
+ const startDate = options.startDate instanceof Date
601
+ ? options.startDate
602
+ : new Date(options.startDate);
603
+ params.start_date = startDate.toISOString();
604
+ }
605
+ if (options.endDate !== undefined && options.endDate !== null) {
606
+ const endDate = options.endDate instanceof Date
607
+ ? options.endDate
608
+ : new Date(options.endDate);
609
+ params.end_date = endDate.toISOString();
610
+ }
611
+ // Timeframe
612
+ if (options.timeframe !== undefined)
613
+ params.timeframe = options.timeframe;
614
+ return params;
615
+ }
616
+ /**
617
+ * Parse leaderboard response to include cache metadata and percentile ranks
618
+ */
619
+ parseLeaderboardResponse(response) {
620
+ const parsed = { ...response };
621
+ // Parse cache metadata if present
622
+ const cacheData = (response.cache_metadata || response.cacheMetadata);
623
+ if (cacheData) {
624
+ parsed.cacheMetadata = {
625
+ cachedAt: cacheData.cached_at ? new Date(cacheData.cached_at) : null,
626
+ ttl: cacheData.ttl || 0,
627
+ };
628
+ delete parsed.cache_metadata;
629
+ }
630
+ // Parse rankings to include percentile if present
631
+ if (parsed.rankings && Array.isArray(parsed.rankings)) {
632
+ parsed.rankings = parsed.rankings.map(entry => ({
633
+ ...entry,
634
+ percentile: entry.percentile !== undefined ? entry.percentile : null,
635
+ }));
217
636
  }
218
- return error;
637
+ return parsed;
219
638
  }
220
639
  }
221
640
  exports.Rooguys = Rooguys;
641
+ // Export error classes
642
+ var errors_2 = require("./errors");
643
+ Object.defineProperty(exports, "RooguysError", { enumerable: true, get: function () { return errors_2.RooguysError; } });
644
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_2.ValidationError; } });
645
+ Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_2.AuthenticationError; } });
646
+ Object.defineProperty(exports, "ForbiddenError", { enumerable: true, get: function () { return errors_2.ForbiddenError; } });
647
+ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_2.NotFoundError; } });
648
+ Object.defineProperty(exports, "ConflictError", { enumerable: true, get: function () { return errors_2.ConflictError; } });
649
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_2.RateLimitError; } });
650
+ Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return errors_2.ServerError; } });
651
+ Object.defineProperty(exports, "mapStatusToError", { enumerable: true, get: function () { return errors_2.mapStatusToError; } });
652
+ // Export HTTP client and utilities
653
+ var http_client_2 = require("./http-client");
654
+ Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return http_client_2.HttpClient; } });
655
+ Object.defineProperty(exports, "extractRateLimitInfo", { enumerable: true, get: function () { return http_client_2.extractRateLimitInfo; } });
656
+ Object.defineProperty(exports, "extractRequestId", { enumerable: true, get: function () { return http_client_2.extractRequestId; } });
657
+ Object.defineProperty(exports, "parseResponseBody", { enumerable: true, get: function () { return http_client_2.parseResponseBody; } });
658
+ // Export types
659
+ __exportStar(require("./types"), exports);
660
+ // Default export
222
661
  exports.default = Rooguys;