@deliverart/sdk-js-error-handler 2.1.50 → 2.1.52

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 (2) hide show
  1. package/README.md +481 -0
  2. 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.50",
4
+ "version": "2.1.52",
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.50"
21
+ "@deliverart/sdk-js-core": "2.1.52"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"