@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.
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ /**
3
+ * Rooguys Node.js SDK HTTP Client
4
+ * Handles standardized response format, rate limit headers, and error mapping
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.HttpClient = void 0;
11
+ exports.extractRateLimitInfo = extractRateLimitInfo;
12
+ exports.extractRequestId = extractRequestId;
13
+ exports.parseResponseBody = parseResponseBody;
14
+ const axios_1 = __importDefault(require("axios"));
15
+ const errors_1 = require("./errors");
16
+ /**
17
+ * Extract rate limit information from response headers
18
+ * @param headers - Response headers (axios format)
19
+ * @returns Rate limit info
20
+ */
21
+ function extractRateLimitInfo(headers) {
22
+ const getHeader = (name) => {
23
+ return headers[name] || headers[name.toLowerCase()];
24
+ };
25
+ return {
26
+ limit: parseInt(getHeader('X-RateLimit-Limit') || getHeader('x-ratelimit-limit') || '1000', 10),
27
+ remaining: parseInt(getHeader('X-RateLimit-Remaining') || getHeader('x-ratelimit-remaining') || '1000', 10),
28
+ reset: parseInt(getHeader('X-RateLimit-Reset') || getHeader('x-ratelimit-reset') || '0', 10),
29
+ };
30
+ }
31
+ /**
32
+ * Extract request ID from response headers or body
33
+ * @param headers - Response headers
34
+ * @param body - Response body
35
+ * @returns Request ID or null
36
+ */
37
+ function extractRequestId(headers, body) {
38
+ // Try headers first
39
+ const getHeader = (name) => {
40
+ return headers[name] || headers[name.toLowerCase()];
41
+ };
42
+ const headerRequestId = getHeader('X-Request-Id') || getHeader('x-request-id');
43
+ if (headerRequestId) {
44
+ return headerRequestId;
45
+ }
46
+ // Fall back to body
47
+ if (body && typeof body === 'object') {
48
+ const bodyObj = body;
49
+ return bodyObj.request_id || bodyObj.requestId || null;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Parse standardized API response format
55
+ * Handles both new format { success: true, data: {...} } and legacy format
56
+ * @param body - Response body
57
+ * @returns Parsed response with data and metadata
58
+ */
59
+ function parseResponseBody(body) {
60
+ if (!body || typeof body !== 'object') {
61
+ return {
62
+ data: body,
63
+ pagination: null,
64
+ requestId: null,
65
+ };
66
+ }
67
+ const bodyObj = body;
68
+ // New standardized format with { success: true, data: {...} }
69
+ if (typeof bodyObj.success === 'boolean') {
70
+ if (bodyObj.success) {
71
+ // If there's a data field, unwrap it; otherwise return the whole body
72
+ // This handles both { success: true, data: {...} } and { success: true, message: "..." }
73
+ const data = 'data' in bodyObj ? bodyObj.data : body;
74
+ return {
75
+ data: data,
76
+ pagination: bodyObj.pagination || null,
77
+ requestId: bodyObj.request_id || null,
78
+ };
79
+ }
80
+ // Error response in standardized format
81
+ return {
82
+ error: bodyObj.error,
83
+ requestId: bodyObj.request_id || null,
84
+ };
85
+ }
86
+ // Legacy format - return as-is
87
+ return {
88
+ data: body,
89
+ pagination: bodyObj.pagination || null,
90
+ requestId: null,
91
+ };
92
+ }
93
+ /**
94
+ * HTTP Client class for making API requests
95
+ */
96
+ class HttpClient {
97
+ constructor(apiKey, options = {}) {
98
+ this.apiKey = apiKey;
99
+ this.baseUrl = options.baseUrl || 'https://api.rooguys.com/v1';
100
+ this.timeout = options.timeout || 10000;
101
+ this.onRateLimitWarning = options.onRateLimitWarning || null;
102
+ this.autoRetry = options.autoRetry || false;
103
+ this.maxRetries = options.maxRetries || 3;
104
+ this.retryDelay = options.retryDelay || 1000;
105
+ this.client = axios_1.default.create({
106
+ baseURL: this.baseUrl,
107
+ timeout: this.timeout,
108
+ headers: {
109
+ 'x-api-key': this.apiKey,
110
+ 'Content-Type': 'application/json',
111
+ },
112
+ });
113
+ }
114
+ /**
115
+ * Sleep for a specified duration
116
+ * @param ms - Milliseconds to sleep
117
+ */
118
+ sleep(ms) {
119
+ return new Promise(resolve => setTimeout(resolve, ms));
120
+ }
121
+ /**
122
+ * Build query string from params object
123
+ * @param params - Query parameters
124
+ * @returns Cleaned params object
125
+ */
126
+ buildParams(params) {
127
+ const cleaned = {};
128
+ for (const [key, value] of Object.entries(params)) {
129
+ if (value !== undefined && value !== null) {
130
+ cleaned[key] = value;
131
+ }
132
+ }
133
+ return cleaned;
134
+ }
135
+ /**
136
+ * Make an HTTP request with optional auto-retry for rate limits
137
+ * @param config - Request configuration
138
+ * @param retryCount - Current retry attempt (internal use)
139
+ * @returns API response with data and metadata
140
+ */
141
+ async request(config, retryCount = 0) {
142
+ var _a, _b, _c;
143
+ const { method = 'GET', path, params = {}, body = null, headers = {}, idempotencyKey = undefined, timeout, } = config;
144
+ // Build request config
145
+ const requestConfig = {
146
+ method,
147
+ url: path,
148
+ params: this.buildParams(params),
149
+ headers: { ...headers },
150
+ };
151
+ if (body !== null) {
152
+ requestConfig.data = body;
153
+ }
154
+ if (timeout) {
155
+ requestConfig.timeout = timeout;
156
+ }
157
+ // Add idempotency key if provided
158
+ if (idempotencyKey) {
159
+ requestConfig.headers = {
160
+ ...requestConfig.headers,
161
+ 'X-Idempotency-Key': idempotencyKey,
162
+ };
163
+ }
164
+ try {
165
+ const response = await this.client.request(requestConfig);
166
+ // Extract headers info
167
+ const rateLimit = extractRateLimitInfo(response.headers);
168
+ // Check for rate limit warning (80% consumed)
169
+ if (rateLimit.remaining < rateLimit.limit * 0.2 && this.onRateLimitWarning) {
170
+ this.onRateLimitWarning(rateLimit);
171
+ }
172
+ // Extract request ID
173
+ const requestId = extractRequestId(response.headers, response.data);
174
+ // Parse response body
175
+ const parsed = parseResponseBody(response.data);
176
+ // Check for error in standardized format
177
+ if (parsed.error) {
178
+ throw (0, errors_1.mapStatusToError)(400, { error: parsed.error }, requestId, {});
179
+ }
180
+ return {
181
+ data: parsed.data,
182
+ requestId: requestId || parsed.requestId || null,
183
+ rateLimit,
184
+ pagination: parsed.pagination,
185
+ };
186
+ }
187
+ catch (error) {
188
+ // Re-throw RooguysError instances
189
+ if (error instanceof errors_1.RooguysError) {
190
+ throw error;
191
+ }
192
+ // Handle Axios errors
193
+ if (axios_1.default.isAxiosError(error)) {
194
+ const axiosError = error;
195
+ const status = ((_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status) || 0;
196
+ const responseData = ((_b = axiosError.response) === null || _b === void 0 ? void 0 : _b.data) || null;
197
+ const responseHeaders = (((_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.headers) || {});
198
+ const requestId = extractRequestId(responseHeaders, responseData);
199
+ const mappedError = (0, errors_1.mapStatusToError)(status, responseData, requestId, {
200
+ 'retry-after': responseHeaders['retry-after'],
201
+ 'Retry-After': responseHeaders['Retry-After'],
202
+ });
203
+ // Auto-retry for rate limit errors if enabled
204
+ if (this.autoRetry && mappedError instanceof errors_1.RateLimitError && retryCount < this.maxRetries) {
205
+ const retryAfterMs = mappedError.retryAfter * 1000;
206
+ await this.sleep(retryAfterMs);
207
+ return this.request(config, retryCount + 1);
208
+ }
209
+ throw mappedError;
210
+ }
211
+ // Handle timeout
212
+ if (error instanceof Error && error.message.includes('timeout')) {
213
+ throw new errors_1.RooguysError('Request timeout', { code: 'TIMEOUT', statusCode: 408 });
214
+ }
215
+ // Handle other errors
216
+ throw new errors_1.RooguysError(error instanceof Error ? error.message : 'Network error', { code: 'NETWORK_ERROR', statusCode: 0 });
217
+ }
218
+ }
219
+ /**
220
+ * Convenience method for GET requests
221
+ */
222
+ get(path, params = {}, options = {}) {
223
+ return this.request({ method: 'GET', path, params, ...options });
224
+ }
225
+ /**
226
+ * Convenience method for POST requests
227
+ */
228
+ post(path, body = null, options = {}) {
229
+ return this.request({ method: 'POST', path, body, ...options });
230
+ }
231
+ /**
232
+ * Convenience method for PUT requests
233
+ */
234
+ put(path, body = null, options = {}) {
235
+ return this.request({ method: 'PUT', path, body, ...options });
236
+ }
237
+ /**
238
+ * Convenience method for PATCH requests
239
+ */
240
+ patch(path, body = null, options = {}) {
241
+ return this.request({ method: 'PATCH', path, body, ...options });
242
+ }
243
+ /**
244
+ * Convenience method for DELETE requests
245
+ */
246
+ delete(path, options = {}) {
247
+ return this.request({ method: 'DELETE', path, ...options });
248
+ }
249
+ }
250
+ exports.HttpClient = HttpClient;
package/dist/index.d.ts CHANGED
@@ -1,47 +1,197 @@
1
- import { RooguysOptions, TrackEventResponse, UserProfile, UserBadge, UserRank, LeaderboardResult, AnswerSubmission, AhaDeclarationResult, AhaScoreResult, BadgeListResult, LevelListResult, Questionnaire, LeaderboardListResult } from './types';
1
+ /**
2
+ * Rooguys Node.js SDK
3
+ * Official TypeScript SDK for the Rooguys Gamification Platform
4
+ */
5
+ import { RooguysOptions, UserProfile, UserBadge, UserRank, LeaderboardResult, LeaderboardFilterOptions, AroundUserResponse, TrackEventResponse, TrackOptions, BatchEvent, BatchTrackResponse, BatchOptions, CreateUserData, UpdateUserData, BatchCreateResponse, GetUserOptions, SearchOptions, PaginatedResponse, AnswerSubmission, AhaDeclarationResult, AhaScoreResult, BadgeListResult, LevelListResult, Questionnaire, LeaderboardListResult, HealthCheckResponse, Timeframe } from './types';
6
+ /**
7
+ * Main Rooguys SDK class
8
+ */
2
9
  export declare class Rooguys {
3
- private apiKey;
4
- private client;
10
+ private _httpClient;
11
+ readonly apiKey: string;
12
+ readonly baseUrl: string;
13
+ readonly timeout: number;
14
+ /**
15
+ * Create a new Rooguys SDK instance
16
+ * @param apiKey - API key for authentication
17
+ * @param options - Configuration options
18
+ */
5
19
  constructor(apiKey: string, options?: RooguysOptions);
20
+ /**
21
+ * Validate email format client-side
22
+ */
23
+ private isValidEmail;
24
+ /**
25
+ * Build request body with only provided fields (partial update support)
26
+ */
27
+ private buildUserRequestBody;
28
+ /**
29
+ * Parse user profile to include activity summary, streak, and inventory
30
+ */
31
+ private parseUserProfile;
32
+ /**
33
+ * Build filter query parameters from options
34
+ */
35
+ private buildFilterParams;
36
+ /**
37
+ * Parse leaderboard response to include cache metadata and percentile ranks
38
+ */
39
+ private parseLeaderboardResponse;
40
+ /**
41
+ * Events module for tracking user events
42
+ */
6
43
  events: {
7
- track: (eventName: string, userId: string, properties?: Record<string, any>, options?: {
44
+ /**
45
+ * Track a single event
46
+ */
47
+ track: (eventName: string, userId: string, properties?: Record<string, unknown>, options?: TrackOptions) => Promise<TrackEventResponse>;
48
+ /**
49
+ * Track multiple events in a single request (batch operation)
50
+ */
51
+ trackBatch: (events: BatchEvent[], options?: BatchOptions) => Promise<BatchTrackResponse>;
52
+ /**
53
+ * @deprecated Use track() instead. The /v1/event endpoint is deprecated.
54
+ */
55
+ trackLegacy: (eventName: string, userId: string, properties?: Record<string, unknown>, options?: {
8
56
  includeProfile?: boolean;
9
57
  }) => Promise<TrackEventResponse>;
10
58
  };
59
+ /**
60
+ * Users module for user management and queries
61
+ */
11
62
  users: {
12
- get: (userId: string) => Promise<UserProfile>;
63
+ /**
64
+ * Create a new user
65
+ */
66
+ create: (userData: CreateUserData) => Promise<UserProfile>;
67
+ /**
68
+ * Update an existing user
69
+ */
70
+ update: (userId: string, userData: UpdateUserData) => Promise<UserProfile>;
71
+ /**
72
+ * Create multiple users in a single request (batch operation)
73
+ */
74
+ createBatch: (users: CreateUserData[]) => Promise<BatchCreateResponse>;
75
+ /**
76
+ * Get user profile with optional field selection
77
+ */
78
+ get: (userId: string, options?: GetUserOptions) => Promise<UserProfile>;
79
+ /**
80
+ * Search users with pagination
81
+ */
82
+ search: (query: string, options?: SearchOptions) => Promise<PaginatedResponse<UserProfile>>;
83
+ /**
84
+ * Get multiple user profiles
85
+ */
13
86
  getBulk: (userIds: string[]) => Promise<{
14
87
  users: UserProfile[];
15
88
  }>;
89
+ /**
90
+ * Get user badges
91
+ */
16
92
  getBadges: (userId: string) => Promise<{
17
93
  badges: UserBadge[];
18
94
  }>;
19
- getRank: (userId: string, timeframe?: "all-time" | "weekly" | "monthly") => Promise<UserRank>;
95
+ /**
96
+ * Get user rank
97
+ */
98
+ getRank: (userId: string, timeframe?: Timeframe) => Promise<UserRank>;
99
+ /**
100
+ * Submit questionnaire answers
101
+ */
20
102
  submitAnswers: (userId: string, questionnaireId: string, answers: AnswerSubmission[]) => Promise<{
21
103
  status: string;
22
104
  message: string;
23
105
  }>;
24
106
  };
107
+ /**
108
+ * Leaderboards module for ranking queries
109
+ */
25
110
  leaderboards: {
26
- getGlobal: (timeframe?: "all-time" | "weekly" | "monthly", page?: number, limit?: number) => Promise<LeaderboardResult>;
27
- list: (page?: number, limit?: number, search?: string) => Promise<LeaderboardListResult>;
28
- getCustom: (leaderboardId: string, page?: number, limit?: number, search?: string) => Promise<LeaderboardResult>;
111
+ /**
112
+ * Get global leaderboard with optional filters
113
+ */
114
+ getGlobal: (timeframeOrOptions?: Timeframe | LeaderboardFilterOptions, page?: number, limit?: number, options?: LeaderboardFilterOptions) => Promise<LeaderboardResult>;
115
+ /**
116
+ * List all leaderboards
117
+ */
118
+ list: (pageOrOptions?: number | {
119
+ page?: number;
120
+ limit?: number;
121
+ search?: string;
122
+ }, limit?: number, search?: string | null) => Promise<LeaderboardListResult>;
123
+ /**
124
+ * Get custom leaderboard with optional filters
125
+ */
126
+ getCustom: (leaderboardId: string, pageOrOptions?: number | LeaderboardFilterOptions, limit?: number, search?: string | null, options?: LeaderboardFilterOptions) => Promise<LeaderboardResult>;
127
+ /**
128
+ * Get user rank in leaderboard
129
+ */
29
130
  getUserRank: (leaderboardId: string, userId: string) => Promise<UserRank>;
131
+ /**
132
+ * Get leaderboard entries around a specific user ("around me" view)
133
+ */
134
+ getAroundUser: (leaderboardId: string, userId: string, range?: number) => Promise<AroundUserResponse>;
30
135
  };
136
+ /**
137
+ * Badges module for badge queries
138
+ */
31
139
  badges: {
140
+ /**
141
+ * List all badges
142
+ */
32
143
  list: (page?: number, limit?: number, activeOnly?: boolean) => Promise<BadgeListResult>;
33
144
  };
145
+ /**
146
+ * Levels module for level queries
147
+ */
34
148
  levels: {
149
+ /**
150
+ * List all levels
151
+ */
35
152
  list: (page?: number, limit?: number) => Promise<LevelListResult>;
36
153
  };
154
+ /**
155
+ * Questionnaires module for questionnaire queries
156
+ */
37
157
  questionnaires: {
158
+ /**
159
+ * Get questionnaire by slug
160
+ */
38
161
  get: (slug: string) => Promise<Questionnaire>;
162
+ /**
163
+ * Get active questionnaire
164
+ */
39
165
  getActive: () => Promise<Questionnaire>;
40
166
  };
167
+ /**
168
+ * Aha moment module for engagement tracking
169
+ */
41
170
  aha: {
171
+ /**
172
+ * Declare aha moment score
173
+ */
42
174
  declare: (userId: string, value: number) => Promise<AhaDeclarationResult>;
175
+ /**
176
+ * Get user aha score
177
+ */
43
178
  getUserScore: (userId: string) => Promise<AhaScoreResult>;
44
179
  };
45
- private handleError;
180
+ /**
181
+ * Health module for API health checks
182
+ */
183
+ health: {
184
+ /**
185
+ * Check API health status
186
+ */
187
+ check: () => Promise<HealthCheckResponse>;
188
+ /**
189
+ * Quick availability check
190
+ */
191
+ isReady: () => Promise<boolean>;
192
+ };
46
193
  }
194
+ export { RooguysError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, ServerError, mapStatusToError, } from './errors';
195
+ export { HttpClient, extractRateLimitInfo, extractRequestId, parseResponseBody, } from './http-client';
196
+ export * from './types';
47
197
  export default Rooguys;