@spacefast/domain-sdk
v0.8.0
Published
Spacefast provider for domain-sdk: attach, verify, monitor, and remove customer domains on a Space.
Readme
@spacefast/domain-sdk
The Spacefast provider for
domain-sdk. Attach, verify, monitor, and remove the
custom domains on one Space through the same typed API you use for Vercel,
Cloudflare, or Netlify — so a product that lets its customers bring a domain
does not have to learn Spacefast's domain endpoints to do it.
Install
npm install @spacefast/domain-sdk @opencoredev/domain-sdk@opencoredev/domain-sdk is a peer dependency: this package supplies the
Spacefast provider, that package supplies the client that drives it.
Quick start
import { createDomainClient } from "@opencoredev/domain-sdk";
import { spacefast } from "@spacefast/domain-sdk";
export const domains = createDomainClient({
provider: spacefast({
token: process.env.SPACEFAST_TOKEN!,
spaceId: process.env.SPACEFAST_SPACE_ID!,
}),
});
const domain = await domains.add("app.customer.com");
console.log(domain.status, domain.records);Scope the token to the Space your product serves customer sites from. apiUrl
is the only other option, and only matters when you point the adapter at a
Spacefast API that is not production.
This is a server-side package. The token can add and delete domains on your Space — keep it out of a browser bundle.
Scope
The adapter manages the custom domains attached to one Space. It does not publish versions, edit customer DNS, register domains, or move a domain between Spaces.
The Space's own *.space.fast (or legacy *.view.fast) hostname comes back from the API as a managed row.
The adapter hides it from get(), list(), and remove(), so it can never be
mistaken for a customer domain or deleted through this client.
Apex and www are one attachment
Spacefast attaches customer.com and www.customer.com together. One
attachment serves both names and returns the DNS records for both, so
get("customer.com") and get("www.customer.com") resolve to the same
Domain.id and each returns the hostname you asked for. Adding the twin of a
hostname you already attached returns the current state instead of creating a
second attachment.
Every other subdomain is its own attachment. Wildcard hostnames are not supported.
What maps to what
| domain-sdk call | Spacefast |
| ------------------ | ---------------------------------------------------------------------------- |
| add(hostname) | POST /v1/spaces/{spaceId}/domains, after a lookup that makes it idempotent |
| get(hostname) | the Space's attachments, plus that attachment's live diagnostics |
| list() | the same attachments, paged with an offset cursor |
| verify(hostname) | POST .../domains/{id}/verify, then a fresh get() |
| remove(hostname) | DELETE .../domains/{id}, and a no-op when nothing is attached |
Routing, ownership, and certificate records come from the attachment's required
records; their live status comes from the Space's domain diagnostics. Spacefast
routes a hostname on two A records and judges the zone by whether what it
observes lands in its own address range, rather than by matching one exact
value. So an A or AAAA record reads as valid once Spacefast observes its
value, or once the hostname resolves into Spacefast's range; invalid when the
zone answers with addresses that are neither; pending while nothing resolves.
A TXT record reads as valid once Spacefast confirms the challenge. Records
read as unknown when diagnostics are unavailable — never as valid.
customer.com and www.customer.com share one attachment but are two
hostnames, each with its own records and its own certificate. The adapter
answers about the hostname you asked for: get("www.customer.com") reports the
www certificate and the www issues, even when the apex is the attachment's
configured hostname.
Most domains need no ownership proof and go straight from DNS to a certificate.
Spacefast requires a TXT record when another Space already serves the
hostname; that record arrives as an ownership record and the domain reports
pending_verification until it resolves. verify() queues a provider check and
returns the refreshed domain — it does not block until the check finishes, so
poll with waitUntilActive() or call get() again.
Certificates are managed by Spacefast. A domain reaches active only once
ownership is settled and the certificate is issued.
| Reported status | What Spacefast is telling you |
| ---------------------- | ---------------------------------------------------------------- |
| unknown | Diagnostics could not be read, so nothing has been observed |
| pending | The attachment exists and this hostname has no diagnostics entry |
| pending_verification | An ownership TXT record is required and has not resolved |
| pending_dns | The records do not point at Spacefast yet |
| pending_certificate | The records resolve and the certificate is still being issued |
| active | Ownership is settled and the certificate is live |
| failed | Verification failed, or certificate issuance is broken |
A broken certificate adds one retryable CERTIFICATE_FAILED issue carrying
Spacefast's reason. A hostname resolving through a proxy adds one
DNS_PROXY_DETECTED issue: Spacefast cannot see the records behind the proxy,
so it cannot validate the domain. Verification problems Spacefast reports as
errors arrive as issues keyed by their own code.
list() is the expensive one
list() reads the Space's whole domain set, pages it with an offset cursor, and
then makes one live DNS and certificate diagnostics read per row on the page.
Those reads query public DNS and Spacefast's certificate store, so a page of 50
is 50 round trips behind one call. Keep pages small, pass a signal you can
cancel, and use get() when you want the state of one domain.
Errors
Spacefast returns RFC 9457 problem documents, and the adapter maps their stable
code rather than their prose or their status. The original code is kept in
error.details.providerCode.
| Spacefast | DomainSdkError.code |
| ----------------------------------------------- | --------------------------------- |
| 401 | AUTHENTICATION_FAILED |
| 403 | PERMISSION_DENIED |
| A spaceId the token cannot read | INVALID_CONFIGURATION |
| A hostname this Space does not hold | DOMAIN_NOT_FOUND |
| domain_in_use, domain_quota_exceeded | DOMAIN_CONFLICT |
| invalid_domain_name, domain_reserved_suffix | INVALID_HOSTNAME |
| 429 | RATE_LIMITED, with retryAfter |
| 5xx, or an origin that never answered | PROVIDER_UNAVAILABLE |
A Space Spacefast cannot read is INVALID_CONFIGURATION, not
DOMAIN_NOT_FOUND — the adapter is pointed at a Space that does not exist,
which is not the same as a customer domain that was never attached. A rejected
hostname arrives as a 422, so that mapping keys on the code, not the status.
add() is safe to retry. When a concurrent attach wins the race and Spacefast
answers 409, the adapter re-reads the Space and returns the attachment that
landed; a hostname some other Space holds still raises DOMAIN_CONFLICT.
Diagnostics are advisory. When they fail in a way worth retrying — a server
error, a rate limit, or an origin that never answered — the adapter still
returns the attachment, with status: "unknown", record statuses of unknown,
and an unknown certificate, rather than hiding a domain that exists or
reporting a verified domain as pending. Cancelling still cancels.
Not in here
- Domain Connect. The adapter hands you the records to publish; it does not drive a registrar's one-click DNS flow, and nothing here claims Domain Connect support.
- Wildcards, domain registration, managed DNS, and moving a domain between
Spaces. Those live in the Spacefast API and the
sfCLI, not in this provider.
More: custom domains and the API reference.
