@crosspost/sdk 0.1.5 → 0.1.7
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/dist/index.cjs +380 -624
- package/dist/index.d.cts +29 -84
- package/dist/index.d.ts +29 -84
- package/dist/index.js +375 -599
- package/package.json +1 -1
- package/src/api/auth.ts +7 -7
- package/src/api/post.ts +3 -15
- package/src/api/system.ts +3 -3
- package/src/core/request.ts +33 -32
- package/src/index.ts +0 -10
- package/src/utils/error.ts +158 -199
package/src/utils/error.ts
CHANGED
@@ -1,166 +1,133 @@
|
|
1
|
-
import {
|
1
|
+
import { ApiErrorCode, type ErrorDetails, type StatusCode } from '@crosspost/types';
|
2
2
|
|
3
3
|
/**
|
4
|
-
*
|
4
|
+
* CrosspostError class for SDK error handling
|
5
5
|
*/
|
6
|
-
export
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
],
|
26
|
-
RATE_LIMIT: [
|
27
|
-
ApiErrorCode.RATE_LIMITED,
|
28
|
-
],
|
29
|
-
POST: [
|
30
|
-
ApiErrorCode.POST_CREATION_FAILED,
|
31
|
-
ApiErrorCode.THREAD_CREATION_FAILED,
|
32
|
-
ApiErrorCode.POST_DELETION_FAILED,
|
33
|
-
ApiErrorCode.POST_INTERACTION_FAILED,
|
34
|
-
],
|
35
|
-
MEDIA: [
|
36
|
-
ApiErrorCode.MEDIA_UPLOAD_FAILED,
|
37
|
-
],
|
38
|
-
};
|
39
|
-
|
40
|
-
/**
|
41
|
-
* Check if an error belongs to a specific category
|
42
|
-
*
|
43
|
-
* @param error The error to check
|
44
|
-
* @param category The category to check against
|
45
|
-
* @returns True if the error belongs to the category, false otherwise
|
46
|
-
*/
|
47
|
-
export function isErrorOfCategory(error: unknown, category: ApiErrorCode[]): boolean {
|
48
|
-
if (error instanceof ApiError) {
|
49
|
-
return category.includes(error.code);
|
6
|
+
export class CrosspostError extends Error {
|
7
|
+
readonly code: ApiErrorCode;
|
8
|
+
readonly status: StatusCode;
|
9
|
+
readonly details?: ErrorDetails;
|
10
|
+
readonly recoverable: boolean;
|
11
|
+
|
12
|
+
constructor(
|
13
|
+
message: string,
|
14
|
+
code: ApiErrorCode,
|
15
|
+
status: StatusCode,
|
16
|
+
details?: ErrorDetails,
|
17
|
+
recoverable: boolean = false,
|
18
|
+
) {
|
19
|
+
super(message);
|
20
|
+
this.name = 'CrosspostError';
|
21
|
+
this.code = code;
|
22
|
+
this.status = status;
|
23
|
+
this.details = details;
|
24
|
+
this.recoverable = recoverable;
|
50
25
|
}
|
51
26
|
|
52
|
-
|
53
|
-
|
27
|
+
/**
|
28
|
+
* Get platform from details if available
|
29
|
+
*/
|
30
|
+
get platform(): string | undefined {
|
31
|
+
return this.details?.platform;
|
54
32
|
}
|
55
33
|
|
56
|
-
|
57
|
-
|
58
|
-
|
34
|
+
/**
|
35
|
+
* Get userId from details if available
|
36
|
+
*/
|
37
|
+
get userId(): string | undefined {
|
38
|
+
return this.details?.userId;
|
59
39
|
}
|
40
|
+
}
|
60
41
|
|
61
|
-
|
42
|
+
/**
|
43
|
+
* Check if an error is a specific type based on its code
|
44
|
+
*/
|
45
|
+
function isErrorCode(error: unknown, codes: ApiErrorCode[]): boolean {
|
46
|
+
return error instanceof CrosspostError && codes.includes(error.code);
|
62
47
|
}
|
63
48
|
|
64
49
|
/**
|
65
50
|
* Check if an error is an authentication error
|
66
|
-
*
|
67
|
-
* @param error The error to check
|
68
|
-
* @returns True if the error is an authentication error, false otherwise
|
69
51
|
*/
|
70
52
|
export function isAuthError(error: unknown): boolean {
|
71
|
-
return
|
53
|
+
return isErrorCode(error, [
|
54
|
+
ApiErrorCode.UNAUTHORIZED,
|
55
|
+
ApiErrorCode.FORBIDDEN,
|
56
|
+
]);
|
72
57
|
}
|
73
58
|
|
74
59
|
/**
|
75
60
|
* Check if an error is a validation error
|
76
|
-
*
|
77
|
-
* @param error The error to check
|
78
|
-
* @returns True if the error is a validation error, false otherwise
|
79
61
|
*/
|
80
62
|
export function isValidationError(error: unknown): boolean {
|
81
|
-
return
|
63
|
+
return isErrorCode(error, [
|
64
|
+
ApiErrorCode.VALIDATION_ERROR,
|
65
|
+
ApiErrorCode.INVALID_REQUEST,
|
66
|
+
]);
|
82
67
|
}
|
83
68
|
|
84
69
|
/**
|
85
70
|
* Check if an error is a network error
|
86
|
-
*
|
87
|
-
* @param error The error to check
|
88
|
-
* @returns True if the error is a network error, false otherwise
|
89
71
|
*/
|
90
72
|
export function isNetworkError(error: unknown): boolean {
|
91
|
-
return
|
73
|
+
return isErrorCode(error, [
|
74
|
+
ApiErrorCode.NETWORK_ERROR,
|
75
|
+
ApiErrorCode.PLATFORM_UNAVAILABLE,
|
76
|
+
]);
|
92
77
|
}
|
93
78
|
|
94
79
|
/**
|
95
80
|
* Check if an error is a platform error
|
96
|
-
*
|
97
|
-
* @param error The error to check
|
98
|
-
* @returns True if the error is a platform error, false otherwise
|
99
81
|
*/
|
100
82
|
export function isPlatformError(error: unknown): boolean {
|
101
|
-
return
|
83
|
+
return error instanceof CrosspostError && !!error.details?.platform;
|
102
84
|
}
|
103
85
|
|
104
86
|
/**
|
105
87
|
* Check if an error is a content policy error
|
106
|
-
*
|
107
|
-
* @param error The error to check
|
108
|
-
* @returns True if the error is a content policy error, false otherwise
|
109
88
|
*/
|
110
89
|
export function isContentError(error: unknown): boolean {
|
111
|
-
return
|
90
|
+
return isErrorCode(error, [
|
91
|
+
ApiErrorCode.CONTENT_POLICY_VIOLATION,
|
92
|
+
ApiErrorCode.DUPLICATE_CONTENT,
|
93
|
+
]);
|
112
94
|
}
|
113
95
|
|
114
96
|
/**
|
115
97
|
* Check if an error is a rate limit error
|
116
|
-
*
|
117
|
-
* @param error The error to check
|
118
|
-
* @returns True if the error is a rate limit error, false otherwise
|
119
98
|
*/
|
120
99
|
export function isRateLimitError(error: unknown): boolean {
|
121
|
-
return
|
100
|
+
return isErrorCode(error, [ApiErrorCode.RATE_LIMITED]);
|
122
101
|
}
|
123
102
|
|
124
103
|
/**
|
125
104
|
* Check if an error is a post-related error
|
126
|
-
*
|
127
|
-
* @param error The error to check
|
128
|
-
* @returns True if the error is a post-related error, false otherwise
|
129
105
|
*/
|
130
106
|
export function isPostError(error: unknown): boolean {
|
131
|
-
return
|
107
|
+
return isErrorCode(error, [
|
108
|
+
ApiErrorCode.POST_CREATION_FAILED,
|
109
|
+
ApiErrorCode.THREAD_CREATION_FAILED,
|
110
|
+
ApiErrorCode.POST_DELETION_FAILED,
|
111
|
+
ApiErrorCode.POST_INTERACTION_FAILED,
|
112
|
+
]);
|
132
113
|
}
|
133
114
|
|
134
115
|
/**
|
135
116
|
* Check if an error is a media-related error
|
136
|
-
*
|
137
|
-
* @param error The error to check
|
138
|
-
* @returns True if the error is a media-related error, false otherwise
|
139
117
|
*/
|
140
118
|
export function isMediaError(error: unknown): boolean {
|
141
|
-
return
|
119
|
+
return isErrorCode(error, [ApiErrorCode.MEDIA_UPLOAD_FAILED]);
|
142
120
|
}
|
143
121
|
|
144
122
|
/**
|
145
123
|
* Check if an error is recoverable
|
146
|
-
*
|
147
|
-
* @param error The error to check
|
148
|
-
* @returns True if the error is recoverable, false otherwise
|
149
124
|
*/
|
150
125
|
export function isRecoverableError(error: unknown): boolean {
|
151
|
-
|
152
|
-
return error.recoverable;
|
153
|
-
}
|
154
|
-
|
155
|
-
return false;
|
126
|
+
return error instanceof CrosspostError && error.recoverable;
|
156
127
|
}
|
157
128
|
|
158
129
|
/**
|
159
130
|
* Get a user-friendly error message
|
160
|
-
*
|
161
|
-
* @param error The error to get the message from
|
162
|
-
* @param defaultMessage The default message to return if no message is found
|
163
|
-
* @returns The error message
|
164
131
|
*/
|
165
132
|
export function getErrorMessage(
|
166
133
|
error: unknown,
|
@@ -169,50 +136,47 @@ export function getErrorMessage(
|
|
169
136
|
if (error instanceof Error) {
|
170
137
|
return error.message || defaultMessage;
|
171
138
|
}
|
172
|
-
|
173
|
-
if (typeof error === 'string') {
|
174
|
-
return error;
|
175
|
-
}
|
176
|
-
|
177
|
-
if (error && typeof error === 'object' && 'message' in error) {
|
178
|
-
return (error as any).message || defaultMessage;
|
179
|
-
}
|
180
|
-
|
181
139
|
return defaultMessage;
|
182
140
|
}
|
183
141
|
|
184
142
|
/**
|
185
|
-
*
|
186
|
-
*
|
187
|
-
* @param error The error to extract details from
|
188
|
-
* @returns The error details or undefined if none are found
|
143
|
+
* Get error details if available
|
189
144
|
*/
|
190
|
-
export function getErrorDetails(error: unknown):
|
191
|
-
if (error instanceof
|
145
|
+
export function getErrorDetails(error: unknown): ErrorDetails | undefined {
|
146
|
+
if (error instanceof CrosspostError) {
|
192
147
|
return error.details;
|
193
148
|
}
|
194
|
-
|
195
|
-
if (error && typeof error === 'object' && 'details' in error) {
|
196
|
-
return (error as any).details;
|
197
|
-
}
|
198
|
-
|
199
149
|
return undefined;
|
200
150
|
}
|
201
151
|
|
152
|
+
/**
|
153
|
+
* Create a new error
|
154
|
+
*/
|
155
|
+
function createError(
|
156
|
+
message: string,
|
157
|
+
code: ApiErrorCode,
|
158
|
+
status: StatusCode,
|
159
|
+
details?: ErrorDetails,
|
160
|
+
recoverable: boolean = false,
|
161
|
+
): CrosspostError {
|
162
|
+
return new CrosspostError(
|
163
|
+
message,
|
164
|
+
code,
|
165
|
+
status,
|
166
|
+
details,
|
167
|
+
recoverable,
|
168
|
+
);
|
169
|
+
}
|
170
|
+
|
202
171
|
/**
|
203
172
|
* Enrich an error with additional context
|
204
|
-
*
|
205
|
-
* @param error The error to enrich
|
206
|
-
* @param context The context to add to the error
|
207
|
-
* @returns The enriched error
|
208
173
|
*/
|
209
174
|
export function enrichErrorWithContext(
|
210
175
|
error: unknown,
|
211
|
-
context: Record<string,
|
176
|
+
context: Record<string, unknown>,
|
212
177
|
): Error {
|
213
|
-
if (error instanceof
|
214
|
-
|
215
|
-
return new ApiError(
|
178
|
+
if (error instanceof CrosspostError) {
|
179
|
+
return createError(
|
216
180
|
error.message,
|
217
181
|
error.code,
|
218
182
|
error.status,
|
@@ -221,42 +185,22 @@ export function enrichErrorWithContext(
|
|
221
185
|
);
|
222
186
|
}
|
223
187
|
|
224
|
-
|
225
|
-
// Create a new PlatformError with the merged details since details is read-only
|
226
|
-
return new PlatformError(
|
227
|
-
error.message,
|
228
|
-
error.platform,
|
229
|
-
error.code,
|
230
|
-
error.recoverable,
|
231
|
-
error.originalError,
|
232
|
-
error.status,
|
233
|
-
error.userId,
|
234
|
-
{ ...(error.details || {}), ...context },
|
235
|
-
);
|
236
|
-
}
|
237
|
-
|
238
|
-
// For regular errors or non-Error objects, create a new ApiError with the context
|
188
|
+
// For regular errors or non-Error objects, create a new CrosspostError
|
239
189
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
240
|
-
return
|
190
|
+
return createError(
|
241
191
|
errorMessage || 'An error occurred',
|
242
192
|
ApiErrorCode.INTERNAL_ERROR,
|
243
193
|
500,
|
244
194
|
{ originalError: error, ...context },
|
245
|
-
false,
|
246
195
|
);
|
247
196
|
}
|
248
197
|
|
249
198
|
/**
|
250
199
|
* Wrapper for API calls with consistent error handling
|
251
|
-
*
|
252
|
-
* @param apiCall The API call to wrap
|
253
|
-
* @param context Optional context to add to any errors
|
254
|
-
* @returns The result of the API call
|
255
|
-
* @throws An enriched error if the API call fails
|
256
200
|
*/
|
257
201
|
export async function apiWrapper<T>(
|
258
202
|
apiCall: () => Promise<T>,
|
259
|
-
context?: Record<string,
|
203
|
+
context?: Record<string, unknown>,
|
260
204
|
): Promise<T> {
|
261
205
|
try {
|
262
206
|
return await apiCall();
|
@@ -273,26 +217,25 @@ export async function apiWrapper<T>(
|
|
273
217
|
// If JSON parsing fails, create a generic error
|
274
218
|
if (jsonError instanceof Error && jsonError.name === 'SyntaxError') {
|
275
219
|
throw enrichErrorWithContext(
|
276
|
-
|
220
|
+
createError(
|
277
221
|
`API request failed with status ${error.status} and non-JSON response`,
|
278
222
|
ApiErrorCode.NETWORK_ERROR,
|
279
|
-
error.status as
|
223
|
+
error.status as StatusCode,
|
280
224
|
{ originalResponse: error.statusText },
|
281
225
|
),
|
282
226
|
context || {},
|
283
227
|
);
|
284
228
|
}
|
285
|
-
// If it's already an enriched error from handleErrorResponse, just throw it
|
286
229
|
throw jsonError;
|
287
230
|
}
|
288
231
|
}
|
289
232
|
|
290
|
-
// If it's already
|
291
|
-
if (error instanceof
|
233
|
+
// If it's already a CrosspostError, just add context
|
234
|
+
if (error instanceof CrosspostError) {
|
292
235
|
throw enrichErrorWithContext(error, context || {});
|
293
236
|
}
|
294
237
|
|
295
|
-
// Otherwise wrap it in
|
238
|
+
// Otherwise wrap it in a CrosspostError
|
296
239
|
throw enrichErrorWithContext(
|
297
240
|
error instanceof Error ? error : new Error(String(error)),
|
298
241
|
context || {},
|
@@ -302,65 +245,81 @@ export async function apiWrapper<T>(
|
|
302
245
|
|
303
246
|
/**
|
304
247
|
* Handles error responses from the API and converts them to appropriate error objects.
|
305
|
-
*
|
306
|
-
* @param data The error response data
|
307
|
-
* @param status The HTTP status code
|
308
|
-
* @returns An ApiError or PlatformError instance
|
309
248
|
*/
|
310
|
-
export function handleErrorResponse(
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
const platform = errorData?.platform || data?.platform;
|
249
|
+
export function handleErrorResponse(
|
250
|
+
data: any,
|
251
|
+
status: number,
|
252
|
+
): CrosspostError {
|
253
|
+
// Validate response format
|
254
|
+
if (!data || typeof data !== 'object' || !('success' in data)) {
|
255
|
+
return createError(
|
256
|
+
'Invalid API response format',
|
257
|
+
ApiErrorCode.INTERNAL_ERROR,
|
258
|
+
status as StatusCode,
|
259
|
+
{ originalResponse: data },
|
260
|
+
);
|
261
|
+
}
|
324
262
|
|
325
|
-
//
|
326
|
-
|
327
|
-
|
328
|
-
|
263
|
+
// Check for errors array
|
264
|
+
if (!data.errors || !Array.isArray(data.errors) || data.errors.length === 0) {
|
265
|
+
return createError(
|
266
|
+
'Invalid error response format',
|
267
|
+
ApiErrorCode.INTERNAL_ERROR,
|
268
|
+
status as StatusCode,
|
269
|
+
{ originalResponse: data },
|
270
|
+
);
|
329
271
|
}
|
330
272
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
273
|
+
// Handle single vs multiple errors
|
274
|
+
if (data.errors.length === 1) {
|
275
|
+
const errorDetail = data.errors[0];
|
276
|
+
|
277
|
+
// Validate error detail structure
|
278
|
+
if (!errorDetail.message || !errorDetail.code) {
|
279
|
+
return createError(
|
280
|
+
'Invalid error detail format',
|
281
|
+
ApiErrorCode.INTERNAL_ERROR,
|
282
|
+
status as StatusCode,
|
283
|
+
{ originalResponse: data },
|
284
|
+
);
|
285
|
+
}
|
286
|
+
|
287
|
+
// Merge meta data from the API response with error details
|
288
|
+
const finalDetails = {
|
289
|
+
...(errorDetail.details || {}),
|
290
|
+
...(data.meta || {}),
|
291
|
+
};
|
292
|
+
|
293
|
+
return createError(
|
294
|
+
errorDetail.message,
|
295
|
+
errorDetail.code as ApiErrorCode,
|
296
|
+
status as StatusCode,
|
297
|
+
finalDetails,
|
298
|
+
errorDetail.recoverable ?? false,
|
340
299
|
);
|
341
300
|
} else {
|
342
|
-
//
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
301
|
+
// Multiple errors - return first error with details about others
|
302
|
+
const firstError = data.errors[0];
|
303
|
+
return createError(
|
304
|
+
'Multiple errors occurred',
|
305
|
+
firstError.code as ApiErrorCode,
|
306
|
+
status as StatusCode,
|
307
|
+
{
|
308
|
+
errors: data.errors,
|
309
|
+
...(data.meta || {}),
|
310
|
+
originalResponse: data,
|
311
|
+
},
|
312
|
+
false,
|
349
313
|
);
|
350
314
|
}
|
351
315
|
}
|
352
316
|
|
353
317
|
/**
|
354
318
|
* Creates a network error with appropriate details
|
355
|
-
*
|
356
|
-
* @param error The original error
|
357
|
-
* @param url The request URL
|
358
|
-
* @param timeout The request timeout
|
359
|
-
* @returns An ApiError instance
|
360
319
|
*/
|
361
|
-
export function createNetworkError(error: unknown, url: string, timeout: number):
|
320
|
+
export function createNetworkError(error: unknown, url: string, timeout: number): CrosspostError {
|
362
321
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
363
|
-
return
|
322
|
+
return createError(
|
364
323
|
`Request timed out after ${timeout}ms`,
|
365
324
|
ApiErrorCode.NETWORK_ERROR,
|
366
325
|
408,
|
@@ -368,7 +327,7 @@ export function createNetworkError(error: unknown, url: string, timeout: number)
|
|
368
327
|
);
|
369
328
|
}
|
370
329
|
|
371
|
-
return
|
330
|
+
return createError(
|
372
331
|
error instanceof Error ? error.message : 'An unexpected error occurred during the request',
|
373
332
|
ApiErrorCode.INTERNAL_ERROR,
|
374
333
|
500,
|