@shipstatic/types 0.9.3 → 0.9.5
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 +13 -3
- package/dist/index.d.ts +21 -2
- package/dist/index.js +45 -8
- package/package.json +1 -1
- package/src/index.ts +52 -8
package/README.md
CHANGED
|
@@ -52,13 +52,23 @@ if (error.isAuthError()) { /* handle auth */ }
|
|
|
52
52
|
// Producer side (API workers): serialize a ShipError to JSON
|
|
53
53
|
return c.json(error.toResponse(), error.status ?? 500);
|
|
54
54
|
|
|
55
|
-
// Consumer side
|
|
55
|
+
// Consumer side — two symmetric helpers cover both HTTP error modes:
|
|
56
|
+
|
|
57
|
+
// 1. Server returned a non-OK response
|
|
56
58
|
if (!response.ok) {
|
|
57
|
-
throw await ShipError.fromHttpResponse(response, 'Get account
|
|
59
|
+
throw await ShipError.fromHttpResponse(response, 'Get account');
|
|
58
60
|
}
|
|
61
|
+
|
|
62
|
+
// 2. fetch itself threw (offline, abort, CORS, ...)
|
|
63
|
+
try { response = await fetch(url); }
|
|
64
|
+
catch (cause) { throw ShipError.fromFetchError(cause, 'Get account'); }
|
|
59
65
|
```
|
|
60
66
|
|
|
61
|
-
`fromHttpResponse` trusts the body's `error` field when it's a known `ErrorType` — so a server's `ShipError.validation(...)` round-trips back to `ErrorType.Validation` on the client. For non-API responses (CDN errors, intermediaries) or malformed bodies it falls back to status-derived (401 → `Authentication`, 429 → `RateLimit`, else → `Api`). Body's `message` and `details` are preserved best-effort.
|
|
67
|
+
`fromHttpResponse` trusts the body's `error` field when it's a known server-producible `ErrorType` — so a server's `ShipError.validation(...)` round-trips back to `ErrorType.Validation` on the client. For non-API responses (CDN errors, intermediaries) or malformed bodies it falls back to status-derived (401 → `Authentication`, 429 → `RateLimit`, else → `Api`). Body's `message` and `details` are preserved best-effort.
|
|
68
|
+
|
|
69
|
+
`fromFetchError` routes by the thrown cause: an existing `ShipError` is returned unchanged, `AbortError` becomes `Cancelled`, a fetch `TypeError` becomes `Network`, anything else becomes `Api` (with no HTTP status — the request never reached the server).
|
|
70
|
+
|
|
71
|
+
Both helpers accept an optional operation-name string for contextual messages (`"Get account was cancelled"`, `"Get account failed: ..."`).
|
|
62
72
|
|
|
63
73
|
### Status Constants
|
|
64
74
|
|
package/dist/index.d.ts
CHANGED
|
@@ -317,9 +317,9 @@ export declare const ErrorType: {
|
|
|
317
317
|
readonly Business: "business_logic_error";
|
|
318
318
|
/** API server error (500) */
|
|
319
319
|
readonly Api: "internal_server_error";
|
|
320
|
-
/** Network/connection error */
|
|
320
|
+
/** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
|
|
321
321
|
readonly Network: "network_error";
|
|
322
|
-
/** Operation was cancelled */
|
|
322
|
+
/** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
|
|
323
323
|
readonly Cancelled: "operation_cancelled";
|
|
324
324
|
/** File operation error */
|
|
325
325
|
readonly File: "file_error";
|
|
@@ -367,6 +367,25 @@ export declare class ShipError extends Error {
|
|
|
367
367
|
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
368
368
|
*/
|
|
369
369
|
static fromHttpResponse(response: Response, fallbackMessage?: string): Promise<ShipError>;
|
|
370
|
+
/**
|
|
371
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
372
|
+
*
|
|
373
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
374
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
375
|
+
* server returning a non-OK response.
|
|
376
|
+
*
|
|
377
|
+
* Routing:
|
|
378
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
379
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
380
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
381
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
382
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
383
|
+
*
|
|
384
|
+
* The optional `operationName` is composed into the message for context:
|
|
385
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
386
|
+
* `"Request"` when omitted.
|
|
387
|
+
*/
|
|
388
|
+
static fromFetchError(cause: unknown, operationName?: string): ShipError;
|
|
370
389
|
static validation(message: string, details?: any): ShipError;
|
|
371
390
|
static notFound(resource: string, id?: string): ShipError;
|
|
372
391
|
static rateLimit(message?: string): ShipError;
|
package/dist/index.js
CHANGED
|
@@ -71,9 +71,9 @@ export const ErrorType = {
|
|
|
71
71
|
Business: 'business_logic_error',
|
|
72
72
|
/** API server error (500) */
|
|
73
73
|
Api: 'internal_server_error',
|
|
74
|
-
/** Network/connection error */
|
|
74
|
+
/** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
|
|
75
75
|
Network: 'network_error',
|
|
76
|
-
/** Operation was cancelled */
|
|
76
|
+
/** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
|
|
77
77
|
Cancelled: 'operation_cancelled',
|
|
78
78
|
/** File operation error */
|
|
79
79
|
File: 'file_error',
|
|
@@ -91,12 +91,16 @@ const ERROR_CATEGORIES = {
|
|
|
91
91
|
auth: new Set([ErrorType.Authentication]),
|
|
92
92
|
};
|
|
93
93
|
/**
|
|
94
|
-
* Lookup set of
|
|
95
|
-
* `ShipError.fromHttpResponse` to validate the
|
|
96
|
-
* trusting it as
|
|
97
|
-
*
|
|
94
|
+
* Lookup set of error types that legitimately appear on the wire — i.e.
|
|
95
|
+
* server-thrown types. Used by `ShipError.fromHttpResponse` to validate the
|
|
96
|
+
* body's `error` field before trusting it as the `ShipError.type`.
|
|
97
|
+
*
|
|
98
|
+
* Excludes `Network` and `Cancelled`, which are client-side-only by design:
|
|
99
|
+
* they originate on the client (fetch failure, abort) and should never be
|
|
100
|
+
* reconstructed from a server response, even if a misbehaving server were
|
|
101
|
+
* to send them. A defensive omission, not a theoretical concern.
|
|
98
102
|
*/
|
|
99
|
-
const
|
|
103
|
+
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter(t => t !== ErrorType.Network && t !== ErrorType.Cancelled));
|
|
100
104
|
/**
|
|
101
105
|
* Simple unified error class for both API and SDK
|
|
102
106
|
*/
|
|
@@ -155,7 +159,7 @@ export class ShipError extends Error {
|
|
|
155
159
|
else if (typeof obj.error === 'string')
|
|
156
160
|
message = obj.error;
|
|
157
161
|
details = obj.details;
|
|
158
|
-
if (typeof obj.error === 'string' &&
|
|
162
|
+
if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {
|
|
159
163
|
bodyType = obj.error;
|
|
160
164
|
}
|
|
161
165
|
}
|
|
@@ -175,6 +179,39 @@ export class ShipError extends Error {
|
|
|
175
179
|
ErrorType.Api);
|
|
176
180
|
return new ShipError(type, message, response.status, details);
|
|
177
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
184
|
+
*
|
|
185
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
186
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
187
|
+
* server returning a non-OK response.
|
|
188
|
+
*
|
|
189
|
+
* Routing:
|
|
190
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
191
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
192
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
193
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
194
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
195
|
+
*
|
|
196
|
+
* The optional `operationName` is composed into the message for context:
|
|
197
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
198
|
+
* `"Request"` when omitted.
|
|
199
|
+
*/
|
|
200
|
+
static fromFetchError(cause, operationName) {
|
|
201
|
+
if (isShipError(cause))
|
|
202
|
+
return cause;
|
|
203
|
+
const op = operationName || 'Request';
|
|
204
|
+
if (cause instanceof Error) {
|
|
205
|
+
if (cause.name === 'AbortError') {
|
|
206
|
+
return ShipError.cancelled(`${op} was cancelled`);
|
|
207
|
+
}
|
|
208
|
+
if (cause instanceof TypeError && cause.message.includes('fetch')) {
|
|
209
|
+
return ShipError.network(`${op} failed: ${cause.message}`, cause);
|
|
210
|
+
}
|
|
211
|
+
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
212
|
+
}
|
|
213
|
+
return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);
|
|
214
|
+
}
|
|
178
215
|
// Factory methods for common errors
|
|
179
216
|
static validation(message, details) {
|
|
180
217
|
return new ShipError(ErrorType.Validation, message, 400, details);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -362,9 +362,9 @@ export const ErrorType = {
|
|
|
362
362
|
Business: 'business_logic_error',
|
|
363
363
|
/** API server error (500) */
|
|
364
364
|
Api: 'internal_server_error',
|
|
365
|
-
/** Network/connection error */
|
|
365
|
+
/** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
|
|
366
366
|
Network: 'network_error',
|
|
367
|
-
/** Operation was cancelled */
|
|
367
|
+
/** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
|
|
368
368
|
Cancelled: 'operation_cancelled',
|
|
369
369
|
/** File operation error */
|
|
370
370
|
File: 'file_error',
|
|
@@ -386,12 +386,20 @@ const ERROR_CATEGORIES = {
|
|
|
386
386
|
} as const;
|
|
387
387
|
|
|
388
388
|
/**
|
|
389
|
-
* Lookup set of
|
|
390
|
-
* `ShipError.fromHttpResponse` to validate the
|
|
391
|
-
* trusting it as
|
|
392
|
-
*
|
|
389
|
+
* Lookup set of error types that legitimately appear on the wire — i.e.
|
|
390
|
+
* server-thrown types. Used by `ShipError.fromHttpResponse` to validate the
|
|
391
|
+
* body's `error` field before trusting it as the `ShipError.type`.
|
|
392
|
+
*
|
|
393
|
+
* Excludes `Network` and `Cancelled`, which are client-side-only by design:
|
|
394
|
+
* they originate on the client (fetch failure, abort) and should never be
|
|
395
|
+
* reconstructed from a server response, even if a misbehaving server were
|
|
396
|
+
* to send them. A defensive omission, not a theoretical concern.
|
|
393
397
|
*/
|
|
394
|
-
const
|
|
398
|
+
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
|
|
399
|
+
Object.values(ErrorType).filter(
|
|
400
|
+
t => t !== ErrorType.Network && t !== ErrorType.Cancelled,
|
|
401
|
+
),
|
|
402
|
+
);
|
|
395
403
|
|
|
396
404
|
/**
|
|
397
405
|
* Standard error response format used everywhere
|
|
@@ -469,7 +477,7 @@ export class ShipError extends Error {
|
|
|
469
477
|
if (typeof obj.message === 'string') message = obj.message;
|
|
470
478
|
else if (typeof obj.error === 'string') message = obj.error;
|
|
471
479
|
details = obj.details;
|
|
472
|
-
if (typeof obj.error === 'string' &&
|
|
480
|
+
if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {
|
|
473
481
|
bodyType = obj.error as ErrorType;
|
|
474
482
|
}
|
|
475
483
|
}
|
|
@@ -492,6 +500,42 @@ export class ShipError extends Error {
|
|
|
492
500
|
return new ShipError(type, message, response.status, details);
|
|
493
501
|
}
|
|
494
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
505
|
+
*
|
|
506
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
507
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
508
|
+
* server returning a non-OK response.
|
|
509
|
+
*
|
|
510
|
+
* Routing:
|
|
511
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
512
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
513
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
514
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
515
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
516
|
+
*
|
|
517
|
+
* The optional `operationName` is composed into the message for context:
|
|
518
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
519
|
+
* `"Request"` when omitted.
|
|
520
|
+
*/
|
|
521
|
+
static fromFetchError(cause: unknown, operationName?: string): ShipError {
|
|
522
|
+
if (isShipError(cause)) return cause;
|
|
523
|
+
|
|
524
|
+
const op = operationName || 'Request';
|
|
525
|
+
|
|
526
|
+
if (cause instanceof Error) {
|
|
527
|
+
if (cause.name === 'AbortError') {
|
|
528
|
+
return ShipError.cancelled(`${op} was cancelled`);
|
|
529
|
+
}
|
|
530
|
+
if (cause instanceof TypeError && cause.message.includes('fetch')) {
|
|
531
|
+
return ShipError.network(`${op} failed: ${cause.message}`, cause);
|
|
532
|
+
}
|
|
533
|
+
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);
|
|
537
|
+
}
|
|
538
|
+
|
|
495
539
|
// Factory methods for common errors
|
|
496
540
|
static validation(message: string, details?: any): ShipError {
|
|
497
541
|
return new ShipError(ErrorType.Validation, message, 400, details);
|