@domainkit/react 0.4.0 → 0.6.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/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { AlertDialog } from "@base-ui/react/alert-dialog";
3
3
  import { RegistryProvider, useAtomSet, useAtomValue } from "@effect/atom-react";
4
- import { Transport } from "domainkit";
4
+ import { DomainName, Transport } from "domainkit";
5
5
  import * as Data from "effect/Data";
6
6
  import * as Effect from "effect/Effect";
7
7
  import * as Atom from "effect/unstable/reactivity/Atom";
@@ -115,10 +115,11 @@ const english = {
115
115
  close: "Close",
116
116
  detectingProvider: "Detecting DNS provider…",
117
117
  disconnectDomain: "Disconnect",
118
- disconnectConsent: "The provider connection will be removed. Existing DNS records will be preserved.",
118
+ disconnectConsent: "This domain will be detached from the provider. Existing DNS records will be preserved.",
119
119
  disconnecting: "Disconnecting…",
120
- disconnectTitle: (provider) => `Disconnect ${provider}?`,
121
- domainDisconnected: "Domain disconnected. DNS records were preserved.",
120
+ detaching: "Detaching domain…",
121
+ disconnectTitle: (provider) => `Detach ${provider}?`,
122
+ domainDisconnected: "Domain detached. DNS records were preserved.",
122
123
  dialogDescription: (domain) => `Authorize DNS changes for ${domain}.`,
123
124
  dialogTitle: (provider) => `Connect ${provider}`,
124
125
  openingAuthorization: "Opening provider authorization…",
@@ -130,7 +131,9 @@ const english = {
130
131
  reviewCleanup: "Remove records",
131
132
  reviewDns: "Review changes",
132
133
  retry: "Try again",
133
- reuseConnection: (connection) => `Use ${connection}`,
134
+ existingConnection: "Existing provider connection",
135
+ targetUnavailable: "No provider targets are available for this connection.",
136
+ attachTarget: (target) => `Use ${target}`,
134
137
  tokenLabel: "API token"
135
138
  };
136
139
  const merge = (overrides = {}) => ({
@@ -327,8 +330,8 @@ function useModel$3(connection, receiptId, onCleaned) {
327
330
  if (current._tag === "Planning" || current._tag === "Cleaning") return Effect.void;
328
331
  get.set(state, State$3.Planning());
329
332
  return Effect.flatMap(Transport.Service, (transport) => transport.cleanup.plan({
330
- connectionId: connection.connectionId,
331
- domain: connection.domain,
333
+ attachmentId: connection.attachment.id,
334
+ domain: connection.attachment.domain,
332
335
  receiptId
333
336
  })).pipe(Effect.tap((plan) => Effect.sync(() => get.set(state, State$3.Review({ plan })))), Effect.catch((error) => Effect.sync(() => get.set(state, failureFromError(error)))), Effect.asVoid);
334
337
  }
@@ -336,8 +339,8 @@ function useModel$3(connection, receiptId, onCleaned) {
336
339
  const plan = current.plan;
337
340
  get.set(state, State$3.Cleaning({ plan }));
338
341
  return Effect.flatMap(Transport.Service, (transport) => transport.cleanup.apply({
339
- connectionId: connection.connectionId,
340
- domain: connection.domain,
342
+ attachmentId: connection.attachment.id,
343
+ domain: connection.attachment.domain,
341
344
  planDigest: plan.digest,
342
345
  receiptId
343
346
  })).pipe(Effect.tap((result) => Effect.sync(() => {
@@ -361,8 +364,8 @@ function useModel$3(connection, receiptId, onCleaned) {
361
364
  state
362
365
  };
363
366
  }, [
364
- connection.connectionId,
365
- connection.domain,
367
+ connection.attachment.domain,
368
+ connection.attachment.id,
366
369
  emit,
367
370
  receiptId,
368
371
  runtime
@@ -509,53 +512,67 @@ function Flow$3({ connection, onCleaned, receiptId, trigger, ...props }) {
509
512
  //#endregion
510
513
  //#region src/provider.tsx
511
514
  var provider_exports = /* @__PURE__ */ __exportAll({ Mark: () => Mark });
515
+ const logoHosts = {
516
+ amazonroute53: "route53.com",
517
+ aws: "amazonaws.com",
518
+ bluehost: "bluehost.com",
519
+ cloudflare: "cloudflare.com",
520
+ digitalocean: "digitalocean.com",
521
+ dnsimple: "dnsimple.com",
522
+ dreamhost: "dreamhost.com",
523
+ dynadot: "dynadot.com",
524
+ gandi: "gandi.net",
525
+ godaddy: "godaddy.com",
526
+ google: "cloud.google.com",
527
+ googlecloud: "cloud.google.com",
528
+ googleclouddns: "cloud.google.com",
529
+ hostgator: "hostgator.com",
530
+ hostinger: "hostinger.com",
531
+ hover: "hover.com",
532
+ ionos: "ionos.com",
533
+ namecheap: "namecheap.com",
534
+ namecom: "name.com",
535
+ netlify: "netlify.com",
536
+ ns1: "ns1.com",
537
+ ovh: "ovh.com",
538
+ porkbun: "porkbun.com",
539
+ route53: "route53.com",
540
+ spaceship: "spaceship.com",
541
+ squarespace: "squarespace.com",
542
+ vercel: "vercel.com"
543
+ };
512
544
  const logoKey = (providerId) => providerId.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
545
+ const logoHost = (providerId) => logoHosts[logoKey(providerId)];
513
546
  const letterMark = (provider) => provider.name.trim().charAt(0).toUpperCase() || "?";
514
- function CloudflareMark() {
515
- return /* @__PURE__ */ jsxs("svg", {
516
- "aria-hidden": "true",
517
- viewBox: "0 0 48 32",
518
- children: [/* @__PURE__ */ jsx("path", {
519
- d: "M30.2 24.6H7.7a6.2 6.2 0 0 1 .5-12.4 9.7 9.7 0 0 1 18.5-2.8 7.7 7.7 0 0 1 3.5 15.2Z",
520
- fill: "#f48120"
521
- }), /* @__PURE__ */ jsx("path", {
522
- d: "M33.1 16.2h1.3a5.2 5.2 0 0 1 .3 10.4H18.2l1.2-3.4h12.9l.8-7Z",
523
- fill: "#faad3d"
524
- })]
525
- });
526
- }
527
- function VercelMark() {
528
- return /* @__PURE__ */ jsx("svg", {
529
- "aria-hidden": "true",
530
- viewBox: "0 0 32 32",
531
- children: /* @__PURE__ */ jsx("path", {
532
- d: "m16 5 13 22H3L16 5Z",
533
- fill: "currentColor"
534
- })
535
- });
536
- }
537
- const builtInMarks = {
538
- cloudflare: CloudflareMark,
539
- vercel: VercelMark
540
- };
541
547
  function DefaultMark({ provider }) {
542
- const BuiltIn = builtInMarks[logoKey(provider.id)];
543
- return BuiltIn === void 0 ? letterMark(provider) : /* @__PURE__ */ jsx(BuiltIn, {});
548
+ const host = logoHost(provider.id);
549
+ const [failed, setFailed] = useState(false);
550
+ if (host === void 0 || failed) return letterMark(provider);
551
+ return /* @__PURE__ */ jsx("img", {
552
+ alt: "",
553
+ height: 32,
554
+ onError: () => setFailed(true),
555
+ referrerPolicy: "no-referrer",
556
+ src: `https://integrations.sh/logo/${host}?sz=64`,
557
+ width: 32
558
+ });
544
559
  }
545
560
  function Mark({ provider, ...props }) {
546
561
  const { marks } = useDomainKit();
547
562
  const replacement = marks[provider.id];
548
- const content = typeof replacement === "function" ? replacement(provider) : replacement ?? /* @__PURE__ */ jsx(DefaultMark, { provider });
563
+ const content = typeof replacement === "function" ? replacement(provider) : replacement ?? /* @__PURE__ */ jsx(DefaultMark, { provider }, provider.id);
549
564
  return usePart("span", props, { providerId: provider.id }, {
550
565
  "aria-label": provider.name,
551
566
  children: content,
552
567
  "data-domainkit-part": "provider-mark",
568
+ "data-domainkit-provider": logoKey(provider.id),
553
569
  role: "img"
554
570
  });
555
571
  }
556
572
  //#endregion
557
573
  //#region src/connection.tsx
558
574
  var connection_exports = /* @__PURE__ */ __exportAll({
575
+ AttachAction: () => AttachAction,
559
576
  Card: () => Card$1,
560
577
  CardActions: () => CardActions,
561
578
  CardIdentity: () => CardIdentity,
@@ -564,18 +581,20 @@ var connection_exports = /* @__PURE__ */ __exportAll({
564
581
  Dialog: () => Dialog$2,
565
582
  DisconnectDialog: () => DisconnectDialog,
566
583
  Flow: () => Flow$2,
584
+ IntegrationAction: () => IntegrationAction,
567
585
  OAuthAction: () => OAuthAction,
568
586
  RetryAction: () => RetryAction$1,
569
- ReuseAction: () => ReuseAction,
570
587
  Root: () => Root$3,
571
588
  State: () => State$2,
572
589
  Status: () => Status$2,
573
590
  TokenAction: () => TokenAction,
574
591
  Trigger: () => Trigger,
592
+ targetLabel: () => targetLabel,
575
593
  useController: () => useController$2,
576
594
  useModel: () => useModel$2
577
595
  });
578
596
  const State$2 = Data.taggedEnum();
597
+ const sameTarget = (left, right) => left.accountId === right.accountId && left.accountKind === right.accountKind && left.zoneId === right.zoneId && left.zoneName === right.zoneName;
579
598
  const Command$2 = Data.taggedEnum();
580
599
  function useModel$2(domain) {
581
600
  const { emit, navigate, runtime } = useDomainKit();
@@ -601,33 +620,29 @@ function useModel$2(domain) {
601
620
  return Effect.void;
602
621
  }
603
622
  const snapshot = get(state);
604
- if (input._tag === "Disconnect") {
623
+ if (input._tag === "Detach") {
605
624
  if (snapshot._tag !== "Connected") return Effect.void;
606
- get.set(actionState, State$2.Disconnecting({ snapshot }));
607
- return Effect.flatMap(Transport.Service, (transport) => transport.connection.removeDomain({
608
- connectionId: snapshot.connectionId,
609
- domain: snapshot.domain,
625
+ get.set(actionState, State$2.Detaching({ snapshot }));
626
+ return Effect.flatMap(Transport.Service, (transport) => Effect.flatMap(transport.connection.detach({
627
+ attachmentId: snapshot.attachment.id,
610
628
  preserveDns: true
611
- })).pipe(Effect.tap((result) => Effect.sync(() => {
612
- get.set(actionState, {
629
+ }), (result) => {
630
+ const detached = {
613
631
  _tag: "Disconnected",
614
- domain: snapshot.domain,
632
+ domain: snapshot.attachment.domain,
615
633
  provider: snapshot.provider,
616
- ...result.remainingDomainCount > 0 ? { reusableConnection: {
617
- connectionId: result.connectionId,
618
- label: snapshot.provider.name
619
- } } : {}
620
- });
621
- emit(Event.DomainDisconnected({
622
- connection: snapshot,
623
- result
624
- }));
625
- })), Effect.catch((error) => Effect.sync(() => get.set(actionState, failureFromError(error)))), Effect.asVoid);
634
+ reusableConnections: []
635
+ };
636
+ return Effect.flatMap(Effect.sync(() => {
637
+ get.set(actionState, detached);
638
+ emit(Event.DomainDetached({
639
+ connection: snapshot,
640
+ result
641
+ }));
642
+ }), () => transport.connection.inspect({ domain }).pipe(Effect.catch(() => Effect.succeed(detached)), Effect.tap((refreshed) => Effect.sync(() => get.set(actionState, refreshed)))));
643
+ })).pipe(Effect.catch((error) => Effect.sync(() => get.set(actionState, failureFromError(error)))), Effect.asVoid);
626
644
  }
627
645
  if (snapshot._tag !== "Disconnected") return Effect.void;
628
- const reusableConnection = snapshot.reusableConnection;
629
- if (input._tag === "Reuse" && reusableConnection === void 0) return Effect.void;
630
- get.set(actionState, State$2.Submitting({ snapshot }));
631
646
  const complete = (request, source) => request.pipe(Effect.tap((result) => Effect.sync(() => {
632
647
  if (result._tag === "Redirect") {
633
648
  get.set(actionState, State$2.Redirecting({ snapshot }));
@@ -640,16 +655,25 @@ function useModel$2(domain) {
640
655
  }));
641
656
  }
642
657
  })), Effect.catch((error) => Effect.sync(() => get.set(actionState, failureFromError(error)))), Effect.asVoid);
658
+ if (input._tag === "Attach") {
659
+ const reusableConnection = snapshot.reusableConnections.find(({ connection }) => connection.id === input.connectionId);
660
+ if (reusableConnection === void 0) return Effect.void;
661
+ const target = reusableConnection.targets.find((candidate) => sameTarget(candidate, input.target));
662
+ if (target === void 0) return Effect.void;
663
+ get.set(actionState, State$2.Submitting({ snapshot }));
664
+ return complete(Effect.flatMap(Transport.Service, (transport) => transport.connection.attach({
665
+ connectionId: input.connectionId,
666
+ domain,
667
+ target
668
+ })), "attach");
669
+ }
670
+ get.set(actionState, State$2.Submitting({ snapshot }));
643
671
  if (input._tag === "Connect") return complete(Effect.flatMap(Transport.Service, (transport) => transport.connection.connect({
644
672
  domain,
645
673
  method: input.method,
646
674
  providerId: snapshot.provider.id
647
675
  })), "connect");
648
- if (reusableConnection === void 0) return Effect.void;
649
- return complete(Effect.flatMap(Transport.Service, (transport) => transport.connection.reuse({
650
- connectionId: reusableConnection.connectionId,
651
- domain
652
- })), "reuse");
676
+ return Effect.void;
653
677
  }),
654
678
  state
655
679
  };
@@ -665,10 +689,13 @@ function useController$2(domain) {
665
689
  const state = useAtomValue(model.state);
666
690
  const execute = useAtomSet(model.command);
667
691
  return {
692
+ attach: (connection, target) => execute(Command$2.Attach({
693
+ connectionId: connection.connection.id,
694
+ target
695
+ })),
668
696
  connect: (method) => execute(Command$2.Connect({ method })),
669
- disconnect: () => execute(Command$2.Disconnect()),
697
+ detach: () => execute(Command$2.Detach()),
670
698
  retry: () => execute(Command$2.Retry()),
671
- reuse: () => execute(Command$2.Reuse()),
672
699
  state
673
700
  };
674
701
  }
@@ -680,7 +707,7 @@ function Root$3({ children, status = "Loading", ...props }) {
680
707
  }
681
708
  function Status$2({ children, state, ...props }) {
682
709
  const { messages } = useDomainKit();
683
- const text = state._tag === "Loading" ? messages.detectingProvider : state._tag === "Connected" ? messages.connected(state.provider.name) : state._tag === "Disconnecting" ? messages.disconnecting : state._tag === "Unsupported" ? messages.automaticUnavailable : state._tag === "Failure" ? state.message : state._tag === "Redirecting" ? messages.openingAuthorization : state._tag === "Submitting" ? messages.connecting : messages.providerAvailable(state.provider.name);
710
+ const text = state._tag === "Loading" ? messages.detectingProvider : state._tag === "Connected" ? messages.connected(state.provider.name) : state._tag === "Detaching" ? messages.detaching : state._tag === "Unsupported" ? messages.automaticUnavailable : state._tag === "Failure" ? state.message : state._tag === "Redirecting" ? messages.openingAuthorization : state._tag === "Submitting" ? messages.connecting : messages.providerAvailable(state.provider.name);
684
711
  return usePart("div", props, { status: state._tag }, {
685
712
  children: children ?? text,
686
713
  "data-domainkit-part": "connection-status",
@@ -714,12 +741,21 @@ function OAuthAction({ controller, label, ...props }) {
714
741
  type: "button"
715
742
  });
716
743
  }
717
- function ReuseAction({ controller, label, ...props }) {
718
- return usePart("button", props, { authentication: "reuse" }, {
744
+ function IntegrationAction({ controller, label, ...props }) {
745
+ return usePart("button", props, { authentication: "integration" }, {
746
+ children: label,
747
+ "data-domainkit-part": "integration-connect",
748
+ disabled: controller.state._tag === "Submitting" || controller.state._tag === "Redirecting",
749
+ onClick: () => controller.connect(Transport.Method.Integration()),
750
+ type: "button"
751
+ });
752
+ }
753
+ function AttachAction({ connection, controller, label, target, ...props }) {
754
+ return usePart("button", props, { authentication: "attach" }, {
719
755
  children: label,
720
- "data-domainkit-part": "reuse-connection",
756
+ "data-domainkit-part": "attach-target",
721
757
  disabled: controller.state._tag === "Submitting" || controller.state._tag === "Redirecting",
722
- onClick: () => void controller.reuse(),
758
+ onClick: () => controller.attach(connection, target),
723
759
  type: "button"
724
760
  });
725
761
  }
@@ -779,12 +815,14 @@ function TokenAction({ controller, method, ...props }) {
779
815
  onSubmit: submit
780
816
  });
781
817
  }
818
+ const targetLabel = (target) => `${target.evidence?.accountName ?? target.accountId} · ${target.zoneName}`;
782
819
  function Dialog$2({ controller, snapshot }) {
783
820
  const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
784
821
  const pending = controller.state._tag === "Submitting" || controller.state._tag === "Redirecting";
822
+ const integrationMethods = snapshot.provider.authentication.filter((method) => method._tag === "Integration");
785
823
  const oauthMethods = snapshot.provider.authentication.filter((method) => method._tag === "OAuth");
786
824
  const tokenMethods = snapshot.provider.authentication.filter((method) => method._tag === "Token");
787
- const hasProviderAccountPath = oauthMethods.length > 0 || snapshot.reusableConnection !== void 0;
825
+ const hasProviderAccountPath = integrationMethods.length > 0 || oauthMethods.length > 0 || snapshot.reusableConnections.length > 0;
788
826
  return /* @__PURE__ */ jsxs(Dialog.Portal, {
789
827
  container: portalContainer,
790
828
  children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
@@ -824,13 +862,36 @@ function Dialog$2({ controller, snapshot }) {
824
862
  children: [
825
863
  hasProviderAccountPath ? /* @__PURE__ */ jsxs("div", {
826
864
  "data-domainkit-part": "provider-authentication",
827
- children: [oauthMethods.map((method) => /* @__PURE__ */ jsx(OAuthAction, {
828
- controller,
829
- label: method.label
830
- }, method._tag)), snapshot.reusableConnection === void 0 ? null : /* @__PURE__ */ jsx(ReuseAction, {
831
- controller,
832
- label: messages.reuseConnection(snapshot.reusableConnection.label)
833
- })]
865
+ children: [
866
+ integrationMethods.map((method) => /* @__PURE__ */ jsx(IntegrationAction, {
867
+ controller,
868
+ label: method.label
869
+ }, method._tag)),
870
+ oauthMethods.map((method) => /* @__PURE__ */ jsx(OAuthAction, {
871
+ controller,
872
+ label: method.label
873
+ }, method._tag)),
874
+ snapshot.reusableConnections.map((connection) => /* @__PURE__ */ jsxs("section", {
875
+ "data-connection-id": connection.connection.id,
876
+ "data-domainkit-part": "reusable-connection",
877
+ children: [/* @__PURE__ */ jsxs("div", {
878
+ "data-domainkit-part": "reusable-connection-heading",
879
+ children: [/* @__PURE__ */ jsx("strong", { children: messages.existingConnection }), /* @__PURE__ */ jsx("span", { children: connection.connection.providerId })]
880
+ }), /* @__PURE__ */ jsx("div", {
881
+ "data-domainkit-part": "target-list",
882
+ "data-state": connection.targets.length === 0 ? "unavailable" : connection.targets.length === 1 ? "unique" : "ambiguous",
883
+ children: connection.targets.length === 0 ? /* @__PURE__ */ jsx("p", {
884
+ "data-domainkit-part": "target-unavailable",
885
+ children: messages.targetUnavailable
886
+ }) : connection.targets.map((target) => /* @__PURE__ */ jsx(AttachAction, {
887
+ connection,
888
+ controller,
889
+ label: messages.attachTarget(targetLabel(target)),
890
+ target
891
+ }, `${target.accountId}:${target.zoneId}`))
892
+ })]
893
+ }, connection.connection.id))
894
+ ]
834
895
  }) : null,
835
896
  hasProviderAccountPath && tokenMethods.length > 0 ? /* @__PURE__ */ jsx("div", {
836
897
  "aria-label": messages.authenticationAlternative,
@@ -918,10 +979,10 @@ function DisconnectDialog({ connection, controller, onOpenChange, open: openProp
918
979
  if (openProp === void 0) setInternalOpen(nextOpen);
919
980
  onOpenChange?.(nextOpen);
920
981
  };
921
- const disconnecting = controller.state._tag === "Disconnecting";
982
+ const detaching = controller.state._tag === "Detaching";
922
983
  return /* @__PURE__ */ jsxs(AlertDialog.Root, {
923
984
  onOpenChange: (nextOpen, eventDetails) => {
924
- if (!nextOpen && disconnecting) {
985
+ if (!nextOpen && detaching) {
925
986
  eventDetails.cancel();
926
987
  return;
927
988
  }
@@ -955,22 +1016,22 @@ function DisconnectDialog({ connection, controller, onOpenChange, open: openProp
955
1016
  "data-domainkit-part": "dialog-description",
956
1017
  children: messages.disconnectConsent
957
1018
  })]
958
- }), disconnecting ? null : /* @__PURE__ */ jsx(AlertDialog.Close, {
1019
+ }), detaching ? null : /* @__PURE__ */ jsx(AlertDialog.Close, {
959
1020
  "aria-label": messages.close,
960
1021
  "data-domainkit-part": "dialog-close",
961
1022
  children: "×"
962
1023
  })]
963
1024
  }), /* @__PURE__ */ jsxs("div", {
964
1025
  "data-domainkit-part": "dialog-footer",
965
- children: [disconnecting ? null : /* @__PURE__ */ jsx(AlertDialog.Close, {
1026
+ children: [detaching ? null : /* @__PURE__ */ jsx(AlertDialog.Close, {
966
1027
  "data-domainkit-part": "dialog-cancel",
967
1028
  children: messages.cancel
968
1029
  }), /* @__PURE__ */ jsx("button", {
969
1030
  "data-domainkit-part": "disconnect-action",
970
- disabled: disconnecting,
971
- onClick: () => controller.disconnect(),
1031
+ disabled: detaching,
1032
+ onClick: () => controller.detach(),
972
1033
  type: "button",
973
- children: disconnecting ? messages.disconnecting : messages.disconnectDomain
1034
+ children: detaching ? messages.detaching : messages.disconnectDomain
974
1035
  })]
975
1036
  })]
976
1037
  })]
@@ -1127,6 +1188,7 @@ function ZoneFile({ copiedIcon, copiedLabel = "Copied", copyIcon, copyLabel = "C
1127
1188
  children: copied ? copiedIcon ?? icons.copied : copyIcon ?? icons.copy
1128
1189
  }), copied ? copiedLabel : copyLabel]
1129
1190
  }), /* @__PURE__ */ jsxs("button", {
1191
+ "aria-label": downloadLabel,
1130
1192
  "data-domainkit-part": "zone-download",
1131
1193
  onClick: () => downloadZoneFile(domain, records),
1132
1194
  type: "button",
@@ -1262,8 +1324,8 @@ function useModel$1(connection, records, onApplied) {
1262
1324
  if (current._tag === "Planning" || current._tag === "Applying") return Effect.void;
1263
1325
  get.set(state, State$1.Planning());
1264
1326
  return Effect.flatMap(Transport.Service, (transport) => transport.provisioning.plan({
1265
- connectionId: connection.connectionId,
1266
- domain: connection.domain,
1327
+ attachmentId: connection.attachment.id,
1328
+ domain: connection.attachment.domain,
1267
1329
  records
1268
1330
  })).pipe(Effect.tap((plan) => Effect.sync(() => get.set(state, State$1.Review({ plan })))), Effect.catch((error) => Effect.sync(() => get.set(state, failureFromError(error)))), Effect.asVoid);
1269
1331
  }
@@ -1271,8 +1333,8 @@ function useModel$1(connection, records, onApplied) {
1271
1333
  const plan = current.plan;
1272
1334
  get.set(state, State$1.Applying({ plan }));
1273
1335
  return Effect.flatMap(Transport.Service, (transport) => transport.provisioning.apply({
1274
- connectionId: connection.connectionId,
1275
- domain: connection.domain,
1336
+ attachmentId: connection.attachment.id,
1337
+ domain: connection.attachment.domain,
1276
1338
  planDigest: plan.digest
1277
1339
  })).pipe(Effect.tap((result) => Effect.sync(() => {
1278
1340
  if (result._tag === "Applied") {
@@ -1295,8 +1357,8 @@ function useModel$1(connection, records, onApplied) {
1295
1357
  state
1296
1358
  };
1297
1359
  }, [
1298
- connection.connectionId,
1299
- connection.domain,
1360
+ connection.attachment.domain,
1361
+ connection.attachment.id,
1300
1362
  emit,
1301
1363
  recordKey,
1302
1364
  runtime
@@ -1482,7 +1544,7 @@ function useModel(config) {
1482
1544
  const previous = current._tag === "Observation" ? current : current._tag === "Failed" ? current.previous : void 0;
1483
1545
  get.set(state, State.Observing({ ...previous === void 0 ? {} : { previous } }));
1484
1546
  return Effect.flatMap(Transport.Service, (transport) => transport.verification.observe({
1485
- ...config.connection === void 0 ? {} : { connectionId: config.connection.connectionId },
1547
+ ...config.connection === void 0 ? {} : { attachmentId: config.connection.attachment.id },
1486
1548
  domain: config.domain,
1487
1549
  records: config.records,
1488
1550
  sources: {
@@ -1497,7 +1559,7 @@ function useModel(config) {
1497
1559
  state
1498
1560
  };
1499
1561
  }, [
1500
- config.connection?.connectionId,
1562
+ config.connection?.attachment.id,
1501
1563
  config.domain,
1502
1564
  provider,
1503
1565
  publicDns,
@@ -1627,11 +1689,11 @@ var domain_exports = /* @__PURE__ */ __exportAll({ Flow: () => Flow });
1627
1689
  function Flow({ domain, receiptId, records }) {
1628
1690
  const controller = useController$2(domain);
1629
1691
  const state = controller.state;
1630
- const connected = state._tag === "Connected" ? state : state._tag === "Disconnecting" ? state.snapshot : void 0;
1692
+ const connected = state._tag === "Connected" ? state : state._tag === "Detaching" ? state.snapshot : void 0;
1631
1693
  return /* @__PURE__ */ jsxs(Root$3, {
1632
1694
  status: state._tag,
1633
1695
  children: [
1634
- state._tag === "Connected" || state._tag === "Disconnecting" ? /* @__PURE__ */ jsx(ConnectedCard, {
1696
+ state._tag === "Connected" || state._tag === "Detaching" ? /* @__PURE__ */ jsx(ConnectedCard, {
1635
1697
  connection: state._tag === "Connected" ? state : state.snapshot,
1636
1698
  controller,
1637
1699
  ...receiptId === void 0 ? {} : { initialReceiptId: receiptId },
@@ -1689,7 +1751,7 @@ function ConnectedCard({ connection, controller, initialReceiptId, records, stat
1689
1751
  value: initialReceiptId
1690
1752
  });
1691
1753
  const receiptId = receipt.host === initialReceiptId ? receipt.value : initialReceiptId;
1692
- const disabled = status === "Disconnecting";
1754
+ const disabled = status === "Detaching";
1693
1755
  const actionsTriggerRef = useRef(null);
1694
1756
  return /* @__PURE__ */ jsxs(Card$1, {
1695
1757
  status,
@@ -1823,8 +1885,12 @@ function ProviderActions({ actionsTriggerRef: actionsTriggerRefProp, cleanup, co
1823
1885
  //#endregion
1824
1886
  //#region src/testing.ts
1825
1887
  var testing_exports = /* @__PURE__ */ __exportAll({
1888
+ attachment: () => attachment,
1889
+ connected: () => connected,
1890
+ connection: () => connection,
1826
1891
  makeFakeTransport: () => makeFakeTransport,
1827
- provider: () => provider
1892
+ provider: () => provider,
1893
+ target: () => target
1828
1894
  });
1829
1895
  const provider = (overrides = {}) => ({
1830
1896
  authentication: [{
@@ -1839,17 +1905,48 @@ const provider = (overrides = {}) => ({
1839
1905
  name: "Cloudflare",
1840
1906
  ...overrides
1841
1907
  });
1908
+ const target = (overrides = {}) => ({
1909
+ accountId: "account-1",
1910
+ accountKind: "account",
1911
+ zoneId: "zone-1",
1912
+ zoneName: DomainName.parse("example.com"),
1913
+ ...overrides
1914
+ });
1915
+ const connection = (overrides = {}) => ({
1916
+ createdAt: /* @__PURE__ */ new Date("2026-08-30T00:00:00.000Z"),
1917
+ id: "connection-1",
1918
+ method: "oauth2",
1919
+ ownerId: "organization-1",
1920
+ providerId: "cloudflare",
1921
+ status: "active",
1922
+ ...overrides
1923
+ });
1924
+ const attachment = (overrides = {}) => ({
1925
+ connectionId: "connection-1",
1926
+ createdAt: /* @__PURE__ */ new Date("2026-08-30T00:00:00.000Z"),
1927
+ domain: DomainName.parse("mail.example.com"),
1928
+ id: "attachment-1",
1929
+ target: target(),
1930
+ ...overrides
1931
+ });
1932
+ const connected = (overrides = {}) => ({
1933
+ _tag: "Connected",
1934
+ attachment: attachment(),
1935
+ connection: connection(),
1936
+ provider: provider(),
1937
+ ...overrides
1938
+ });
1842
1939
  function makeFakeTransport(options) {
1843
1940
  const calls = {
1844
1941
  apply: [],
1942
+ attach: [],
1845
1943
  cleanupApply: [],
1846
1944
  cleanupPlan: [],
1847
1945
  connect: [],
1946
+ detach: [],
1848
1947
  inspect: [],
1849
1948
  observe: [],
1850
- plan: [],
1851
- removeDomain: [],
1852
- reuse: []
1949
+ plan: []
1853
1950
  };
1854
1951
  const inspections = Array.isArray(options.inspect) ? [...options.inspect] : [options.inspect];
1855
1952
  const transport = {
@@ -1877,8 +1974,8 @@ function makeFakeTransport(options) {
1877
1974
  calls.connect.push(input);
1878
1975
  return options.connect ?? {
1879
1976
  _tag: "Connected",
1880
- connectionId: "connection-1",
1881
- domain: input.domain,
1977
+ attachment: attachment({ domain: DomainName.parse(input.domain) }),
1978
+ connection: connection(),
1882
1979
  provider: provider()
1883
1980
  };
1884
1981
  },
@@ -1893,22 +1990,26 @@ function makeFakeTransport(options) {
1893
1990
  });
1894
1991
  return snapshot;
1895
1992
  },
1896
- reuse: async (input) => {
1897
- calls.reuse.push(input);
1898
- return options.reuse ?? {
1993
+ attach: async (input) => {
1994
+ calls.attach.push(input);
1995
+ return options.attach ?? {
1899
1996
  _tag: "Connected",
1900
- connectionId: input.connectionId,
1901
- domain: input.domain,
1997
+ attachment: attachment({
1998
+ connectionId: input.connectionId,
1999
+ domain: DomainName.parse(input.domain),
2000
+ target: input.target
2001
+ }),
2002
+ connection: connection({ id: input.connectionId }),
1902
2003
  provider: provider()
1903
2004
  };
1904
2005
  },
1905
- removeDomain: async (input) => {
1906
- calls.removeDomain.push(input);
1907
- return options.removeDomain ?? {
1908
- _tag: "Removed",
1909
- connectionId: input.connectionId,
1910
- domain: input.domain,
1911
- remainingDomainCount: 0
2006
+ detach: async (input) => {
2007
+ calls.detach.push(input);
2008
+ return options.detach ?? {
2009
+ _tag: "Detached",
2010
+ attachment: attachment(),
2011
+ connection: connection(),
2012
+ remainingAttachments: 0
1912
2013
  };
1913
2014
  }
1914
2015
  },