@crosspost/sdk 0.1.2 → 0.1.3

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,310 @@
1
+ import { ApiError, ApiErrorCode, Platform, PlatformError } from '@crosspost/types';
2
+ import { handleErrorResponse as baseHandleErrorResponse } from './error.ts';
3
+
4
+ /**
5
+ * Error categories grouped by type for better organization and usage
6
+ */
7
+ export const ERROR_CATEGORIES = {
8
+ AUTH: [
9
+ ApiErrorCode.UNAUTHORIZED,
10
+ ApiErrorCode.FORBIDDEN,
11
+ ],
12
+ VALIDATION: [
13
+ ApiErrorCode.VALIDATION_ERROR,
14
+ ApiErrorCode.INVALID_REQUEST,
15
+ ],
16
+ NETWORK: [
17
+ ApiErrorCode.NETWORK_ERROR,
18
+ ],
19
+ PLATFORM: [
20
+ ApiErrorCode.PLATFORM_ERROR,
21
+ ApiErrorCode.PLATFORM_UNAVAILABLE,
22
+ ],
23
+ CONTENT: [
24
+ ApiErrorCode.CONTENT_POLICY_VIOLATION,
25
+ ApiErrorCode.DUPLICATE_CONTENT,
26
+ ],
27
+ RATE_LIMIT: [
28
+ ApiErrorCode.RATE_LIMITED,
29
+ ],
30
+ POST: [
31
+ ApiErrorCode.POST_CREATION_FAILED,
32
+ ApiErrorCode.THREAD_CREATION_FAILED,
33
+ ApiErrorCode.POST_DELETION_FAILED,
34
+ ApiErrorCode.POST_INTERACTION_FAILED,
35
+ ],
36
+ MEDIA: [
37
+ ApiErrorCode.MEDIA_UPLOAD_FAILED,
38
+ ],
39
+ };
40
+
41
+ /**
42
+ * Check if an error belongs to a specific category
43
+ *
44
+ * @param error The error to check
45
+ * @param category The category to check against
46
+ * @returns True if the error belongs to the category, false otherwise
47
+ */
48
+ export function isErrorOfCategory(error: unknown, category: ApiErrorCode[]): boolean {
49
+ if (error instanceof ApiError) {
50
+ return category.includes(error.code);
51
+ }
52
+
53
+ if (error instanceof PlatformError) {
54
+ return category.includes(error.code);
55
+ }
56
+
57
+ // Fallback for error-like objects with code property
58
+ if (error && typeof error === 'object' && 'code' in error) {
59
+ return category.includes((error as any).code);
60
+ }
61
+
62
+ return false;
63
+ }
64
+
65
+ /**
66
+ * Check if an error is an authentication error
67
+ *
68
+ * @param error The error to check
69
+ * @returns True if the error is an authentication error, false otherwise
70
+ */
71
+ export function isAuthError(error: unknown): boolean {
72
+ return isErrorOfCategory(error, ERROR_CATEGORIES.AUTH);
73
+ }
74
+
75
+ /**
76
+ * Check if an error is a validation error
77
+ *
78
+ * @param error The error to check
79
+ * @returns True if the error is a validation error, false otherwise
80
+ */
81
+ export function isValidationError(error: unknown): boolean {
82
+ return isErrorOfCategory(error, ERROR_CATEGORIES.VALIDATION);
83
+ }
84
+
85
+ /**
86
+ * Check if an error is a network error
87
+ *
88
+ * @param error The error to check
89
+ * @returns True if the error is a network error, false otherwise
90
+ */
91
+ export function isNetworkError(error: unknown): boolean {
92
+ return isErrorOfCategory(error, ERROR_CATEGORIES.NETWORK);
93
+ }
94
+
95
+ /**
96
+ * Check if an error is a platform error
97
+ *
98
+ * @param error The error to check
99
+ * @returns True if the error is a platform error, false otherwise
100
+ */
101
+ export function isPlatformError(error: unknown): boolean {
102
+ return isErrorOfCategory(error, ERROR_CATEGORIES.PLATFORM) || error instanceof PlatformError;
103
+ }
104
+
105
+ /**
106
+ * Check if an error is a content policy error
107
+ *
108
+ * @param error The error to check
109
+ * @returns True if the error is a content policy error, false otherwise
110
+ */
111
+ export function isContentError(error: unknown): boolean {
112
+ return isErrorOfCategory(error, ERROR_CATEGORIES.CONTENT);
113
+ }
114
+
115
+ /**
116
+ * Check if an error is a rate limit error
117
+ *
118
+ * @param error The error to check
119
+ * @returns True if the error is a rate limit error, false otherwise
120
+ */
121
+ export function isRateLimitError(error: unknown): boolean {
122
+ return isErrorOfCategory(error, ERROR_CATEGORIES.RATE_LIMIT);
123
+ }
124
+
125
+ /**
126
+ * Check if an error is a post-related error
127
+ *
128
+ * @param error The error to check
129
+ * @returns True if the error is a post-related error, false otherwise
130
+ */
131
+ export function isPostError(error: unknown): boolean {
132
+ return isErrorOfCategory(error, ERROR_CATEGORIES.POST);
133
+ }
134
+
135
+ /**
136
+ * Check if an error is a media-related error
137
+ *
138
+ * @param error The error to check
139
+ * @returns True if the error is a media-related error, false otherwise
140
+ */
141
+ export function isMediaError(error: unknown): boolean {
142
+ return isErrorOfCategory(error, ERROR_CATEGORIES.MEDIA);
143
+ }
144
+
145
+ /**
146
+ * Check if an error is recoverable
147
+ *
148
+ * @param error The error to check
149
+ * @returns True if the error is recoverable, false otherwise
150
+ */
151
+ export function isRecoverableError(error: unknown): boolean {
152
+ if (error instanceof ApiError || error instanceof PlatformError) {
153
+ return error.recoverable;
154
+ }
155
+
156
+ return false;
157
+ }
158
+
159
+ /**
160
+ * Get a user-friendly error message
161
+ *
162
+ * @param error The error to get the message from
163
+ * @param defaultMessage The default message to return if no message is found
164
+ * @returns The error message
165
+ */
166
+ export function getErrorMessage(
167
+ error: unknown,
168
+ defaultMessage: string = 'An error occurred',
169
+ ): string {
170
+ if (error instanceof Error) {
171
+ return error.message || defaultMessage;
172
+ }
173
+
174
+ if (typeof error === 'string') {
175
+ return error;
176
+ }
177
+
178
+ if (error && typeof error === 'object' && 'message' in error) {
179
+ return (error as any).message || defaultMessage;
180
+ }
181
+
182
+ return defaultMessage;
183
+ }
184
+
185
+ /**
186
+ * Extract error details if available
187
+ *
188
+ * @param error The error to extract details from
189
+ * @returns The error details or undefined if none are found
190
+ */
191
+ export function getErrorDetails(error: unknown): Record<string, any> | undefined {
192
+ if (error instanceof ApiError || error instanceof PlatformError) {
193
+ return error.details;
194
+ }
195
+
196
+ if (error && typeof error === 'object' && 'details' in error) {
197
+ return (error as any).details;
198
+ }
199
+
200
+ return undefined;
201
+ }
202
+
203
+ /**
204
+ * Enrich an error with additional context
205
+ *
206
+ * @param error The error to enrich
207
+ * @param context The context to add to the error
208
+ * @returns The enriched error
209
+ */
210
+ export function enrichErrorWithContext(
211
+ error: unknown,
212
+ context: Record<string, any>,
213
+ ): Error {
214
+ if (error instanceof ApiError) {
215
+ // Create a new ApiError with the merged details since details is read-only
216
+ return new ApiError(
217
+ error.message,
218
+ error.code,
219
+ error.status,
220
+ { ...(error.details || {}), ...context },
221
+ error.recoverable,
222
+ );
223
+ }
224
+
225
+ if (error instanceof PlatformError) {
226
+ // Create a new PlatformError with the merged details since details is read-only
227
+ return new PlatformError(
228
+ error.message,
229
+ error.platform,
230
+ error.code,
231
+ error.recoverable,
232
+ error.originalError,
233
+ error.status,
234
+ error.userId,
235
+ { ...(error.details || {}), ...context },
236
+ );
237
+ }
238
+
239
+ // For regular errors or non-Error objects, create a new ApiError with the context
240
+ const errorMessage = error instanceof Error ? error.message : String(error);
241
+ return new ApiError(
242
+ errorMessage || 'An error occurred',
243
+ ApiErrorCode.INTERNAL_ERROR,
244
+ 500,
245
+ { originalError: error, ...context },
246
+ false,
247
+ );
248
+ }
249
+
250
+ /**
251
+ * Re-export the handleErrorResponse function from error.ts
252
+ * for backward compatibility
253
+ */
254
+ export function handleErrorResponse(data: any, status: number): ApiError | PlatformError {
255
+ return baseHandleErrorResponse(data, status);
256
+ }
257
+
258
+ /**
259
+ * Wrapper for API calls with consistent error handling
260
+ *
261
+ * @param apiCall The API call to wrap
262
+ * @param context Optional context to add to any errors
263
+ * @returns The result of the API call
264
+ * @throws An enriched error if the API call fails
265
+ */
266
+ export async function apiWrapper<T>(
267
+ apiCall: () => Promise<T>,
268
+ context?: Record<string, any>,
269
+ ): Promise<T> {
270
+ try {
271
+ return await apiCall();
272
+ } catch (error) {
273
+ // If it's a Response object, use handleErrorResponse
274
+ if (error instanceof Response) {
275
+ try {
276
+ const errorData = await error.json();
277
+ throw enrichErrorWithContext(
278
+ handleErrorResponse(errorData, error.status),
279
+ context || {},
280
+ );
281
+ } catch (jsonError) {
282
+ // If JSON parsing fails, create a generic error
283
+ if (jsonError instanceof Error && jsonError.name === 'SyntaxError') {
284
+ throw enrichErrorWithContext(
285
+ new ApiError(
286
+ `API request failed with status ${error.status} and non-JSON response`,
287
+ ApiErrorCode.NETWORK_ERROR,
288
+ error.status as any,
289
+ { originalResponse: error.statusText },
290
+ ),
291
+ context || {},
292
+ );
293
+ }
294
+ // If it's already an enriched error from handleErrorResponse, just throw it
295
+ throw jsonError;
296
+ }
297
+ }
298
+
299
+ // If it's already an ApiError or PlatformError, just add context
300
+ if (error instanceof ApiError || error instanceof PlatformError) {
301
+ throw enrichErrorWithContext(error, context || {});
302
+ }
303
+
304
+ // Otherwise wrap it in an ApiError
305
+ throw enrichErrorWithContext(
306
+ error instanceof Error ? error : new Error(String(error)),
307
+ context || {},
308
+ );
309
+ }
310
+ }