Shared TypeScript types for the ShipStatic platform.
Single source of truth for types used across API, SDK, CLI, and web applications.
# Included with the ShipStatic SDK
npm install @shipstatic/ship
# Direct installation
npm install @shipstatic/typesimporttype{ListResponse,ListOptions,Deployment,DeploymentListResponse,DeploymentDeleteResponse,DeploymentSetOptions,Domain,DomainSetResult,DomainSetOptions,DomainListResponse,DnsRecord,DnsLookup,DomainDnsResponse,DomainRecordsResponse,DomainShareResponse,DomainValidateResponse,DomainDeleteResponse,DomainVerifyResponse,Token,TokenListResponse,TokenCreateResponse,TokenCreateOptions,TokenDeleteResponse,Account,Caps,AccountDeleteResponse,AccountKeyResponse,LabelsResponse,SetupInstructionsResponse,StaticFile}from'@shipstatic/types';// Every public path, declared once — the API mounts from it, clients request against it.import{API_PATHS}from'@shipstatic/types';A mutation answers with the resource it affected — the entity when it
survives, otherwise the *DeleteResponse shape: the resource noun carrying
the canonical key, plus the resource's own state field where the resource is
mid-transition. No message, no success, no constant flags.
import{ShipError,ErrorType,isShipError}from'@shipstatic/types';throwShipError.validation('File too large');throwShipError.notFound('Deployment',id);throwShipError.forbidden('Account terminated');throwShipError.authentication();throwShipError.business('Plan limit reached');if(isShipError(error)){console.log(error.status,error.type,error.message);}if(error.isClientError()){/* Business | Config | File | Validation */}if(error.isAuthError()){/* handle auth */}if(error.type===ErrorType.Validation){/* specific-type checks */}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:
// Producer side (API workers): serialize a ShipError to JSONreturnc.json(error.toResponse(),error.status??500);// Consumer side — two symmetric helpers cover both HTTP error modes:// Both helpers take an optional operationName for context-aware fallback messages.// 1. Server returned a non-OK responseif(!response.ok){throwawaitShipError.fromHttpResponse(response,'Get account');}// 2. fetch itself threw (offline, abort, CORS, ...)try{response=awaitfetch(url);}catch(cause){throwShipError.fromFetchError(cause,'Get account');}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, 403 → Forbidden, 429 → RateLimit, else → Api). Client-only types (Network, Timeout, Cancelled, File, Config) are filtered out of the trusted set. Body's message and details are preserved best-effort.
fromFetchError routes by the thrown cause: an existing ShipError is returned unchanged, AbortError becomes Cancelled, TimeoutError becomes Timeout, a fetch TypeError becomes Network, anything else becomes Api (with no HTTP status — the request never reached the server). Timeout is a distinct type inside the network category — isNetworkError() is true for it — so a surface can retry it like any transport failure while still saying "timed out" rather than "check your connection".
Both helpers accept an optional operation-name string for contextual messages ("Get account was cancelled", "Get account failed: ...").
import{DeploymentStatus,// pending | success | failed | deletingDomainStatus,// pending | partial | success | pausedAccountPlan,// free | pro | scale | sponsored — tiers only; suspension and deletion are account factsFileValidationStatus,// pending | processing_error | excluded | validation_failed | readyAuthMethod,// session | apiKey | token | agent | oauth | webhook | system}from'@shipstatic/types';importtype{PlatformLimits,// per-request size limits from /limits (file size, file count, total size)Plan,PlansResponse,// the public plan menu from /plansBillingInterval,// 'month' | 'year' — Stripe's own recurring intervalCheckoutSession,BillingPortalSession,// the two hosted Stripe pagesActivityListResponse,PingResponse,}from'@shipstatic/types';SDK interface definitions:
importtype{DeploymentResource,DomainResource,AccountResource,TokenResource,}from'@shipstatic/types';import{validateApiKey,validateDeployToken,validateIdempotencyKey,validateApiUrl,isDeployment,isBlockedExtension,}from'@shipstatic/types';isBlockedExtension(filename, blocked) takes the blocklist rather than owning
one — the platform's list is hosting policy that the API owns and evolves, and
it reaches clients as PlatformLimits.blockedExtensions from GET /limits.
The field is optional: an API that predates it sends nothing, which means "no
client-side check", never "an empty policy".
constlimits=awaitship.getLimits();isBlockedExtension('virus.exe',limits.blockedExtensions??[]);importtype{ValidatableFile,FileValidationResult,ValidationIssue,UploadedFile,}from'@shipstatic/types';import{isPlatformDomain,isCustomDomain,extractSubdomain,generateDeploymentUrl,generateDomainUrl,}from'@shipstatic/types';import{LABEL_CONSTRAINTS,LABEL_PATTERN,serializeLabels,deserializeLabels,}from'@shipstatic/types';import{PASSWORD_CONSTRAINTS,// { MIN_LENGTH: 6, MAX_LENGTH: 128 }validatePassword,// (value: unknown) => string | undefined}from'@shipstatic/types';import{DEFAULT_API,API_KEY,// { PREFIX, HEX_LENGTH, TOTAL_LENGTH, HINT_LENGTH }DEPLOY_TOKEN,// { PREFIX, HEX_LENGTH, TOTAL_LENGTH }DEPLOYMENT_CONFIG_FILENAME,}from'@shipstatic/types';import{ShipError,isShipError,typeDeployment,DeploymentStatus}from'@shipstatic/types';functionprocessDeployment(deployment: Deployment){if(deployment.status===DeploymentStatus.FAILED){throwShipError.business('Deployment failed');}}Part of ShipStatic. This package is a building block; the ways to actually deploy something are listed at shipstatic.com.
MIT