@shipstatic/types 0.9.4 → 0.9.6
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 +17 -4
- package/dist/index.d.ts +27 -9
- package/dist/index.js +47 -24
- package/package.json +1 -1
- package/src/index.ts +50 -28
package/README.md
CHANGED
|
@@ -43,7 +43,8 @@ if (isShipError(error)) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (error.isClientError()) { /* Business | Config | File | Validation */ }
|
|
46
|
-
if (error.isAuthError())
|
|
46
|
+
if (error.isAuthError()) { /* handle auth */ }
|
|
47
|
+
if (error.type === ErrorType.Validation) { /* specific-type checks */ }
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
**HTTP client integration.** Both producer and consumer sides of the wire have first-class helpers, so every HTTP client across the platform reconstructs the same `ShipError` shape:
|
|
@@ -52,13 +53,25 @@ if (error.isAuthError()) { /* handle auth */ }
|
|
|
52
53
|
// Producer side (API workers): serialize a ShipError to JSON
|
|
53
54
|
return c.json(error.toResponse(), error.status ?? 500);
|
|
54
55
|
|
|
55
|
-
// Consumer side
|
|
56
|
+
// Consumer side — two symmetric helpers cover both HTTP error modes:
|
|
57
|
+
|
|
58
|
+
// Both helpers take an optional operationName for context-aware fallback messages.
|
|
59
|
+
|
|
60
|
+
// 1. Server returned a non-OK response
|
|
56
61
|
if (!response.ok) {
|
|
57
|
-
throw await ShipError.fromHttpResponse(response, 'Get account
|
|
62
|
+
throw await ShipError.fromHttpResponse(response, 'Get account');
|
|
58
63
|
}
|
|
64
|
+
|
|
65
|
+
// 2. fetch itself threw (offline, abort, CORS, ...)
|
|
66
|
+
try { response = await fetch(url); }
|
|
67
|
+
catch (cause) { throw ShipError.fromFetchError(cause, 'Get account'); }
|
|
59
68
|
```
|
|
60
69
|
|
|
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.
|
|
70
|
+
`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.
|
|
71
|
+
|
|
72
|
+
`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).
|
|
73
|
+
|
|
74
|
+
Both helpers accept an optional operation-name string for contextual messages (`"Get account was cancelled"`, `"Get account failed: ..."`).
|
|
62
75
|
|
|
63
76
|
### Status Constants
|
|
64
77
|
|
package/dist/index.d.ts
CHANGED
|
@@ -354,8 +354,8 @@ export declare class ShipError extends Error {
|
|
|
354
354
|
* Construct a `ShipError` from an HTTP error response.
|
|
355
355
|
*
|
|
356
356
|
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
357
|
-
* resolution: `body.message` → `body.error` → `
|
|
358
|
-
*
|
|
357
|
+
* resolution: `body.message` → `body.error` → `"<operationName> failed with
|
|
358
|
+
* status <N>"`.
|
|
359
359
|
*
|
|
360
360
|
* Type resolution: trusts `body.error` when it's a known `ErrorType`
|
|
361
361
|
* (preserves the wire's intent — server's `ShipError.validation(...)`
|
|
@@ -363,27 +363,45 @@ export declare class ShipError extends Error {
|
|
|
363
363
|
* status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
|
|
364
364
|
* non-API responses (CDN errors, intermediaries) or malformed bodies.
|
|
365
365
|
*
|
|
366
|
+
* `operationName` (e.g. `"Get account"`) is used to compose the fallback
|
|
367
|
+
* message. Defaults to `"Request"`. Same convention as `fromFetchError`.
|
|
368
|
+
*
|
|
366
369
|
* Async because it reads the response body. Returns rather than throws so
|
|
367
370
|
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
368
371
|
*/
|
|
369
|
-
static fromHttpResponse(response: Response,
|
|
372
|
+
static fromHttpResponse(response: Response, operationName?: string): Promise<ShipError>;
|
|
373
|
+
/**
|
|
374
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
375
|
+
*
|
|
376
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
377
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
378
|
+
* server returning a non-OK response.
|
|
379
|
+
*
|
|
380
|
+
* Routing:
|
|
381
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
382
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
383
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
384
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
385
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
386
|
+
*
|
|
387
|
+
* The optional `operationName` is composed into the message for context:
|
|
388
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
389
|
+
* `"Request"` when omitted.
|
|
390
|
+
*/
|
|
391
|
+
static fromFetchError(cause: unknown, operationName?: string): ShipError;
|
|
370
392
|
static validation(message: string, details?: any): ShipError;
|
|
371
393
|
static notFound(resource: string, id?: string): ShipError;
|
|
372
394
|
static rateLimit(message?: string): ShipError;
|
|
373
395
|
static authentication(message?: string, details?: any): ShipError;
|
|
374
396
|
static business(message: string, status?: number): ShipError;
|
|
375
|
-
static network(message: string,
|
|
397
|
+
static network(message: string, details?: any): ShipError;
|
|
376
398
|
static cancelled(message: string): ShipError;
|
|
377
|
-
static file(message: string,
|
|
399
|
+
static file(message: string, details?: any): ShipError;
|
|
378
400
|
static config(message: string, details?: any): ShipError;
|
|
379
401
|
static api(message: string, status?: number): ShipError;
|
|
380
|
-
get filePath(): string | undefined;
|
|
381
402
|
isClientError(): boolean;
|
|
382
403
|
isNetworkError(): boolean;
|
|
383
404
|
isAuthError(): boolean;
|
|
384
|
-
isValidationError(): boolean;
|
|
385
|
-
isFileError(): boolean;
|
|
386
|
-
isConfigError(): boolean;
|
|
387
405
|
isType(errorType: ErrorType): boolean;
|
|
388
406
|
}
|
|
389
407
|
/**
|
package/dist/index.js
CHANGED
|
@@ -132,8 +132,8 @@ export class ShipError extends Error {
|
|
|
132
132
|
* Construct a `ShipError` from an HTTP error response.
|
|
133
133
|
*
|
|
134
134
|
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
135
|
-
* resolution: `body.message` → `body.error` → `
|
|
136
|
-
*
|
|
135
|
+
* resolution: `body.message` → `body.error` → `"<operationName> failed with
|
|
136
|
+
* status <N>"`.
|
|
137
137
|
*
|
|
138
138
|
* Type resolution: trusts `body.error` when it's a known `ErrorType`
|
|
139
139
|
* (preserves the wire's intent — server's `ShipError.validation(...)`
|
|
@@ -141,10 +141,13 @@ export class ShipError extends Error {
|
|
|
141
141
|
* status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
|
|
142
142
|
* non-API responses (CDN errors, intermediaries) or malformed bodies.
|
|
143
143
|
*
|
|
144
|
+
* `operationName` (e.g. `"Get account"`) is used to compose the fallback
|
|
145
|
+
* message. Defaults to `"Request"`. Same convention as `fromFetchError`.
|
|
146
|
+
*
|
|
144
147
|
* Async because it reads the response body. Returns rather than throws so
|
|
145
148
|
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
146
149
|
*/
|
|
147
|
-
static async fromHttpResponse(response,
|
|
150
|
+
static async fromHttpResponse(response, operationName) {
|
|
148
151
|
let message;
|
|
149
152
|
let details;
|
|
150
153
|
let bodyType;
|
|
@@ -171,14 +174,47 @@ export class ShipError extends Error {
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
catch {
|
|
174
|
-
// Body unreadable; fall through to
|
|
177
|
+
// Body unreadable; fall through to operationName-derived message.
|
|
175
178
|
}
|
|
176
|
-
message = message ||
|
|
179
|
+
message = message || `${operationName || 'Request'} failed with status ${response.status}`;
|
|
177
180
|
const type = bodyType ?? (response.status === 401 ? ErrorType.Authentication :
|
|
178
181
|
response.status === 429 ? ErrorType.RateLimit :
|
|
179
182
|
ErrorType.Api);
|
|
180
183
|
return new ShipError(type, message, response.status, details);
|
|
181
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
187
|
+
*
|
|
188
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
189
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
190
|
+
* server returning a non-OK response.
|
|
191
|
+
*
|
|
192
|
+
* Routing:
|
|
193
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
194
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
195
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
196
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
197
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
198
|
+
*
|
|
199
|
+
* The optional `operationName` is composed into the message for context:
|
|
200
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
201
|
+
* `"Request"` when omitted.
|
|
202
|
+
*/
|
|
203
|
+
static fromFetchError(cause, operationName) {
|
|
204
|
+
if (isShipError(cause))
|
|
205
|
+
return cause;
|
|
206
|
+
const op = operationName || 'Request';
|
|
207
|
+
if (cause instanceof Error) {
|
|
208
|
+
if (cause.name === 'AbortError') {
|
|
209
|
+
return ShipError.cancelled(`${op} was cancelled`);
|
|
210
|
+
}
|
|
211
|
+
if (cause instanceof TypeError && cause.message.includes('fetch')) {
|
|
212
|
+
return ShipError.network(`${op} failed: ${cause.message}`, { cause });
|
|
213
|
+
}
|
|
214
|
+
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
215
|
+
}
|
|
216
|
+
return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);
|
|
217
|
+
}
|
|
182
218
|
// Factory methods for common errors
|
|
183
219
|
static validation(message, details) {
|
|
184
220
|
return new ShipError(ErrorType.Validation, message, 400, details);
|
|
@@ -196,14 +232,14 @@ export class ShipError extends Error {
|
|
|
196
232
|
static business(message, status = 400) {
|
|
197
233
|
return new ShipError(ErrorType.Business, message, status);
|
|
198
234
|
}
|
|
199
|
-
static network(message,
|
|
200
|
-
return new ShipError(ErrorType.Network, message, undefined,
|
|
235
|
+
static network(message, details) {
|
|
236
|
+
return new ShipError(ErrorType.Network, message, undefined, details);
|
|
201
237
|
}
|
|
202
238
|
static cancelled(message) {
|
|
203
239
|
return new ShipError(ErrorType.Cancelled, message);
|
|
204
240
|
}
|
|
205
|
-
static file(message,
|
|
206
|
-
return new ShipError(ErrorType.File, message, undefined,
|
|
241
|
+
static file(message, details) {
|
|
242
|
+
return new ShipError(ErrorType.File, message, undefined, details);
|
|
207
243
|
}
|
|
208
244
|
static config(message, details) {
|
|
209
245
|
return new ShipError(ErrorType.Config, message, undefined, details);
|
|
@@ -211,11 +247,8 @@ export class ShipError extends Error {
|
|
|
211
247
|
static api(message, status = 500) {
|
|
212
248
|
return new ShipError(ErrorType.Api, message, status);
|
|
213
249
|
}
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
return this.details?.filePath;
|
|
217
|
-
}
|
|
218
|
-
// Helper methods for error type checking using categorization
|
|
250
|
+
// Semantic-category type guards. For specific-type checks, use
|
|
251
|
+
// `error.type === ErrorType.X` directly or the generic `isType(t)`.
|
|
219
252
|
isClientError() {
|
|
220
253
|
return ERROR_CATEGORIES.client.has(this.type);
|
|
221
254
|
}
|
|
@@ -225,16 +258,6 @@ export class ShipError extends Error {
|
|
|
225
258
|
isAuthError() {
|
|
226
259
|
return ERROR_CATEGORIES.auth.has(this.type);
|
|
227
260
|
}
|
|
228
|
-
isValidationError() {
|
|
229
|
-
return this.type === ErrorType.Validation;
|
|
230
|
-
}
|
|
231
|
-
isFileError() {
|
|
232
|
-
return this.type === ErrorType.File;
|
|
233
|
-
}
|
|
234
|
-
isConfigError() {
|
|
235
|
-
return this.type === ErrorType.Config;
|
|
236
|
-
}
|
|
237
|
-
// Generic type checker
|
|
238
261
|
isType(errorType) {
|
|
239
262
|
return this.type === errorType;
|
|
240
263
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -448,8 +448,8 @@ export class ShipError extends Error {
|
|
|
448
448
|
* Construct a `ShipError` from an HTTP error response.
|
|
449
449
|
*
|
|
450
450
|
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
451
|
-
* resolution: `body.message` → `body.error` → `
|
|
452
|
-
*
|
|
451
|
+
* resolution: `body.message` → `body.error` → `"<operationName> failed with
|
|
452
|
+
* status <N>"`.
|
|
453
453
|
*
|
|
454
454
|
* Type resolution: trusts `body.error` when it's a known `ErrorType`
|
|
455
455
|
* (preserves the wire's intent — server's `ShipError.validation(...)`
|
|
@@ -457,12 +457,15 @@ export class ShipError extends Error {
|
|
|
457
457
|
* status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
|
|
458
458
|
* non-API responses (CDN errors, intermediaries) or malformed bodies.
|
|
459
459
|
*
|
|
460
|
+
* `operationName` (e.g. `"Get account"`) is used to compose the fallback
|
|
461
|
+
* message. Defaults to `"Request"`. Same convention as `fromFetchError`.
|
|
462
|
+
*
|
|
460
463
|
* Async because it reads the response body. Returns rather than throws so
|
|
461
464
|
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
462
465
|
*/
|
|
463
466
|
static async fromHttpResponse(
|
|
464
467
|
response: Response,
|
|
465
|
-
|
|
468
|
+
operationName?: string,
|
|
466
469
|
): Promise<ShipError> {
|
|
467
470
|
let message: string | undefined;
|
|
468
471
|
let details: unknown;
|
|
@@ -486,10 +489,10 @@ export class ShipError extends Error {
|
|
|
486
489
|
if (text) message = text;
|
|
487
490
|
}
|
|
488
491
|
} catch {
|
|
489
|
-
// Body unreadable; fall through to
|
|
492
|
+
// Body unreadable; fall through to operationName-derived message.
|
|
490
493
|
}
|
|
491
494
|
|
|
492
|
-
message = message ||
|
|
495
|
+
message = message || `${operationName || 'Request'} failed with status ${response.status}`;
|
|
493
496
|
|
|
494
497
|
const type = bodyType ?? (
|
|
495
498
|
response.status === 401 ? ErrorType.Authentication :
|
|
@@ -500,6 +503,42 @@ export class ShipError extends Error {
|
|
|
500
503
|
return new ShipError(type, message, response.status, details);
|
|
501
504
|
}
|
|
502
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Construct a `ShipError` from an error caught around a `fetch()` call.
|
|
508
|
+
*
|
|
509
|
+
* The mirror of `fromHttpResponse` for the *other* side of the HTTP error
|
|
510
|
+
* story — the network layer failing (offline, CORS, abort) rather than the
|
|
511
|
+
* server returning a non-OK response.
|
|
512
|
+
*
|
|
513
|
+
* Routing:
|
|
514
|
+
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
515
|
+
* - `AbortError` → `ShipError.cancelled(...)`
|
|
516
|
+
* - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
|
|
517
|
+
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
518
|
+
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
519
|
+
*
|
|
520
|
+
* The optional `operationName` is composed into the message for context:
|
|
521
|
+
* `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
|
|
522
|
+
* `"Request"` when omitted.
|
|
523
|
+
*/
|
|
524
|
+
static fromFetchError(cause: unknown, operationName?: string): ShipError {
|
|
525
|
+
if (isShipError(cause)) return cause;
|
|
526
|
+
|
|
527
|
+
const op = operationName || 'Request';
|
|
528
|
+
|
|
529
|
+
if (cause instanceof Error) {
|
|
530
|
+
if (cause.name === 'AbortError') {
|
|
531
|
+
return ShipError.cancelled(`${op} was cancelled`);
|
|
532
|
+
}
|
|
533
|
+
if (cause instanceof TypeError && cause.message.includes('fetch')) {
|
|
534
|
+
return ShipError.network(`${op} failed: ${cause.message}`, { cause });
|
|
535
|
+
}
|
|
536
|
+
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);
|
|
540
|
+
}
|
|
541
|
+
|
|
503
542
|
// Factory methods for common errors
|
|
504
543
|
static validation(message: string, details?: any): ShipError {
|
|
505
544
|
return new ShipError(ErrorType.Validation, message, 400, details);
|
|
@@ -522,16 +561,16 @@ export class ShipError extends Error {
|
|
|
522
561
|
return new ShipError(ErrorType.Business, message, status);
|
|
523
562
|
}
|
|
524
563
|
|
|
525
|
-
static network(message: string,
|
|
526
|
-
return new ShipError(ErrorType.Network, message, undefined,
|
|
564
|
+
static network(message: string, details?: any): ShipError {
|
|
565
|
+
return new ShipError(ErrorType.Network, message, undefined, details);
|
|
527
566
|
}
|
|
528
567
|
|
|
529
568
|
static cancelled(message: string): ShipError {
|
|
530
569
|
return new ShipError(ErrorType.Cancelled, message);
|
|
531
570
|
}
|
|
532
571
|
|
|
533
|
-
static file(message: string,
|
|
534
|
-
return new ShipError(ErrorType.File, message, undefined,
|
|
572
|
+
static file(message: string, details?: any): ShipError {
|
|
573
|
+
return new ShipError(ErrorType.File, message, undefined, details);
|
|
535
574
|
}
|
|
536
575
|
|
|
537
576
|
static config(message: string, details?: any): ShipError {
|
|
@@ -542,12 +581,8 @@ export class ShipError extends Error {
|
|
|
542
581
|
return new ShipError(ErrorType.Api, message, status);
|
|
543
582
|
}
|
|
544
583
|
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
return this.details?.filePath;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Helper methods for error type checking using categorization
|
|
584
|
+
// Semantic-category type guards. For specific-type checks, use
|
|
585
|
+
// `error.type === ErrorType.X` directly or the generic `isType(t)`.
|
|
551
586
|
isClientError(): boolean {
|
|
552
587
|
return ERROR_CATEGORIES.client.has(this.type);
|
|
553
588
|
}
|
|
@@ -560,19 +595,6 @@ export class ShipError extends Error {
|
|
|
560
595
|
return ERROR_CATEGORIES.auth.has(this.type);
|
|
561
596
|
}
|
|
562
597
|
|
|
563
|
-
isValidationError(): boolean {
|
|
564
|
-
return this.type === ErrorType.Validation;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
isFileError(): boolean {
|
|
568
|
-
return this.type === ErrorType.File;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
isConfigError(): boolean {
|
|
572
|
-
return this.type === ErrorType.Config;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Generic type checker
|
|
576
598
|
isType(errorType: ErrorType): boolean {
|
|
577
599
|
return this.type === errorType;
|
|
578
600
|
}
|