@foru-ms/sdk 1.1.1 → 1.2.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.
Files changed (47) hide show
  1. package/README.md +286 -11
  2. package/dist/Client.d.ts +63 -4
  3. package/dist/Client.js +124 -22
  4. package/dist/errors.d.ts +52 -0
  5. package/dist/errors.js +95 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.js +3 -0
  8. package/dist/resources/Integrations.d.ts +7 -0
  9. package/dist/resources/Integrations.js +6 -0
  10. package/dist/resources/Posts.d.ts +12 -0
  11. package/dist/resources/Posts.js +44 -0
  12. package/dist/resources/PrivateMessages.d.ts +4 -0
  13. package/dist/resources/PrivateMessages.js +6 -0
  14. package/dist/resources/SSO.d.ts +10 -0
  15. package/dist/resources/SSO.js +11 -0
  16. package/dist/resources/Tags.d.ts +8 -0
  17. package/dist/resources/Tags.js +24 -0
  18. package/dist/resources/Threads.d.ts +20 -0
  19. package/dist/resources/Threads.js +85 -0
  20. package/dist/resources/Users.d.ts +10 -0
  21. package/dist/resources/Users.js +26 -0
  22. package/dist/resources/Webhooks.d.ts +69 -0
  23. package/dist/resources/Webhooks.js +115 -0
  24. package/dist/response-types.d.ts +105 -0
  25. package/dist/response-types.js +2 -0
  26. package/dist/utils.d.ts +80 -0
  27. package/dist/utils.js +138 -0
  28. package/examples/README.md +38 -0
  29. package/examples/authentication.ts +79 -0
  30. package/examples/error-handling.ts +133 -0
  31. package/examples/managing-threads.ts +130 -0
  32. package/examples/pagination.ts +81 -0
  33. package/examples/webhooks.ts +176 -0
  34. package/package.json +1 -1
  35. package/src/Client.ts +165 -25
  36. package/src/errors.ts +95 -0
  37. package/src/index.ts +3 -0
  38. package/src/resources/Integrations.ts +11 -0
  39. package/src/resources/Posts.ts +56 -0
  40. package/src/resources/PrivateMessages.ts +10 -0
  41. package/src/resources/SSO.ts +17 -0
  42. package/src/resources/Tags.ts +32 -0
  43. package/src/resources/Threads.ts +109 -0
  44. package/src/resources/Users.ts +36 -0
  45. package/src/resources/Webhooks.ts +131 -0
  46. package/src/response-types.ts +113 -0
  47. package/src/utils.ts +182 -0
package/README.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # @foru-ms/sdk
2
2
 
