@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 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()) { /* handle auth */ }
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 (SDK, web app): rehydrate from any error Response
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 failed');
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. The optional second arg is a fallback message used when the body has nothing usable.
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` → `fallbackMessage`
358
- * `Request failed with status N`.
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, fallbackMessage?: string): Promise<ShipError>;
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, cause?: Error): ShipError;
397
+ static network(message: string, details?: any): ShipError;
376
398
  static cancelled(message: string): ShipError;
377
- static file(message: string, filePath?: string): ShipError;
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` → `fallbackMessage`
136
- * `Request failed with status N`.
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, fallbackMessage) {
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 fallback.
177
+ // Body unreadable; fall through to operationName-derived message.
175
178
  }
176
- message = message || fallbackMessage || `Request failed with status ${response.status}`;
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, cause) {
200
- return new ShipError(ErrorType.Network, message, undefined, { cause });
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, filePath) {
206
- return new ShipError(ErrorType.File, message, undefined, { filePath });
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
- // Helper getter for accessing file path from details
215
- get filePath() {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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` → `fallbackMessage`
452
- * `Request failed with status N`.
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
- fallbackMessage?: string,
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 fallback.
492
+ // Body unreadable; fall through to operationName-derived message.
490
493
  }
491
494
 
492
- message = message || fallbackMessage || `Request failed with status ${response.status}`;
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, cause?: Error): ShipError {
526
- return new ShipError(ErrorType.Network, message, undefined, { cause });
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, filePath?: string): ShipError {
534
- return new ShipError(ErrorType.File, message, undefined, { filePath });
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
- // Helper getter for accessing file path from details
546
- get filePath(): string | undefined {
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
  }