domainkit/client
The browser transport over the routes you mounted, its optional capability groups, the connect methods it sends, and the Promise adapters.
import { Transport } from "domainkit/client";
| Namespace | Carries |
|---|---|
Transport |
Interface, fromFetch, fromAsync, toAsync, capabilities, allCapabilities, Method |
Capability groups
Every group on Transport.Interface is optional, so a host that mounts part of the route group gets
a transport that typechecks and a UI that renders only what the server can serve.
| Group | Methods |
|---|---|
connection |
inspect, discover, zones, start, reconnect, attach, detach, disconnect |
provisioning |
plan, approve, reject, apply, attempt, receipt |
verification |
observe, latest |
cleanup |
plan, approve, reject, apply |
connection.zones(filter?) answers with every zone this owner’s connections reach, how each
connection stands, and the provider catalog, so a picker builds itself from one call. Each
connection carries the label the provider gave its account at connect time, or null when the
provider named none.
connection.start takes an optional domain: without one it connects the account alone.
connection.reconnect({ connectionId, method }) replaces the credential behind a connection the
owner already holds, keeping its domains.
provisioning.receipt(receiptId) reads what one apply landed, for a surface holding the id rather
than the attempt.
verification.observe(domain, options?) takes an optional requirements array. Supplied
requirements win; without them the server uses the attachment’s latest provisioning receipt, and a
domain with neither answers InvalidInput. verification.latest(domain) reads the stored readiness
without observing, and answers null until the domain has been observed once.
/**
* A host that mounts only part of the group declares only those groups. `Transport.capabilities`
* reports what is there, and `@domainkit/react` renders only those parts of the flow.
*/
export const connectOnly = Transport.fromFetch("/api/domainkit", {
capabilities: ["connection"],
});
export const declared = Transport.capabilities(connectOnly); // ["connection"]Building one
/** Point it at the path the route group is mounted on. No credential ever reaches this code. */
export const transport = Transport.fromFetch("/api/domainkit");/** Headers can be static or resolved per request, for a token your app refreshes on its own. */
export const authenticated = Transport.fromFetch("https://api.acme.dev/domainkit", {
headers: async () => ({ authorization: `Bearer ${await currentAccessToken()}` }),
});headers is either a HeadersInit or a function returning one, awaited per request, so a token
your app refreshes is read at call time.
Calling it
Every method returns Effect<A, DomainKit.Error> with no requirements.
/** Every method is an `Effect` that fails with the `DomainKit.Error` the lifecycle raised. */
export const plan = Effect.gen(function* () {
const provisioning = transport.provisioning;
if (provisioning === undefined) return null;
return yield* provisioning.plan({
domain: "app.example.com",
requirements: [DnsRecord.cname({ name: "app.example.com", target: "edge.acme.dev" })],
});
});/**
* Verification takes the requirements to look for. Without them the server uses the domain's
* latest provisioning receipt, and a domain with neither answers `InvalidInput`.
*/
export const readiness = Effect.gen(function* () {
const verification = transport.verification;
if (verification === undefined) return null;
return yield* verification.observe("app.example.com", {
requirements: [DnsRecord.cname({ name: "app.example.com", target: "edge.acme.dev" })],
});
});/**
* `latest` reads the stored readiness without observing, for a surface whose host owns the clock.
* It is `null` until the domain has been observed once.
*/
export const stored = Effect.gen(function* () {
const verification = transport.verification;
if (verification === undefined) return null;
return yield* verification.latest("app.example.com");
});/** The same transport in Promises, for a component tree that does not run Effect. */
export const api = Transport.toAsync(transport);
export const inspect = async (domain: string) => api.connection?.inspect(domain);Connect methods
Transport.Method mirrors Connect.Method over the wire, so a client never writes a tag literal.
| Constructor | Sends |
|---|---|
Method.token(values) |
A lone string, or the values the method descriptor names |
Method.oauth({ returnTo }) |
An OAuth start that answers with a redirect |
Method.integration({ returnTo }) |
A marketplace install that answers with a redirect |
Failures
A failure arrives as the same DomainKit.Error the lifecycle raised, reason intact. A reply the
transport cannot read as one is classified from its status, naming the transport’s base URL as the
origin.
| Status | Becomes |
|---|---|
| 401 | Unauthenticated |
| 403 | Forbidden |
| 409 | ProviderConflict |
| 429 and 5xx | ProviderUnavailable, retryable, with retryAfterMs from retry-after |
| anything else | ProviderRejected, carrying the status as its code |