@domainkit/react 0.8.0 → 0.9.0

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 CHANGED
@@ -1,72 +1,150 @@
1
1
  # @domainkit/react
2
2
 
3
- React flows and UI parts for DomainKit.
4
-
5
- The package can render the full domain flow, one focused flow, or DNS records. Every stateful
6
- operation goes through your authenticated server transport; provider credentials never belong in the
7
- browser.
3
+ React flows for custom-domain setup, over a transport your server owns. Provider credentials never
4
+ reach the browser.
8
5
 
9
6
  ## Install
10
7
 
11
8
  ```sh
12
- npm install @domainkit/react domainkit effect@rc @effect/atom-react@rc react react-dom
9
+ npm install @domainkit/react domainkit effect@rc react react-dom
13
10
  ```
14
11
 
15
12
  React 19 is required. Install `domainkit` and `@domainkit/react` at the same release version.
16
13
 
17
- ## Complete flow
14
+ ## Two components
18
15
 
19
16
  ```tsx
17
+ import { DnsRecord } from "domainkit";
18
+ import { Transport } from "domainkit/client";
20
19
  import { Domain, DomainKit } from "@domainkit/react";
21
20
  import "@domainkit/react/styles.css";
22
21
 
23
- export function DomainSetup() {
22
+ const transport = Transport.fromFetch("/api/domainkit");
23
+
24
+ const requirements = [
25
+ DnsRecord.cname({ name: "app.example.com", target: "edge.acme.dev", purpose: "Serve your site" }),
26
+ DnsRecord.txt({
27
+ name: "_acme.app.example.com",
28
+ value: "acme-verify=7f3a",
29
+ purpose: "Prove ownership",
30
+ }),
31
+ ];
32
+
33
+ export function DomainSettings() {
24
34
  return (
25
- <DomainKit.Root transport={transport}>
26
- <Domain.Flow domain="example.com" records={records} />
35
+ <DomainKit.Root transport={transport} colorScheme="inherit">
36
+ <Domain.Flow domain="app.example.com" requirements={requirements} />
27
37
  </DomainKit.Root>
28
38
  );
29
39
  }
30
40
  ```
31
41
 
32
- `Domain.Flow` handles connection, plan review, apply, verification, cleanup, and removal of the
33
- domain attachment. Your app still owns authentication, authorization, saved attempts, provider
34
- credentials, and the server-side `Transport` implementation.
42
+ `Domain.Flow` connects a provider, plans the DNS changes, takes the customer's approval or refusal,
43
+ applies the plan, observes the records, and cleans them up against the apply receipt. It renders
44
+ only the capability groups the transport declares.
45
+
46
+ `Transport.fromFetch` points at the routes you mounted from `domainkit/server`. Writing it inline in
47
+ JSX is fine: `DomainKit.Root` keeps the transport's identity for the whole mount, so the controllers
48
+ do not restart on every render. Pass `revision` to re-inspect every mounted domain after a change
49
+ the UI did not make.
50
+
51
+ An interactive provider returns the customer to the page they started from. Pass `returnTo` to name
52
+ a different destination, or `null` to leave the server's `defaultReturnTo` in charge.
53
+
54
+ ## Own one piece, keep the rest
55
+
56
+ Every part of the flow is a slot with a default.
57
+
58
+ ```tsx
59
+ <Domain.Flow
60
+ domain="app.example.com"
61
+ requirements={requirements}
62
+ slots={{
63
+ records: ({ records, readiness }) => <MyTable records={records} readiness={readiness} />,
64
+ }}
65
+ onApplied={(receipt) => track("dns.applied", { receiptId: receipt.id })}
66
+ />
67
+ ```
68
+
69
+ | Slot | Receives | Default |
70
+ | -------------- | ----------------------------------------------- | ---------------------------------------------------------- |
71
+ | `connection` | `{ controller, domain }` | `Connect.Card` once connected, `Connect.Dialog` until then |
72
+ | `records` | `{ records, readiness, controller, domain }` | `Records.Table` |
73
+ | `verification` | `{ controller, domain }` | `Verify.Status` with per-requirement evidence |
74
+ | `actions` | `{ connection, provisioning, cleanup, domain }` | Review changes, Approve, Decline, Remove records |
75
+
76
+ `Domain.Flow` adds no layout container around a slot: its output is a direct child of the flow root,
77
+ so your own grid can place it without `display: contents`.
78
+
79
+ ## The member view
80
+
81
+ A customer who may read a domain but not change it gets `readOnly`. Status renders; every control
82
+ that would start a write does not.
83
+
84
+ ```tsx
85
+ <DomainKit.Root transport={transport} readOnly>
86
+ <Domain.Flow domain="app.example.com" requirements={requirements} />
87
+ </DomainKit.Root>
88
+ ```
89
+
90
+ `Domain.Flow` takes the same flag when only one domain on the page is read-only, and `useReadOnly()`
91
+ tells a part of your own which mode it is in. Capability gating is the other half: a group the
92
+ transport does not declare never renders, so a transport built with
93
+ `Transport.fromFetch(url, { capabilities: ["connection", "verification"] })` already hides
94
+ provisioning and cleanup. `readOnly` covers the authorization a transport cannot express, such as a
95
+ member of an organisation who reaches the same routes.
35
96
 
