@shipstatic/types 0.9.0 → 0.9.2
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 -5
- package/dist/index.d.ts +82 -71
- package/dist/index.js +104 -41
- package/package.json +1 -1
- package/src/index.ts +133 -94
package/README.md
CHANGED
|
@@ -46,6 +46,20 @@ if (error.isClientError()) { /* Business | Config | File | Validation */ }
|
|
|
46
46
|
if (error.isAuthError()) { /* handle auth */ }
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
**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:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Producer side (API workers): serialize a ShipError to JSON
|
|
53
|
+
return c.json(error.toResponse(), error.status ?? 500);
|
|
54
|
+
|
|
55
|
+
// Consumer side (SDK, web app): rehydrate from any error Response
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw await ShipError.fromHttpResponse(response, 'Get account failed');
|
|
58
|
+
}
|
|
59
|
+
```
|
|
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.
|
|
62
|
+
|
|
49
63
|
### Status Constants
|
|
50
64
|
|
|
51
65
|
```typescript
|
|
@@ -62,7 +76,7 @@ import {
|
|
|
62
76
|
|
|
63
77
|
```typescript
|
|
64
78
|
import type {
|
|
65
|
-
|
|
79
|
+
PlatformLimits, // plan-based caps from /limits (file size, file count, total size)
|
|
66
80
|
BillingStatus,
|
|
67
81
|
CheckoutSession,
|
|
68
82
|
ActivityListResponse,
|
|
@@ -80,8 +94,6 @@ import type {
|
|
|
80
94
|
DomainResource,
|
|
81
95
|
AccountResource,
|
|
82
96
|
TokenResource,
|
|
83
|
-
BillingResource,
|
|
84
|
-
KeysResource,
|
|
85
97
|
} from '@shipstatic/types';
|
|
86
98
|
```
|
|
87
99
|
|
|
@@ -138,8 +150,8 @@ import {
|
|
|
138
150
|
```typescript
|
|
139
151
|
import {
|
|
140
152
|
DEFAULT_API,
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
API_KEY, // { PREFIX, HEX_LENGTH, TOTAL_LENGTH, HINT_LENGTH }
|
|
154
|
+
DEPLOY_TOKEN, // { PREFIX, HEX_LENGTH, TOTAL_LENGTH }
|
|
143
155
|
DEPLOYMENT_CONFIG_FILENAME,
|
|
144
156
|
} from '@shipstatic/types';
|
|
145
157
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -296,31 +296,37 @@ export interface AccountOverrides {
|
|
|
296
296
|
totalSize?: number;
|
|
297
297
|
}
|
|
298
298
|
/**
|
|
299
|
-
* All possible error types in the ShipStatic platform
|
|
300
|
-
*
|
|
299
|
+
* All possible error types in the ShipStatic platform.
|
|
300
|
+
*
|
|
301
|
+
* Developer-friendly key names map to stable wire-format string values.
|
|
302
|
+
* Both the value and the type are exported under the same name so callers
|
|
303
|
+
* can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type
|
|
304
|
+
* annotation) without ceremony — matching the pattern other status objects
|
|
305
|
+
* (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.
|
|
301
306
|
*/
|
|
302
|
-
export declare
|
|
307
|
+
export declare const ErrorType: {
|
|
303
308
|
/** Validation failed (400) */
|
|
304
|
-
Validation
|
|
309
|
+
readonly Validation: "validation_failed";
|
|
305
310
|
/** Resource not found (404) */
|
|
306
|
-
NotFound
|
|
311
|
+
readonly NotFound: "not_found";
|
|
307
312
|
/** Rate limit exceeded (429) */
|
|
308
|
-
RateLimit
|
|
313
|
+
readonly RateLimit: "rate_limit_exceeded";
|
|
309
314
|
/** Authentication required (401) */
|
|
310
|
-
Authentication
|
|
315
|
+
readonly Authentication: "authentication_failed";
|
|
311
316
|
/** Business logic error (400) */
|
|
312
|
-
Business
|
|
313
|
-
/** API server error (500)
|
|
314
|
-
Api
|
|
317
|
+
readonly Business: "business_logic_error";
|
|
318
|
+
/** API server error (500) */
|
|
319
|
+
readonly Api: "internal_server_error";
|
|
315
320
|
/** Network/connection error */
|
|
316
|
-
Network
|
|
321
|
+
readonly Network: "network_error";
|
|
317
322
|
/** Operation was cancelled */
|
|
318
|
-
Cancelled
|
|
323
|
+
readonly Cancelled: "operation_cancelled";
|
|
319
324
|
/** File operation error */
|
|
320
|
-
File
|
|
325
|
+
readonly File: "file_error";
|
|
321
326
|
/** Configuration error */
|
|
322
|
-
Config
|
|
323
|
-
}
|
|
327
|
+
readonly Config: "config_error";
|
|
328
|
+
};
|
|
329
|
+
export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
|
|
324
330
|
/**
|
|
325
331
|
* Standard error response format used everywhere
|
|
326
332
|
*/
|
|
@@ -344,8 +350,20 @@ export declare class ShipError extends Error {
|
|
|
344
350
|
constructor(type: ErrorType, message: string, status?: number | undefined, details?: any | undefined);
|
|
345
351
|
/** Convert to wire format */
|
|
346
352
|
toResponse(): ErrorResponse;
|
|
347
|
-
/**
|
|
348
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Construct a `ShipError` from an HTTP error response.
|
|
355
|
+
*
|
|
356
|
+
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
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.
|
|
362
|
+
*
|
|
363
|
+
* Async because it reads the response body. Returns rather than throws so
|
|
364
|
+
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
365
|
+
*/
|
|
366
|
+
static fromHttpResponse(response: Response, fallbackMessage?: string): Promise<ShipError>;
|
|
349
367
|
static validation(message: string, details?: any): ShipError;
|
|
350
368
|
static notFound(resource: string, id?: string): ShipError;
|
|
351
369
|
static rateLimit(message?: string): ShipError;
|
|
@@ -380,12 +398,22 @@ export declare class ShipError extends Error {
|
|
|
380
398
|
*/
|
|
381
399
|
export declare function isShipError(error: unknown): error is ShipError;
|
|
382
400
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
401
|
+
* Plan-based platform limits returned by the `/config` endpoint.
|
|
402
|
+
*
|
|
403
|
+
* The SDK fetches these once on first API call to drive client-side
|
|
404
|
+
* file-size / file-count / total-size validation that mirrors what the API
|
|
405
|
+
* would enforce server-side. Limits vary by account plan.
|
|
406
|
+
*
|
|
407
|
+
* Distinct from `ResolvedConfig` (which carries the *client's* credentials
|
|
408
|
+
* and API URL after defaulting); this one carries the *platform's* posted
|
|
409
|
+
* caps for the current account.
|
|
385
410
|
*/
|
|
386
|
-
export interface
|
|
411
|
+
export interface PlatformLimits {
|
|
412
|
+
/** Maximum size in bytes for a single file. */
|
|
387
413
|
maxFileSize: number;
|
|
414
|
+
/** Maximum number of files in a single deployment. */
|
|
388
415
|
maxFilesCount: number;
|
|
416
|
+
/** Maximum total size in bytes across all files in a deployment. */
|
|
389
417
|
maxTotalSize: number;
|
|
390
418
|
}
|
|
391
419
|
/**
|
|
@@ -457,13 +485,32 @@ export interface PingResponse {
|
|
|
457
485
|
/** Optional timestamp */
|
|
458
486
|
timestamp?: number;
|
|
459
487
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
export declare const
|
|
465
|
-
|
|
466
|
-
|
|
488
|
+
/**
|
|
489
|
+
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
490
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
491
|
+
*/
|
|
492
|
+
export declare const API_KEY: {
|
|
493
|
+
/** Prefix that identifies an API key. */
|
|
494
|
+
readonly PREFIX: "ship-";
|
|
495
|
+
/** Number of hex characters following the prefix. */
|
|
496
|
+
readonly HEX_LENGTH: 64;
|
|
497
|
+
/** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */
|
|
498
|
+
readonly TOTAL_LENGTH: 69;
|
|
499
|
+
/** Number of trailing characters used to display a redacted hint (e.g. last 4). */
|
|
500
|
+
readonly HINT_LENGTH: 4;
|
|
501
|
+
};
|
|
502
|
+
/**
|
|
503
|
+
* Shape constants for deploy tokens (`token-{64 hex chars}`).
|
|
504
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
505
|
+
*/
|
|
506
|
+
export declare const DEPLOY_TOKEN: {
|
|
507
|
+
/** Prefix that identifies a deploy token. */
|
|
508
|
+
readonly PREFIX: "token-";
|
|
509
|
+
/** Number of hex characters following the prefix. */
|
|
510
|
+
readonly HEX_LENGTH: 64;
|
|
511
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 70`). */
|
|
512
|
+
readonly TOTAL_LENGTH: 70;
|
|
513
|
+
};
|
|
467
514
|
export declare const AuthMethod: {
|
|
468
515
|
readonly JWT: "jwt";
|
|
469
516
|
readonly API_KEY: "apiKey";
|
|
@@ -584,26 +631,16 @@ export interface ProgressInfo {
|
|
|
584
631
|
/** Default API URL if not otherwise configured. */
|
|
585
632
|
export declare const DEFAULT_API = "https://api.shipstatic.com";
|
|
586
633
|
/**
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
|
|
591
|
-
export type BrowserDeployInput = File[];
|
|
592
|
-
/**
|
|
593
|
-
* Node-specific deploy input — file or directory path(s) on disk. A single
|
|
594
|
-
* path or an array of paths; directories are walked recursively. The Node
|
|
595
|
-
* SDK rejects any other shape at runtime.
|
|
596
|
-
*/
|
|
597
|
-
export type NodeDeployInput = string | string[];
|
|
598
|
-
/**
|
|
599
|
-
* Universal deploy input — the union of every platform's accepted shape.
|
|
634
|
+
* Universal deploy input — the union of every shape the SDK accepts.
|
|
635
|
+
*
|
|
636
|
+
* - **Browser**: `File[]` (typically from `<input type="file">` or drag-and-drop)
|
|
637
|
+
* - **Node**: `string | string[]` (file or directory path(s) on disk; directories are walked)
|
|
600
638
|
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
* platform's SDK validates at runtime and throws on the wrong shape.
|
|
639
|
+
* Each platform's SDK narrows its `deploy()` signature to the relevant shape
|
|
640
|
+
* and rejects anything else at runtime. Use the structural types directly
|
|
641
|
+
* (`File[]`, `string | string[]`) when writing platform-specific code.
|
|
605
642
|
*/
|
|
606
|
-
export type DeployInput =
|
|
643
|
+
export type DeployInput = File[] | string | string[];
|
|
607
644
|
/**
|
|
608
645
|
* Options for deployment creation at the API contract level.
|
|
609
646
|
* SDK implementations may extend with additional options (timeout, signal, callbacks, etc.).
|
|
@@ -706,32 +743,6 @@ export interface CheckoutSession {
|
|
|
706
743
|
/** URL to redirect user to Creem checkout page */
|
|
707
744
|
url: string;
|
|
708
745
|
}
|
|
709
|
-
/**
|
|
710
|
-
* Billing resource interface - the contract all implementations must follow
|
|
711
|
-
*
|
|
712
|
-
* IMPOSSIBLE SIMPLICITY: No sync() method needed!
|
|
713
|
-
* Webhooks are the single source of truth. Frontend just polls status().
|
|
714
|
-
*/
|
|
715
|
-
export interface BillingResource {
|
|
716
|
-
/**
|
|
717
|
-
* Create a checkout session
|
|
718
|
-
* @returns Checkout session with URL to redirect user
|
|
719
|
-
*/
|
|
720
|
-
checkout: () => Promise<CheckoutSession>;
|
|
721
|
-
/**
|
|
722
|
-
* Get current billing status
|
|
723
|
-
* @returns Billing status and usage information
|
|
724
|
-
*/
|
|
725
|
-
status: () => Promise<BillingStatus>;
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Keys resource interface - the contract all implementations must follow
|
|
729
|
-
*/
|
|
730
|
-
export interface KeysResource {
|
|
731
|
-
create: () => Promise<{
|
|
732
|
-
apiKey: string;
|
|
733
|
-
}>;
|
|
734
|
-
}
|
|
735
746
|
/**
|
|
736
747
|
* All activity event types logged in the system.
|
|
737
748
|
* Uses dot notation consistently: {resource}.{action}
|
package/dist/index.js
CHANGED
|
@@ -50,34 +50,40 @@ export const AccountPlan = {
|
|
|
50
50
|
// ERROR SYSTEM
|
|
51
51
|
// =============================================================================
|
|
52
52
|
/**
|
|
53
|
-
* All possible error types in the ShipStatic platform
|
|
54
|
-
*
|
|
53
|
+
* All possible error types in the ShipStatic platform.
|
|
54
|
+
*
|
|
55
|
+
* Developer-friendly key names map to stable wire-format string values.
|
|
56
|
+
* Both the value and the type are exported under the same name so callers
|
|
57
|
+
* can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type
|
|
58
|
+
* annotation) without ceremony — matching the pattern other status objects
|
|
59
|
+
* (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.
|
|
55
60
|
*/
|
|
56
|
-
export
|
|
57
|
-
(function (ErrorType) {
|
|
61
|
+
export const ErrorType = {
|
|
58
62
|
/** Validation failed (400) */
|
|
59
|
-
|
|
63
|
+
Validation: 'validation_failed',
|
|
60
64
|
/** Resource not found (404) */
|
|
61
|
-
|
|
65
|
+
NotFound: 'not_found',
|
|
62
66
|
/** Rate limit exceeded (429) */
|
|
63
|
-
|
|
67
|
+
RateLimit: 'rate_limit_exceeded',
|
|
64
68
|
/** Authentication required (401) */
|
|
65
|
-
|
|
69
|
+
Authentication: 'authentication_failed',
|
|
66
70
|
/** Business logic error (400) */
|
|
67
|
-
|
|
68
|
-
/** API server error (500)
|
|
69
|
-
|
|
71
|
+
Business: 'business_logic_error',
|
|
72
|
+
/** API server error (500) */
|
|
73
|
+
Api: 'internal_server_error',
|
|
70
74
|
/** Network/connection error */
|
|
71
|
-
|
|
75
|
+
Network: 'network_error',
|
|
72
76
|
/** Operation was cancelled */
|
|
73
|
-
|
|
77
|
+
Cancelled: 'operation_cancelled',
|
|
74
78
|
/** File operation error */
|
|
75
|
-
|
|
79
|
+
File: 'file_error',
|
|
76
80
|
/** Configuration error */
|
|
77
|
-
|
|
78
|
-
}
|
|
81
|
+
Config: 'config_error',
|
|
82
|
+
};
|
|
79
83
|
/**
|
|
80
|
-
* Categorizes error types for
|
|
84
|
+
* Categorizes error types for the `isClientError` / `isNetworkError` /
|
|
85
|
+
* `isAuthError` helpers. Each `Set` is typed against the wider `ErrorType`
|
|
86
|
+
* union so `.has(error.type)` accepts any value from the union.
|
|
81
87
|
*/
|
|
82
88
|
const ERROR_CATEGORIES = {
|
|
83
89
|
client: new Set([ErrorType.Business, ErrorType.Config, ErrorType.File, ErrorType.Validation]),
|
|
@@ -111,9 +117,49 @@ export class ShipError extends Error {
|
|
|
111
117
|
details
|
|
112
118
|
};
|
|
113
119
|
}
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Construct a `ShipError` from an HTTP error response.
|
|
122
|
+
*
|
|
123
|
+
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
124
|
+
* 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.
|
|
129
|
+
*
|
|
130
|
+
* Async because it reads the response body. Returns rather than throws so
|
|
131
|
+
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
132
|
+
*/
|
|
133
|
+
static async fromHttpResponse(response, fallbackMessage) {
|
|
134
|
+
let message;
|
|
135
|
+
let details;
|
|
136
|
+
try {
|
|
137
|
+
const contentType = response.headers.get('content-type');
|
|
138
|
+
if (contentType?.includes('application/json')) {
|
|
139
|
+
const json = await response.json();
|
|
140
|
+
if (json && typeof json === 'object') {
|
|
141
|
+
const obj = json;
|
|
142
|
+
if (typeof obj.message === 'string')
|
|
143
|
+
message = obj.message;
|
|
144
|
+
else if (typeof obj.error === 'string')
|
|
145
|
+
message = obj.error;
|
|
146
|
+
details = obj.details;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const text = await response.text();
|
|
151
|
+
if (text)
|
|
152
|
+
message = text;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Body unreadable; fall through to fallback.
|
|
157
|
+
}
|
|
158
|
+
message = message || fallbackMessage || `Request failed with status ${response.status}`;
|
|
159
|
+
const type = response.status === 401 ? ErrorType.Authentication :
|
|
160
|
+
response.status === 429 ? ErrorType.RateLimit :
|
|
161
|
+
ErrorType.Api;
|
|
162
|
+
return new ShipError(type, message, response.status, details);
|
|
117
163
|
}
|
|
118
164
|
// Factory methods for common errors
|
|
119
165
|
static validation(message, details) {
|
|
@@ -299,15 +345,32 @@ export function hasUnbuiltMarker(filePath) {
|
|
|
299
345
|
const segments = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
300
346
|
return segments.some(s => UNBUILT_PROJECT_MARKERS.has(s));
|
|
301
347
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
export const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
350
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
351
|
+
*/
|
|
352
|
+
export const API_KEY = {
|
|
353
|
+
/** Prefix that identifies an API key. */
|
|
354
|
+
PREFIX: 'ship-',
|
|
355
|
+
/** Number of hex characters following the prefix. */
|
|
356
|
+
HEX_LENGTH: 64,
|
|
357
|
+
/** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */
|
|
358
|
+
TOTAL_LENGTH: 69,
|
|
359
|
+
/** Number of trailing characters used to display a redacted hint (e.g. last 4). */
|
|
360
|
+
HINT_LENGTH: 4,
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Shape constants for deploy tokens (`token-{64 hex chars}`).
|
|
364
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
365
|
+
*/
|
|
366
|
+
export const DEPLOY_TOKEN = {
|
|
367
|
+
/** Prefix that identifies a deploy token. */
|
|
368
|
+
PREFIX: 'token-',
|
|
369
|
+
/** Number of hex characters following the prefix. */
|
|
370
|
+
HEX_LENGTH: 64,
|
|
371
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 70`). */
|
|
372
|
+
TOTAL_LENGTH: 70,
|
|
373
|
+
};
|
|
311
374
|
// Authentication Method Constants
|
|
312
375
|
export const AuthMethod = {
|
|
313
376
|
JWT: 'jwt',
|
|
@@ -327,30 +390,30 @@ export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '
|
|
|
327
390
|
* Validate API key format
|
|
328
391
|
*/
|
|
329
392
|
export function validateApiKey(apiKey) {
|
|
330
|
-
if (!apiKey.startsWith(
|
|
331
|
-
throw ShipError.validation(`API key must start with "${
|
|
393
|
+
if (!apiKey.startsWith(API_KEY.PREFIX)) {
|
|
394
|
+
throw ShipError.validation(`API key must start with "${API_KEY.PREFIX}"`);
|
|
332
395
|
}
|
|
333
|
-
if (apiKey.length !==
|
|
334
|
-
throw ShipError.validation(`API key must be ${
|
|
396
|
+
if (apiKey.length !== API_KEY.TOTAL_LENGTH) {
|
|
397
|
+
throw ShipError.validation(`API key must be ${API_KEY.TOTAL_LENGTH} characters total (${API_KEY.PREFIX} + ${API_KEY.HEX_LENGTH} hex chars)`);
|
|
335
398
|
}
|
|
336
|
-
const hexPart = apiKey.slice(
|
|
399
|
+
const hexPart = apiKey.slice(API_KEY.PREFIX.length);
|
|
337
400
|
if (!/^[a-f0-9]{64}$/i.test(hexPart)) {
|
|
338
|
-
throw ShipError.validation(`API key must contain ${
|
|
401
|
+
throw ShipError.validation(`API key must contain ${API_KEY.HEX_LENGTH} hexadecimal characters after "${API_KEY.PREFIX}" prefix`);
|
|
339
402
|
}
|
|
340
403
|
}
|
|
341
404
|
/**
|
|
342
405
|
* Validate deploy token format
|
|
343
406
|
*/
|
|
344
407
|
export function validateDeployToken(deployToken) {
|
|
345
|
-
if (!deployToken.startsWith(
|
|
346
|
-
throw ShipError.validation(`Deploy token must start with "${
|
|
408
|
+
if (!deployToken.startsWith(DEPLOY_TOKEN.PREFIX)) {
|
|
409
|
+
throw ShipError.validation(`Deploy token must start with "${DEPLOY_TOKEN.PREFIX}"`);
|
|
347
410
|
}
|
|
348
|
-
if (deployToken.length !==
|
|
349
|
-
throw ShipError.validation(`Deploy token must be ${
|
|
411
|
+
if (deployToken.length !== DEPLOY_TOKEN.TOTAL_LENGTH) {
|
|
412
|
+
throw ShipError.validation(`Deploy token must be ${DEPLOY_TOKEN.TOTAL_LENGTH} characters total (${DEPLOY_TOKEN.PREFIX} + ${DEPLOY_TOKEN.HEX_LENGTH} hex chars)`);
|
|
350
413
|
}
|
|
351
|
-
const hexPart = deployToken.slice(
|
|
414
|
+
const hexPart = deployToken.slice(DEPLOY_TOKEN.PREFIX.length);
|
|
352
415
|
if (!/^[a-f0-9]{64}$/i.test(hexPart)) {
|
|
353
|
-
throw ShipError.validation(`Deploy token must contain ${
|
|
416
|
+
throw ShipError.validation(`Deploy token must contain ${DEPLOY_TOKEN.HEX_LENGTH} hexadecimal characters after "${DEPLOY_TOKEN.PREFIX}" prefix`);
|
|
354
417
|
}
|
|
355
418
|
}
|
|
356
419
|
/**
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -341,39 +341,48 @@ export interface AccountOverrides {
|
|
|
341
341
|
// =============================================================================
|
|
342
342
|
|
|
343
343
|
/**
|
|
344
|
-
* All possible error types in the ShipStatic platform
|
|
345
|
-
*
|
|
344
|
+
* All possible error types in the ShipStatic platform.
|
|
345
|
+
*
|
|
346
|
+
* Developer-friendly key names map to stable wire-format string values.
|
|
347
|
+
* Both the value and the type are exported under the same name so callers
|
|
348
|
+
* can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type
|
|
349
|
+
* annotation) without ceremony — matching the pattern other status objects
|
|
350
|
+
* (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.
|
|
346
351
|
*/
|
|
347
|
-
export
|
|
352
|
+
export const ErrorType = {
|
|
348
353
|
/** Validation failed (400) */
|
|
349
|
-
Validation
|
|
354
|
+
Validation: 'validation_failed',
|
|
350
355
|
/** Resource not found (404) */
|
|
351
|
-
NotFound
|
|
356
|
+
NotFound: 'not_found',
|
|
352
357
|
/** Rate limit exceeded (429) */
|
|
353
|
-
RateLimit
|
|
358
|
+
RateLimit: 'rate_limit_exceeded',
|
|
354
359
|
/** Authentication required (401) */
|
|
355
|
-
Authentication
|
|
360
|
+
Authentication: 'authentication_failed',
|
|
356
361
|
/** Business logic error (400) */
|
|
357
|
-
Business
|
|
358
|
-
/** API server error (500)
|
|
359
|
-
Api
|
|
362
|
+
Business: 'business_logic_error',
|
|
363
|
+
/** API server error (500) */
|
|
364
|
+
Api: 'internal_server_error',
|
|
360
365
|
/** Network/connection error */
|
|
361
|
-
Network
|
|
366
|
+
Network: 'network_error',
|
|
362
367
|
/** Operation was cancelled */
|
|
363
|
-
Cancelled
|
|
368
|
+
Cancelled: 'operation_cancelled',
|
|
364
369
|
/** File operation error */
|
|
365
|
-
File
|
|
370
|
+
File: 'file_error',
|
|
366
371
|
/** Configuration error */
|
|
367
|
-
Config
|
|
368
|
-
}
|
|
372
|
+
Config: 'config_error',
|
|
373
|
+
} as const;
|
|
374
|
+
|
|
375
|
+
export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
|
|
369
376
|
|
|
370
377
|
/**
|
|
371
|
-
* Categorizes error types for
|
|
378
|
+
* Categorizes error types for the `isClientError` / `isNetworkError` /
|
|
379
|
+
* `isAuthError` helpers. Each `Set` is typed against the wider `ErrorType`
|
|
380
|
+
* union so `.has(error.type)` accepts any value from the union.
|
|
372
381
|
*/
|
|
373
382
|
const ERROR_CATEGORIES = {
|
|
374
|
-
client: new Set([ErrorType.Business, ErrorType.Config, ErrorType.File, ErrorType.Validation]),
|
|
375
|
-
network: new Set([ErrorType.Network]),
|
|
376
|
-
auth: new Set([ErrorType.Authentication]),
|
|
383
|
+
client: new Set<ErrorType>([ErrorType.Business, ErrorType.Config, ErrorType.File, ErrorType.Validation]),
|
|
384
|
+
network: new Set<ErrorType>([ErrorType.Network]),
|
|
385
|
+
auth: new Set<ErrorType>([ErrorType.Authentication]),
|
|
377
386
|
} as const;
|
|
378
387
|
|
|
379
388
|
/**
|
|
@@ -419,9 +428,52 @@ export class ShipError extends Error {
|
|
|
419
428
|
};
|
|
420
429
|
}
|
|
421
430
|
|
|
422
|
-
/**
|
|
423
|
-
|
|
424
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Construct a `ShipError` from an HTTP error response.
|
|
433
|
+
*
|
|
434
|
+
* Best-effort body parse for `{ message, error?, details? }`. Message
|
|
435
|
+
* 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.
|
|
440
|
+
*
|
|
441
|
+
* Async because it reads the response body. Returns rather than throws so
|
|
442
|
+
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
443
|
+
*/
|
|
444
|
+
static async fromHttpResponse(
|
|
445
|
+
response: Response,
|
|
446
|
+
fallbackMessage?: string,
|
|
447
|
+
): Promise<ShipError> {
|
|
448
|
+
let message: string | undefined;
|
|
449
|
+
let details: unknown;
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const contentType = response.headers.get('content-type');
|
|
453
|
+
if (contentType?.includes('application/json')) {
|
|
454
|
+
const json: unknown = await response.json();
|
|
455
|
+
if (json && typeof json === 'object') {
|
|
456
|
+
const obj = json as Record<string, unknown>;
|
|
457
|
+
if (typeof obj.message === 'string') message = obj.message;
|
|
458
|
+
else if (typeof obj.error === 'string') message = obj.error;
|
|
459
|
+
details = obj.details;
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
const text = await response.text();
|
|
463
|
+
if (text) message = text;
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
// Body unreadable; fall through to fallback.
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
message = message || fallbackMessage || `Request failed with status ${response.status}`;
|
|
470
|
+
|
|
471
|
+
const type =
|
|
472
|
+
response.status === 401 ? ErrorType.Authentication :
|
|
473
|
+
response.status === 429 ? ErrorType.RateLimit :
|
|
474
|
+
ErrorType.Api;
|
|
475
|
+
|
|
476
|
+
return new ShipError(type, message, response.status, details);
|
|
425
477
|
}
|
|
426
478
|
|
|
427
479
|
// Factory methods for common errors
|
|
@@ -536,12 +588,22 @@ export function isShipError(error: unknown): error is ShipError {
|
|
|
536
588
|
// =============================================================================
|
|
537
589
|
|
|
538
590
|
/**
|
|
539
|
-
*
|
|
540
|
-
*
|
|
591
|
+
* Plan-based platform limits returned by the `/config` endpoint.
|
|
592
|
+
*
|
|
593
|
+
* The SDK fetches these once on first API call to drive client-side
|
|
594
|
+
* file-size / file-count / total-size validation that mirrors what the API
|
|
595
|
+
* would enforce server-side. Limits vary by account plan.
|
|
596
|
+
*
|
|
597
|
+
* Distinct from `ResolvedConfig` (which carries the *client's* credentials
|
|
598
|
+
* and API URL after defaulting); this one carries the *platform's* posted
|
|
599
|
+
* caps for the current account.
|
|
541
600
|
*/
|
|
542
|
-
export interface
|
|
601
|
+
export interface PlatformLimits {
|
|
602
|
+
/** Maximum size in bytes for a single file. */
|
|
543
603
|
maxFileSize: number;
|
|
604
|
+
/** Maximum number of files in a single deployment. */
|
|
544
605
|
maxFilesCount: number;
|
|
606
|
+
/** Maximum total size in bytes across all files in a deployment. */
|
|
545
607
|
maxTotalSize: number;
|
|
546
608
|
}
|
|
547
609
|
|
|
@@ -667,16 +729,33 @@ export interface PingResponse {
|
|
|
667
729
|
timestamp?: number;
|
|
668
730
|
}
|
|
669
731
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
export const
|
|
732
|
+
/**
|
|
733
|
+
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
734
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
735
|
+
*/
|
|
736
|
+
export const API_KEY = {
|
|
737
|
+
/** Prefix that identifies an API key. */
|
|
738
|
+
PREFIX: 'ship-',
|
|
739
|
+
/** Number of hex characters following the prefix. */
|
|
740
|
+
HEX_LENGTH: 64,
|
|
741
|
+
/** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */
|
|
742
|
+
TOTAL_LENGTH: 69,
|
|
743
|
+
/** Number of trailing characters used to display a redacted hint (e.g. last 4). */
|
|
744
|
+
HINT_LENGTH: 4,
|
|
745
|
+
} as const;
|
|
675
746
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
747
|
+
/**
|
|
748
|
+
* Shape constants for deploy tokens (`token-{64 hex chars}`).
|
|
749
|
+
* Single source of truth used by validation utilities and auth middleware.
|
|
750
|
+
*/
|
|
751
|
+
export const DEPLOY_TOKEN = {
|
|
752
|
+
/** Prefix that identifies a deploy token. */
|
|
753
|
+
PREFIX: 'token-',
|
|
754
|
+
/** Number of hex characters following the prefix. */
|
|
755
|
+
HEX_LENGTH: 64,
|
|
756
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 70`). */
|
|
757
|
+
TOTAL_LENGTH: 70,
|
|
758
|
+
} as const;
|
|
680
759
|
|
|
681
760
|
// Authentication Method Constants
|
|
682
761
|
export const AuthMethod = {
|
|
@@ -703,17 +782,17 @@ export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '
|
|
|
703
782
|
* Validate API key format
|
|
704
783
|
*/
|
|
705
784
|
export function validateApiKey(apiKey: string): void {
|
|
706
|
-
if (!apiKey.startsWith(
|
|
707
|
-
throw ShipError.validation(`API key must start with "${
|
|
785
|
+
if (!apiKey.startsWith(API_KEY.PREFIX)) {
|
|
786
|
+
throw ShipError.validation(`API key must start with "${API_KEY.PREFIX}"`);
|
|
708
787
|
}
|
|
709
788
|
|
|
710
|
-
if (apiKey.length !==
|
|
711
|
-
throw ShipError.validation(`API key must be ${
|
|
789
|
+
if (apiKey.length !== API_KEY.TOTAL_LENGTH) {
|
|
790
|
+
throw ShipError.validation(`API key must be ${API_KEY.TOTAL_LENGTH} characters total (${API_KEY.PREFIX} + ${API_KEY.HEX_LENGTH} hex chars)`);
|
|
712
791
|
}
|
|
713
792
|
|
|
714
|
-
const hexPart = apiKey.slice(
|
|
793
|
+
const hexPart = apiKey.slice(API_KEY.PREFIX.length);
|
|
715
794
|
if (!/^[a-f0-9]{64}$/i.test(hexPart)) {
|
|
716
|
-
throw ShipError.validation(`API key must contain ${
|
|
795
|
+
throw ShipError.validation(`API key must contain ${API_KEY.HEX_LENGTH} hexadecimal characters after "${API_KEY.PREFIX}" prefix`);
|
|
717
796
|
}
|
|
718
797
|
}
|
|
719
798
|
|
|
@@ -721,17 +800,17 @@ export function validateApiKey(apiKey: string): void {
|
|
|
721
800
|
* Validate deploy token format
|
|
722
801
|
*/
|
|
723
802
|
export function validateDeployToken(deployToken: string): void {
|
|
724
|
-
if (!deployToken.startsWith(
|
|
725
|
-
throw ShipError.validation(`Deploy token must start with "${
|
|
803
|
+
if (!deployToken.startsWith(DEPLOY_TOKEN.PREFIX)) {
|
|
804
|
+
throw ShipError.validation(`Deploy token must start with "${DEPLOY_TOKEN.PREFIX}"`);
|
|
726
805
|
}
|
|
727
806
|
|
|
728
|
-
if (deployToken.length !==
|
|
729
|
-
throw ShipError.validation(`Deploy token must be ${
|
|
807
|
+
if (deployToken.length !== DEPLOY_TOKEN.TOTAL_LENGTH) {
|
|
808
|
+
throw ShipError.validation(`Deploy token must be ${DEPLOY_TOKEN.TOTAL_LENGTH} characters total (${DEPLOY_TOKEN.PREFIX} + ${DEPLOY_TOKEN.HEX_LENGTH} hex chars)`);
|
|
730
809
|
}
|
|
731
810
|
|
|
732
|
-
const hexPart = deployToken.slice(
|
|
811
|
+
const hexPart = deployToken.slice(DEPLOY_TOKEN.PREFIX.length);
|
|
733
812
|
if (!/^[a-f0-9]{64}$/i.test(hexPart)) {
|
|
734
|
-
throw ShipError.validation(`Deploy token must contain ${
|
|
813
|
+
throw ShipError.validation(`Deploy token must contain ${DEPLOY_TOKEN.HEX_LENGTH} hexadecimal characters after "${DEPLOY_TOKEN.PREFIX}" prefix`);
|
|
735
814
|
}
|
|
736
815
|
}
|
|
737
816
|
|
|
@@ -886,28 +965,16 @@ export const DEFAULT_API = 'https://api.shipstatic.com';
|
|
|
886
965
|
// =============================================================================
|
|
887
966
|
|
|
888
967
|
/**
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
|
|
893
|
-
export type BrowserDeployInput = File[];
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Node-specific deploy input — file or directory path(s) on disk. A single
|
|
897
|
-
* path or an array of paths; directories are walked recursively. The Node
|
|
898
|
-
* SDK rejects any other shape at runtime.
|
|
899
|
-
*/
|
|
900
|
-
export type NodeDeployInput = string | string[];
|
|
901
|
-
|
|
902
|
-
/**
|
|
903
|
-
* Universal deploy input — the union of every platform's accepted shape.
|
|
968
|
+
* Universal deploy input — the union of every shape the SDK accepts.
|
|
969
|
+
*
|
|
970
|
+
* - **Browser**: `File[]` (typically from `<input type="file">` or drag-and-drop)
|
|
971
|
+
* - **Node**: `string | string[]` (file or directory path(s) on disk; directories are walked)
|
|
904
972
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* platform's SDK validates at runtime and throws on the wrong shape.
|
|
973
|
+
* Each platform's SDK narrows its `deploy()` signature to the relevant shape
|
|
974
|
+
* and rejects anything else at runtime. Use the structural types directly
|
|
975
|
+
* (`File[]`, `string | string[]`) when writing platform-specific code.
|
|
909
976
|
*/
|
|
910
|
-
export type DeployInput =
|
|
977
|
+
export type DeployInput = File[] | string | string[];
|
|
911
978
|
|
|
912
979
|
/**
|
|
913
980
|
* Options for deployment creation at the API contract level.
|
|
@@ -1010,34 +1077,6 @@ export interface CheckoutSession {
|
|
|
1010
1077
|
url: string;
|
|
1011
1078
|
}
|
|
1012
1079
|
|
|
1013
|
-
/**
|
|
1014
|
-
* Billing resource interface - the contract all implementations must follow
|
|
1015
|
-
*
|
|
1016
|
-
* IMPOSSIBLE SIMPLICITY: No sync() method needed!
|
|
1017
|
-
* Webhooks are the single source of truth. Frontend just polls status().
|
|
1018
|
-
*/
|
|
1019
|
-
export interface BillingResource {
|
|
1020
|
-
/**
|
|
1021
|
-
* Create a checkout session
|
|
1022
|
-
* @returns Checkout session with URL to redirect user
|
|
1023
|
-
*/
|
|
1024
|
-
checkout: () => Promise<CheckoutSession>;
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Get current billing status
|
|
1028
|
-
* @returns Billing status and usage information
|
|
1029
|
-
*/
|
|
1030
|
-
status: () => Promise<BillingStatus>;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
/**
|
|
1035
|
-
* Keys resource interface - the contract all implementations must follow
|
|
1036
|
-
*/
|
|
1037
|
-
export interface KeysResource {
|
|
1038
|
-
create: () => Promise<{ apiKey: string }>;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
1080
|
// =============================================================================
|
|
1042
1081
|
// ACTIVITY TYPES
|
|
1043
1082
|
// =============================================================================
|