@shipstatic/types 0.9.2 → 0.9.3

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
@@ -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
@@ -90,6 +90,13 @@ const ERROR_CATEGORIES = {
90
90
  network: new Set([ErrorType.Network]),
91
91
  auth: new Set([ErrorType.Authentication]),
92
92
  };
93
+ /**
94
+ * Lookup set of known wire-format error type strings. Used by
95
+ * `ShipError.fromHttpResponse` to validate the body's `error` field before
96
+ * trusting it as an `ErrorType`. Defensive against malformed/unknown values
97
+ * that could otherwise leak into the typed `ShipError.type` field.
98
+ */
99
+ const KNOWN_ERROR_TYPES = new Set(Object.values(ErrorType));
93
100
  /**
94
101
  * Simple unified error class for both API and SDK
95
102
  */
@@ -122,10 +129,13 @@ export class ShipError extends Error {
122
129
  *
123
130
  * Best-effort body parse for `{ message, error?, details? }`. Message
124
131
  * 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.
132
+ * `Request failed with status N`.
133
+ *
134
+ * Type resolution: trusts `body.error` when it's a known `ErrorType`
135
+ * (preserves the wire's intent server's `ShipError.validation(...)`
136
+ * round-trips back to `ErrorType.Validation` on the client). Falls back to
137
+ * status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
138
+ * non-API responses (CDN errors, intermediaries) or malformed bodies.
129
139
  *
130
140
  * Async because it reads the response body. Returns rather than throws so
131
141
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
@@ -133,6 +143,7 @@ export class ShipError extends Error {
133
143
  static async fromHttpResponse(response, fallbackMessage) {
134
144
  let message;
135
145
  let details;
146
+ let bodyType;
136
147
  try {
137
148
  const contentType = response.headers.get('content-type');
138
149
  if (contentType?.includes('application/json')) {
@@ -144,6 +155,9 @@ export class ShipError extends Error {
144
155
  else if (typeof obj.error === 'string')
145
156
  message = obj.error;
146
157
  details = obj.details;
158
+ if (typeof obj.error === 'string' && KNOWN_ERROR_TYPES.has(obj.error)) {
159
+ bodyType = obj.error;
160
+ }
147
161
  }
148
162
  }
149
163
  else {
@@ -156,9 +170,9 @@ export class ShipError extends Error {
156
170
  // Body unreadable; fall through to fallback.
157
171
  }
158
172
  message = message || fallbackMessage || `Request failed with status ${response.status}`;
159
- const type = response.status === 401 ? ErrorType.Authentication :
173
+ const type = bodyType ?? (response.status === 401 ? ErrorType.Authentication :
160
174
  response.status === 429 ? ErrorType.RateLimit :
161
- ErrorType.Api;
175
+ ErrorType.Api);
162
176
  return new ShipError(type, message, response.status, details);
163
177
  }
164
178
  // Factory methods for common errors
@@ -193,12 +207,6 @@ export class ShipError extends Error {
193
207
  static api(message, status = 500) {
194
208
  return new ShipError(ErrorType.Api, message, status);
195
209
  }
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
210
  // Helper getter for accessing file path from details
203
211
  get filePath() {
204
212
  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.3",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -385,6 +385,14 @@ const ERROR_CATEGORIES = {
385
385
  auth: new Set<ErrorType>([ErrorType.Authentication]),
386
386
  } as const;
387
387
 
388
+ /**
389
+ * Lookup set of known wire-format error type strings. Used by
390
+ * `ShipError.fromHttpResponse` to validate the body's `error` field before
391
+ * trusting it as an `ErrorType`. Defensive against malformed/unknown values
392
+ * that could otherwise leak into the typed `ShipError.type` field.
393
+ */
394
+ const KNOWN_ERROR_TYPES = new Set<string>(Object.values(ErrorType));
395
+
388
396
  /**
389
397
  * Standard error response format used everywhere
390
398
  */
@@ -433,10 +441,13 @@ export class ShipError extends Error {
433
441
  *
434
442
  * Best-effort body parse for `{ message, error?, details? }`. Message
435
443
  * 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.
444
+ * `Request failed with status N`.
445
+ *
446
+ * Type resolution: trusts `body.error` when it's a known `ErrorType`
447
+ * (preserves the wire's intent server's `ShipError.validation(...)`
448
+ * round-trips back to `ErrorType.Validation` on the client). Falls back to
449
+ * status-derived (401 → Authentication, 429 → RateLimit, else → Api) for
450
+ * non-API responses (CDN errors, intermediaries) or malformed bodies.
440
451
  *
441
452
  * Async because it reads the response body. Returns rather than throws so
442
453
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
@@ -447,6 +458,7 @@ export class ShipError extends Error {
447
458
  ): Promise<ShipError> {
448
459
  let message: string | undefined;
449
460
  let details: unknown;
461
+ let bodyType: ErrorType | undefined;
450
462
 
451
463
  try {
452
464
  const contentType = response.headers.get('content-type');
@@ -457,6 +469,9 @@ export class ShipError extends Error {
457
469
  if (typeof obj.message === 'string') message = obj.message;
458
470
  else if (typeof obj.error === 'string') message = obj.error;
459
471
  details = obj.details;
472
+ if (typeof obj.error === 'string' && KNOWN_ERROR_TYPES.has(obj.error)) {
473
+ bodyType = obj.error as ErrorType;
474
+ }
460
475
  }
461
476
  } else {
462
477
  const text = await response.text();
@@ -468,10 +483,11 @@ export class ShipError extends Error {
468
483
 
469
484
  message = message || fallbackMessage || `Request failed with status ${response.status}`;
470
485
 
471
- const type =
486
+ const type = bodyType ?? (
472
487
  response.status === 401 ? ErrorType.Authentication :
473
488
  response.status === 429 ? ErrorType.RateLimit :
474
- ErrorType.Api;
489
+ ErrorType.Api
490
+ );
475
491
 
476
492
  return new ShipError(type, message, response.status, details);
477
493
  }
@@ -518,14 +534,6 @@ export class ShipError extends Error {
518
534
  return new ShipError(ErrorType.Api, message, status);
519
535
  }
520
536
 
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
537
  // Helper getter for accessing file path from details
530
538
  get filePath(): string | undefined {
531
539
  return this.details?.filePath;