36
- ## Choose the parts you need
97
+ Verification does not wait for a connection: the flow observes the requirements it was given, so a
98
+ domain with no provider attached still reports which records are in place.
37
99
 
38
- - `Domain.Flow` — the full lifecycle;
39
- - `Connection.Flow`, `Provisioning.Flow`, and `Cleanup.Flow` — focused flows;
40
- - `Verification`'s model and parts — provider and public-DNS evidence with an observe action;
41
- - `useModel` hooks and parts — your own layout and controls;
42
- - `Records.Table`, `Records.Card`, and record parts — DNS display without a root or transport.
100
+ Observation stays available in read-only, because checking DNS reads the world rather than changing
101
+ the domain. Retrying is not: a flow that becomes read-only after a write failed keeps the failure on
102
+ screen and drops the retry.
103
+ If your `Identity.authorize` denies `observe` to members, pass `slots={{ verification: () => null }}`.
43
104
 
44
- The packaged flows use the same exported models and parts. Components support server rendering;
45
- clipboard, download, and navigation behavior runs only from browser interactions.
105
+ ## Controllers
46
106
 
47
- ## Host transport
107
+ Compose the flow yourself from the same hooks. Each takes one options object and returns a named
108
+ `Controller` whose `State` is a tagged union.
48
109
 
49
- ```ts
50
- import { Transport } from "domainkit";
110
+ ```tsx
111
+ const connection = Connect.useController({ domain });
112
+ const provisioning = Provision.useController({ domain, requirements, onApplied });
113
+ const cleanup = Cleanup.useController({ domain, receiptId, onCleaned });
114
+ const verification = Verify.useController({ domain, polling: true });
115
+ ```
51
116
 
52
- export const transport = Transport.layerFromAsync({
53
- connection: api.connections,
54
- provisioning: api.provisioning,
55
- verification: api.verification,
56
- cleanup: api.cleanup,
57
- });
117
+ `approve` authorizes the plan digest and applies it. `reject` records the customer's refusal; the
118
+ attempt is terminal and a new plan is needed. `retry` builds a new plan when the reason says the old
119
+ one is gone, and re-runs the failed step otherwise.
120
+
121
+ ## Presentation
122
+
123
+ `DomainKit.Root` takes `messages`, `marks`, `icons`, `theme`, `colorScheme`, and `portalContainer`,
124
+ so branding stays in your app. `Messages.Catalog` holds every user-visible string, including one
125
+ sentence per `DomainKit.Error` reason; nothing renders a tag. Provider artwork comes from `marks`,
126
+ with the provider's initial as the fallback and no request at render time. The stylesheet is
127
+ opt-in and every color is a `--domainkit-*` custom property.
128
+
129
+ Every rule ships inside `@layer domainkit`, so your own stylesheet wins without out-specifying a
130
+ part selector and a Tailwind utility class lands where you put it. To let the package win instead,
131
+ order a layer of your own below it:
132
+
133
+ ```css
134
+ @layer domainkit, app;
58
135
  ```
59
136
 
60
- The stylesheet is opt-in. `DomainKit.Root` accepts your messages, provider marks, icons, design
61
- tokens, color scheme, and portal container, so branding stays in your app.
62
- Known provider marks load from `integrations.sh` and fall back to the provider's initial when a mark
63
- is unavailable. Replace them through the `marks` prop when your app owns the artwork.
137
+ Every dialog and popover takes a `render` prop that replaces the surface with your own.
138
+
139
+ ## Next.js
140
+
141
+ The package entry carries `"use client"`. A transport is an object of functions, so build it inside
142
+ a client module rather than passing one from a server component.
64
143
 
65
144
  ## Learn more
66
145
 
67
146
  - [React source](https://github.com/AryaLabsHQ/domainkit/tree/main/packages/react)
68
- - [React component catalog](https://domain-kit.dev/components)
69
- - [Application transport contract](https://github.com/AryaLabsHQ/domainkit/blob/main/packages/domainkit/src/transport.ts)
147
+ - [Transport contract](https://github.com/AryaLabsHQ/domainkit/blob/main/packages/domainkit/src/Transport.ts)
70
148
  - [Issues](https://github.com/AryaLabsHQ/domainkit/issues)
71
149
 
72
150
  ## License