@foru-ms/sdk 1.1.0 → 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 +375 -38
  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/dist/utils.js ADDED
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RetryHelper = exports.Validator = exports.PaginationHelper = void 0;
4
+ /**
5
+ * Pagination utilities for working with cursor-based pagination
6
+ */
7
+ class PaginationHelper {
8
+ /**
9
+ * Async generator to auto-paginate through all results
10
+ * @param fetchPage - Function to fetch a page of results
11
+ * @yields Individual items from each page
12
+ * @example
13
+ * ```typescript
14
+ * const helper = new PaginationHelper();
15
+ * for await (const thread of helper.paginateAll((cursor) =>
16
+ * client.threads.list({ cursor })
17
+ * )) {
18
+ * console.log(thread);
19
+ * }
20
+ * ```
21
+ */
22
+ async *paginateAll(fetchPage) {
23
+ let cursor;
24
+ let hasMore = true;
25
+ while (hasMore) {
26
+ const response = await fetchPage(cursor);
27
+ // Determine which array and cursor to use
28
+ const items = response.threads || response.posts || response.users || response.tags || [];
29
+ const nextCursor = response.nextThreadCursor || response.nextPostCursor ||
30
+ response.nextUserCursor || response.nextTagCursor;
31
+ for (const item of items) {
32
+ yield item;
33
+ }
34
+ cursor = nextCursor;
35
+ hasMore = !!nextCursor;
36
+ }
37
+ }
38
+ /**
39
+ * Fetch all items from a paginated endpoint
40
+ * @param fetchPage - Function to fetch a page of results
41
+ * @param maxPages - Maximum number of pages to fetch (default: Infinity)
42
+ * @returns Array of all items
43
+ */
44
+ async fetchAllPages(fetchPage, maxPages = Infinity) {
45
+ const allItems = [];
46
+ let cursor;
47
+ let pageCount = 0;
48
+ while (pageCount < maxPages) {
49
+ const response = await fetchPage(cursor);
50
+ const items = response.threads || response.posts || response.users || response.tags || [];
51
+ const nextCursor = response.nextThreadCursor || response.nextPostCursor ||
52
+ response.nextUserCursor || response.nextTagCursor;
53
+ allItems.push(...items);
54
+ cursor = nextCursor;
55
+ pageCount++;
56
+ if (!nextCursor)
57
+ break;
58
+ }
59
+ return allItems;
60
+ }
61
+ }
62
+ exports.PaginationHelper = PaginationHelper;
63
+ /**
64
+ * Input validation utilities
65
+ */
66
+ class Validator {
67
+ /**
68
+ * Validate that a string is not empty
69
+ */
70
+ static isNonEmptyString(value, fieldName) {
71
+ if (typeof value !== 'string' || value.trim().length === 0) {
72
+ throw new Error(`${fieldName} must be a non-empty string`);
73
+ }
74
+ }
75
+ /**
76
+ * Validate email format
77
+ */
78
+ static isValidEmail(email) {
79
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
80
+ return emailRegex.test(email);
81
+ }
82
+ /**
83
+ * Validate that a value is one of the allowed options
84
+ */
85
+ static isOneOf(value, options, fieldName) {
86
+ if (!options.includes(value)) {
87
+ throw new Error(`${fieldName} must be one of: ${options.join(', ')}`);
88
+ }
89
+ }
90
+ /**
91
+ * Validate cursor format (if needed)
92
+ */
93
+ static isValidCursor(cursor) {
94
+ return typeof cursor === 'string' && cursor.length > 0;
95
+ }
96
+ }
97
+ exports.Validator = Validator;
98
+ /**
99
+ * Retry utilities for handling transient failures
100
+ */
101
+ class RetryHelper {
102
+ /**
103
+ * Execute a function with exponential backoff retry
104
+ * @param fn - Function to execute
105
+ * @param maxRetries - Maximum number of retry attempts
106
+ * @param initialDelay - Initial delay in milliseconds
107
+ * @returns Result of the function
108
+ */
109
+ static async withRetry(fn, maxRetries = 3, initialDelay = 1000) {
110
+ let lastError;
111
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
112
+ try {
113
+ return await fn();
114
+ }
115
+ catch (error) {
116
+ lastError = error;
117
+ // Don't retry on client errors (4xx) except 429
118
+ if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
119
+ throw error;
120
+ }
121
+ // Don't retry on last attempt
122
+ if (attempt === maxRetries) {
123
+ break;
124
+ }
125
+ // Calculate delay with exponential backoff
126
+ const delay = error.retryAfter
127
+ ? error.retryAfter * 1000
128
+ : initialDelay * Math.pow(2, attempt);
129
+ await this.sleep(delay);
130
+ }
131
+ }
132
+ throw lastError;
133
+ }
134
+ static sleep(ms) {
135
+ return new Promise(resolve => setTimeout(resolve, ms));
136
+ }
137
+ }
138
+ exports.RetryHelper = RetryHelper;
@@ -0,0 +1,38 @@
1
+ # SDK Examples
2
+
3
+ This directory contains example code demonstrating common use cases for the Foru.ms SDK.
4
+
5
+ ## Examples
6
+
7
+ - [Authentication](./authentication.ts) - Login, registration, and token management
8
+ - [Managing Threads](./managing-threads.ts) - Create, update, and interact with threads
9
+ - [Pagination](./pagination.ts) - Working with cursor-based pagination
10
+ - [Webhooks](./webhooks.ts) - Setting up and verifying webhooks
11
+ - [Error Handling](./error-handling.ts) - Properly handling SDK errors
12
+
13
+ ## Running Examples
14
+
15
+ ```bash
16
+ # Install dependencies
17
+ npm install @foru-ms/sdk
18
+
19
+ # Run an example with ts-node
20
+ npx ts-node examples/authentication.ts
21
+
22
+ # Or compile and run
23
+ tsc examples/authentication.ts && node examples/authentication.js
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ Most examples require an API key. Set it as an environment variable:
29
+
30
+ ```bash
31
+ export FORU_API_KEY="your_api_key_here"
32
+ ```
33
+
34
+ For user-authenticated examples, you'll also need a user token:
35
+
36
+ ```bash
37
+ export FORU_USER_TOKEN="user_jwt_token"
38
+ ```
@@ -0,0 +1,79 @@
1
+ import { ForumClient, AuthenticationError } from '@foru-ms/sdk';
2
+
3
+ /**
4
+ * Example: Authentication with Foru.ms SDK
5
+ * Demonstrates user registration, login, and token management
6
+ */
7
+
8
+ async function main() {
9
+ // Initialize client with API key
10
+ const client = new ForumClient({
11
+ apiKey: process.env.FORU_API_KEY || 'your_api_key',
12
+ });
13
+
14
+ try {
15
+ // Example 1: User Registration
16
+ console.log('=== User Registration ===');
17
+ const newUser = await client.auth.register({
18
+ username: 'john_doe',
19
+ email: 'john@example.com',
20
+ password: 'securePassword123',
21
+ displayName: 'John Doe',
22
+ });
23
+ console.log('User registered:', newUser);
24
+
25
+ // Example 2: User Login
26
+ console.log('\n=== User Login ===');
27
+ const loginResponse = await client.auth.login({
28
+ login: 'john@example.com', // Can be username or email
29
+ password: 'securePassword123',
30
+ });
31
+ console.log('Login successful, token:', loginResponse.token);
32
+
33
+ // Store the token for authenticated requests
34
+ client.setToken(loginResponse.token);
35
+
36
+ // Example 3: Get Current User
37
+ console.log('\n=== Get Current User ===');
38
+ const currentUser = await client.auth.me();
39
+ console.log('Current user:', currentUser);
40
+
41
+ // Example 4: Password Reset Flow
42
+ console.log('\n=== Password Reset ===');
43
+
44
+ // Step 1: Request password reset
45
+ await client.auth.forgotPassword('john@example.com');
46
+ console.log('Password reset email sent');
47
+
48
+ // Step 2: Reset password with token (received via email)
49
+ // Note: In a real app, this would be done after user clicks the email link
50
+ const resetToken = 'token_from_email';
51
+ await client.auth.resetPassword({
52
+ password: 'newSecurePassword123',
53
+ token: resetToken,
54
+ });
55
+ console.log('Password reset successful');
56
+
57
+ // Example 5: Check Authentication Status
58
+ console.log('\n=== Authentication Status ===');
59
+ console.log('Is authenticated:', client.isAuthenticated());
60
+ console.log('Current token:', client.token);
61
+
62
+ // Example 6: Logout (clear token)
63
+ console.log('\n=== Logout ===');
64
+ client.clearToken();
65
+ console.log('Token cleared');
66
+ console.log('Is authenticated:', client.isAuthenticated());
67
+
68
+ } catch (error) {
69
+ if (error instanceof AuthenticationError) {
70
+ console.error('Authentication failed:', error.message);
71
+ console.error('Status code:', error.statusCode);
72
+ } else {
73
+ console.error('An error occurred:', error);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Run the example
79
+ main().catch(console.error);
@@ -0,0 +1,133 @@
1
+ import {
2
+ ForumClient,
3
+ ForumAPIError,
4
+ AuthenticationError,
5
+ AuthorizationError,
6
+ NotFoundError,
7
+ ValidationError,
8
+ RateLimitError,
9
+ ServerError,
10
+ NetworkError
11
+ } from '@foru-ms/sdk';
12
+
13
+ /**
14
+ * Example: Error Handling
15
+ * Demonstrates proper error handling with custom error classes
16
+ */
17
+
18
+ async function main() {
19
+ const client = new ForumClient({
20
+ apiKey: process.env.FORU_API_KEY || 'your_api_key',
21
+ maxRetries: 3,
22
+ enableRetry: true,
23
+ });
24
+
25
+ // Example 1: Catch specific error types
26
+ console.log('=== Handling Specific Errors ===');
27
+ try {
28
+ await client.auth.login({
29
+ login: 'nonexistent@example.com',
30
+ password: 'wrong_password',
31
+ });
32
+ } catch (error) {
33
+ if (error instanceof AuthenticationError) {
34
+ console.log('Authentication failed');
35
+ console.log('Message:', error.message);
36
+ console.log('Status:', error.statusCode);
37
+ } else if (error instanceof NetworkError) {
38
+ console.log('Network error occurred');
39
+ console.log('Cause:', error.cause);
40
+ } else {
41
+ console.log('Unexpected error:', error);
42
+ }
43
+ }
44
+
45
+ // Example 2: Handle not found errors
46
+ console.log('\n=== Handling Not Found ===');
47
+ try {
48
+ await client.threads.retrieve('non-existent-id');
49
+ } catch (error) {
50
+ if (error instanceof NotFoundError) {
51
+ console.log('Thread not found');
52
+ console.log('Status:', error.statusCode); // 404
53
+ }
54
+ }
55
+
56
+ // Example 3: Handle validation errors
57
+ console.log('\n=== Handling Validation Errors ===');
58
+ try {
59
+ await client.threads.create({
60
+ title: '', // Invalid: empty title
61
+ body: 'Test',
62
+ userId: 'user-123',
63
+ });
64
+ } catch (error) {
65
+ if (error instanceof ValidationError) {
66
+ console.log('Validation failed');
67
+ console.log('Details:', error.response);
68
+ }
69
+ }
70
+
71
+ // Example 4: Handle rate limits
72
+ console.log('\n=== Handling Rate Limits ===');
73
+ try {
74
+ // Make many requests quickly
75
+ for (let i = 0; i < 100; i++) {
76
+ await client.threads.list();
77
+ }
78
+ } catch (error) {
79
+ if (error instanceof RateLimitError) {
80
+ console.log('Rate limit exceeded');
81
+ console.log('Retry after:', error.retryAfter, 'seconds');
82
+ console.log('Wait until:', new Date(Date.now() + error.retryAfter! * 1000));
83
+ }
84
+ }
85
+
86
+ // Example 5: Handle server errors with retry
87
+ console.log('\n=== Handling Server Errors ===');
88
+ const clientWithoutRetry = new ForumClient({
89
+ apiKey: process.env.FORU_API_KEY || 'your_api_key',
90
+ enableRetry: false, // Disable auto-retry
91
+ });
92
+
93
+ try {
94
+ // This might fail with 500 error
95
+ await clientWithoutRetry.threads.list();
96
+ } catch (error) {
97
+ if (error instanceof ServerError) {
98
+ console.log('Server error occurred');
99
+ console.log('Status:', error.statusCode);
100
+ console.log('You might want to retry manually');
101
+ }
102
+ }
103
+
104
+ // Example 6: Generic error handling
105
+ console.log('\n=== Generic Error Handling ===');
106
+ try {
107
+ await client.threads.delete('thread-123');
108
+ } catch (error) {
109
+ if (error instanceof ForumAPIError) {
110
+ // All API errors inherit from ForumAPIError
111
+ console.log('API Error:', error.message);
112
+ console.log('Status Code:', error.statusCode);
113
+ console.log('Response:', error.response);
114
+ } else {
115
+ // Non-API errors (network, etc.)
116
+ console.log('Unexpected error:', error);
117
+ }
118
+ }
119
+
120
+ // Example 7: Authorization errors
121
+ console.log('\n=== Handling Authorization Errors ===');
122
+ try {
123
+ // Try to access admin-only endpoint
124
+ await client.users.delete('some-user-id');
125
+ } catch (error) {
126
+ if (error instanceof AuthorizationError) {
127
+ console.log('Permission denied');
128
+ console.log('Status:', error.statusCode); // 403
129
+ }
130
+ }
131
+ }
132
+
133
+ main().catch(console.error);
@@ -0,0 +1,130 @@
1
+ import { ForumClient } from '@foru-ms/sdk';
2
+
3
+ /**
4
+ * Example: Managing Threads
5
+ * Demonstrates thread creation, updates, and interactions
6
+ */
7
+
8
+ async function main() {
9
+ const client = new ForumClient({
10
+ apiKey: process.env.FORU_API_KEY || 'your_api_key',
11
+ });
12
+
13
+ // Set user token for authenticated actions
14
+ client.setToken(process.env.FORU_USER_TOKEN || 'user_token');
15
+
16
+ const userId = 'user-123';
17
+
18
+ // Example 1: Create a Thread
19
+ console.log('=== Creating a Thread ===');
20
+ const newThread = await client.threads.create({
21
+ title: 'How to use the SDK?',
22
+ body: 'I need help getting started with the Foru.ms SDK.',
23
+ userId,
24
+ tags: ['help', 'sdk'],
25
+ });
26
+ console.log('Thread created:', newThread.id);
27
+
28
+ // Example 2: Create a Thread with Poll
29
+ console.log('\n=== Creating a Thread with Poll ===');
30
+ const pollThread = await client.threads.create({
31
+ title: 'What\'s your favorite feature?',
32
+ body: 'Vote for your favorite SDK feature!',
33
+ userId,
34
+ poll: {
35
+ title: 'Favorite Feature',
36
+ options: [
37
+ { title: 'Type Safety', color: '#3B82F6' },
38
+ { title: 'Auto Pagination', color: '#10B981' },
39
+ { title: 'Error Handling', color: '#F59E0B' },
40
+ ],
41
+ },
42
+ });
43
+ console.log('Poll thread created:', pollThread.id);
44
+
45
+ // Example 3: Update a Thread
46
+ console.log('\n=== Updating a Thread ===');
47
+ const updatedThread = await client.threads.update(newThread.id, {
48
+ title: 'How to use the SDK? [SOLVED]',
49
+ locked: false,
50
+ pinned: true,
51
+ });
52
+ console.log('Thread updated');
53
+
54
+ // Example 4: Like a Thread
55
+ console.log('\n=== Liking a Thread ===');
56
+ await client.threads.like(newThread.id, userId);
57
+ console.log('Thread liked');
58
+
59
+ // Get likes
60
+ const likes = await client.threads.getLikes(newThread.id);
61
+ console.log('Total likes:', likes.count);
62
+
63
+ // Example 5: Subscribe to a Thread
64
+ console.log('\n=== Subscribing to Thread ===');
65
+ await client.threads.subscribe(newThread.id, userId);
66
+ console.log('Subscribed to thread');
67
+
68
+ // Example 6: Vote in a Poll
69
+ console.log('\n=== Voting in Poll ===');
70
+ await client.threads.vote(pollThread.id, 'option-id-1', userId);
71
+ console.log('Vote cast');
72
+
73
+ // Get poll results
74
+ const pollResults = await client.threads.getPoll(pollThread.id, userId);
75
+ console.log('Poll results:', pollResults);
76
+
77
+ // Example 7: Add Posts to Thread
78
+ console.log('\n=== Adding Posts ===');
79
+ const post = await client.posts.create({
80
+ threadId: newThread.id,
81
+ body: 'Thanks for posting! Check out our documentation.',
82
+ userId: 'moderator-123',
83
+ });
84
+ console.log('Post created:', post.id);
85
+
86
+ // Reply to the post
87
+ const reply = await client.posts.create({
88
+ threadId: newThread.id,
89
+ body: 'Thank you! That helps a lot.',
90
+ userId,
91
+ parentId: post.id, // This makes it a reply
92
+ });
93
+ console.log('Reply created:', reply.id);
94
+
95
+ // Example 8: Get Thread Posts
96
+ console.log('\n=== Getting Thread Posts ===');
97
+ const threadPosts = await client.threads.getPosts(newThread.id, {
98
+ filter: 'newest',
99
+ });
100
+ console.log('Total posts:', threadPosts.count);
101
+
102
+ // Example 9: Upvote/Downvote Threads
103
+ console.log('\n=== Voting on Threads ===');
104
+ await client.threads.upvote(newThread.id, userId);
105
+ console.log('Thread upvoted');
106
+
107
+ const upvotes = await client.threads.getUpvotes(newThread.id);
108
+ console.log('Total upvotes:', upvotes.count);
109
+
110
+ // Example 10: List Threads with Filters
111
+ console.log('\n=== Listing Threads ===');
112
+ const threads = await client.threads.list({
113
+ filter: 'newest',
114
+ tagId: 'help',
115
+ query: 'SDK',
116
+ });
117
+ console.log('Found threads:', threads.threads.length);
118
+
119
+ // Example 11: Get Subscribers
120
+ console.log('\n=== Getting Subscribers ===');
121
+ const subscribers = await client.threads.getSubscribers(newThread.id);
122
+ console.log('Total subscribers:', subscribers.count);
123
+
124
+ // Example 12: Delete a Thread
125
+ console.log('\n=== Deleting a Thread ===');
126
+ const deleted = await client.threads.delete(newThread.id, { userId });
127
+ console.log('Thread deleted:', deleted.deleted);
128
+ }
129
+
130
+ main().catch(console.error);
@@ -0,0 +1,81 @@
1
+ import { ForumClient } from '@foru-ms/sdk';
2
+
3
+ /**
4
+ * Example: Working with Pagination
5
+ * Demonstrates different ways to paginate through results
6
+ */
7
+
8
+ async function main() {
9
+ const client = new ForumClient({
10
+ apiKey: process.env.FORU_API_KEY || 'your_api_key',
11
+ });
12
+
13
+ // Example 1: Manual Pagination
14
+ console.log('=== Manual Pagination ===');
15
+ let cursor: string | undefined;
16
+ let pageCount = 0;
17
+ const maxPages = 3;
18
+
19
+ do {
20
+ const response = await client.threads.list({ cursor, filter: 'newest' });
21
+ console.log(`Page ${++pageCount}:`, response.threads.length, 'threads');
22
+
23
+ // Process threads
24
+ response.threads.forEach(thread => {
25
+ console.log(` - ${thread.title}`);
26
+ });
27
+
28
+ cursor = response.nextThreadCursor;
29
+ } while (cursor && pageCount < maxPages);
30
+
31
+ // Example 2: Auto-pagination with async iterator
32
+ console.log('\n=== Auto-Pagination (Async Iterator) ===');
33
+ let count = 0;
34
+ const maxItems = 10;
35
+
36
+ for await (const thread of client.pagination.paginateAll(
37
+ (cursor) => client.threads.list({ cursor, filter: 'newest' })
38
+ )) {
39
+ console.log(`Thread ${++count}:`, thread.title);
40
+
41
+ if (count >= maxItems) break; // Limit for demo
42
+ }
43
+
44
+ // Example 3: Fetch all pages at once
45
+ console.log('\n=== Fetch All Pages ===');
46
+ const allThreads = await client.pagination.fetchAllPages(
47
+ (cursor) => client.threads.list({ cursor }),
48
+ 3 // Max 3 pages
49
+ );
50
+ console.log('Total threads fetched:', allThreads.length);
51
+
52
+ // Example 4: Paginate with filters
53
+ console.log('\n=== Pagination with Filters ===');
54
+ cursor = undefined;
55
+ pageCount = 0;
56
+
57
+ do {
58
+ const response = await client.posts.list({
59
+ cursor,
60
+ filter: 'newest',
61
+ type: 'created', // Only posts created by user
62
+ userId: 'user-123',
63
+ });
64
+
65
+ console.log(`Page ${++pageCount}:`, response.posts.length, 'posts');
66
+ cursor = response.nextPostCursor;
67
+ } while (cursor && pageCount < 2);
68
+
69
+ // Example 5: Check rate limits during pagination
70
+ console.log('\n=== Rate Limit Monitoring ===');
71
+ await client.threads.list();
72
+
73
+ if (client.lastRateLimitInfo) {
74
+ console.log('Rate Limit Info:');
75
+ console.log(' Limit:', client.lastRateLimitInfo.limit);
76
+ console.log(' Remaining:', client.lastRateLimitInfo.remaining);
77
+ console.log(' Resets at:', new Date(client.lastRateLimitInfo.reset * 1000));
78
+ }
79
+ }
80
+
81
+ main().catch(console.error);