3
- The official JavaScript/TypeScript SDK for [Foru.ms](https://foru.ms). Build powerful community features directly into your application.
3
+ The official JavaScript/ TypeScript SDK for [Foru.ms](https://foru.ms). Build powerful community features directly into your application.
4
4
 
5
- This SDK is fully typed and provides a comprehensive interface to the Foru.ms API.
5
+ This SDK is fully typed and provides a comprehensive interface to the Foru.ms API with built-in error handling, automatic retries, and pagination helpers.
6
+
7
+ ## Features
8
+
9
+ ✨ **Fully Typed** - Complete TypeScript support with detailed type definitions
10
+ 🔄 **Auto Retry** - Automatic retry logic for rate limits and server errors
11
+ 📄 **Easy Pagination** - Built-in helpers for cursor-based pagination
12
+ 🚨 **Rich Error Classes** - Specific error types for better error handling
13
+ 🔐 **Webhook Verification** - Built-in signature verification for webhooks
14
+ 📊 **Rate Limit Tracking** - Automatic tracking of API rate limits
15
+ 📚 **Comprehensive Examples** - Detailed examples for common use cases
6
16
 
7
17
  ## Installation
8
18
 
@@ -14,20 +24,156 @@ yarn add @foru-ms/sdk
14
24
  pnpm add @foru-ms/sdk
15
25
  ```
16
26
 
17
- ## Setup & Initialization
27
+ ## Quick Start
18
28
 
19
29
  ```typescript
20
30
  import { ForumClient } from '@foru-ms/sdk';
21
31
 
22
32
  const client = new ForumClient({
23
33
  apiKey: 'your_api_key',
24
- // baseUrl: 'https://api.foru.ms/v1' // Optional
34
+ baseUrl: 'https://api.foru.ms/v1', // Optional
35
+ maxRetries: 3, // Optional, default: 3
36
+ enableRetry: true, // Optional, default: true
25
37
  });
26
38
 
27
39
  // Set an authentication token (JWT) for user-scoped requests
28
40
  client.setToken('user_jwt_token');
41
+
42
+ // Create a thread
43
+ const thread = await client.threads.create({
44
+ title: 'My First Thread',
45
+ body: 'Hello, Foru.ms!',
46
+ userId: 'user-123',
47
+ });
48
+
49
+ // List threads with auto-pagination
50
+ for await (const thread of client.pagination.paginateAll(
51
+ (cursor) => client.threads.list({ cursor })
52
+ )) {
53
+ console.log(thread.title);
54
+ }
55
+ ```
56
+
57
+ ## Error Handling
58
+
59
+ The SDK provides specific error classes for different scenarios:
60
+
61
+ ```typescript
62
+ import {
63
+ ForumClient,
64
+ AuthenticationError,
65
+ NotFoundError,
66
+ RateLimitError,
67
+ ValidationError
68
+ } from '@foru-ms/sdk';
69
+
70
+ try {
71
+ await client.threads.retrieve('thread-id');
72
+ } catch (error) {
73
+ if (error instanceof NotFoundError) {
74
+ console.log('Thread not found');
75
+ } else if (error instanceof RateLimitError) {
76
+ console.log('Rate limited, retry after:', error.retryAfter);
77
+ } else if (error instanceof AuthenticationError) {
78
+ console.log('Authentication failed');
79
+ }
80
+ }
29
81
  ```
30
82
 
83
+ **Available Error Classes:**
84
+ - `ForumAPIError` - Base class for all API errors
85
+ - `AuthenticationError` - 401 errors
86
+ - `AuthorizationError` - 403 errors
87
+ - `NotFoundError` - 404 errors
88
+ - `ValidationError` - 422 errors
89
+ - `RateLimitError` - 429 errors
90
+ - `ServerError` - 5xx errors
91
+ - `NetworkError` - Network/connection errors
92
+
93
+ ## Pagination
94
+
95
+ The SDK provides multiple ways to handle pagination:
96
+
97
+ ```typescript
98
+ // Auto-pagination with async iterator
99
+ for await (const thread of client.pagination.paginateAll(
100
+ (cursor) => client.threads.list({ cursor, filter: 'newest' })
101
+ )) {
102
+ console.log(thread.title);
103
+ }
104
+
105
+ // Fetch all pages at once
106
+ const allThreads = await client.pagination.fetchAllPages(
107
+ (cursor) => client.threads.list({ cursor }),
108
+ 5 // max pages
109
+ );
110
+
111
+ // Manual pagination
112
+ let cursor: string | undefined;
113
+ do {
114
+ const response = await client.threads.list({ cursor });
115
+ // Process threads...
116
+ cursor = response.nextThreadCursor;
117
+ } while (cursor);
118
+ ```
119
+
120
+ ## Webhooks
121
+
122
+ Verify webhook signatures for security. All webhooks include:
123
+ - `X-Webhook-Signature`: HMAC-SHA256 signature
124
+ - `X-Webhook-Timestamp`: Unix timestamp (milliseconds)
125
+ - `X-Webhook-Event`: Event type
126
+
127
+ ```typescript
128
+ app.post('/webhooks/foru', (req, res) => {
129
+ const signature = req.headers['x-webhook-signature'];
130
+ const timestamp = req.headers['x-webhook-timestamp'];
131
+ const event = req.headers['x-webhook-event'];
132
+
133
+ // Verify signature with timestamp (prevents replay attacks)
134
+ const isValid = client.webhooks.verifySignature(
135
+ req.body,
136
+ signature,
137
+ timestamp,
138
+ 'your_webhook_secret'
139
+ );
140
+
141
+ if (!isValid) {
142
+ return res.status(401).send('Invalid signature');
143
+ }
144
+
145
+ // Process webhook...
146
+ console.log('Event:', event);
147
+ res.send('OK');
148
+ });
149
+ ```
150
+
151
+ ## Rate Limiting
152
+
153
+ Track rate limit information automatically:
154
+
155
+ ```typescript
156
+ await client.threads.list();
157
+
158
+ if (client.lastRateLimitInfo) {
159
+ console.log('Limit:', client.lastRateLimitInfo.limit);
160
+ console.log('Remaining:', client.lastRateLimitInfo.remaining);
161
+ console.log('Resets at:', new Date(client.lastRateLimitInfo.reset * 1000));
162
+ }
163
+ ```
164
+
165
+ ## Examples
166
+
167
+ Check the `/examples` directory for detailed examples:
168
+
169
+ - [Authentication](./examples/authentication.ts) - Login, registration, token management
170
+ - [Managing Threads](./examples/managing-threads.ts) - CRUD operations, polls, interactions
171
+ - [Pagination](./examples/pagination.ts) - Different pagination strategies
172
+ - [Error Handling](./examples/error-handling.ts) - Comprehensive error handling
173
+ - [Webhooks](./examples/webhooks.ts) - Setup and verification
174
+
175
+
176
+
31
177
  ## API Reference
32
178
 
33
179
  ### Auth (`client.auth`)
@@ -48,10 +194,20 @@ client.setToken('user_jwt_token');
48
194
  * `getPosts(id: string, params: { cursor?: string; filter?: 'newest' | 'oldest' })`: Get posts in a thread.
49
195
  * `like(id: string, userId: string, extendedData?: any)`: Like a thread.
50
196
  * `unlike(id: string, userId: string)`: Unlike a thread.
197
+ * `getLikes(id: string, params?: { cursor?: string })`: Get users who liked a thread.
51
198
  * `dislike(id: string, userId: string, extendedData?: any)`: Dislike a thread.
52
199
  * `undislike(id: string, userId: string)`: Remove dislike from a thread.
200
+ * `getDislikes(id: string, params?: { cursor?: string })`: Get users who disliked a thread.
53
201
  * `subscribe(id: string, userId: string)`: Subscribe to a thread.
54
202
  * `unsubscribe(id: string, userId: string)`: Unsubscribe from a thread.
203
+ * `getSubscribers(id: string, params?: { cursor?: string })`: Get users subscribed to a thread.
204
+ * `upvote(id: string, userId: string, extendedData?: any)`: Upvote a thread.
205
+ * `unupvote(id: string, userId: string)`: Remove upvote from a thread.
206
+ * `getUpvotes(id: string, params?: { cursor?: string })`: Get users who upvoted a thread.
207
+ * `downvote(id: string, userId: string, extendedData?: any)`: Downvote a thread.
208
+ * `undownvote(id: string, userId: string)`: Remove downvote from a thread.
209
+ * `getDownvotes(id: string, params?: { cursor?: string })`: Get users who downvoted a thread.
210
+ * `getPoll(id: string, userId?: string)`: Get poll results.
55
211
  * `vote(id: string, userId: string, optionId: string)`: Vote in a thread poll.
56
212
  * `voteUpdate(id: string, userId: string, optionId: string)`: Change vote.
57
213
  * `unvote(id: string, userId: string)`: Remove vote.
@@ -66,12 +222,16 @@ client.setToken('user_jwt_token');
66
222
  * `getChildren(id: string, params: { cursor?: string; filter?: 'newest' | 'oldest' })`: Get child posts (nested replies).
67
223
  * `like(id: string, userId: string, extendedData?: any)`: Like a post.
68
224
  * `unlike(id: string, userId: string)`: Unlike a post.
225
+ * `getLikes(id: string, params?: { cursor?: string })`: Get users who liked a post.
69
226
  * `dislike(id: string, userId: string, extendedData?: any)`: Dislike a post.
70
227
  * `undislike(id: string, userId: string)`: Remove dislike.
228
+ * `getDislikes(id: string, params?: { cursor?: string })`: Get users who disliked a post.
71
229
  * `upvote(id: string, userId: string, extendedData?: any)`: Upvote a post.
72
230
  * `unupvote(id: string, userId: string)`: Remove upvote.
231
+ * `getUpvotes(id: string, params?: { cursor?: string })`: Get users who upvoted a post.
73
232
  * `downvote(id: string, userId: string, extendedData?: any)`: Downvote a post.
74
233
  * `undownvote(id: string, userId: string)`: Remove downvote.
234
+ * `getDownvotes(id: string, params?: { cursor?: string })`: Get users who downvoted a post.
75
235
 
76
236
  ### Users (`client.users`)
77
237
 
@@ -80,6 +240,8 @@ client.setToken('user_jwt_token');
80
240
  * `create(payload: { username: string; email: string; password: string; displayName?: string; emailVerified?: boolean; roles?: string[]; bio?: string; signature?: string; url?: string; extendedData?: Record<string, any> })`: Create a user (Admin).
81
241
  * `update(id: string, payload: { username?: string; email?: string; password?: string; displayName?: string; emailVerified?: boolean; roles?: string[]; bio?: string; signature?: string; url?: string; extendedData?: Record<string, any> })`: Update a user.
82
242
  * `delete(id: string)`: Delete a user.
243
+ * `getThreads(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all threads created by a user.
244
+ * `getPosts(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all posts created by a user.
83
245
  * `getFollowers(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get user's followers.
84
246
  * `getFollowing(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get who a user follows.
85
247
  * `follow(id: string, followerId: string, extendedData?: any)`: Follow a user.
@@ -93,8 +255,10 @@ client.setToken('user_jwt_token');
93
255
  * `retrieve(id: string, params?: { userId?: string })`: Get a tag.
94
256
  * `update(id: string, payload: { name?: string; description?: string; color?: string; extendedData?: Record<string, any> })`: Update a tag.
95
257
  * `delete(id: string)`: Delete a tag.
258
+ * `getThreads(id: string, params?: { query?: string; cursor?: string; filter?: 'newest' | 'oldest' })`: Get all threads with a specific tag.
96
259
  * `subscribe(id: string, userId: string)`: Subscribe to a tag.
97
260
  * `unsubscribe(id: string, userId: string)`: Unsubscribe from a tag.
261
+ * `getSubscribers(id: string, params?: { cursor?: string })`: Get users subscribed to a tag.
98
262
  * `listSubscribed(params: { userId: string; query?: string; cursor?: string })`: List tags a user is subscribed to.
99
263
 
100
264
 
@@ -129,6 +293,7 @@ client.setToken('user_jwt_token');
129
293
  * `list()`: Get all configured integrations.
130
294
  * `create(payload: { type: 'SLACK' | 'DISCORD' | 'SALESFORCE' | 'HUBSPOT' | 'OKTA' | 'AUTH0'; name: string; config: any })`: Configure an integration (Slack, Discord, etc.).
131
295
  * `retrieve(id: string)`: Get integration details.
296
+ * `update(id: string, payload: { name?: string; config?: any; active?: boolean })`: Update an integration.
132
297
  * `delete(id: string)`: Remove an integration.
133
298
 
134
299
  ### Private Messages (`client.privateMessages`)
@@ -137,6 +302,7 @@ client.setToken('user_jwt_token');
137
302
  * `create(payload: { title?: string; body: string; recipientId: string; senderId?: string; extendedData?: Record<string, any> })`: Send a direct message.
138
303
  * `retrieve(id: string)`: Get a message thread.
139
304
  * `reply(id: string, payload: { body: string; senderId: string; recipientId: string; extendedData?: Record<string, any> })`: Reply to a message.
305
+ * `update(id: string, payload: { read?: boolean; extendedData?: Record<string, any> })`: Update a message (e.g., mark as read).
140
306
  * `delete(id: string)`: Delete a message.
141
307
 
142
308
  ### Reports (`client.reports`)
@@ -161,6 +327,8 @@ client.setToken('user_jwt_token');
161
327
 
162
328
  * `list()`: List SSO providers.
163
329
  * `create(payload: { provider: 'OKTA' | 'AUTH0' | 'SAML'; domain: string; config: any })`: Configure SSO.
330
+ * `retrieve(id: string)`: Get SSO provider details.
331
+ * `update(id: string, payload: { domain?: string; config?: any; active?: boolean })`: Update SSO configuration.
164
332
  * `delete(id: string)`: Remove SSO provider.
165
333
 
166
334
  ## Types
@@ -230,22 +398,129 @@ import {
230
398
 
231
399
  // Utility Types
232
400
  PaginatedResponse,
233
- InteractionType
401
+ InteractionType,
402
+
403
+ // Error Classes
404
+ ForumAPIError,
405
+ AuthenticationError,
406
+ AuthorizationError,
407
+ NotFoundError,
408
+ ValidationError,
409
+ RateLimitError,
410
+ ServerError,
411
+ NetworkError,
412
+
413
+ // Response Types
414
+ RateLimitInfo,
415
+ ResponseMetadata,
416
+ APIResponse,
417
+ InteractionListResponse,
418
+ PollResponse,
419
+ PollOption,
420
+
421
+ // Utilities
422
+ PaginationHelper,
423
+ Validator,
424
+ RetryHelper
234
425
  } from '@foru-ms/sdk';
235
426
  ```
236
427
 
237
- ## Error Handling
428
+ ## Advanced Usage
238
429
 
239
- All methods return a Promise. If the API returns a non-200 status, the Promise rejects with an Error object containing the server message.
430
+ ### Custom Client Options
240
431
 
241
432
  ```typescript
242
- try {
243
- await client.threads.create({ ... });
244
- } catch (err: any) {
245
- console.error("Error creating thread:", err.message);
433
+ const client = new ForumClient({
434
+ apiKey: 'your_api_key',
435
+ baseUrl: 'https://api.foru.ms/v1',
436
+ maxRetries: 5, // Increase retry attempts
437
+ enableRetry: true, // Enable auto-retry (default: true)
438
+ });
439
+
440
+ // Access rate limit info after any request
441
+ await client.threads.list();
442
+ console.log('Rate limit:', client.lastRateLimitInfo);
443
+
444
+ // Check authentication status
445
+ if (client.isAuthenticated()) {
446
+ const user = await client.auth.me();
447
+ }
448
+ ```
449
+
450
+ ### Async Iteration for Large Datasets
451
+
452
+ ```typescript
453
+ // Automatically fetch all pages
454
+ for await (const user of client.pagination.paginateAll(
455
+ (cursor) => client.users.list({ cursor })
456
+ )) {
457
+ await processUser(user);
458
+ // Pagination happens automatically in the background
246
459
  }
247
460
  ```
248
461
 
462
+ ### Batch Operations
463
+
464
+ ```typescript
465
+ // Fetch multiple items efficiently
466
+ const threadIds = ['id1', 'id2', 'id3'];
467
+ const threads = await Promise.all(
468
+ threadIds.map(id => client.threads.retrieve(id))
469
+ );
470
+ ```
471
+
472
+ ## Best Practices
473
+
474
+ 1. **Error Handling**: Always use try-catch with specific error types
475
+ 2. **Rate Limits**: Monitor `client.lastRateLimitInfo` for API usage
476
+ 3. **Webhooks**: Always verify signatures to prevent unauthorized requests
477
+ 4. **Pagination**: Use async iterators for large result sets
478
+ 5. **Authentication**: Store tokens securely, never in client-side code
479
+
480
+ ## Troubleshooting
481
+
482
+ ### Rate Limit Errors
483
+
484
+ If you encounter rate limit errors frequently, consider:
485
+ - Implementing caching for frequently accessed data
486
+ - Using pagination helpers to avoid fetching too much data
487
+ - Batching operations where possible
488
+
489
+ ### Authentication Issues
490
+
491
+ - Ensure your API key is valid and active
492
+ - Check that tokens haven't expired
493
+ - Use `client.isAuthenticated()` to verify authentication status
494
+
495
+ ### Webhook Verification Failures
496
+
497
+ - Verify you're using the correct webhook secret
498
+ - Check that timestamps aren't too old (default: 5 minutes)
499
+ - Ensure you're passing the raw payload object, not a string
500
+
501
+ ## Contributing
502
+
503
+ We welcome contributions! Please see our contributing guidelines for more information.
504
+
505
+ ## Support
506
+
507
+ - 📧 Email: support@foru.ms
508
+ - 📚 Documentation: https://docs.foru.ms
509
+ - 🐛 Issues: https://github.com/foru-ms/sdk/issues
510
+
511
+ ## Changelog
512
+
513
+ ### v1.2.0
514
+ - Added custom error classes for better error handling
515
+ - Added automatic retry logic with exponential backoff
516
+ - Added pagination helpers for easy iteration
517
+ - Added webhook signature verification
518
+ - Added 22 new endpoints
519
+ - Enhanced documentation and examples
520
+
521
+ ### v1.x
522
+ - Initial SDK release with core functionality
523
+
249
524
  ## License
250
525
 
251
526
  MIT
package/dist/Client.d.ts CHANGED
@@ -12,10 +12,42 @@ import { PrivateMessagesResource } from './resources/PrivateMessages';
12
12
  import { ReportsResource } from './resources/Reports';
13
13
  import { RolesResource } from './resources/Roles';
14
14
  import { SSOResource } from './resources/SSO';
15
+ import { PaginationHelper } from './utils';
16
+ import { RateLimitInfo } from './response-types';
17
+ export interface ClientOptions {
18
+ /** API key for authentication */
19
+ apiKey: string;
20
+ /** Base URL for the API (default: https://api.foru.ms/v1) */
21
+ baseUrl?: string;
22
+ /** Maximum number of retry attempts for failed requests (default: 3) */
23
+ maxRetries?: number;
24
+ /** Enable automatic retry for rate limits and server errors (default: true) */
25
+ enableRetry?: boolean;
26
+ }
27
+ /**
28
+ * Main client for interacting with the Foru.ms API
29
+ * @example
30
+ * ```typescript
31
+ * const client = new ForumClient({
32
+ * apiKey: 'your_api_key',
33
+ * baseUrl: 'https://api.foru.ms/v1'
34
+ * });
35
+ *
36
+ * // Set user token
37
+ * client.setToken('user_jwt_token');
38
+ *
39
+ * // Use resources
40
+ * const threads = await client.threads.list();
41
+ * ```
42
+ */
15
43
  export declare class ForumClient {
16
44
  apiKey: string;
17
45
  token: string | null;
18
46
  baseUrl: string;
47
+ maxRetries: number;
48
+ enableRetry: boolean;
49
+ /** Last known rate limit info */
50
+ lastRateLimitInfo?: RateLimitInfo;
19
51
  auth: AuthResource;
20
52
  threads: ThreadsResource;
21
53
  posts: PostsResource;
@@ -30,10 +62,37 @@ export declare class ForumClient {
30
62
  reports: ReportsResource;
31
63
  roles: RolesResource;
32
64
  sso: SSOResource;
33
- constructor(options: {
34
- apiKey: string;
35
- baseUrl?: string;
36
- });
65
+ /** Pagination helper for auto-paginating through results */
66
+ pagination: PaginationHelper;
67
+ constructor(options: ClientOptions);
68
+ /**
69
+ * Make an HTTP request to the API
70
+ * @param path - API endpoint path
71
+ * @param options - Fetch options
72
+ * @returns Promise resolving to the response data
73
+ * @throws {ForumAPIError} When the API returns an error
74
+ * @throws {NetworkError} When the network request fails
75
+ */
37
76
  request<T>(path: string, options?: RequestInit): Promise<T>;
77
+ /**
78
+ * Set the authentication token for user-scoped requests
79
+ * @param token - JWT token
80
+ */
38
81
  setToken(token: string): void;
82
+ /**
83
+ * Clear the authentication token
84
+ */
85
+ clearToken(): void;
86
+ /**
87
+ * Check if client is authenticated
88
+ */
89
+ isAuthenticated(): boolean;
90
+ /**
91
+ * Extract and store rate limit information from response headers
92
+ */
93
+ private extractRateLimitInfo;
94
+ /**
95
+ * Handle error responses by throwing appropriate error types
96
+ */
97
+ private handleErrorResponse;
39
98
  }
package/dist/Client.js CHANGED
@@ -15,13 +15,34 @@ const PrivateMessages_1 = require("./resources/PrivateMessages");
15
15
  const Reports_1 = require("./resources/Reports");
16
16
  const Roles_1 = require("./resources/Roles");
17
17
  const SSO_1 = require("./resources/SSO");
18
+ const errors_1 = require("./errors");
19
+ const utils_1 = require("./utils");
18
20
  // Polyfill fetch if needed (e.g. older Node versions)
19
21
  const fetch = globalThis.fetch || require('cross-fetch');
22
+ /**
23
+ * Main client for interacting with the Foru.ms API
24
+ * @example
25
+ * ```typescript
26
+ * const client = new ForumClient({
27
+ * apiKey: 'your_api_key',
28
+ * baseUrl: 'https://api.foru.ms/v1'
29
+ * });
30
+ *
31
+ * // Set user token
32
+ * client.setToken('user_jwt_token');
33
+ *
34
+ * // Use resources
35
+ * const threads = await client.threads.list();
36
+ * ```
37
+ */
20
38
  class ForumClient {
21
39
  constructor(options) {
40
+ var _a, _b;
22
41
  this.token = null;
23
42
  this.apiKey = options.apiKey;
24
43
  this.baseUrl = options.baseUrl || 'https://api.foru.ms/v1';
44
+ this.maxRetries = (_a = options.maxRetries) !== null && _a !== void 0 ? _a : 3;
45
+ this.enableRetry = (_b = options.enableRetry) !== null && _b !== void 0 ? _b : true;
25
46
  this.auth = new Auth_1.AuthResource(this);
26
47
  this.threads = new Threads_1.ThreadsResource(this);
27
48
  this.posts = new Posts_1.PostsResource(this);
@@ -36,35 +57,116 @@ class ForumClient {
36
57
  this.reports = new Reports_1.ReportsResource(this);
37
58
  this.roles = new Roles_1.RolesResource(this);
38
59
  this.sso = new SSO_1.SSOResource(this);
60
+ this.pagination = new utils_1.PaginationHelper();
39
61
  }
62
+ /**
63
+ * Make an HTTP request to the API
64
+ * @param path - API endpoint path
65
+ * @param options - Fetch options
66
+ * @returns Promise resolving to the response data
67
+ * @throws {ForumAPIError} When the API returns an error
68
+ * @throws {NetworkError} When the network request fails
69
+ */
40
70
  async request(path, options = {}) {
41
- const headers = {
42
- 'Content-Type': 'application/json',
43
- 'x-api-key': this.apiKey,
44
- ...options.headers,
71
+ const makeRequest = async () => {
72
+ const headers = {
73
+ 'Content-Type': 'application/json',
74
+ 'x-api-key': this.apiKey,
75
+ ...options.headers,
76
+ };
77
+ if (this.token) {
78
+ headers['Authorization'] = `Bearer ${this.token}`;
79
+ }
80
+ let response;
81
+ try {
82
+ response = await fetch(`${this.baseUrl}${path}`, {
83
+ ...options,
84
+ headers,
85
+ });
86
+ }
87
+ catch (error) {
88
+ throw new errors_1.NetworkError('Network request failed', error);
89
+ }
90
+ // Extract rate limit info
91
+ this.extractRateLimitInfo(response);
92
+ const contentType = response.headers.get('content-type');
93
+ let data;
94
+ if (contentType && contentType.includes('application/json')) {
95
+ data = await response.json();
96
+ }
97
+ else {
98
+ data = await response.text();
99
+ }
100
+ if (!response.ok) {
101
+ this.handleErrorResponse(response.status, data);
102
+ }
103
+ return data;
45
104
  };
46
- if (this.token) {
47
- headers['Authorization'] = `Bearer ${this.token}`;
105
+ // Apply retry logic if enabled
106
+ if (this.enableRetry) {
107
+ return utils_1.RetryHelper.withRetry(makeRequest, this.maxRetries);
48
108
  }
49
- const response = await fetch(`${this.baseUrl}${path}`, {
50
- ...options,
51
- headers,
52
- });
53
- const contentType = response.headers.get('content-type');
54
- let data;
55
- if (contentType && contentType.includes('application/json')) {
56
- data = await response.json();
57
- }
58
- else {
59
- data = await response.text();
60
- }
61
- if (!response.ok) {
62
- throw new Error((data && data.message) || data.error || 'API Error');
63
- }
64
- return data;
109
+ return makeRequest();
65
110
  }
111
+ /**
112
+ * Set the authentication token for user-scoped requests
113
+ * @param token - JWT token
114
+ */
66
115
  setToken(token) {
67
116
  this.token = token;
68
117
  }
118
+ /**
119
+ * Clear the authentication token
120
+ */
121
+ clearToken() {
122
+ this.token = null;
123
+ }
124
+ /**
125
+ * Check if client is authenticated
126
+ */
127
+ isAuthenticated() {
128
+ return this.token !== null;
129
+ }
130
+ /**
131
+ * Extract and store rate limit information from response headers
132
+ */
133
+ extractRateLimitInfo(response) {
134
+ const limit = response.headers.get('x-ratelimit-limit');
135
+ const remaining = response.headers.get('x-ratelimit-remaining');
136
+ const reset = response.headers.get('x-ratelimit-reset');
137
+ if (limit && remaining && reset) {
138
+ this.lastRateLimitInfo = {
139
+ limit: parseInt(limit, 10),
140
+ remaining: parseInt(remaining, 10),
141
+ reset: parseInt(reset, 10),
142
+ };
143
+ }
144
+ }
145
+ /**
146
+ * Handle error responses by throwing appropriate error types
147
+ */
148
+ handleErrorResponse(status, data) {
149
+ const message = (data && data.message) || data.error || 'API Error';
150
+ switch (status) {
151
+ case 401:
152
+ throw new errors_1.AuthenticationError(message, data);
153
+ case 403:
154
+ throw new errors_1.AuthorizationError(message, data);
155
+ case 404:
156
+ throw new errors_1.NotFoundError(message, data);
157
+ case 422:
158
+ throw new errors_1.ValidationError(message, data);
159
+ case 429:
160
+ const retryAfter = data.retryAfter || 60;
161
+ throw new errors_1.RateLimitError(message, retryAfter, data);
162
+ case 500:
163
+ case 502:
164
+ case 503:
165
+ case 504:
166
+ throw new errors_1.ServerError(message, status, data);
167
+ default:
168
+ throw new errors_1.ForumAPIError(message, status, data);
169
+ }
170
+ }
69
171
  }
70
172
  exports.ForumClient = ForumClient;