@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/README.md +478 -113
- package/dist/__tests__/utils/mockClient.d.ts +65 -3
- package/dist/__tests__/utils/mockClient.js +144 -5
- package/dist/errors.d.ts +123 -0
- package/dist/errors.js +163 -0
- package/dist/http-client.d.ts +167 -0
- package/dist/http-client.js +250 -0
- package/dist/index.d.ts +160 -10
- package/dist/index.js +585 -146
- package/dist/types.d.ts +372 -50
- package/dist/types.js +21 -0
- package/package.json +1 -1
- package/src/__tests__/property/request-construction.property.test.ts +142 -91
- package/src/__tests__/property/response-parsing.property.test.ts +118 -67
- package/src/__tests__/property/sdk-modules.property.test.ts +450 -0
- package/src/__tests__/unit/aha.test.ts +61 -50
- package/src/__tests__/unit/badges.test.ts +27 -33
- package/src/__tests__/unit/config.test.ts +94 -126
- package/src/__tests__/unit/errors.test.ts +106 -150
- package/src/__tests__/unit/events.test.ts +119 -144
- package/src/__tests__/unit/leaderboards.test.ts +173 -40
- package/src/__tests__/unit/levels.test.ts +25 -33
- package/src/__tests__/unit/questionnaires.test.ts +33 -42
- package/src/__tests__/unit/users.test.ts +214 -99
- package/src/__tests__/utils/mockClient.ts +193 -6
- package/src/errors.ts +255 -0
- package/src/http-client.ts +433 -0
- package/src/index.ts +742 -150
- package/src/types.ts +429 -51
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { AxiosResponse } from 'axios';
|
|
2
|
+
import { Rooguys } from '../../index';
|
|
1
3
|
export interface MockAxiosInstance {
|
|
2
4
|
get: jest.Mock;
|
|
3
5
|
post: jest.Mock;
|
|
6
|
+
put: jest.Mock;
|
|
7
|
+
patch: jest.Mock;
|
|
8
|
+
delete: jest.Mock;
|
|
9
|
+
request: jest.Mock;
|
|
4
10
|
defaults: {
|
|
5
11
|
headers: {
|
|
6
12
|
common: Record<string, string>;
|
|
@@ -16,7 +22,63 @@ export interface MockAxiosInstance {
|
|
|
16
22
|
};
|
|
17
23
|
}
|
|
18
24
|
export declare function createMockAxiosInstance(): MockAxiosInstance;
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Create a mock Rooguys client with mocked HTTP client
|
|
27
|
+
* Returns both the client and the mock axios instance for setting up expectations
|
|
28
|
+
*/
|
|
29
|
+
export declare function createMockRooguysClient(apiKey?: string): {
|
|
30
|
+
client: Rooguys;
|
|
31
|
+
mockAxios: MockAxiosInstance;
|
|
21
32
|
};
|
|
22
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Create a mock AxiosResponse with rate limit headers
|
|
35
|
+
*/
|
|
36
|
+
export declare function mockAxiosResponse<T>(data: T, status?: number, headers?: Record<string, string>): AxiosResponse<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a mock AxiosResponse for standardized API format
|
|
39
|
+
*/
|
|
40
|
+
export declare function mockStandardizedResponse<T>(data: T, requestId?: string, headers?: Record<string, string>): AxiosResponse<{
|
|
41
|
+
success: true;
|
|
42
|
+
data: T;
|
|
43
|
+
request_id: string;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Create a mock success response (legacy format - data directly in response)
|
|
47
|
+
*/
|
|
48
|
+
export declare function mockSuccessResponse<T>(data: T, headers?: Record<string, string>): AxiosResponse<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Create a mock error that mimics an Axios error
|
|
51
|
+
*/
|
|
52
|
+
export declare function mockErrorResponse(status: number, message: string, code?: string, details?: Array<{
|
|
53
|
+
field: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}>): any;
|
|
56
|
+
/**
|
|
57
|
+
* Create a mock rate limit error
|
|
58
|
+
*/
|
|
59
|
+
export declare function mockRateLimitError(retryAfter?: number): any;
|
|
60
|
+
/**
|
|
61
|
+
* Helper to set up mock for a successful request
|
|
62
|
+
* The mock axios instance uses request() method internally
|
|
63
|
+
*/
|
|
64
|
+
export declare function setupMockRequest<T>(mockAxios: MockAxiosInstance, responseData: T, headers?: Record<string, string>): void;
|
|
65
|
+
/**
|
|
66
|
+
* Helper to set up mock for a failed request
|
|
67
|
+
*/
|
|
68
|
+
export declare function setupMockRequestError(mockAxios: MockAxiosInstance, status: number, message: string, code?: string, details?: Array<{
|
|
69
|
+
field: string;
|
|
70
|
+
message: string;
|
|
71
|
+
}>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get the last request config from mock axios
|
|
74
|
+
*/
|
|
75
|
+
export declare function getLastRequestConfig(mockAxios: MockAxiosInstance): any;
|
|
76
|
+
/**
|
|
77
|
+
* Assert that a request was made with specific config
|
|
78
|
+
*/
|
|
79
|
+
export declare function expectRequestWith(mockAxios: MockAxiosInstance, expected: {
|
|
80
|
+
method?: string;
|
|
81
|
+
url?: string;
|
|
82
|
+
data?: any;
|
|
83
|
+
params?: any;
|
|
84
|
+
}): void;
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createMockAxiosInstance = createMockAxiosInstance;
|
|
4
|
+
exports.createMockRooguysClient = createMockRooguysClient;
|
|
5
|
+
exports.mockAxiosResponse = mockAxiosResponse;
|
|
6
|
+
exports.mockStandardizedResponse = mockStandardizedResponse;
|
|
4
7
|
exports.mockSuccessResponse = mockSuccessResponse;
|
|
5
8
|
exports.mockErrorResponse = mockErrorResponse;
|
|
9
|
+
exports.mockRateLimitError = mockRateLimitError;
|
|
10
|
+
exports.setupMockRequest = setupMockRequest;
|
|
11
|
+
exports.setupMockRequestError = setupMockRequestError;
|
|
12
|
+
exports.getLastRequestConfig = getLastRequestConfig;
|
|
13
|
+
exports.expectRequestWith = expectRequestWith;
|
|
14
|
+
const index_1 = require("../../index");
|
|
6
15
|
function createMockAxiosInstance() {
|
|
7
16
|
return {
|
|
8
17
|
get: jest.fn(),
|
|
9
18
|
post: jest.fn(),
|
|
19
|
+
put: jest.fn(),
|
|
20
|
+
patch: jest.fn(),
|
|
21
|
+
delete: jest.fn(),
|
|
22
|
+
request: jest.fn(),
|
|
10
23
|
defaults: {
|
|
11
24
|
headers: {
|
|
12
25
|
common: {},
|
|
@@ -18,18 +31,144 @@ function createMockAxiosInstance() {
|
|
|
18
31
|
},
|
|
19
32
|
};
|
|
20
33
|
}
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Create a mock Rooguys client with mocked HTTP client
|
|
36
|
+
* Returns both the client and the mock axios instance for setting up expectations
|
|
37
|
+
*/
|
|
38
|
+
function createMockRooguysClient(apiKey = 'test-api-key') {
|
|
39
|
+
const client = new index_1.Rooguys(apiKey);
|
|
40
|
+
const mockAxios = createMockAxiosInstance();
|
|
41
|
+
// Access the internal HttpClient and replace its axios instance
|
|
42
|
+
const httpClient = client._httpClient;
|
|
43
|
+
httpClient.client = mockAxios;
|
|
44
|
+
return { client, mockAxios };
|
|
23
45
|
}
|
|
24
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Create a mock AxiosResponse with rate limit headers
|
|
48
|
+
*/
|
|
49
|
+
function mockAxiosResponse(data, status = 200, headers = {}) {
|
|
50
|
+
return {
|
|
51
|
+
data,
|
|
52
|
+
status,
|
|
53
|
+
statusText: status === 200 ? 'OK' : 'Error',
|
|
54
|
+
headers: {
|
|
55
|
+
'x-ratelimit-limit': '1000',
|
|
56
|
+
'x-ratelimit-remaining': '999',
|
|
57
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + 3600),
|
|
58
|
+
...headers,
|
|
59
|
+
},
|
|
60
|
+
config: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a mock AxiosResponse for standardized API format
|
|
65
|
+
*/
|
|
66
|
+
function mockStandardizedResponse(data, requestId = 'req-123', headers = {}) {
|
|
67
|
+
return mockAxiosResponse({
|
|
68
|
+
success: true,
|
|
69
|
+
data,
|
|
70
|
+
request_id: requestId,
|
|
71
|
+
}, 200, {
|
|
72
|
+
'x-request-id': requestId,
|
|
73
|
+
...headers,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a mock success response (legacy format - data directly in response)
|
|
78
|
+
*/
|
|
79
|
+
function mockSuccessResponse(data, headers = {}) {
|
|
80
|
+
return mockAxiosResponse(data, 200, headers);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a mock error that mimics an Axios error
|
|
84
|
+
*/
|
|
85
|
+
function mockErrorResponse(status, message, code, details) {
|
|
25
86
|
const error = new Error(message);
|
|
26
87
|
error.response = {
|
|
27
88
|
status,
|
|
28
89
|
data: {
|
|
29
|
-
|
|
30
|
-
|
|
90
|
+
success: false,
|
|
91
|
+
error: {
|
|
92
|
+
message,
|
|
93
|
+
code: code || 'ERROR',
|
|
94
|
+
details,
|
|
95
|
+
},
|
|
96
|
+
request_id: 'req-error-123',
|
|
97
|
+
},
|
|
98
|
+
headers: {
|
|
99
|
+
'x-ratelimit-limit': '1000',
|
|
100
|
+
'x-ratelimit-remaining': '999',
|
|
101
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + 3600),
|
|
102
|
+
'x-request-id': 'req-error-123',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
error.isAxiosError = true;
|
|
106
|
+
return error;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create a mock rate limit error
|
|
110
|
+
*/
|
|
111
|
+
function mockRateLimitError(retryAfter = 60) {
|
|
112
|
+
const error = new Error('Rate limit exceeded');
|
|
113
|
+
error.response = {
|
|
114
|
+
status: 429,
|
|
115
|
+
data: {
|
|
116
|
+
success: false,
|
|
117
|
+
error: {
|
|
118
|
+
message: 'Rate limit exceeded',
|
|
119
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
120
|
+
},
|
|
121
|
+
request_id: 'req-ratelimit-123',
|
|
122
|
+
},
|
|
123
|
+
headers: {
|
|
124
|
+
'x-ratelimit-limit': '1000',
|
|
125
|
+
'x-ratelimit-remaining': '0',
|
|
126
|
+
'x-ratelimit-reset': String(Math.floor(Date.now() / 1000) + retryAfter),
|
|
127
|
+
'retry-after': String(retryAfter),
|
|
128
|
+
'x-request-id': 'req-ratelimit-123',
|
|
31
129
|
},
|
|
32
130
|
};
|
|
33
131
|
error.isAxiosError = true;
|
|
34
132
|
return error;
|
|
35
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Helper to set up mock for a successful request
|
|
136
|
+
* The mock axios instance uses request() method internally
|
|
137
|
+
*/
|
|
138
|
+
function setupMockRequest(mockAxios, responseData, headers = {}) {
|
|
139
|
+
mockAxios.request.mockResolvedValue(mockAxiosResponse(responseData, 200, headers));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Helper to set up mock for a failed request
|
|
143
|
+
*/
|
|
144
|
+
function setupMockRequestError(mockAxios, status, message, code, details) {
|
|
145
|
+
mockAxios.request.mockRejectedValue(mockErrorResponse(status, message, code, details));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the last request config from mock axios
|
|
149
|
+
*/
|
|
150
|
+
function getLastRequestConfig(mockAxios) {
|
|
151
|
+
const calls = mockAxios.request.mock.calls;
|
|
152
|
+
if (calls.length === 0)
|
|
153
|
+
return null;
|
|
154
|
+
return calls[calls.length - 1][0];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Assert that a request was made with specific config
|
|
158
|
+
*/
|
|
159
|
+
function expectRequestWith(mockAxios, expected) {
|
|
160
|
+
const lastConfig = getLastRequestConfig(mockAxios);
|
|
161
|
+
expect(lastConfig).toBeTruthy();
|
|
162
|
+
if (expected.method) {
|
|
163
|
+
expect(lastConfig.method).toBe(expected.method);
|
|
164
|
+
}
|
|
165
|
+
if (expected.url) {
|
|
166
|
+
expect(lastConfig.url).toBe(expected.url);
|
|
167
|
+
}
|
|
168
|
+
if (expected.data) {
|
|
169
|
+
expect(lastConfig.data).toEqual(expected.data);
|
|
170
|
+
}
|
|
171
|
+
if (expected.params) {
|
|
172
|
+
expect(lastConfig.params).toEqual(expected.params);
|
|
173
|
+
}
|
|
174
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rooguys Node.js SDK Error Classes
|
|
3
|
+
* Typed error classes for different API error scenarios
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Field-level error detail
|
|
7
|
+
*/
|
|
8
|
+
export interface FieldError {
|
|
9
|
+
field: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Base error options
|
|
14
|
+
*/
|
|
15
|
+
export interface RooguysErrorOptions {
|
|
16
|
+
code?: string;
|
|
17
|
+
requestId?: string | null;
|
|
18
|
+
statusCode?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Base error class for all Rooguys SDK errors
|
|
22
|
+
*/
|
|
23
|
+
export declare class RooguysError extends Error {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
readonly requestId: string | null;
|
|
26
|
+
readonly statusCode: number;
|
|
27
|
+
constructor(message: string, { code, requestId, statusCode }?: RooguysErrorOptions);
|
|
28
|
+
toJSON(): Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validation error options
|
|
32
|
+
*/
|
|
33
|
+
export interface ValidationErrorOptions extends RooguysErrorOptions {
|
|
34
|
+
fieldErrors?: FieldError[] | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validation error (HTTP 400)
|
|
38
|
+
* Thrown when request validation fails
|
|
39
|
+
*/
|
|
40
|
+
export declare class ValidationError extends RooguysError {
|
|
41
|
+
readonly fieldErrors: FieldError[] | null;
|
|
42
|
+
constructor(message: string, { code, requestId, fieldErrors }?: ValidationErrorOptions);
|
|
43
|
+
toJSON(): Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Authentication error (HTTP 401)
|
|
47
|
+
* Thrown when API key is invalid or missing
|
|
48
|
+
*/
|
|
49
|
+
export declare class AuthenticationError extends RooguysError {
|
|
50
|
+
constructor(message: string, { code, requestId }?: RooguysErrorOptions);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Forbidden error (HTTP 403)
|
|
54
|
+
* Thrown when access is denied
|
|
55
|
+
*/
|
|
56
|
+
export declare class ForbiddenError extends RooguysError {
|
|
57
|
+
constructor(message: string, { code, requestId }?: RooguysErrorOptions);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Not found error (HTTP 404)
|
|
61
|
+
* Thrown when requested resource doesn't exist
|
|
62
|
+
*/
|
|
63
|
+
export declare class NotFoundError extends RooguysError {
|
|
64
|
+
constructor(message: string, { code, requestId }?: RooguysErrorOptions);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Conflict error (HTTP 409)
|
|
68
|
+
* Thrown when resource already exists or state conflict
|
|
69
|
+
*/
|
|
70
|
+
export declare class ConflictError extends RooguysError {
|
|
71
|
+
constructor(message: string, { code, requestId }?: RooguysErrorOptions);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Rate limit error options
|
|
75
|
+
*/
|
|
76
|
+
export interface RateLimitErrorOptions extends RooguysErrorOptions {
|
|
77
|
+
retryAfter?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rate limit error (HTTP 429)
|
|
81
|
+
* Thrown when rate limit is exceeded
|
|
82
|
+
*/
|
|
83
|
+
export declare class RateLimitError extends RooguysError {
|
|
84
|
+
readonly retryAfter: number;
|
|
85
|
+
constructor(message: string, { code, requestId, retryAfter }?: RateLimitErrorOptions);
|
|
86
|
+
toJSON(): Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Server error (HTTP 500+)
|
|
90
|
+
* Thrown when server encounters an error
|
|
91
|
+
*/
|
|
92
|
+
export declare class ServerError extends RooguysError {
|
|
93
|
+
constructor(message: string, { code, requestId, statusCode }?: RooguysErrorOptions);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Error response body structure
|
|
97
|
+
*/
|
|
98
|
+
export interface ErrorResponseBody {
|
|
99
|
+
error?: {
|
|
100
|
+
message?: string;
|
|
101
|
+
code?: string;
|
|
102
|
+
details?: FieldError[];
|
|
103
|
+
} | string;
|
|
104
|
+
message?: string;
|
|
105
|
+
code?: string;
|
|
106
|
+
details?: FieldError[];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Response headers for error mapping
|
|
110
|
+
*/
|
|
111
|
+
export interface ErrorResponseHeaders {
|
|
112
|
+
'retry-after'?: string;
|
|
113
|
+
'Retry-After'?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Map HTTP status code to appropriate error class
|
|
117
|
+
* @param status - HTTP status code
|
|
118
|
+
* @param errorBody - Error response body
|
|
119
|
+
* @param requestId - Request ID from response
|
|
120
|
+
* @param headers - Response headers
|
|
121
|
+
* @returns Appropriate error instance
|
|
122
|
+
*/
|
|
123
|
+
export declare function mapStatusToError(status: number, errorBody: ErrorResponseBody | null, requestId: string | null, headers?: ErrorResponseHeaders): RooguysError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Rooguys Node.js SDK Error Classes
|
|
4
|
+
* Typed error classes for different API error scenarios
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ServerError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.AuthenticationError = exports.ValidationError = exports.RooguysError = void 0;
|
|
8
|
+
exports.mapStatusToError = mapStatusToError;
|
|
9
|
+
/**
|
|
10
|
+
* Base error class for all Rooguys SDK errors
|
|
11
|
+
*/
|
|
12
|
+
class RooguysError extends Error {
|
|
13
|
+
constructor(message, { code = 'UNKNOWN_ERROR', requestId = null, statusCode = 500 } = {}) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'RooguysError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.requestId = requestId;
|
|
18
|
+
this.statusCode = statusCode;
|
|
19
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
20
|
+
if (Error.captureStackTrace) {
|
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
toJSON() {
|
|
25
|
+
return {
|
|
26
|
+
name: this.name,
|
|
27
|
+
message: this.message,
|
|
28
|
+
code: this.code,
|
|
29
|
+
requestId: this.requestId,
|
|
30
|
+
statusCode: this.statusCode,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.RooguysError = RooguysError;
|
|
35
|
+
/**
|
|
36
|
+
* Validation error (HTTP 400)
|
|
37
|
+
* Thrown when request validation fails
|
|
38
|
+
*/
|
|
39
|
+
class ValidationError extends RooguysError {
|
|
40
|
+
constructor(message, { code = 'VALIDATION_ERROR', requestId = null, fieldErrors = null } = {}) {
|
|
41
|
+
super(message, { code, requestId, statusCode: 400 });
|
|
42
|
+
this.name = 'ValidationError';
|
|
43
|
+
this.fieldErrors = fieldErrors;
|
|
44
|
+
}
|
|
45
|
+
toJSON() {
|
|
46
|
+
return {
|
|
47
|
+
...super.toJSON(),
|
|
48
|
+
fieldErrors: this.fieldErrors,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ValidationError = ValidationError;
|
|
53
|
+
/**
|
|
54
|
+
* Authentication error (HTTP 401)
|
|
55
|
+
* Thrown when API key is invalid or missing
|
|
56
|
+
*/
|
|
57
|
+
class AuthenticationError extends RooguysError {
|
|
58
|
+
constructor(message, { code = 'AUTHENTICATION_ERROR', requestId = null } = {}) {
|
|
59
|
+
super(message, { code, requestId, statusCode: 401 });
|
|
60
|
+
this.name = 'AuthenticationError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.AuthenticationError = AuthenticationError;
|
|
64
|
+
/**
|
|
65
|
+
* Forbidden error (HTTP 403)
|
|
66
|
+
* Thrown when access is denied
|
|
67
|
+
*/
|
|
68
|
+
class ForbiddenError extends RooguysError {
|
|
69
|
+
constructor(message, { code = 'FORBIDDEN', requestId = null } = {}) {
|
|
70
|
+
super(message, { code, requestId, statusCode: 403 });
|
|
71
|
+
this.name = 'ForbiddenError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.ForbiddenError = ForbiddenError;
|
|
75
|
+
/**
|
|
76
|
+
* Not found error (HTTP 404)
|
|
77
|
+
* Thrown when requested resource doesn't exist
|
|
78
|
+
*/
|
|
79
|
+
class NotFoundError extends RooguysError {
|
|
80
|
+
constructor(message, { code = 'NOT_FOUND', requestId = null } = {}) {
|
|
81
|
+
super(message, { code, requestId, statusCode: 404 });
|
|
82
|
+
this.name = 'NotFoundError';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.NotFoundError = NotFoundError;
|
|
86
|
+
/**
|
|
87
|
+
* Conflict error (HTTP 409)
|
|
88
|
+
* Thrown when resource already exists or state conflict
|
|
89
|
+
*/
|
|
90
|
+
class ConflictError extends RooguysError {
|
|
91
|
+
constructor(message, { code = 'CONFLICT', requestId = null } = {}) {
|
|
92
|
+
super(message, { code, requestId, statusCode: 409 });
|
|
93
|
+
this.name = 'ConflictError';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ConflictError = ConflictError;
|
|
97
|
+
/**
|
|
98
|
+
* Rate limit error (HTTP 429)
|
|
99
|
+
* Thrown when rate limit is exceeded
|
|
100
|
+
*/
|
|
101
|
+
class RateLimitError extends RooguysError {
|
|
102
|
+
constructor(message, { code = 'RATE_LIMIT_EXCEEDED', requestId = null, retryAfter = 60 } = {}) {
|
|
103
|
+
super(message, { code, requestId, statusCode: 429 });
|
|
104
|
+
this.name = 'RateLimitError';
|
|
105
|
+
this.retryAfter = retryAfter;
|
|
106
|
+
}
|
|
107
|
+
toJSON() {
|
|
108
|
+
return {
|
|
109
|
+
...super.toJSON(),
|
|
110
|
+
retryAfter: this.retryAfter,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.RateLimitError = RateLimitError;
|
|
115
|
+
/**
|
|
116
|
+
* Server error (HTTP 500+)
|
|
117
|
+
* Thrown when server encounters an error
|
|
118
|
+
*/
|
|
119
|
+
class ServerError extends RooguysError {
|
|
120
|
+
constructor(message, { code = 'SERVER_ERROR', requestId = null, statusCode = 500 } = {}) {
|
|
121
|
+
super(message, { code, requestId, statusCode });
|
|
122
|
+
this.name = 'ServerError';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.ServerError = ServerError;
|
|
126
|
+
/**
|
|
127
|
+
* Map HTTP status code to appropriate error class
|
|
128
|
+
* @param status - HTTP status code
|
|
129
|
+
* @param errorBody - Error response body
|
|
130
|
+
* @param requestId - Request ID from response
|
|
131
|
+
* @param headers - Response headers
|
|
132
|
+
* @returns Appropriate error instance
|
|
133
|
+
*/
|
|
134
|
+
function mapStatusToError(status, errorBody, requestId, headers = {}) {
|
|
135
|
+
const errorObj = typeof (errorBody === null || errorBody === void 0 ? void 0 : errorBody.error) === 'object' ? errorBody.error : null;
|
|
136
|
+
const message = (errorObj === null || errorObj === void 0 ? void 0 : errorObj.message) ||
|
|
137
|
+
(typeof (errorBody === null || errorBody === void 0 ? void 0 : errorBody.error) === 'string' ? errorBody.error : null) ||
|
|
138
|
+
(errorBody === null || errorBody === void 0 ? void 0 : errorBody.message) ||
|
|
139
|
+
'An error occurred';
|
|
140
|
+
const code = (errorObj === null || errorObj === void 0 ? void 0 : errorObj.code) || (errorBody === null || errorBody === void 0 ? void 0 : errorBody.code) || 'UNKNOWN_ERROR';
|
|
141
|
+
const fieldErrors = (errorObj === null || errorObj === void 0 ? void 0 : errorObj.details) || (errorBody === null || errorBody === void 0 ? void 0 : errorBody.details) || null;
|
|
142
|
+
switch (status) {
|
|
143
|
+
case 400:
|
|
144
|
+
return new ValidationError(message, { code, requestId, fieldErrors });
|
|
145
|
+
case 401:
|
|
146
|
+
return new AuthenticationError(message, { code, requestId });
|
|
147
|
+
case 403:
|
|
148
|
+
return new ForbiddenError(message, { code, requestId });
|
|
149
|
+
case 404:
|
|
150
|
+
return new NotFoundError(message, { code, requestId });
|
|
151
|
+
case 409:
|
|
152
|
+
return new ConflictError(message, { code, requestId });
|
|
153
|
+
case 429: {
|
|
154
|
+
const retryAfter = parseInt(headers['retry-after'] || headers['Retry-After'] || '60', 10);
|
|
155
|
+
return new RateLimitError(message, { code, requestId, retryAfter });
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
if (status >= 500) {
|
|
159
|
+
return new ServerError(message, { code, requestId, statusCode: status });
|
|
160
|
+
}
|
|
161
|
+
return new RooguysError(message, { code, requestId, statusCode: status });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rooguys Node.js SDK HTTP Client
|
|
3
|
+
* Handles standardized response format, rate limit headers, and error mapping
|
|
4
|
+
*/
|
|
5
|
+
import { ErrorResponseBody } from './errors';
|
|
6
|
+
/**
|
|
7
|
+
* Rate limit information extracted from response headers
|
|
8
|
+
*/
|
|
9
|
+
export interface RateLimitInfo {
|
|
10
|
+
/** Maximum requests allowed in the window */
|
|
11
|
+
limit: number;
|
|
12
|
+
/** Remaining requests in the current window */
|
|
13
|
+
remaining: number;
|
|
14
|
+
/** Unix timestamp when the limit resets */
|
|
15
|
+
reset: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Cache metadata from API responses
|
|
19
|
+
*/
|
|
20
|
+
export interface CacheMetadata {
|
|
21
|
+
/** When the data was cached */
|
|
22
|
+
cachedAt: Date | null;
|
|
23
|
+
/** Time-to-live in seconds */
|
|
24
|
+
ttl: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pagination information
|
|
28
|
+
*/
|
|
29
|
+
export interface Pagination {
|
|
30
|
+
page: number;
|
|
31
|
+
limit: number;
|
|
32
|
+
total: number;
|
|
33
|
+
totalPages: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* API response wrapper with metadata
|
|
37
|
+
*/
|
|
38
|
+
export interface ApiResponse<T> {
|
|
39
|
+
/** Response data */
|
|
40
|
+
data: T;
|
|
41
|
+
/** Request ID for debugging */
|
|
42
|
+
requestId: string | null;
|
|
43
|
+
/** Rate limit information */
|
|
44
|
+
rateLimit: RateLimitInfo;
|
|
45
|
+
/** Pagination info if present */
|
|
46
|
+
pagination?: Pagination | null;
|
|
47
|
+
/** Cache metadata if present */
|
|
48
|
+
cacheMetadata?: CacheMetadata | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Request configuration options
|
|
52
|
+
*/
|
|
53
|
+
export interface RequestConfig {
|
|
54
|
+
/** HTTP method */
|
|
55
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
56
|
+
/** API endpoint path */
|
|
57
|
+
path: string;
|
|
58
|
+
/** Query parameters */
|
|
59
|
+
params?: Record<string, string | number | boolean | undefined | null>;
|
|
60
|
+
/** Request body */
|
|
61
|
+
body?: unknown;
|
|
62
|
+
/** Additional headers */
|
|
63
|
+
headers?: Record<string, string>;
|
|
64
|
+
/** Idempotency key for POST requests */
|
|
65
|
+
idempotencyKey?: string;
|
|
66
|
+
/** Request timeout in ms */
|
|
67
|
+
timeout?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* HTTP Client options
|
|
71
|
+
*/
|
|
72
|
+
export interface HttpClientOptions {
|
|
73
|
+
/** Base URL for API */
|
|
74
|
+
baseUrl?: string;
|
|
75
|
+
/** Request timeout in ms */
|
|
76
|
+
timeout?: number;
|
|
77
|
+
/** Callback when rate limit is 80% consumed */
|
|
78
|
+
onRateLimitWarning?: ((rateLimit: RateLimitInfo) => void) | null;
|
|
79
|
+
/** Enable auto-retry for rate-limited requests */
|
|
80
|
+
autoRetry?: boolean;
|
|
81
|
+
/** Maximum retry attempts for rate limits */
|
|
82
|
+
maxRetries?: number;
|
|
83
|
+
/** Base delay for retries in ms */
|
|
84
|
+
retryDelay?: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parsed response body result
|
|
88
|
+
*/
|
|
89
|
+
interface ParsedResponseBody<T> {
|
|
90
|
+
data?: T;
|
|
91
|
+
error?: ErrorResponseBody['error'];
|
|
92
|
+
pagination?: Pagination | null;
|
|
93
|
+
requestId?: string | null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract rate limit information from response headers
|
|
97
|
+
* @param headers - Response headers (axios format)
|
|
98
|
+
* @returns Rate limit info
|
|
99
|
+
*/
|
|
100
|
+
export declare function extractRateLimitInfo(headers: Record<string, string | undefined>): RateLimitInfo;
|
|
101
|
+
/**
|
|
102
|
+
* Extract request ID from response headers or body
|
|
103
|
+
* @param headers - Response headers
|
|
104
|
+
* @param body - Response body
|
|
105
|
+
* @returns Request ID or null
|
|
106
|
+
*/
|
|
107
|
+
export declare function extractRequestId(headers: Record<string, string | undefined>, body: unknown): string | null;
|
|
108
|
+
/**
|
|
109
|
+
* Parse standardized API response format
|
|
110
|
+
* Handles both new format { success: true, data: {...} } and legacy format
|
|
111
|
+
* @param body - Response body
|
|
112
|
+
* @returns Parsed response with data and metadata
|
|
113
|
+
*/
|
|
114
|
+
export declare function parseResponseBody<T>(body: unknown): ParsedResponseBody<T>;
|
|
115
|
+
/**
|
|
116
|
+
* HTTP Client class for making API requests
|
|
117
|
+
*/
|
|
118
|
+
export declare class HttpClient {
|
|
119
|
+
private client;
|
|
120
|
+
private apiKey;
|
|
121
|
+
private baseUrl;
|
|
122
|
+
private timeout;
|
|
123
|
+
private onRateLimitWarning;
|
|
124
|
+
private autoRetry;
|
|
125
|
+
private maxRetries;
|
|
126
|
+
private retryDelay;
|
|
127
|
+
constructor(apiKey: string, options?: HttpClientOptions);
|
|
128
|
+
/**
|
|
129
|
+
* Sleep for a specified duration
|
|
130
|
+
* @param ms - Milliseconds to sleep
|
|
131
|
+
*/
|
|
132
|
+
private sleep;
|
|
133
|
+
/**
|
|
134
|
+
* Build query string from params object
|
|
135
|
+
* @param params - Query parameters
|
|
136
|
+
* @returns Cleaned params object
|
|
137
|
+
*/
|
|
138
|
+
private buildParams;
|
|
139
|
+
/**
|
|
140
|
+
* Make an HTTP request with optional auto-retry for rate limits
|
|
141
|
+
* @param config - Request configuration
|
|
142
|
+
* @param retryCount - Current retry attempt (internal use)
|
|
143
|
+
* @returns API response with data and metadata
|
|
144
|
+
*/
|
|
145
|
+
request<T>(config: RequestConfig, retryCount?: number): Promise<ApiResponse<T>>;
|
|
146
|
+
/**
|
|
147
|
+
* Convenience method for GET requests
|
|
148
|
+
*/
|
|
149
|
+
get<T>(path: string, params?: Record<string, string | number | boolean | undefined | null>, options?: Partial<RequestConfig>): Promise<ApiResponse<T>>;
|
|
150
|
+
/**
|
|
151
|
+
* Convenience method for POST requests
|
|
152
|
+
*/
|
|
153
|
+
post<T>(path: string, body?: unknown, options?: Partial<RequestConfig>): Promise<ApiResponse<T>>;
|
|
154
|
+
/**
|
|
155
|
+
* Convenience method for PUT requests
|
|
156
|
+
*/
|
|
157
|
+
put<T>(path: string, body?: unknown, options?: Partial<RequestConfig>): Promise<ApiResponse<T>>;
|
|
158
|
+
/**
|
|
159
|
+
* Convenience method for PATCH requests
|
|
160
|
+
*/
|
|
161
|
+
patch<T>(path: string, body?: unknown, options?: Partial<RequestConfig>): Promise<ApiResponse<T>>;
|
|
162
|
+
/**
|
|
163
|
+
* Convenience method for DELETE requests
|
|
164
|
+
*/
|
|
165
|
+
delete<T>(path: string, options?: Partial<RequestConfig>): Promise<ApiResponse<T>>;
|
|
166
|
+
}
|
|
167
|
+
export {};
|