Skip to content
DomainKit
Esc
↑↓navigate↵open⌘Jpreview
On this page

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

Last updated on

Was this page helpful?