@deliverart/sdk-js-error-handler 2.1.49 → 2.1.51
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 +481 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# @deliverart/sdk-js-error-handler
|
|
2
|
+
|
|
3
|
+
Error handling plugin for the DeliverArt JavaScript SDK. Automatically handles API errors and transforms them into typed exceptions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
|
|
11
|
+
# or
|
|
12
|
+
yarn add @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Exported Types
|
|
16
|
+
|
|
17
|
+
### ErrorHandlerPlugin
|
|
18
|
+
```typescript
|
|
19
|
+
class ErrorHandlerPlugin implements ApiClientPlugin<{}>
|
|
20
|
+
```
|
|
21
|
+
Plugin that intercepts API responses and transforms error responses into typed exceptions.
|
|
22
|
+
|
|
23
|
+
**Constructor:**
|
|
24
|
+
- `constructor()` - No configuration required
|
|
25
|
+
|
|
26
|
+
### ApiError
|
|
27
|
+
```typescript
|
|
28
|
+
class ApiError extends Error {
|
|
29
|
+
readonly name = 'ApiError'
|
|
30
|
+
readonly code?: string
|
|
31
|
+
readonly request?: RequestInit
|
|
32
|
+
readonly response?: Response
|
|
33
|
+
readonly status?: number
|
|
34
|
+
readonly data?: ApiErrorData
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
Main error class for API errors.
|
|
38
|
+
|
|
39
|
+
**Properties:**
|
|
40
|
+
- `name: 'ApiError'` - Error name
|
|
41
|
+
- `code?: string` - Error code from the API
|
|
42
|
+
- `request?: RequestInit` - Original request init
|
|
43
|
+
- `response?: Response` - HTTP response object
|
|
44
|
+
- `status?: number` - HTTP status code
|
|
45
|
+
- `data?: ApiErrorData` - Typed error data
|
|
46
|
+
|
|
47
|
+
### ApiErrorData
|
|
48
|
+
```typescript
|
|
49
|
+
type ApiErrorData =
|
|
50
|
+
| {
|
|
51
|
+
type: 'VALIDATION_ERROR'
|
|
52
|
+
value: ValidationErrorData
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
type: 'UNKNOWN_ERROR'
|
|
56
|
+
value: unknown
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
Discriminated union type for different error types.
|
|
60
|
+
|
|
61
|
+
**VALIDATION_ERROR** - Returned when API responds with 422 status (validation failed)
|
|
62
|
+
- `value: ValidationErrorData` - Contains validation violations
|
|
63
|
+
|
|
64
|
+
**UNKNOWN_ERROR** - Returned for all other error responses
|
|
65
|
+
- `value: unknown` - Contains the raw error response
|
|
66
|
+
|
|
67
|
+
### ValidationErrorData
|
|
68
|
+
```typescript
|
|
69
|
+
interface ValidationErrorData {
|
|
70
|
+
status: number
|
|
71
|
+
violations: Array<{
|
|
72
|
+
propertyPath: string
|
|
73
|
+
message: string
|
|
74
|
+
code: string | null
|
|
75
|
+
}>
|
|
76
|
+
detail: string
|
|
77
|
+
type: string
|
|
78
|
+
title: string
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
Structured validation error data from the API.
|
|
82
|
+
|
|
83
|
+
**Properties:**
|
|
84
|
+
- `status: number` - HTTP status code (422)
|
|
85
|
+
- `violations: Array` - List of validation violations
|
|
86
|
+
- `propertyPath: string` - Field path that failed validation (e.g., "email", "address.city")
|
|
87
|
+
- `message: string` - Human-readable error message
|
|
88
|
+
- `code: string | null` - Validation error code
|
|
89
|
+
- `detail: string` - Detailed error description
|
|
90
|
+
- `type: string` - Error type identifier
|
|
91
|
+
- `title: string` - Error title
|
|
92
|
+
|
|
93
|
+
### FormState
|
|
94
|
+
```typescript
|
|
95
|
+
type FormState<T> =
|
|
96
|
+
| {
|
|
97
|
+
success: true
|
|
98
|
+
data: T
|
|
99
|
+
}
|
|
100
|
+
| {
|
|
101
|
+
success: false
|
|
102
|
+
error: ApiErrorData
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
Utility type for handling form submission states (useful for React Server Actions).
|
|
106
|
+
|
|
107
|
+
### FormStateError
|
|
108
|
+
```typescript
|
|
109
|
+
class FormStateError extends Error {
|
|
110
|
+
constructor(public readonly error: ApiErrorData)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
Error wrapper for form state errors.
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
|
|
117
|
+
### Basic Setup
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { createApiClient } from '@deliverart/sdk-js-core';
|
|
121
|
+
import { ErrorHandlerPlugin } from '@deliverart/sdk-js-error-handler';
|
|
122
|
+
|
|
123
|
+
const client = createApiClient({
|
|
124
|
+
baseUrl: 'https://api.deliverart.com'
|
|
125
|
+
})
|
|
126
|
+
.addPlugin(new ErrorHandlerPlugin());
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Handling Validation Errors
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ApiError } from '@deliverart/sdk-js-error-handler';
|
|
133
|
+
import { CreateUser } from './requests';
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
await client.call(new CreateUser({
|
|
137
|
+
email: 'invalid-email',
|
|
138
|
+
name: ''
|
|
139
|
+
}));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
|
|
142
|
+
const violations = error.data.value.violations;
|
|
143
|
+
|
|
144
|
+
violations.forEach(violation => {
|
|
145
|
+
console.error(`${violation.propertyPath}: ${violation.message}`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Output:
|
|
149
|
+
// email: This value is not a valid email address.
|
|
150
|
+
// name: This value should not be blank.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Displaying Validation Errors in Forms
|
|
156
|
+
|
|
157
|
+
#### React Example
|
|
158
|
+
```typescript
|
|
159
|
+
import { useState } from 'react';
|
|
160
|
+
import { ApiError } from '@deliverart/sdk-js-error-handler';
|
|
161
|
+
|
|
162
|
+
function CreateUserForm() {
|
|
163
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
164
|
+
|
|
165
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
setErrors({});
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await client.call(new CreateUser(formData));
|
|
171
|
+
// Success!
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
|
|
174
|
+
const newErrors: Record<string, string> = {};
|
|
175
|
+
|
|
176
|
+
error.data.value.violations.forEach(violation => {
|
|
177
|
+
newErrors[violation.propertyPath] = violation.message;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
setErrors(newErrors);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<form onSubmit={handleSubmit}>
|
|
187
|
+
<input name="email" />
|
|
188
|
+
{errors.email && <span className="error">{errors.email}</span>}
|
|
189
|
+
|
|
190
|
+
<input name="name" />
|
|
191
|
+
{errors.name && <span className="error">{errors.name}</span>}
|
|
192
|
+
|
|
193
|
+
<button type="submit">Create User</button>
|
|
194
|
+
</form>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Handling Different HTTP Status Codes
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { ApiError } from '@deliverart/sdk-js-error-handler';
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await client.call(new GetUser('123'));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error instanceof ApiError) {
|
|
208
|
+
switch (error.status) {
|
|
209
|
+
case 400:
|
|
210
|
+
console.error('Bad Request:', error.message);
|
|
211
|
+
break;
|
|
212
|
+
case 401:
|
|
213
|
+
console.error('Unauthorized - please login');
|
|
214
|
+
window.location.href = '/login';
|
|
215
|
+
break;
|
|
216
|
+
case 403:
|
|
217
|
+
console.error('Forbidden - insufficient permissions');
|
|
218
|
+
break;
|
|
219
|
+
case 404:
|
|
220
|
+
console.error('User not found');
|
|
221
|
+
break;
|
|
222
|
+
case 422:
|
|
223
|
+
// Validation error - handled above
|
|
224
|
+
break;
|
|
225
|
+
case 500:
|
|
226
|
+
console.error('Server error');
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
console.error('Unknown error:', error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Using FormState with React Server Actions
|
|
236
|
+
|
|
237
|
+
FormState is particularly useful for Next.js Server Actions:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// app/actions/create-user.ts
|
|
241
|
+
'use server';
|
|
242
|
+
|
|
243
|
+
import { throwableToFormState, FormState } from '@deliverart/sdk-js-error-handler';
|
|
244
|
+
import { sdk } from '@/lib/sdk';
|
|
245
|
+
import { CreateUser } from '@deliverart/sdk-js-user';
|
|
246
|
+
|
|
247
|
+
export async function createUserAction(
|
|
248
|
+
formData: FormData
|
|
249
|
+
): Promise<FormState<{ id: string }>> {
|
|
250
|
+
return throwableToFormState(async () => {
|
|
251
|
+
const result = await sdk.call(new CreateUser({
|
|
252
|
+
name: formData.get('name') as string,
|
|
253
|
+
email: formData.get('email') as string,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
return { id: result.id };
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// app/components/CreateUserForm.tsx
|
|
263
|
+
'use client';
|
|
264
|
+
|
|
265
|
+
import { useFormState } from 'react-dom';
|
|
266
|
+
import { createUserAction } from '../actions/create-user';
|
|
267
|
+
|
|
268
|
+
export function CreateUserForm() {
|
|
269
|
+
const [state, formAction] = useFormState(createUserAction, null);
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<form action={formAction}>
|
|
273
|
+
<input name="name" required />
|
|
274
|
+
{state?.success === false &&
|
|
275
|
+
state.error.type === 'VALIDATION_ERROR' &&
|
|
276
|
+
state.error.value.violations
|
|
277
|
+
.filter(v => v.propertyPath === 'name')
|
|
278
|
+
.map(v => <span key={v.code} className="error">{v.message}</span>)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
<input name="email" type="email" required />
|
|
282
|
+
{state?.success === false &&
|
|
283
|
+
state.error.type === 'VALIDATION_ERROR' &&
|
|
284
|
+
state.error.value.violations
|
|
285
|
+
.filter(v => v.propertyPath === 'email')
|
|
286
|
+
.map(v => <span key={v.code} className="error">{v.message}</span>)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
<button type="submit">Create User</button>
|
|
290
|
+
|
|
291
|
+
{state?.success === true && (
|
|
292
|
+
<p className="success">User created with ID: {state.data.id}</p>
|
|
293
|
+
)}
|
|
294
|
+
</form>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Global Error Handler
|
|
300
|
+
|
|
301
|
+
Set up a global error handler for all API errors:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// lib/error-handler.ts
|
|
305
|
+
import { ApiError } from '@deliverart/sdk-js-error-handler';
|
|
306
|
+
|
|
307
|
+
export function handleApiError(error: unknown) {
|
|
308
|
+
if (!(error instanceof ApiError)) {
|
|
309
|
+
console.error('Non-API error:', error);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Log to error tracking service (e.g., Sentry)
|
|
314
|
+
console.error('API Error:', {
|
|
315
|
+
status: error.status,
|
|
316
|
+
code: error.code,
|
|
317
|
+
message: error.message,
|
|
318
|
+
data: error.data,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Show user-friendly message
|
|
322
|
+
if (error.status === 401) {
|
|
323
|
+
alert('Please login to continue');
|
|
324
|
+
window.location.href = '/login';
|
|
325
|
+
} else if (error.status === 403) {
|
|
326
|
+
alert('You do not have permission to perform this action');
|
|
327
|
+
} else if (error.status === 404) {
|
|
328
|
+
alert('The requested resource was not found');
|
|
329
|
+
} else if (error.data?.type === 'VALIDATION_ERROR') {
|
|
330
|
+
const firstViolation = error.data.value.violations[0];
|
|
331
|
+
alert(`Validation error: ${firstViolation.message}`);
|
|
332
|
+
} else {
|
|
333
|
+
alert('An unexpected error occurred. Please try again.');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// Usage in your app
|
|
340
|
+
try {
|
|
341
|
+
await client.call(request);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
handleApiError(error);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Type Guards
|
|
348
|
+
|
|
349
|
+
Create type guards for cleaner error handling:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { ApiError, ApiErrorData } from '@deliverart/sdk-js-error-handler';
|
|
353
|
+
|
|
354
|
+
function isValidationError(error: unknown): error is ApiError & {
|
|
355
|
+
data: { type: 'VALIDATION_ERROR', value: ValidationErrorData }
|
|
356
|
+
} {
|
|
357
|
+
return error instanceof ApiError &&
|
|
358
|
+
error.data?.type === 'VALIDATION_ERROR';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isUnauthorizedError(error: unknown): error is ApiError {
|
|
362
|
+
return error instanceof ApiError && error.status === 401;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isNotFoundError(error: unknown): error is ApiError {
|
|
366
|
+
return error instanceof ApiError && error.status === 404;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Usage
|
|
370
|
+
try {
|
|
371
|
+
await client.call(request);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (isValidationError(error)) {
|
|
374
|
+
// TypeScript knows error.data.value is ValidationErrorData
|
|
375
|
+
error.data.value.violations.forEach(v => {
|
|
376
|
+
console.log(v.propertyPath, v.message);
|
|
377
|
+
});
|
|
378
|
+
} else if (isUnauthorizedError(error)) {
|
|
379
|
+
redirectToLogin();
|
|
380
|
+
} else if (isNotFoundError(error)) {
|
|
381
|
+
show404Page();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Converting FormState Errors Back to Exceptions
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import { formStateErrorToThrowable, FormState } from '@deliverart/sdk-js-error-handler';
|
|
390
|
+
|
|
391
|
+
const result: FormState<User> = await createUserAction(formData);
|
|
392
|
+
|
|
393
|
+
if (!result.success) {
|
|
394
|
+
// Convert FormState error back to throwable error
|
|
395
|
+
throw formStateErrorToThrowable(result.error);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// TypeScript knows result.data is User
|
|
399
|
+
console.log(result.data.id);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## How It Works
|
|
403
|
+
|
|
404
|
+
The plugin adds a response middleware that:
|
|
405
|
+
|
|
406
|
+
1. Checks if the response is not OK (`!response.ok`)
|
|
407
|
+
2. If status is 422, attempts to parse the response as validation error
|
|
408
|
+
3. If parsing succeeds, throws `ApiError` with `VALIDATION_ERROR` type
|
|
409
|
+
4. For all other error responses, throws `ApiError` with `UNKNOWN_ERROR` type
|
|
410
|
+
5. Includes the original request, response, and parsed body in the error
|
|
411
|
+
|
|
412
|
+
## Best Practices
|
|
413
|
+
|
|
414
|
+
### 1. Centralized Error Handling
|
|
415
|
+
Create a utility function for consistent error handling across your app:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
export function getErrorMessage(error: unknown): string {
|
|
419
|
+
if (error instanceof ApiError) {
|
|
420
|
+
if (error.data?.type === 'VALIDATION_ERROR') {
|
|
421
|
+
const firstViolation = error.data.value.violations[0];
|
|
422
|
+
return firstViolation?.message ?? 'Validation failed';
|
|
423
|
+
}
|
|
424
|
+
return error.message;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (error instanceof Error) {
|
|
428
|
+
return error.message;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return 'An unexpected error occurred';
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### 2. Field-Level Error Mapping
|
|
436
|
+
Create a helper to map violations to form fields:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { ValidationErrorData } from '@deliverart/sdk-js-error-handler';
|
|
440
|
+
|
|
441
|
+
export function violationsToFieldErrors(
|
|
442
|
+
violations: ValidationErrorData['violations']
|
|
443
|
+
): Record<string, string> {
|
|
444
|
+
return violations.reduce((acc, violation) => {
|
|
445
|
+
acc[violation.propertyPath] = violation.message;
|
|
446
|
+
return acc;
|
|
447
|
+
}, {} as Record<string, string>);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Usage
|
|
451
|
+
if (error.data?.type === 'VALIDATION_ERROR') {
|
|
452
|
+
const fieldErrors = violationsToFieldErrors(error.data.value.violations);
|
|
453
|
+
setErrors(fieldErrors);
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 3. Toast Notifications
|
|
458
|
+
Integrate with toast notification libraries:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { toast } from 'sonner';
|
|
462
|
+
import { ApiError } from '@deliverart/sdk-js-error-handler';
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
await client.call(request);
|
|
466
|
+
toast.success('Operation completed successfully');
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
|
|
469
|
+
error.data.value.violations.forEach(v => {
|
|
470
|
+
toast.error(v.message);
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
toast.error('An error occurred');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## License
|
|
479
|
+
|
|
480
|
+
MIT
|
|
481
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deliverart/sdk-js-error-handler",
|
|
3
3
|
"description": "Error handling utilities for Deliverart SDK in JavaScript",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.51",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@deliverart/sdk-js-core": "2.1.
|
|
21
|
+
"@deliverart/sdk-js-core": "2.1.51"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|