@shipstatic/types 0.9.2 → 0.9.4

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
@@ -58,7 +58,7 @@ if (!response.ok) {
58
58
  }
59
59
  ```
60
60
 
61
- `fromHttpResponse` derives the error type from HTTP status (401 → `Authentication`, 429 → `RateLimit`, else → `Api`), preserving the body's `message`, `error`, and `details` best-effort. The optional second arg is a fallback message used when the body has nothing usable.
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.
62
62
 
63
63
  ### Status Constants
64
64
 
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";
@@ -355,10 +355,13 @@ export declare class ShipError extends Error {
355
355
  *
356
356
  * Best-effort body parse for `{ message, error?, details? }`. Message
357
357
  * resolution: `body.message` → `body.error` → `fallbackMessage` →
358
- * `Request failed with status N`. Status drives the error type — same
359
- * convention used by the SDK and web console — so `error.status === 429`
360
- * always lines up with `ErrorType.RateLimit`, etc., regardless of what the
361
- * body's `error` field claims.
358
+ * `Request failed with status N`.
359
+ *
360
+ * Type resolution: trusts `body.error` when it's a known `ErrorType`
361
+ * (preserves the wire's intent server's `ShipError.validation(...)`
362
+ * round-trips back to `ErrorType.Validation` on the client). Falls back to
363
+ * status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
364
+ * non-API responses (CDN errors, intermediaries) or malformed bodies.
362
365
  *
363
366
  * Async because it reads the response body. Returns rather than throws so
364
367
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
@@ -374,8 +377,6 @@ export declare class ShipError extends Error {
374
377
  static file(message: string, filePath?: string): ShipError;
375
378
  static config(message: string, details?: any): ShipError;
376
379
  static api(message: string, status?: number): ShipError;
377
- static database(message: string, status?: number): ShipError;
378
- static storage(message: string, status?: number): ShipError;
379
380
  get filePath(): string | undefined;
380
381
  isClientError(): boolean;
381
382
  isNetworkError(): boolean;
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',
@@ -90,6 +90,17 @@ const ERROR_CATEGORIES = {
90
90
  network: new Set([ErrorType.Network]),
91
91
  auth: new Set([ErrorType.Authentication]),
92
92
  };
93
+ /**
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.
102
+ */
103
+ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter(t => t !== ErrorType.Network && t !== ErrorType.Cancelled));
93
104
  /**
94
105
  * Simple unified error class for both API and SDK
95
106
  */
@@ -122,10 +133,13 @@ export class ShipError extends Error {
122
133
  *
123
134
  * Best-effort body parse for `{ message, error?, details? }`. Message
124
135
  * resolution: `body.message` → `body.error` → `fallbackMessage` →
125
- * `Request failed with status N`. Status drives the error type — same
126
- * convention used by the SDK and web console — so `error.status === 429`
127
- * always lines up with `ErrorType.RateLimit`, etc., regardless of what the
128
- * body's `error` field claims.
136
+ * `Request failed with status N`.
137
+ *
138
+ * Type resolution: trusts `body.error` when it's a known `ErrorType`
139
+ * (preserves the wire's intent server's `ShipError.validation(...)`
140
+ * round-trips back to `ErrorType.Validation` on the client). Falls back to
141
+ * status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
142
+ * non-API responses (CDN errors, intermediaries) or malformed bodies.
129
143
  *
130
144
  * Async because it reads the response body. Returns rather than throws so
131
145
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
@@ -133,6 +147,7 @@ export class ShipError extends Error {
133
147
  static async fromHttpResponse(response, fallbackMessage) {
134
148
  let message;
135
149
  let details;
150
+ let bodyType;
136
151
  try {
137
152
  const contentType = response.headers.get('content-type');
138
153
  if (contentType?.includes('application/json')) {
@@ -144,6 +159,9 @@ export class ShipError extends Error {
144
159
  else if (typeof obj.error === 'string')
145
160
  message = obj.error;
146
161
  details = obj.details;
162
+ if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {
163
+ bodyType = obj.error;
164
+ }
147
165
  }
148
166
  }
149
167
  else {
@@ -156,9 +174,9 @@ export class ShipError extends Error {
156
174
  // Body unreadable; fall through to fallback.
157
175
  }
158
176
  message = message || fallbackMessage || `Request failed with status ${response.status}`;
159
- const type = response.status === 401 ? ErrorType.Authentication :
177
+ const type = bodyType ?? (response.status === 401 ? ErrorType.Authentication :
160
178
  response.status === 429 ? ErrorType.RateLimit :
161
- ErrorType.Api;
179
+ ErrorType.Api);
162
180
  return new ShipError(type, message, response.status, details);
163
181
  }
164
182
  // Factory methods for common errors
@@ -193,12 +211,6 @@ export class ShipError extends Error {
193
211
  static api(message, status = 500) {
194
212
  return new ShipError(ErrorType.Api, message, status);
195
213
  }
196
- static database(message, status = 500) {
197
- return new ShipError(ErrorType.Api, message, status);
198
- }
199
- static storage(message, status = 500) {
200
- return new ShipError(ErrorType.Api, message, status);
201
- }
202
214
  // Helper getter for accessing file path from details
203
215
  get filePath() {
204
216
  return this.details?.filePath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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',
@@ -385,6 +385,22 @@ const ERROR_CATEGORIES = {
385
385
  auth: new Set<ErrorType>([ErrorType.Authentication]),
386
386
  } as const;
387
387
 
388
+ /**
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.
397
+ */
398
+ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
399
+ Object.values(ErrorType).filter(
400
+ t => t !== ErrorType.Network && t !== ErrorType.Cancelled,
401
+ ),
402
+ );
403
+
388
404
  /**
389
405
  * Standard error response format used everywhere
390
406
  */
@@ -433,10 +449,13 @@ export class ShipError extends Error {
433
449
  *
434
450
  * Best-effort body parse for `{ message, error?, details? }`. Message
435
451
  * resolution: `body.message` → `body.error` → `fallbackMessage` →
436
- * `Request failed with status N`. Status drives the error type — same
437
- * convention used by the SDK and web console — so `error.status === 429`
438
- * always lines up with `ErrorType.RateLimit`, etc., regardless of what the
439
- * body's `error` field claims.
452
+ * `Request failed with status N`.
453
+ *
454
+ * Type resolution: trusts `body.error` when it's a known `ErrorType`
455
+ * (preserves the wire's intent server's `ShipError.validation(...)`
456
+ * round-trips back to `ErrorType.Validation` on the client). Falls back to
457
+ * status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
458
+ * non-API responses (CDN errors, intermediaries) or malformed bodies.
440
459
  *
441
460
  * Async because it reads the response body. Returns rather than throws so
442
461
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
@@ -447,6 +466,7 @@ export class ShipError extends Error {
447
466
  ): Promise<ShipError> {
448
467
  let message: string | undefined;
449
468
  let details: unknown;
469
+ let bodyType: ErrorType | undefined;
450
470
 
451
471
  try {
452
472
  const contentType = response.headers.get('content-type');
@@ -457,6 +477,9 @@ export class ShipError extends Error {
457
477
  if (typeof obj.message === 'string') message = obj.message;
458
478
  else if (typeof obj.error === 'string') message = obj.error;
459
479
  details = obj.details;
480
+ if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {
481
+ bodyType = obj.error as ErrorType;
482
+ }
460
483
  }
461
484
  } else {
462
485
  const text = await response.text();
@@ -468,10 +491,11 @@ export class ShipError extends Error {
468
491
 
469
492
  message = message || fallbackMessage || `Request failed with status ${response.status}`;
470
493
 
471
- const type =
494
+ const type = bodyType ?? (
472
495
  response.status === 401 ? ErrorType.Authentication :
473
496
  response.status === 429 ? ErrorType.RateLimit :
474
- ErrorType.Api;
497
+ ErrorType.Api
498
+ );
475
499
 
476
500
  return new ShipError(type, message, response.status, details);
477
501
  }
@@ -518,14 +542,6 @@ export class ShipError extends Error {
518
542
  return new ShipError(ErrorType.Api, message, status);
519
543
  }
520
544
 
521
- static database(message: string, status: number = 500): ShipError {
522
- return new ShipError(ErrorType.Api, message, status);
523
- }
524
-
525
- static storage(message: string, status: number = 500): ShipError {
526
- return new ShipError(ErrorType.Api, message, status);
527
- }
528
-
529
545
  // Helper getter for accessing file path from details
530
546
  get filePath(): string | undefined {
531
547
  return this.details?.filePath;