domainkit 0.19.0 → 0.20.1

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Add custom domains to a TypeScript SaaS application.
4
4
 
5
+ Documentation: [domain-kit.dev](https://domain-kit.dev)
6
+
5
7
  DomainKit turns DNS requirements into plans a customer can review, applies only the plan digest
6
8
  they approved, keeps a receipt of every write, and plans cleanup from that receipt. Cloudflare and
7
9
  Vercel are built in; a provider is one declarative value, so tokens-only providers are the same
@@ -10,7 +12,7 @@ shape minus the OAuth case.
10
12
  ## Install
11
13
 
12
14
  ```sh
13
- npm install domainkit effect@rc
15
+ npm install domainkit effect@^4
14
16
  ```
15
17
 
16
18
  Node.js 24.10 or newer and Effect 4 are required.
@@ -65,6 +67,15 @@ Plans are additive and fail closed: exact records are `Noop`, missing records ar
65
67
  incompatible state is `Conflict`. DomainKit never updates or deletes a record it did not create,
66
68
  and cleanup is its own plan, approval, and receipt built from the apply receipt.
67
69
 
70
+ A requirement's `policy` says what may share its name. `exclusive` conflicts with any incompatible
71
+ record at that name and type, even when the exact record is also present, and `append` tolerates
72
+ neighbours. `DnsRecord.spf({ name, value, ttl?, purpose? })` is a standard TXT requirement with an
73
+ explicit SPF constraint: it plans beside unrelated TXT such as verification tokens, and it
74
+ conflicts (`spf-conflict`) with a different SPF record, with more than one SPF record, or with
75
+ another requested SPF value. `Verify` applies the same rules. DomainKit validates only the `v=spf1`
76
+ version token; it doesn't merge, replace, or fully parse SPF policies. A generic `DnsRecord.txt`
77
+ appends, even when its value is SPF.
78
+
68
79
  Several domains at once are one batch: `Provision.batch.create` plans them with
69
80
  `Policy.batchConcurrency` in flight, `approve` binds one digest over every plan, and `apply` walks
70
81
  the approved domains under their own attempt leases, so a second apply skips what the first holds
@@ -125,7 +136,7 @@ are yours and the copy is not.
125
136
 
126
137
  ```ts
127
138
  import { Effect, Layer } from "effect";
128
- import { HttpApi, HttpApiBuilder } from "effect/unstable/httpapi";
139
+ import { HttpApi, HttpApiBuilder } from "effect/http-api";
129
140
  import { DomainKit, Reason } from "domainkit";
130
141
  import { Server } from "domainkit/server";
131
142
 
@@ -1,5 +1,6 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
2
  import { Model as Model$1, fromStringUnsafe } from "./DomainName.mjs";
3
+ import { isSpf } from "./internal/spf.mjs";
3
4
  import { Schema } from "effect";
4
5
  //#region src/DnsRecord.ts
5
6
  /**
@@ -34,6 +35,7 @@ var DnsRecord_exports = /* @__PURE__ */ __exportAll({
34
35
  mx: () => mx,
35
36
  ns: () => ns,
36
37
  sameSet: () => sameSet,
38
+ spf: () => spf,
37
39
  srv: () => srv,
38
40
  txt: () => txt
39
41
  });
@@ -82,10 +84,11 @@ var CNAME = class extends Schema.TaggedClass("@domainkit/DnsRecord/CNAME")("CNAM
82
84
  ...Common,
83
85
  target: Name
84
86
  }) {};
85
- var TXT = class extends Schema.TaggedClass("@domainkit/DnsRecord/TXT")("TXT", {
87
+ var TXT = class extends Schema.TaggedClass("@domainkit/DnsRecord/TXT")("TXT", Schema.Struct({
86
88
  ...Common,
87
- value: NonEmpty
88
- }) {};
89
+ value: NonEmpty,
90
+ constraint: Schema.optionalKey(Schema.Literal("spf"))
91
+ }).check(Schema.makeFilter((record) => record.constraint !== "spf" || isSpf(record.value) ? void 0 : "Expected an SPF version token (v=spf1 followed by space or end)"))) {};
89
92
  var MX = class extends Schema.TaggedClass("@domainkit/DnsRecord/MX")("MX", {
90
93
  ...Common,
91
94
  exchange: Name,
@@ -150,6 +153,12 @@ const txt = (input) => new TXT({
150
153
  ...common(input.name, input, "append"),
151
154
  value: input.value
152
155
  });
156
+ /** A TXT requirement that permits unrelated TXT but requires a single matching SPF record. */
157
+ const spf = (input) => new TXT({
158
+ ...common(input.name, input, "append"),
159
+ value: input.value,
160
+ constraint: "spf"
161
+ });
153
162
  const mx = (input) => new MX({
154
163
  ...common(input.name, input, "append"),
155
164
  exchange: fromStringUnsafe(input.exchange),
@@ -187,8 +196,8 @@ const data = (record) => {
187
196
  }
188
197
  };
189
198
  /**
190
- * Structural equality over type, name, and data. `ttl`, `policy`, and `purpose` are requirement
191
- * metadata, not record identity, so they are ignored; TTL drift is reported on plan operations.
199
+ * Structural equality over type, name, and data. Requirement metadata (`ttl`, `policy`,
200
+ * `constraint`, and `purpose`) is ignored; TTL drift is reported on plan operations.
192
201
  */
193
202
  const equals = (left, right) => {
194
203
  if (left._tag === "Opaque" || right._tag === "Opaque") return left._tag === "Opaque" && right._tag === "Opaque" && left.name === right.name && left.type === right.type && JSON.stringify(left.raw) === JSON.stringify(right.raw);
@@ -208,6 +217,6 @@ function isIpv6(value) {
208
217
  }
209
218
  }
210
219
  //#endregion
211
- export { A, AAAA, CAA, CNAME, DnsRecord_exports, MX, Model, NS, Observed, Opaque, Policy, SRV, TXT, Type, a, aaaa, caa, cname, data, equals, isDnsRecord, mx, ns, sameSet, srv, txt };
220
+ export { A, AAAA, CAA, CNAME, DnsRecord_exports, MX, Model, NS, Observed, Opaque, Policy, SRV, TXT, Type, a, aaaa, caa, cname, data, equals, isDnsRecord, mx, ns, sameSet, spf, srv, txt };
212
221
 
213
222
  //# sourceMappingURL=DnsRecord.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"DnsRecord.mjs","names":["DomainName.Model","DomainName.fromStringUnsafe"],"sources":["../src/DnsRecord.ts"],"sourcesContent":["/**\n * DNS record requirements a product asks a customer to satisfy, and the observed records a\n * provider or resolver reports back.\n *\n * Constructors are lowercase functions (`DnsRecord.cname(...)`), Effect style. `policy` defaults\n * to `exclusive` for CNAME and `append` for everything else; `ttl` defaults to `null` (provider\n * default); `purpose` is an optional human label the UI shows.\n */\nimport { Schema } from \"effect\";\n\nimport * as DomainName from \"./DomainName.ts\";\n\n// DomainName, internal/error, Reason, Plan, and DnsRecord form an import cycle. Field schemas\n// that cross it are read lazily so any module can be the first one evaluated.\nconst Name = Schema.suspend(() => DomainName.Model);\n\nexport const Type = Schema.Literals([\"A\", \"AAAA\", \"CNAME\", \"TXT\", \"MX\", \"CAA\", \"NS\", \"SRV\"]);\nexport type Type = typeof Type.Type;\n\nexport const Policy = Schema.Literals([\"exclusive\", \"append\"]);\nexport type Policy = typeof Policy.Type;\n\nconst Ttl = Schema.NullOr(\n Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 2_147_483_647 })),\n);\nconst Port = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 65_535 }));\nconst Priority = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 65_535 }));\nconst Ipv4 = Schema.String.check(\n Schema.makeFilter<string>((value) => (isIpv4(value) ? undefined : \"Expected an IPv4 address\")),\n);\nconst Ipv6 = Schema.String.check(\n Schema.makeFilter<string>((value) => (isIpv6(value) ? undefined : \"Expected an IPv6 address\")),\n);\nconst NonEmpty = Schema.String.check(Schema.isMinLength(1));\n\nconst Common = {\n name: Name,\n ttl: Ttl,\n policy: Policy,\n purpose: Schema.optionalKey(Schema.String),\n};\n\nexport class A extends Schema.TaggedClass<A>(\"@domainkit/DnsRecord/A\")(\"A\", {\n ...Common,\n address: Ipv4,\n}) {}\nexport class AAAA extends Schema.TaggedClass<AAAA>(\"@domainkit/DnsRecord/AAAA\")(\"AAAA\", {\n ...Common,\n address: Ipv6,\n}) {}\nexport class CNAME extends Schema.TaggedClass<CNAME>(\"@domainkit/DnsRecord/CNAME\")(\"CNAME\", {\n ...Common,\n target: Name,\n}) {}\nexport class TXT extends Schema.TaggedClass<TXT>(\"@domainkit/DnsRecord/TXT\")(\"TXT\", {\n ...Common,\n value: NonEmpty,\n}) {}\nexport class MX extends Schema.TaggedClass<MX>(\"@domainkit/DnsRecord/MX\")(\"MX\", {\n ...Common,\n exchange: Name,\n priority: Priority,\n}) {}\nexport class CAA extends Schema.TaggedClass<CAA>(\"@domainkit/DnsRecord/CAA\")(\"CAA\", {\n ...Common,\n flags: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 255 })),\n tag: NonEmpty,\n value: NonEmpty,\n}) {}\nexport class NS extends Schema.TaggedClass<NS>(\"@domainkit/DnsRecord/NS\")(\"NS\", {\n ...Common,\n nameserver: Name,\n}) {}\nexport class SRV extends Schema.TaggedClass<SRV>(\"@domainkit/DnsRecord/SRV\")(\"SRV\", {\n ...Common,\n target: Name,\n port: Port,\n priority: Priority,\n weight: Priority,\n}) {}\n\nexport const Model = Schema.Union([A, AAAA, CNAME, TXT, MX, CAA, NS, SRV]);\nexport type Model = typeof Model.Type;\nexport type Encoded = typeof Model.Encoded;\n\n/** A provider record DomainKit cannot model but must not overwrite. */\nexport class Opaque extends Schema.TaggedClass<Opaque>(\"@domainkit/DnsRecord/Opaque\")(\"Opaque\", {\n name: Schema.String,\n type: Schema.String,\n raw: Schema.Unknown,\n}) {}\nexport const Observed = Schema.Union([Model, Opaque]);\nexport type Observed = typeof Observed.Type;\n\ntype Options = { readonly ttl?: number; readonly policy?: Policy; readonly purpose?: string };\n\nconst common = (name: string, options: Options, defaultPolicy: Policy) => ({\n name: DomainName.fromStringUnsafe(name),\n ttl: options.ttl ?? null,\n policy: options.policy ?? defaultPolicy,\n ...(options.purpose === undefined ? {} : { purpose: options.purpose }),\n});\n\nexport const a = (input: { readonly name: string; readonly address: string } & Options): A =>\n new A({ ...common(input.name, input, \"append\"), address: input.address });\nexport const aaaa = (input: { readonly name: string; readonly address: string } & Options): AAAA =>\n new AAAA({ ...common(input.name, input, \"append\"), address: input.address });\nexport const cname = (\n input: { readonly name: string; readonly target: string } & Omit<Options, \"policy\">,\n): CNAME =>\n new CNAME({\n ...common(input.name, input, \"exclusive\"),\n target: DomainName.fromStringUnsafe(input.target),\n });\nexport const txt = (input: { readonly name: string; readonly value: string } & Options): TXT =>\n new TXT({ ...common(input.name, input, \"append\"), value: input.value });\nexport const mx = (\n input: { readonly name: string; readonly exchange: string; readonly priority: number } & Options,\n): MX =>\n new MX({\n ...common(input.name, input, \"append\"),\n exchange: DomainName.fromStringUnsafe(input.exchange),\n priority: input.priority,\n });\nexport const caa = (\n input: {\n readonly name: string;\n readonly flags: number;\n readonly tag: string;\n readonly value: string;\n } & Options,\n): CAA =>\n new CAA({\n ...common(input.name, input, \"append\"),\n flags: input.flags,\n tag: input.tag,\n value: input.value,\n });\nexport const ns = (input: { readonly name: string; readonly nameserver: string } & Options): NS =>\n new NS({\n ...common(input.name, input, \"append\"),\n nameserver: DomainName.fromStringUnsafe(input.nameserver),\n });\nexport const srv = (\n input: {\n readonly name: string;\n readonly target: string;\n readonly port: number;\n readonly priority: number;\n readonly weight: number;\n } & Options,\n): SRV =>\n new SRV({\n ...common(input.name, input, \"append\"),\n target: DomainName.fromStringUnsafe(input.target),\n port: input.port,\n priority: input.priority,\n weight: input.weight,\n });\n\nexport const isDnsRecord = (input: unknown): input is Model => Schema.is(Model)(input);\n\n/** The record's data portion as a canonical string, for display and zone-file rendering. */\nexport const data = (record: Model): string => {\n switch (record._tag) {\n case \"A\":\n case \"AAAA\":\n return record.address;\n case \"CNAME\":\n return record.target;\n case \"NS\":\n return record.nameserver;\n case \"TXT\":\n return record.value;\n case \"MX\":\n return `${record.priority} ${record.exchange}`;\n case \"CAA\":\n return `${record.flags} ${record.tag} ${record.value}`;\n case \"SRV\":\n return `${record.priority} ${record.weight} ${record.port} ${record.target}`;\n }\n};\n\n/**\n * Structural equality over type, name, and data. `ttl`, `policy`, and `purpose` are requirement\n * metadata, not record identity, so they are ignored; TTL drift is reported on plan operations.\n */\nexport const equals = (left: Observed, right: Observed): boolean => {\n if (left._tag === \"Opaque\" || right._tag === \"Opaque\") {\n return (\n left._tag === \"Opaque\" &&\n right._tag === \"Opaque\" &&\n left.name === right.name &&\n left.type === right.type &&\n JSON.stringify(left.raw) === JSON.stringify(right.raw)\n );\n }\n return left._tag === right._tag && left.name === right.name && data(left) === data(right);\n};\n\n/** `true` when both records occupy the same name and type, regardless of data. */\nexport const sameSet = (left: Observed, right: Observed): boolean =>\n left.name === right.name &&\n (left._tag === \"Opaque\" ? left.type : left._tag) ===\n (right._tag === \"Opaque\" ? right.type : right._tag);\n\nfunction isIpv4(value: string): boolean {\n const parts = value.split(\".\");\n return (\n parts.length === 4 &&\n parts.every(\n (part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255 && String(Number(part)) === part,\n )\n );\n}\n\nfunction isIpv6(value: string): boolean {\n try {\n return new URL(`http://[${value}]/`).hostname.startsWith(\"[\");\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DnsRecord.mjs","names":["DomainName.Model","DomainName.fromStringUnsafe"],"sources":["../src/DnsRecord.ts"],"sourcesContent":["/**\n * DNS record requirements a product asks a customer to satisfy, and the observed records a\n * provider or resolver reports back.\n *\n * Constructors are lowercase functions (`DnsRecord.cname(...)`), Effect style. `policy` defaults\n * to `exclusive` for CNAME and `append` for everything else; `ttl` defaults to `null` (provider\n * default); `purpose` is an optional human label the UI shows.\n */\nimport { Schema } from \"effect\";\n\nimport * as DomainName from \"./DomainName.ts\";\nimport { isSpf } from \"./internal/spf.ts\";\n\n// DomainName, internal/error, Reason, Plan, and DnsRecord form an import cycle. Field schemas\n// that cross it are read lazily so any module can be the first one evaluated.\nconst Name = Schema.suspend(() => DomainName.Model);\n\nexport const Type = Schema.Literals([\"A\", \"AAAA\", \"CNAME\", \"TXT\", \"MX\", \"CAA\", \"NS\", \"SRV\"]);\nexport type Type = typeof Type.Type;\n\nexport const Policy = Schema.Literals([\"exclusive\", \"append\"]);\nexport type Policy = typeof Policy.Type;\n\nconst Ttl = Schema.NullOr(\n Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 2_147_483_647 })),\n);\nconst Port = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 65_535 }));\nconst Priority = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 65_535 }));\nconst Ipv4 = Schema.String.check(\n Schema.makeFilter<string>((value) => (isIpv4(value) ? undefined : \"Expected an IPv4 address\")),\n);\nconst Ipv6 = Schema.String.check(\n Schema.makeFilter<string>((value) => (isIpv6(value) ? undefined : \"Expected an IPv6 address\")),\n);\nconst NonEmpty = Schema.String.check(Schema.isMinLength(1));\n\nconst Common = {\n name: Name,\n ttl: Ttl,\n policy: Policy,\n purpose: Schema.optionalKey(Schema.String),\n};\n\nexport class A extends Schema.TaggedClass<A>(\"@domainkit/DnsRecord/A\")(\"A\", {\n ...Common,\n address: Ipv4,\n}) {}\nexport class AAAA extends Schema.TaggedClass<AAAA>(\"@domainkit/DnsRecord/AAAA\")(\"AAAA\", {\n ...Common,\n address: Ipv6,\n}) {}\nexport class CNAME extends Schema.TaggedClass<CNAME>(\"@domainkit/DnsRecord/CNAME\")(\"CNAME\", {\n ...Common,\n target: Name,\n}) {}\nexport class TXT extends Schema.TaggedClass<TXT>(\"@domainkit/DnsRecord/TXT\")(\n \"TXT\",\n Schema.Struct({\n ...Common,\n value: NonEmpty,\n constraint: Schema.optionalKey(Schema.Literal(\"spf\")),\n }).check(\n Schema.makeFilter((record) =>\n record.constraint !== \"spf\" || isSpf(record.value)\n ? undefined\n : \"Expected an SPF version token (v=spf1 followed by space or end)\",\n ),\n ),\n) {}\nexport class MX extends Schema.TaggedClass<MX>(\"@domainkit/DnsRecord/MX\")(\"MX\", {\n ...Common,\n exchange: Name,\n priority: Priority,\n}) {}\nexport class CAA extends Schema.TaggedClass<CAA>(\"@domainkit/DnsRecord/CAA\")(\"CAA\", {\n ...Common,\n flags: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 255 })),\n tag: NonEmpty,\n value: NonEmpty,\n}) {}\nexport class NS extends Schema.TaggedClass<NS>(\"@domainkit/DnsRecord/NS\")(\"NS\", {\n ...Common,\n nameserver: Name,\n}) {}\nexport class SRV extends Schema.TaggedClass<SRV>(\"@domainkit/DnsRecord/SRV\")(\"SRV\", {\n ...Common,\n target: Name,\n port: Port,\n priority: Priority,\n weight: Priority,\n}) {}\n\nexport const Model = Schema.Union([A, AAAA, CNAME, TXT, MX, CAA, NS, SRV]);\nexport type Model = typeof Model.Type;\nexport type Encoded = typeof Model.Encoded;\n\n/** A provider record DomainKit cannot model but must not overwrite. */\nexport class Opaque extends Schema.TaggedClass<Opaque>(\"@domainkit/DnsRecord/Opaque\")(\"Opaque\", {\n name: Schema.String,\n type: Schema.String,\n raw: Schema.Unknown,\n}) {}\nexport const Observed = Schema.Union([Model, Opaque]);\nexport type Observed = typeof Observed.Type;\n\ntype Options = { readonly ttl?: number; readonly policy?: Policy; readonly purpose?: string };\n\nconst common = (name: string, options: Options, defaultPolicy: Policy) => ({\n name: DomainName.fromStringUnsafe(name),\n ttl: options.ttl ?? null,\n policy: options.policy ?? defaultPolicy,\n ...(options.purpose === undefined ? {} : { purpose: options.purpose }),\n});\n\nexport const a = (input: { readonly name: string; readonly address: string } & Options): A =>\n new A({ ...common(input.name, input, \"append\"), address: input.address });\nexport const aaaa = (input: { readonly name: string; readonly address: string } & Options): AAAA =>\n new AAAA({ ...common(input.name, input, \"append\"), address: input.address });\nexport const cname = (\n input: { readonly name: string; readonly target: string } & Omit<Options, \"policy\">,\n): CNAME =>\n new CNAME({\n ...common(input.name, input, \"exclusive\"),\n target: DomainName.fromStringUnsafe(input.target),\n });\nexport const txt = (input: { readonly name: string; readonly value: string } & Options): TXT =>\n new TXT({ ...common(input.name, input, \"append\"), value: input.value });\n/** A TXT requirement that permits unrelated TXT but requires a single matching SPF record. */\nexport const spf = (\n input: { readonly name: string; readonly value: string } & Omit<Options, \"policy\">,\n): TXT =>\n new TXT({ ...common(input.name, input, \"append\"), value: input.value, constraint: \"spf\" });\nexport const mx = (\n input: { readonly name: string; readonly exchange: string; readonly priority: number } & Options,\n): MX =>\n new MX({\n ...common(input.name, input, \"append\"),\n exchange: DomainName.fromStringUnsafe(input.exchange),\n priority: input.priority,\n });\nexport const caa = (\n input: {\n readonly name: string;\n readonly flags: number;\n readonly tag: string;\n readonly value: string;\n } & Options,\n): CAA =>\n new CAA({\n ...common(input.name, input, \"append\"),\n flags: input.flags,\n tag: input.tag,\n value: input.value,\n });\nexport const ns = (input: { readonly name: string; readonly nameserver: string } & Options): NS =>\n new NS({\n ...common(input.name, input, \"append\"),\n nameserver: DomainName.fromStringUnsafe(input.nameserver),\n });\nexport const srv = (\n input: {\n readonly name: string;\n readonly target: string;\n readonly port: number;\n readonly priority: number;\n readonly weight: number;\n } & Options,\n): SRV =>\n new SRV({\n ...common(input.name, input, \"append\"),\n target: DomainName.fromStringUnsafe(input.target),\n port: input.port,\n priority: input.priority,\n weight: input.weight,\n });\n\nexport const isDnsRecord = (input: unknown): input is Model => Schema.is(Model)(input);\n\n/** The record's data portion as a canonical string, for display and zone-file rendering. */\nexport const data = (record: Model): string => {\n switch (record._tag) {\n case \"A\":\n case \"AAAA\":\n return record.address;\n case \"CNAME\":\n return record.target;\n case \"NS\":\n return record.nameserver;\n case \"TXT\":\n return record.value;\n case \"MX\":\n return `${record.priority} ${record.exchange}`;\n case \"CAA\":\n return `${record.flags} ${record.tag} ${record.value}`;\n case \"SRV\":\n return `${record.priority} ${record.weight} ${record.port} ${record.target}`;\n }\n};\n\n/**\n * Structural equality over type, name, and data. Requirement metadata (`ttl`, `policy`,\n * `constraint`, and `purpose`) is ignored; TTL drift is reported on plan operations.\n */\nexport const equals = (left: Observed, right: Observed): boolean => {\n if (left._tag === \"Opaque\" || right._tag === \"Opaque\") {\n return (\n left._tag === \"Opaque\" &&\n right._tag === \"Opaque\" &&\n left.name === right.name &&\n left.type === right.type &&\n JSON.stringify(left.raw) === JSON.stringify(right.raw)\n );\n }\n return left._tag === right._tag && left.name === right.name && data(left) === data(right);\n};\n\n/** `true` when both records occupy the same name and type, regardless of data. */\nexport const sameSet = (left: Observed, right: Observed): boolean =>\n left.name === right.name &&\n (left._tag === \"Opaque\" ? left.type : left._tag) ===\n (right._tag === \"Opaque\" ? right.type : right._tag);\n\nfunction isIpv4(value: string): boolean {\n const parts = value.split(\".\");\n return (\n parts.length === 4 &&\n parts.every(\n (part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255 && String(Number(part)) === part,\n )\n );\n}\n\nfunction isIpv6(value: string): boolean {\n try {\n return new URL(`http://[${value}]/`).hostname.startsWith(\"[\");\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
package/dist/Plan.mjs CHANGED
@@ -50,6 +50,7 @@ var Conflict = class extends Schema.TaggedClass("@domainkit/Plan/Conflict")("Con
50
50
  existing: Schema.Array(Observed),
51
51
  reason: Schema.Literals([
52
52
  "exclusive-name",
53
+ "spf-conflict",
53
54
  "cname-collision",
54
55
  "value-mismatch",
55
56
  "opaque",
package/dist/Plan.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Plan.mjs","names":["DnsRecord.Model","DnsRecord.Observed","Errors.decode","DnsRecord.data"],"sources":["../src/Plan.ts"],"sourcesContent":["/**\n * A reviewable, digest-bound set of DNS operations. Plans are additive and fail closed: matching\n * records are `Noop`, missing records are `Create`, incompatible state is `Conflict`. A\n * provisioning plan never updates or deletes; a cleanup plan carries `Delete` operations for\n * records a receipt proves DomainKit created.\n */\nimport { Effect, Schema } from \"effect\";\n\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\n\n// Read lazily: DnsRecord sits in the same import cycle (see DnsRecord.ts).\nconst Record = Schema.suspend(() => DnsRecord.Model);\nconst Observed = Schema.suspend(() => DnsRecord.Observed);\n\nexport const PlanId = Schema.String.pipe(Schema.brand(\"@domainkit/PlanId\"));\nexport type PlanId = typeof PlanId.Type;\n\nexport const Digest = Schema.String.pipe(Schema.brand(\"@domainkit/Digest\"));\nexport type Digest = typeof Digest.Type;\n\nexport const OperationId = Schema.String.pipe(Schema.brand(\"@domainkit/OperationId\"));\nexport type OperationId = typeof OperationId.Type;\n\nexport const Kind = Schema.Literals([\"provisioning\", \"cleanup\"]);\nexport type Kind = typeof Kind.Type;\n\nexport class Create extends Schema.TaggedClass<Create>(\"@domainkit/Plan/Create\")(\"Create\", {\n id: OperationId,\n record: Record,\n}) {}\nexport class Noop extends Schema.TaggedClass<Noop>(\"@domainkit/Plan/Noop\")(\"Noop\", {\n id: OperationId,\n record: Record,\n existing: Observed,\n ttlDrift: Schema.Boolean,\n}) {}\nexport class Conflict extends Schema.TaggedClass<Conflict>(\"@domainkit/Plan/Conflict\")(\"Conflict\", {\n id: OperationId,\n record: Record,\n existing: Schema.Array(Observed),\n reason: Schema.Literals([\n \"exclusive-name\",\n \"cname-collision\",\n \"value-mismatch\",\n \"opaque\",\n \"missing\",\n ]),\n}) {}\n/** Cleanup only: remove the provider record a receipt proves DomainKit created. */\nexport class Delete extends Schema.TaggedClass<Delete>(\"@domainkit/Plan/Delete\")(\"Delete\", {\n id: OperationId,\n record: Record,\n providerRecordId: Schema.String,\n}) {}\nexport const Operation = Schema.Union([Create, Noop, Conflict, Delete]);\nexport type Operation = typeof Operation.Type;\n\nexport const Version = Schema.Literal(\"domainkit.plan.v2\");\n\nexport class Model extends Schema.Class<Model>(\"@domainkit/Plan\")({\n id: PlanId,\n version: Version,\n kind: Kind,\n digest: Digest,\n domain: Schema.String,\n zone: Schema.String,\n provider: Schema.String,\n attachmentId: Schema.String,\n operations: Schema.Array(Operation),\n createdAt: Schema.DateTimeUtcFromString,\n expiresAt: Schema.DateTimeUtcFromString,\n}) {}\nexport type Encoded = typeof Model.Encoded;\n\nexport const decode = (input: unknown): Effect.Effect<Model, Errors.DomainKitError> =>\n Errors.decode(Model, input);\nexport const encode: (plan: Model) => Encoded = Schema.encodeSync(Model);\n\n/** `true` when the plan has no conflicts and at least one write (`Create` or `Delete`). */\nexport const isApplicable = (plan: Model): boolean =>\n conflicts(plan).length === 0 && writes(plan).length > 0;\n\nexport const conflicts = (plan: Model): ReadonlyArray<Conflict> =>\n plan.operations.filter((operation): operation is Conflict => operation._tag === \"Conflict\");\n\n/** The operations that would touch the provider: creates for provisioning, deletes for cleanup. */\nexport const writes = (plan: Model): ReadonlyArray<Create | Delete> =>\n plan.operations.filter(\n (operation): operation is Create | Delete =>\n operation._tag === \"Create\" || operation._tag === \"Delete\",\n );\n\n/** Manual instructions for hosts whose customers apply records by hand. */\nexport const renderInstructions = (plan: Model): string =>\n plan.operations\n .map((operation) => {\n const verb =\n operation._tag === \"Create\"\n ? \"ADD\"\n : operation._tag === \"Delete\"\n ? \"REMOVE\"\n : operation._tag === \"Noop\"\n ? \"KEEP\"\n : `CONFLICT (${operation.reason})`;\n const record = operation.record;\n const ttl = record.ttl === null ? \"\" : ` ttl=${record.ttl}`;\n return `${verb} ${record._tag} ${record.name} ${DnsRecord.data(record)}${ttl}`;\n })\n .join(\"\\n\");\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,MAAM,SAAS,OAAO,cAAcA,OAAe;AACnD,MAAM,WAAW,OAAO,cAAcC,UAAkB;AAExD,MAAa,SAAS,OAAO,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAG1E,MAAa,SAAS,OAAO,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAG1E,MAAa,cAAc,OAAO,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAGpF,MAAa,OAAO,OAAO,SAAS,CAAC,gBAAgB,SAAS,CAAC;AAG/D,IAAa,SAAb,cAA4B,OAAO,YAAoB,wBAAwB,CAAC,CAAC,UAAU;CACzF,IAAI;CACJ,QAAQ;AACV,CAAC,CAAC,CAAC,CAAC;AACJ,IAAa,OAAb,cAA0B,OAAO,YAAkB,sBAAsB,CAAC,CAAC,QAAQ;CACjF,IAAI;CACJ,QAAQ;CACR,UAAU;CACV,UAAU,OAAO;AACnB,CAAC,CAAC,CAAC,CAAC;AACJ,IAAa,WAAb,cAA8B,OAAO,YAAsB,0BAA0B,CAAC,CAAC,YAAY;CACjG,IAAI;CACJ,QAAQ;CACR,UAAU,OAAO,MAAM,QAAQ;CAC/B,QAAQ,OAAO,SAAS;EACtB;EACA;EACA;EACA;EACA;CACF,CAAC;AACH,CAAC,CAAC,CAAC,CAAC;;AAEJ,IAAa,SAAb,cAA4B,OAAO,YAAoB,wBAAwB,CAAC,CAAC,UAAU;CACzF,IAAI;CACJ,QAAQ;CACR,kBAAkB,OAAO;AAC3B,CAAC,CAAC,CAAC,CAAC;AACJ,MAAa,YAAY,OAAO,MAAM;CAAC;CAAQ;CAAM;CAAU;AAAM,CAAC;AAGtE,MAAa,UAAU,OAAO,QAAQ,mBAAmB;AAEzD,IAAa,QAAb,cAA2B,OAAO,MAAa,iBAAiB,CAAC,CAAC;CAChE,IAAI;CACJ,SAAS;CACT,MAAM;CACN,QAAQ;CACR,QAAQ,OAAO;CACf,MAAM,OAAO;CACb,UAAU,OAAO;CACjB,cAAc,OAAO;CACrB,YAAY,OAAO,MAAM,SAAS;CAClC,WAAW,OAAO;CAClB,WAAW,OAAO;AACpB,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAa,UAAU,UACrBC,SAAc,OAAO,KAAK;AAC5B,MAAa,SAAmC,OAAO,WAAW,KAAK;;AAGvE,MAAa,gBAAgB,SAC3B,UAAU,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC,SAAS;AAExD,MAAa,aAAa,SACxB,KAAK,WAAW,QAAQ,cAAqC,UAAU,SAAS,UAAU;;AAG5F,MAAa,UAAU,SACrB,KAAK,WAAW,QACb,cACC,UAAU,SAAS,YAAY,UAAU,SAAS,QACtD;;AAGF,MAAa,sBAAsB,SACjC,KAAK,WACF,KAAK,cAAc;CAClB,MAAM,OACJ,UAAU,SAAS,WACf,QACA,UAAU,SAAS,WACjB,WACA,UAAU,SAAS,SACjB,SACA,aAAa,UAAU,OAAO;CACxC,MAAM,SAAS,UAAU;CACzB,MAAM,MAAM,OAAO,QAAQ,OAAO,KAAK,QAAQ,OAAO;CACtD,OAAO,GAAG,KAAK,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAGC,KAAe,MAAM,IAAI;AAC3E,CAAC,CAAC,CACD,KAAK,IAAI"}
1
+ {"version":3,"file":"Plan.mjs","names":["DnsRecord.Model","DnsRecord.Observed","Errors.decode","DnsRecord.data"],"sources":["../src/Plan.ts"],"sourcesContent":["/**\n * A reviewable, digest-bound set of DNS operations. Plans are additive and fail closed: matching\n * records are `Noop`, missing records are `Create`, incompatible state is `Conflict`. A\n * provisioning plan never updates or deletes; a cleanup plan carries `Delete` operations for\n * records a receipt proves DomainKit created.\n */\nimport { Effect, Schema } from \"effect\";\n\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\n\n// Read lazily: DnsRecord sits in the same import cycle (see DnsRecord.ts).\nconst Record = Schema.suspend(() => DnsRecord.Model);\nconst Observed = Schema.suspend(() => DnsRecord.Observed);\n\nexport const PlanId = Schema.String.pipe(Schema.brand(\"@domainkit/PlanId\"));\nexport type PlanId = typeof PlanId.Type;\n\nexport const Digest = Schema.String.pipe(Schema.brand(\"@domainkit/Digest\"));\nexport type Digest = typeof Digest.Type;\n\nexport const OperationId = Schema.String.pipe(Schema.brand(\"@domainkit/OperationId\"));\nexport type OperationId = typeof OperationId.Type;\n\nexport const Kind = Schema.Literals([\"provisioning\", \"cleanup\"]);\nexport type Kind = typeof Kind.Type;\n\nexport class Create extends Schema.TaggedClass<Create>(\"@domainkit/Plan/Create\")(\"Create\", {\n id: OperationId,\n record: Record,\n}) {}\nexport class Noop extends Schema.TaggedClass<Noop>(\"@domainkit/Plan/Noop\")(\"Noop\", {\n id: OperationId,\n record: Record,\n existing: Observed,\n ttlDrift: Schema.Boolean,\n}) {}\nexport class Conflict extends Schema.TaggedClass<Conflict>(\"@domainkit/Plan/Conflict\")(\"Conflict\", {\n id: OperationId,\n record: Record,\n existing: Schema.Array(Observed),\n reason: Schema.Literals([\n \"exclusive-name\",\n \"spf-conflict\",\n \"cname-collision\",\n \"value-mismatch\",\n \"opaque\",\n \"missing\",\n ]),\n}) {}\n/** Cleanup only: remove the provider record a receipt proves DomainKit created. */\nexport class Delete extends Schema.TaggedClass<Delete>(\"@domainkit/Plan/Delete\")(\"Delete\", {\n id: OperationId,\n record: Record,\n providerRecordId: Schema.String,\n}) {}\nexport const Operation = Schema.Union([Create, Noop, Conflict, Delete]);\nexport type Operation = typeof Operation.Type;\n\nexport const Version = Schema.Literal(\"domainkit.plan.v2\");\n\nexport class Model extends Schema.Class<Model>(\"@domainkit/Plan\")({\n id: PlanId,\n version: Version,\n kind: Kind,\n digest: Digest,\n domain: Schema.String,\n zone: Schema.String,\n provider: Schema.String,\n attachmentId: Schema.String,\n operations: Schema.Array(Operation),\n createdAt: Schema.DateTimeUtcFromString,\n expiresAt: Schema.DateTimeUtcFromString,\n}) {}\nexport type Encoded = typeof Model.Encoded;\n\nexport const decode = (input: unknown): Effect.Effect<Model, Errors.DomainKitError> =>\n Errors.decode(Model, input);\nexport const encode: (plan: Model) => Encoded = Schema.encodeSync(Model);\n\n/** `true` when the plan has no conflicts and at least one write (`Create` or `Delete`). */\nexport const isApplicable = (plan: Model): boolean =>\n conflicts(plan).length === 0 && writes(plan).length > 0;\n\nexport const conflicts = (plan: Model): ReadonlyArray<Conflict> =>\n plan.operations.filter((operation): operation is Conflict => operation._tag === \"Conflict\");\n\n/** The operations that would touch the provider: creates for provisioning, deletes for cleanup. */\nexport const writes = (plan: Model): ReadonlyArray<Create | Delete> =>\n plan.operations.filter(\n (operation): operation is Create | Delete =>\n operation._tag === \"Create\" || operation._tag === \"Delete\",\n );\n\n/** Manual instructions for hosts whose customers apply records by hand. */\nexport const renderInstructions = (plan: Model): string =>\n plan.operations\n .map((operation) => {\n const verb =\n operation._tag === \"Create\"\n ? \"ADD\"\n : operation._tag === \"Delete\"\n ? \"REMOVE\"\n : operation._tag === \"Noop\"\n ? \"KEEP\"\n : `CONFLICT (${operation.reason})`;\n const record = operation.record;\n const ttl = record.ttl === null ? \"\" : ` ttl=${record.ttl}`;\n return `${verb} ${record._tag} ${record.name} ${DnsRecord.data(record)}${ttl}`;\n })\n .join(\"\\n\");\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,MAAM,SAAS,OAAO,cAAcA,OAAe;AACnD,MAAM,WAAW,OAAO,cAAcC,UAAkB;AAExD,MAAa,SAAS,OAAO,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAG1E,MAAa,SAAS,OAAO,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAG1E,MAAa,cAAc,OAAO,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAGpF,MAAa,OAAO,OAAO,SAAS,CAAC,gBAAgB,SAAS,CAAC;AAG/D,IAAa,SAAb,cAA4B,OAAO,YAAoB,wBAAwB,CAAC,CAAC,UAAU;CACzF,IAAI;CACJ,QAAQ;AACV,CAAC,CAAC,CAAC,CAAC;AACJ,IAAa,OAAb,cAA0B,OAAO,YAAkB,sBAAsB,CAAC,CAAC,QAAQ;CACjF,IAAI;CACJ,QAAQ;CACR,UAAU;CACV,UAAU,OAAO;AACnB,CAAC,CAAC,CAAC,CAAC;AACJ,IAAa,WAAb,cAA8B,OAAO,YAAsB,0BAA0B,CAAC,CAAC,YAAY;CACjG,IAAI;CACJ,QAAQ;CACR,UAAU,OAAO,MAAM,QAAQ;CAC/B,QAAQ,OAAO,SAAS;EACtB;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH,CAAC,CAAC,CAAC,CAAC;;AAEJ,IAAa,SAAb,cAA4B,OAAO,YAAoB,wBAAwB,CAAC,CAAC,UAAU;CACzF,IAAI;CACJ,QAAQ;CACR,kBAAkB,OAAO;AAC3B,CAAC,CAAC,CAAC,CAAC;AACJ,MAAa,YAAY,OAAO,MAAM;CAAC;CAAQ;CAAM;CAAU;AAAM,CAAC;AAGtE,MAAa,UAAU,OAAO,QAAQ,mBAAmB;AAEzD,IAAa,QAAb,cAA2B,OAAO,MAAa,iBAAiB,CAAC,CAAC;CAChE,IAAI;CACJ,SAAS;CACT,MAAM;CACN,QAAQ;CACR,QAAQ,OAAO;CACf,MAAM,OAAO;CACb,UAAU,OAAO;CACjB,cAAc,OAAO;CACrB,YAAY,OAAO,MAAM,SAAS;CAClC,WAAW,OAAO;CAClB,WAAW,OAAO;AACpB,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAa,UAAU,UACrBC,SAAc,OAAO,KAAK;AAC5B,MAAa,SAAmC,OAAO,WAAW,KAAK;;AAGvE,MAAa,gBAAgB,SAC3B,UAAU,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC,SAAS;AAExD,MAAa,aAAa,SACxB,KAAK,WAAW,QAAQ,cAAqC,UAAU,SAAS,UAAU;;AAG5F,MAAa,UAAU,SACrB,KAAK,WAAW,QACb,cACC,UAAU,SAAS,YAAY,UAAU,SAAS,QACtD;;AAGF,MAAa,sBAAsB,SACjC,KAAK,WACF,KAAK,cAAc;CAClB,MAAM,OACJ,UAAU,SAAS,WACf,QACA,UAAU,SAAS,WACjB,WACA,UAAU,SAAS,SACjB,SACA,aAAa,UAAU,OAAO;CACxC,MAAM,SAAS,UAAU;CACzB,MAAM,MAAM,OAAO,QAAQ,OAAO,KAAK,QAAQ,OAAO;CACtD,OAAO,GAAG,KAAK,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAGC,KAAe,MAAM,IAAI;AAC3E,CAAC,CAAC,CACD,KAAK,IAAI"}
package/dist/Server.mjs CHANGED
@@ -13,14 +13,14 @@ import { Service as Service$4 } from "./Provision.mjs";
13
13
  import { Service as Service$5 } from "./Cleanup.mjs";
14
14
  import { Evidence, HostEvidence, Service as Service$6 } from "./Verify.mjs";
15
15
  import { Context, Effect, Layer, Option, Schema } from "effect";
16
- import * as HttpRouter from "effect/unstable/http/HttpRouter";
17
- import * as HttpServer from "effect/unstable/http/HttpServer";
18
- import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
19
- import * as HttpApi from "effect/unstable/httpapi/HttpApi";
20
- import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
21
- import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
22
- import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
23
- import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema";
16
+ import * as HttpRouter from "effect/http/HttpRouter";
17
+ import * as HttpServer from "effect/http/HttpServer";
18
+ import * as HttpServerRequest from "effect/http/HttpServerRequest";
19
+ import * as HttpApi from "effect/http-api/HttpApi";
20
+ import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
21
+ import * as HttpApiEndpoint from "effect/http-api/HttpApiEndpoint";
22
+ import * as HttpApiGroup from "effect/http-api/HttpApiGroup";
23
+ import * as HttpApiSchema from "effect/http-api/HttpApiSchema";
24
24
  //#region src/Server.ts
25
25
  /**
26
26
  * domainkit/server — the whole lifecycle as one HttpApi group the host mounts.
@@ -1 +1 @@
1
- {"version":3,"file":"Server.mjs","names":["Storage.AuthMethod","DnsRecord.Model","Plan.OperationId","Plan.Model","Storage.AttemptStatus","Approval.Model","Receipt.Model","Storage.Rejection","Plan.Digest","Storage.BatchId","Storage.BatchStatus","Storage.BatchApproval","Storage.Overall","Storage.RequirementStatus","Verify.Evidence","Verify.HostEvidence","Plan.PlanId","Approval.ApprovalId","Receipt.ReceiptId","Errors.fail","Reason.InvalidInput","Errors.DomainKitError","Connect.Service","Provision.Service","Cleanup.Service","Verify.Service","Storage.Service","Providers.Service","Principal.Service","Reason.NotFound"],"sources":["../src/Server.ts"],"sourcesContent":["/**\n * domainkit/server — the whole lifecycle as one HttpApi group the host mounts.\n *\n * const Api = HttpApi.make(\"app\").add(Server.group)\n * const ApiLive = HttpApiBuilder.layer(Api).pipe(\n * Layer.provide([Server.layer(Api), DomainKitLive, IdentityLive]),\n * )\n *\n * The host provides `Identity` (request -> `Principal.Interface`). Everything else comes from\n * `DomainKit.layer`. Non-HttpApi hosts use `toWebHandler`. The group carries no prefix of its\n * own, so `group.prefix(\"/internal/dns\")` or a host router mount is all a different base path\n * takes; callback URLs follow the mount.\n */\nimport { Context, Effect, Layer, Option, Schema } from \"effect\";\nimport * as HttpRouter from \"effect/unstable/http/HttpRouter\";\nimport * as HttpServer from \"effect/unstable/http/HttpServer\";\nimport * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport * as HttpApi from \"effect/unstable/httpapi/HttpApi\";\nimport * as HttpApiBuilder from \"effect/unstable/httpapi/HttpApiBuilder\";\nimport * as HttpApiEndpoint from \"effect/unstable/httpapi/HttpApiEndpoint\";\nimport * as HttpApiGroup from \"effect/unstable/httpapi/HttpApiGroup\";\nimport * as HttpApiSchema from \"effect/unstable/httpapi/HttpApiSchema\";\n\nimport * as Approval from \"./Approval.ts\";\nimport * as Cleanup from \"./Cleanup.ts\";\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Provision from \"./Provision.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Receipt from \"./Receipt.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Verify from \"./Verify.ts\";\n\n/**\n * What DomainKit already knows about the request when it asks the host who is making it. Only\n * `/callback/:provider` passes one, because it is the only route that points at a flow DomainKit\n * recorded earlier; every other route calls `principal` with the request alone.\n */\nexport interface IdentityContext {\n /** The route being served. */\n readonly endpoint: EndpointName;\n /** The flow this callback claims to finish, as DomainKit recorded it when the flow started. */\n readonly continuation: Storage.ContinuationHeader;\n}\n\n/** The only service a host must implement for the server. */\nexport interface IdentityService {\n /**\n * `context` is present only on the callback, where a host that keeps per-request tenancy can use\n * `context.continuation.ownerId` to pick the right tenant out of a session that holds several.\n * Ignoring it is safe everywhere: DomainKit checks the principal against the recorded flow on\n * that route regardless, so a host that answers with the wrong one is refused rather than\n * trusted.\n */\n readonly principal: (\n request: HttpServerRequest.HttpServerRequest,\n context?: IdentityContext,\n ) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;\n /**\n * Which routes this principal may reach, checked after `principal` on every request. Fail with\n * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches\n * every route, which is the right default for a host whose own middleware already gates the\n * mount.\n */\n readonly authorize?: (\n principal: Principal.Interface,\n endpoint: EndpointName,\n ) => Effect.Effect<void, Errors.DomainKitError>;\n}\n\nexport class Identity extends Context.Service<Identity, IdentityService>()(\n \"@domainkit/server/Identity\",\n) {}\n\n// ---------------------------------------------------------------------------------------------\n// Wire schemas (the transport contract `domainkit/client` and `@domainkit/react` consume)\n// ---------------------------------------------------------------------------------------------\n\nexport const ConnectionStatus = Schema.Literals([\"disconnected\", \"connected\", \"reconnect\"]);\nexport type ConnectionStatus = typeof ConnectionStatus.Type;\n\n/** One value a token method needs, so a form renders without knowing the provider. */\nexport const Field = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n secret: Schema.Boolean,\n});\nexport type Field = typeof Field.Type;\n\n/** How a provider can be connected; `fields` is `null` for the interactive methods. */\nexport const MethodDescriptor = Schema.Struct({\n kind: Storage.AuthMethod,\n label: Schema.String,\n docsUrl: Schema.NullOr(Schema.String),\n fields: Schema.NullOr(Schema.Array(Field)),\n});\nexport type MethodDescriptor = typeof MethodDescriptor.Type;\n\n/** Everything the UI needs about a domain, flattened; provider context never crosses the wire. */\n/** The attachment a domain holds, with the zone label the provider gave when it was created. */\nexport const Attachment = Schema.Struct({\n id: Schema.String,\n label: Schema.String,\n});\nexport type Attachment = typeof Attachment.Type;\n\nexport const Snapshot = Schema.Struct({\n domain: Schema.String,\n attachment: Schema.NullOr(Attachment),\n connectionId: Schema.NullOr(Schema.String),\n provider: Schema.NullOr(Schema.String),\n method: Schema.NullOr(Storage.AuthMethod),\n status: ConnectionStatus,\n lastReceiptId: Schema.NullOr(Schema.String),\n /** How many domains the connection serves, including this one; `0` without a connection. */\n connectionDomains: Schema.Number,\n reusable: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n method: Storage.AuthMethod,\n }),\n ),\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Snapshot = typeof Snapshot.Type;\n\n/**\n * How the client asks to connect. `values` is keyed by the provider's declared token fields, as\n * `Snapshot.providers[].methods[].fields` names them. Secrets are plaintext in transit and never\n * come back out.\n */\nexport class Token extends Schema.TaggedClass<Token>(\"@domainkit/server/Method/Token\")(\"Token\", {\n values: Schema.Record(Schema.String, Schema.String),\n}) {}\nexport class OAuth extends Schema.TaggedClass<OAuth>(\"@domainkit/server/Method/OAuth\")(\"OAuth\", {\n returnTo: Schema.optionalKey(Schema.String),\n}) {}\nexport class Integration extends Schema.TaggedClass<Integration>(\n \"@domainkit/server/Method/Integration\",\n)(\"Integration\", { returnTo: Schema.optionalKey(Schema.String) }) {}\nexport const Method = Schema.Union([Token, OAuth, Integration]);\nexport type Method = typeof Method.Type;\n\n/** A start without a domain connects the account alone; the customer picks a zone afterwards. */\nexport const StartPayload = Schema.Struct({\n domain: Schema.optionalKey(Schema.String),\n provider: Schema.String,\n method: Method,\n});\nexport type StartPayload = typeof StartPayload.Type;\n\n/** How the customer proves the account again. The provider is the connection's own. */\nexport const ReconnectPayload = Schema.Struct({ method: Method });\nexport type ReconnectPayload = typeof ReconnectPayload.Type;\n\n/** A zone the connection can serve, named for a customer to pick from. */\nexport class Candidate extends Schema.Class<Candidate>(\"@domainkit/server/Candidate\")({\n zone: Schema.String,\n label: Schema.String,\n}) {}\n\n/**\n * The connection the start produced. `label` names the account the way the customer will read it:\n * the attached zone's label, or the provider's name when the start carried no domain. `snapshot`\n * is the domain's state, and is null for a start that attached none.\n */\nexport class Connected extends Schema.TaggedClass<Connected>(\"@domainkit/server/Started/Connected\")(\n \"Connected\",\n {\n connectionId: Schema.String,\n provider: Schema.String,\n label: Schema.String,\n snapshot: Schema.NullOr(Snapshot),\n },\n) {}\nexport class Redirect extends Schema.TaggedClass<Redirect>(\"@domainkit/server/Started/Redirect\")(\n \"Redirect\",\n { authorizationUrl: Schema.String },\n) {}\nexport class SelectionRequired extends Schema.TaggedClass<SelectionRequired>(\n \"@domainkit/server/Started/SelectionRequired\",\n)(\"SelectionRequired\", { connectionId: Schema.String, candidates: Schema.Array(Candidate) }) {}\nexport const Started = Schema.Union([Connected, Redirect, SelectionRequired]);\nexport type Started = typeof Started.Type;\n\n/**\n * Which of the principal's connections already reaches a domain. The class names carry a\n * `Discovery` prefix because `Started` owns the unprefixed `SelectionRequired`; the wire tags are\n * the ones `Connect.Discovery` uses.\n */\nexport class DiscoveryResolved extends Schema.TaggedClass<DiscoveryResolved>(\n \"@domainkit/server/Discovery/Resolved\",\n)(\"Resolved\", { connectionId: Schema.String, zone: Schema.String, label: Schema.String }) {}\nexport class DiscoverySelectionRequired extends Schema.TaggedClass<DiscoverySelectionRequired>(\n \"@domainkit/server/Discovery/SelectionRequired\",\n)(\"SelectionRequired\", {\n candidates: Schema.Array(\n Schema.Struct({ connectionId: Schema.String, zone: Schema.String, label: Schema.String }),\n ),\n}) {}\n/** `host` names the registered provider whose nameservers serve the domain, by definition id. */\nexport class DiscoveryNotFound extends Schema.TaggedClass<DiscoveryNotFound>(\n \"@domainkit/server/Discovery/NotFound\",\n)(\"NotFound\", {\n nameservers: Schema.Array(Schema.String),\n host: Schema.NullOr(Schema.Struct({ provider: Schema.String })),\n}) {}\nexport const Discovery = Schema.Union([\n DiscoveryResolved,\n DiscoverySelectionRequired,\n DiscoveryNotFound,\n]);\nexport type Discovery = typeof Discovery.Type;\n\n/** One zone a connection reaches, named for a customer to pick from. */\nexport const Zone = Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n zone: Schema.String,\n label: Schema.String,\n nameservers: Schema.optionalKey(Schema.Array(Schema.String)),\n});\nexport type Zone = typeof Zone.Type;\n\n/**\n * Every zone the principal's connections reach, and where each connection stands. A connection\n * marked `reconnect` contributes no zones and needs the customer to authorize it again.\n */\nexport const Zones = Schema.Struct({\n zones: Schema.Array(Zone),\n connections: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n /** What the provider called the account at connect time; `null` when it named none. */\n label: Schema.NullOr(Schema.String),\n status: ConnectionStatus,\n }),\n ),\n /** Every provider a customer could connect, so a picker offers one without a second call. */\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Zones = typeof Zones.Type;\n\nexport const AttachPayload = Schema.Struct({\n domain: Schema.String,\n /** Which candidate zone to attach when `SelectionRequired` came back. */\n zone: Schema.optionalKey(Schema.String),\n});\nexport type AttachPayload = typeof AttachPayload.Type;\n\nexport const PlanPayload = Schema.Struct({\n requirements: Schema.Array(DnsRecord.Model),\n});\nexport type PlanPayload = typeof PlanPayload.Type;\n\nexport const ObservePayload = Schema.Struct({\n /** Records to observe instead of the attachment's receipt; required for an unattached domain. */\n requirements: Schema.optionalKey(Schema.Array(DnsRecord.Model)),\n});\nexport type ObservePayload = typeof ObservePayload.Type;\n\nexport const ApprovePayload = Schema.Struct({\n /** A subset of the plan's writes; omit to approve every write. */\n operationIds: Schema.optionalKey(Schema.Array(Plan.OperationId)),\n});\nexport type ApprovePayload = typeof ApprovePayload.Type;\n\nexport const RejectPayload = Schema.Struct({\n /** Why the customer declined, for the audit trail. */\n reason: Schema.optionalKey(Schema.String),\n});\nexport type RejectPayload = typeof RejectPayload.Type;\n\n/** One durable plan -> approval -> receipt lifecycle, as `GET /plans/:planId` returns it. */\nexport const Attempt = Schema.Struct({\n plan: Plan.Model,\n status: Storage.AttemptStatus,\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n});\nexport type Attempt = typeof Attempt.Type;\n\n/** What a batch's create and resume routes take: one domain and its requirements per item. */\nexport const BatchPayload = Schema.Struct({\n items: Schema.Array(\n Schema.Struct({ domain: Schema.String, requirements: Schema.Array(DnsRecord.Model) }),\n ),\n});\nexport type BatchPayload = typeof BatchPayload.Type;\n\n/** The digest the principal read, which the batch's current plans must still produce. */\nexport const BatchApprovePayload = Schema.Struct({ digest: Plan.Digest });\nexport type BatchApprovePayload = typeof BatchApprovePayload.Type;\n\n/** One domain's place in a batch, with whatever its attempt holds. */\nexport const BatchItem = Schema.Struct({\n attachmentId: Schema.String,\n position: Schema.Number,\n /** Null while the item has no plan; `planFailure` then says why. */\n plan: Schema.NullOr(Plan.Model),\n status: Schema.NullOr(Storage.AttemptStatus),\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n failure: Schema.NullOr(Schema.String),\n planFailure: Schema.NullOr(Schema.String),\n});\nexport type BatchItem = typeof BatchItem.Type;\n\n/** Many domains planned together, as every batch route returns them. */\nexport const Batch = Schema.Struct({\n id: Storage.BatchId,\n status: Storage.BatchStatus,\n /** What `POST /batches/:batchId/approvals` takes; null while any item is unplanned. */\n digest: Schema.NullOr(Plan.Digest),\n approval: Schema.NullOr(Storage.BatchApproval),\n rejection: Schema.NullOr(Storage.Rejection),\n createdAt: Schema.DateTimeUtcFromString,\n updatedAt: Schema.DateTimeUtcFromString,\n completedAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n items: Schema.Array(BatchItem),\n});\nexport type Batch = typeof Batch.Type;\n\n/** A batch without its plans, as the unfinished index returns them. */\nexport const BatchSummary = Schema.Struct({\n id: Storage.BatchId,\n status: Storage.BatchStatus,\n itemCount: Schema.Number,\n createdAt: Schema.DateTimeUtcFromString,\n updatedAt: Schema.DateTimeUtcFromString,\n completedAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type BatchSummary = typeof BatchSummary.Type;\n\n/** `Verify.Readiness` on the wire; timestamps encode as ISO strings. */\nexport const Readiness = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n overall: Storage.Overall,\n requirements: Schema.Array(\n Schema.Struct({\n /** `Verify.requirementKey(record)`, so a client pairs rows by content, not by position. */\n key: Schema.String,\n operationId: Schema.NullOr(Plan.OperationId),\n record: DnsRecord.Model,\n status: Storage.RequirementStatus,\n evidence: Schema.Array(Verify.Evidence),\n }),\n ),\n host: Schema.Array(Verify.HostEvidence),\n checkedAt: Schema.DateTimeUtcFromString,\n nextCheckAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type Readiness = typeof Readiness.Type;\n\nconst Redirected = HttpApiSchema.WithHeaders(HttpApiSchema.Empty(302), {\n location: Schema.String,\n});\n\n// ---------------------------------------------------------------------------------------------\n// Errors: one wire body, the status from `DomainKit.Error.httpStatus`\n// ---------------------------------------------------------------------------------------------\n\n/** `DomainKit.Error` narrowed to the reasons that answer with `status`. */\nconst errorAt = (status: number) =>\n Errors.DomainKitError.check(\n Schema.makeFilter((error: Errors.DomainKitError) => error.httpStatus === status),\n ).pipe(HttpApiSchema.status(status));\n\nconst errors = [400, 401, 403, 404, 409, 500, 501, 502, 503].map(errorAt);\n\n// ---------------------------------------------------------------------------------------------\n// The group\n// ---------------------------------------------------------------------------------------------\n\nexport const group = HttpApiGroup.make(\"domainkit\")\n .add(\n HttpApiEndpoint.get(\"inspect\", \"/domains/:domain\", {\n params: { domain: Schema.String },\n success: Snapshot,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"discover\", \"/domains/:domain/discovery\", {\n params: { domain: Schema.String },\n success: Discovery,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"zones\", \"/zones\", {\n query: { provider: Schema.optionalKey(Schema.String) },\n success: Zones,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"start\", \"/connections\", {\n payload: StartPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"callback\", \"/callback/:provider\", {\n params: { provider: Schema.String },\n // `Connect.complete` reads every callback parameter off the request URL, because a provider\n // may return `error`, `teamId`, or `configurationId` too. These two are declared so the\n // OpenAPI document names what a provider is expected to send back.\n query: { state: Schema.String, code: Schema.optionalKey(Schema.String) },\n success: Redirected,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reconnect\", \"/connections/:connectionId/reconnections\", {\n params: { connectionId: Schema.String },\n payload: ReconnectPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"attach\", \"/connections/:connectionId/attachments\", {\n params: { connectionId: Schema.String },\n payload: AttachPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"detach\", \"/attachments/:attachmentId\", {\n params: { attachmentId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"disconnect\", \"/connections/:connectionId\", {\n params: { connectionId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"createPlan\", \"/domains/:domain/plans\", {\n params: { domain: Schema.String },\n payload: PlanPayload,\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approve\", \"/plans/:planId/approvals\", {\n params: { planId: Plan.PlanId },\n payload: ApprovePayload,\n success: Approval.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reject\", \"/plans/:planId/rejections\", {\n params: { planId: Plan.PlanId },\n payload: RejectPayload,\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"apply\", \"/approvals/:approvalId/apply\", {\n params: { approvalId: Approval.ApprovalId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"plan\", \"/plans/:planId\", {\n params: { planId: Plan.PlanId },\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"receipt\", \"/receipts/:receiptId\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"observe\", \"/domains/:domain/observations\", {\n params: { domain: Schema.String },\n payload: ObservePayload,\n success: Readiness,\n error: errors,\n }),\n )\n .add(\n // The stored observation, or `null` when nothing has been observed for the domain yet. A\n // domain with no readiness is not a missing route, so it answers 200 with a null body and\n // leaves 404 to name a domain this owner cannot reach.\n HttpApiEndpoint.get(\"readiness\", \"/domains/:domain/readiness\", {\n params: { domain: Schema.String },\n success: Schema.NullOr(Readiness),\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"cleanupPlan\", \"/receipts/:receiptId/cleanup-plans\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n // `Idempotency-Key` is the batch's identity for this owner: a retried create answers with the\n // batch the first call made rather than planning the same domains twice.\n HttpApiEndpoint.post(\"createBatch\", \"/batches\", {\n headers: { \"idempotency-key\": Schema.String },\n payload: BatchPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n // The one listing there is: what this owner still owes a move on. A finished batch is read by\n // its own id.\n HttpApiEndpoint.get(\"batches\", \"/batches\", {\n query: { unfinished: Schema.Literal(\"true\") },\n success: Schema.Array(BatchSummary),\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"batch\", \"/batches/:batchId\", {\n params: { batchId: Storage.BatchId },\n success: Batch,\n error: errors,\n }),\n )\n .add(\n // Plan the items that still have none. The batch stores pointers, not requirements, so the\n // host supplies them again.\n HttpApiEndpoint.post(\"planBatch\", \"/batches/:batchId/plans\", {\n params: { batchId: Storage.BatchId },\n payload: BatchPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approveBatch\", \"/batches/:batchId/approvals\", {\n params: { batchId: Storage.BatchId },\n payload: BatchApprovePayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"rejectBatch\", \"/batches/:batchId/rejections\", {\n params: { batchId: Storage.BatchId },\n payload: RejectPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"applyBatch\", \"/batches/:batchId/apply\", {\n params: { batchId: Storage.BatchId },\n success: Batch,\n error: errors,\n }),\n );\n// Cleanup approve, reject, and apply reuse those routes: the attempt knows its kind.\n\nexport type Group = typeof group;\n\n/** Every route in the group, by name; what `Identity.authorize` is asked about. */\nexport type EndpointName = HttpApiGroup.Endpoints<Group>[\"identifier\"];\n\nexport interface Options {\n /**\n * Where the callback route is reachable from the provider, without the `/callback/:provider`\n * suffix. Defaults to the incoming request's origin and mount path.\n */\n readonly callbackBaseUrl?: string;\n /** Where interactive flows return when the callback carries no `returnTo`. */\n readonly defaultReturnTo?: string;\n}\n\n/** Everything the handlers need: the lifecycle services, Storage, and the host's `Identity`. */\nexport type Services =\n | Provision.Service\n | Cleanup.Service\n | Connect.Service\n | Verify.Service\n | Providers.Service\n | Storage.Service\n | Identity;\n\n// ---------------------------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------------------------\n\nconst START_ROUTE = \"/connections\";\nconst reconnectRoute = (connectionId: string) =>\n `/connections/${encodeURIComponent(connectionId)}/reconnections`;\nconst callbackRoute = (provider: string) => `/callback/${encodeURIComponent(provider)}`;\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** The request as an absolute URL: the web original when there is one, else scheme plus `Host`. */\nconst absoluteUrl = (\n request: HttpServerRequest.HttpServerRequest,\n): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n try {\n return Effect.succeed(new URL(request.originalUrl));\n } catch {\n const url = HttpServerRequest.toURL(request);\n return Option.isNone(url)\n ? invalid(\"The request has no usable origin; set callbackBaseUrl\", \"callbackBaseUrl\")\n : Effect.succeed(url.value);\n }\n });\n\n/**\n * The absolute URL the provider redirects back to. `callbackBaseUrl` wins when the host set it,\n * because a proxy that rewrites `Host` leaves the request pointing at an origin the browser never\n * sees; otherwise it follows the prefix the group is mounted at, derived by stripping the handling\n * route's own path off the request.\n */\nconst callbackUrlFor = (input: {\n readonly request: HttpServerRequest.HttpServerRequest;\n readonly provider: string;\n readonly options: Options;\n /** The path of the route serving this request, stripped to find the mount. */\n readonly route: string;\n}): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n const suffix = `/callback/${encodeURIComponent(input.provider)}`;\n const configured = input.options.callbackBaseUrl;\n if (configured !== undefined) {\n return Effect.try({\n try: () => new URL(`${configured.replace(/\\/+$/, \"\")}${suffix}`),\n catch: () => callbackConfigurationError(`${configured} is not a URL`),\n });\n }\n return Effect.flatMap(absoluteUrl(input.request), (url) => {\n const path = url.pathname;\n if (!path.endsWith(input.route)) {\n return invalid(\n `The ${input.route} route is not mounted where it says; set callbackBaseUrl`,\n \"callbackBaseUrl\",\n );\n }\n return Effect.succeed(\n new URL(`${url.origin}${path.slice(0, path.length - input.route.length)}${suffix}`),\n );\n });\n });\n\n/**\n * One refusal for every way a callback can fail to match a flow: no such `state`, one that has\n * expired, one presented at another provider's route, and one presented by a session that did not\n * start it. The text is constant on purpose — a caller holding a leaked `state` must not be able\n * to tell \"no such flow\" from \"not your flow\".\n */\nconst noSuchFlow = Errors.fail(\n new Reason.InvalidInput({\n message: \"This callback does not match a connection you started\",\n field: \"state\",\n }),\n);\n\nconst callbackConfigurationError = (message: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field: \"callbackBaseUrl\" }),\n });\n\n/**\n * Where the callback may send the customer: a path under the callback's own base, or an absolute\n * URL on its origin. A destination that leaves the application is an open redirect, whoever\n * supplied it.\n */\nconst sameOrigin = (destination: string, callback: URL): string | null => {\n try {\n // Resolving against the callback is what makes this safe: the URL parser normalizes the forms\n // a browser would follow off-origin (`//host`, `/\\host`, `\\/host`, encoded hosts) into a real\n // origin, so comparing origins catches every one of them.\n const url = new URL(destination, callback);\n return url.origin === callback.origin ? url.toString() : null;\n } catch {\n return null;\n }\n};\n\nconst snapshotOf = (snapshot: Connect.Snapshot): Snapshot => ({\n domain: snapshot.domain,\n attachment:\n snapshot.attachment === null\n ? null\n : { id: snapshot.attachment.id, label: snapshot.attachment.label },\n connectionId: snapshot.connection?.id ?? null,\n provider: snapshot.authorization?.provider ?? null,\n method: snapshot.authorization?.method ?? null,\n status:\n snapshot.connection === null\n ? \"disconnected\"\n : snapshot.authorization?.revocation === \"active\"\n ? \"connected\"\n : \"reconnect\",\n lastReceiptId: snapshot.lastReceiptId,\n connectionDomains: snapshot.connectionDomains,\n reusable: snapshot.reusable.map(({ connection, provider, method }) => ({\n connectionId: connection.id,\n provider,\n method,\n })),\n providers: snapshot.providers.map(({ id, name, methods }) => ({ id, name, methods })),\n});\n\n/** The wire method as `Connect` takes it; interactive cases carry the caller's return destination. */\nconst methodOf = (method: Method): Connect.Method => {\n switch (method._tag) {\n case \"Token\":\n return Connect.Method.token(method.values);\n case \"OAuth\":\n return Connect.Method.oauth(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n case \"Integration\":\n return Connect.Method.integration(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n }\n};\n\nconst candidatesOf = (\n candidates: ReadonlyArray<{ readonly zone: string; readonly label: string }>,\n): ReadonlyArray<Candidate> => candidates.map(({ zone, label }) => new Candidate({ zone, label }));\n\n/**\n * Handlers for `group` inside the host's API. Every handler derives `Principal` from `Identity`\n * for the request it is serving and provides it to the lifecycle services.\n */\nexport const layer = <ApiId extends string, Groups extends HttpApiGroup.Constraint>(\n api: HttpApi.HttpApi<ApiId, Groups>,\n options: Options = {},\n): Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services> =>\n // The host's API carries its own groups beside this one; only `\"domainkit\"` is implemented here,\n // and `HttpApiBuilder.group` needs the group's own endpoint types to check the handlers.\n HttpApiBuilder.group(api as unknown as HttpApi.HttpApi<ApiId, Group>, \"domainkit\", (handlers) =>\n Effect.gen(function* () {\n const identity = yield* Identity;\n const connect = yield* Connect.Service;\n const provision = yield* Provision.Service;\n const cleanup = yield* Cleanup.Service;\n const verify = yield* Verify.Service;\n const storage = yield* Storage.Service;\n const providers = yield* Providers.Service;\n\n /**\n * Run a lifecycle effect as the principal the host derives from this request, once the host\n * has allowed that principal onto this route.\n */\n const as = <A>(\n endpoint: EndpointName,\n request: HttpServerRequest.HttpServerRequest,\n effect: Effect.Effect<A, Errors.DomainKitError, Principal.Service>,\n context?: IdentityContext,\n ): Effect.Effect<A, Errors.DomainKitError> =>\n Effect.flatMap(identity.principal(request, context), (principal) =>\n Effect.flatMap(identity.authorize?.(principal, endpoint) ?? Effect.void, () =>\n Effect.provideService(effect, Principal.Service, principal),\n ),\n );\n\n const snapshot = (domain: string) => Effect.map(connect.inspect(domain), snapshotOf);\n\n const discovered = (discovery: Connect.Discovery): Discovery => {\n switch (discovery._tag) {\n case \"Resolved\":\n return new DiscoveryResolved({\n connectionId: discovery.connectionId,\n zone: discovery.target.zone,\n label: discovery.target.label,\n });\n case \"SelectionRequired\":\n return new DiscoverySelectionRequired({\n candidates: discovery.candidates.map(({ connectionId, target }) => ({\n connectionId,\n zone: target.zone,\n label: target.label,\n })),\n });\n case \"NotFound\":\n return new DiscoveryNotFound({\n nameservers: discovery.nameservers,\n host: discovery.host,\n });\n }\n };\n\n /**\n * The connection a start or an attach produced. `label` is the zone the attachment named,\n * or the provider itself where the connection carries no domain yet.\n */\n const connected = (input: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n readonly domain: string | undefined;\n }): Effect.Effect<Connected, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(input.connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n return new Connected({\n connectionId: input.connection.id,\n provider: authorization.provider,\n label: input.attachment?.label ?? authorization.label ?? definition.name,\n snapshot: input.domain === undefined ? null : yield* snapshot(input.domain),\n });\n });\n\n const startedOf = (\n started: Connect.Started,\n domain: string | undefined,\n ): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> => {\n switch (started._tag) {\n case \"Connected\":\n return connected({\n attachment: started.attachment,\n connection: started.connection,\n domain,\n });\n case \"Redirect\":\n return Effect.succeed(new Redirect({ authorizationUrl: started.authorizationUrl }));\n case \"SelectionRequired\":\n return Effect.succeed(\n new SelectionRequired({\n connectionId: started.connection.id,\n candidates: candidatesOf(started.candidates),\n }),\n );\n }\n };\n\n /**\n * Attach with a zone the client picked out of a `SelectionRequired`. The provider target\n * behind a zone never crosses the wire, so resolve the candidates again and match by zone.\n */\n const attachAt = (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly zone: string | undefined;\n }): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const attached = (attachment: Storage.Attachment) =>\n connected({ attachment, connection, domain: input.domain });\n const first = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n });\n if (!(\"_tag\" in first)) return yield* attached(first);\n if (input.zone === undefined) {\n return new SelectionRequired({\n connectionId: input.connectionId,\n candidates: candidatesOf(first.candidates),\n });\n }\n const target = first.candidates.find(({ zone }) => zone === input.zone);\n if (target === undefined) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }));\n }\n const second = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n target,\n });\n return \"_tag\" in second\n ? yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }))\n : yield* attached(second);\n });\n\n /** Provisioning and cleanup share the approve and apply routes; the attempt knows its kind. */\n const kindOf = (attempt: Storage.Attempt) =>\n attempt.kind === \"cleanup\" ? cleanup : provision;\n\n return handlers\n .handle(\"inspect\", ({ params, request }) => as(\"inspect\", request, snapshot(params.domain)))\n .handle(\"discover\", ({ params, request }) =>\n as(\"discover\", request, Effect.map(connect.discover(params.domain), discovered)),\n )\n .handle(\"zones\", ({ query, request }) =>\n as(\n \"zones\",\n request,\n Effect.map(\n connect.zones(query.provider === undefined ? {} : { provider: query.provider }),\n (listing): Zones => ({\n zones: listing.zones.map(({ connectionId, provider, target }) => ({\n connectionId,\n provider,\n zone: target.zone,\n label: target.label,\n ...(target.nameservers === undefined ? {} : { nameservers: target.nameservers }),\n })),\n connections: listing.connections,\n providers: listing.providers,\n }),\n ),\n ),\n )\n .handle(\"start\", ({ payload, request }) =>\n as(\n \"start\",\n request,\n Effect.gen(function* () {\n // Token methods connect in one call; the interactive ones need somewhere to land.\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: payload.provider,\n options,\n route: START_ROUTE,\n })).toString();\n const started = yield* connect.start({\n provider: payload.provider,\n ...(payload.domain === undefined ? {} : { domain: payload.domain }),\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, payload.domain);\n }),\n ),\n )\n .handle(\"reconnect\", ({ params, payload, request }) =>\n as(\n \"reconnect\",\n request,\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(params.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: authorization.provider,\n options,\n route: reconnectRoute(params.connectionId),\n })).toString();\n const started = yield* connect.reconnect({\n connectionId: params.connectionId,\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, undefined);\n }),\n ),\n )\n .handle(\"callback\", ({ params, query, request }) =>\n Effect.gen(function* () {\n // The one route that establishes its identity from something other than the request.\n // A provider callback is a top-level navigation carrying a `state` the provider echoed\n // back and whatever credential the browser attaches by itself, so the flow DomainKit\n // recorded is the only authority on whose it is. Read its header first — a read, never\n // a spend, so a leaked `state` cannot burn the flow it names — tell the host which\n // flow is being finished, and then hold the host's answer against the record.\n const header = yield* storage.continuations\n .header(query.state)\n .pipe(\n Effect.catch((error) =>\n error.reason._tag === \"NotFound\" || error.reason._tag === \"Expired\"\n ? Effect.succeed(null)\n : Effect.fail(error),\n ),\n );\n const continuation =\n header !== null && header.provider === params.provider ? header : null;\n // The host authenticates even when there is nothing to finish, so the status cannot\n // tell a caller whether the `state` it holds names a flow at all.\n return yield* as(\n \"callback\",\n request,\n Effect.gen(function* () {\n // The owner comes from the record and the actor from the host's own session;\n // nothing in the query string contributes either. The scoped `get` below keeps\n // its owner filter, so a host that answers with the wrong principal still fails\n // closed even if this check were ever removed.\n const principal = yield* Principal.Service;\n if (\n continuation === null ||\n principal.ownerId !== continuation.ownerId ||\n principal.actorId !== continuation.actorId\n ) {\n return yield* noSuchFlow;\n }\n const url = yield* absoluteUrl(request);\n // Resolve the destination against the callback's public base, not the request: a\n // proxy that rewrites `Host` leaves the request on an origin the browser never\n // sees.\n const base = yield* callbackUrlFor({\n request,\n provider: params.provider,\n options,\n route: callbackRoute(params.provider),\n });\n // Where the customer lands comes from the continuation this owner started, never\n // from the provider's query string, and it is resolved before the callback is\n // spent: a server with nowhere to send them must not connect the provider and\n // then fail.\n // The host's authentication sits between the header read and this one, so the\n // flow can expire or be spent in that window. It refuses the same way it would\n // have a moment earlier, rather than changing shape because of when it happened.\n // This is also where the owner filter fails closed when a host resolves the wrong\n // principal, and that refusal must look like every other one.\n const flow = yield* storage.continuations\n .get(query.state)\n .pipe(\n Effect.catch((error) =>\n error.reason._tag === \"NotFound\" || error.reason._tag === \"Expired\"\n ? noSuchFlow\n : Effect.fail(error),\n ),\n );\n const requested = flow.returnTo ?? options.defaultReturnTo;\n if (requested === undefined) {\n return yield* invalid(\n \"The flow carried no returnTo and the server has no defaultReturnTo\",\n \"returnTo\",\n );\n }\n const destination = sameOrigin(requested, base);\n if (destination === null) {\n return yield* invalid(`${requested} leaves this application`, \"returnTo\");\n }\n yield* connect.complete({\n continuationId: query.state,\n callbackUrl: url.toString(),\n });\n return HttpApiSchema.withHeaders({\n body: undefined as void,\n headers: { location: destination },\n });\n }),\n continuation === null ? undefined : { endpoint: \"callback\", continuation },\n );\n }),\n )\n .handle(\"attach\", ({ params, payload, request }) =>\n as(\n \"attach\",\n request,\n attachAt({\n connectionId: params.connectionId,\n domain: payload.domain,\n zone: payload.zone,\n }),\n ),\n )\n .handle(\"detach\", ({ params, request }) =>\n as(\"detach\", request, connect.detach(params.attachmentId)),\n )\n .handle(\"disconnect\", ({ params, request }) =>\n as(\"disconnect\", request, connect.disconnect(params.connectionId)),\n )\n .handle(\"createPlan\", ({ params, payload, request }) =>\n as(\n \"createPlan\",\n request,\n provision.plan({ domain: params.domain, requirements: payload.requirements }),\n ),\n )\n .handle(\"approve\", ({ params, payload, request }) =>\n as(\n \"approve\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).approve(params.planId, {\n ...(payload.operationIds === undefined\n ? {}\n : { operationIds: payload.operationIds, allowPartial: true }),\n });\n }),\n ),\n )\n .handle(\"reject\", ({ params, payload, request }) =>\n as(\n \"reject\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).reject(params.planId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n });\n }),\n ),\n )\n .handle(\"apply\", ({ params, request }) =>\n as(\n \"apply\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.byApproval(params.approvalId);\n return yield* kindOf(attempt).apply(params.approvalId);\n }),\n ),\n )\n .handle(\"plan\", ({ params, request }) =>\n as(\n \"plan\",\n request,\n Effect.map(storage.attempts.get(params.planId), (attempt) => ({\n plan: attempt.plan,\n status: attempt.status,\n approval: attempt.approval,\n receipt: attempt.receipt,\n rejection: attempt.rejection,\n })),\n ),\n )\n .handle(\"receipt\", ({ params, request }) =>\n as(\n \"receipt\",\n request,\n Effect.flatMap(storage.attempts.byReceipt(params.receiptId), (attempt) =>\n attempt.receipt === null\n ? Errors.fail(new Reason.NotFound({ entity: \"receipt\", id: params.receiptId }))\n : Effect.succeed(attempt.receipt),\n ),\n ),\n )\n .handle(\"observe\", ({ params, payload, request }) =>\n as(\n \"observe\",\n request,\n verify.observe({\n domain: params.domain,\n ...(payload.requirements === undefined ? {} : { requirements: payload.requirements }),\n }),\n ),\n )\n .handle(\"readiness\", ({ params, request }) =>\n as(\"readiness\", request, verify.latest(params.domain)),\n )\n .handle(\"cleanupPlan\", ({ params, request }) =>\n as(\"cleanupPlan\", request, cleanup.plan({ receiptId: params.receiptId })),\n )\n .handle(\"createBatch\", ({ headers, payload, request }) =>\n as(\n \"createBatch\",\n request,\n provision.batch.create({\n items: payload.items,\n idempotencyKey: headers[\"idempotency-key\"],\n }),\n ),\n )\n .handle(\"batches\", ({ request }) =>\n as(\"batches\", request, provision.batch.list({ unfinished: true })),\n )\n .handle(\"batch\", ({ params, request }) =>\n as(\"batch\", request, provision.batch.get(params.batchId)),\n )\n .handle(\"planBatch\", ({ params, payload, request }) =>\n as(\n \"planBatch\",\n request,\n provision.batch.resumePlanning(params.batchId, { items: payload.items }),\n ),\n )\n .handle(\"approveBatch\", ({ params, payload, request }) =>\n as(\n \"approveBatch\",\n request,\n provision.batch.approve(params.batchId, { digest: payload.digest }),\n ),\n )\n .handle(\"rejectBatch\", ({ params, payload, request }) =>\n as(\n \"rejectBatch\",\n request,\n provision.batch.reject(params.batchId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n }),\n ),\n )\n .handle(\"applyBatch\", ({ params, request }) =>\n as(\"applyBatch\", request, provision.batch.apply(params.batchId)),\n );\n }),\n ) as Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services>;\n\n/** A standalone API for hosts not on HttpApi. Mount at any prefix. */\nexport const api: HttpApi.HttpApi<\"domainkit\", Group> = HttpApi.make(\"domainkit\").add(group);\n\nexport interface WebHandlerOptions extends Options {\n /** Mount every route under this path, e.g. `/api/domainkit`. */\n readonly prefix?: `/${string}`;\n}\n\n/** The Promise edge: a `fetch`-shaped handler over the group for hosts that are not Effect-native. */\nexport const toWebHandler = (\n services: Layer.Layer<Services, Errors.DomainKitError>,\n options: WebHandlerOptions = {},\n): {\n readonly handler: (request: Request) => Promise<Response>;\n readonly dispose: () => Promise<void>;\n} => {\n // `group.prefix` widens the endpoint paths in the type, which `Group` states unprefixed; the\n // handlers are keyed by endpoint identifier, so the routes still line up.\n const prefixed =\n options.prefix === undefined\n ? api\n : (HttpApi.make(\"domainkit\").add(group.prefix(options.prefix)) as unknown as HttpApi.HttpApi<\n \"domainkit\",\n Group\n >);\n const app = HttpApiBuilder.layer(prefixed).pipe(\n Layer.provide(layer(prefixed, options)),\n Layer.provide(services),\n Layer.provide(HttpServer.layerServices),\n );\n return HttpRouter.toWebHandler(app, { disableLogger: true });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Server.mjs","names":["Storage.AuthMethod","DnsRecord.Model","Plan.OperationId","Plan.Model","Storage.AttemptStatus","Approval.Model","Receipt.Model","Storage.Rejection","Plan.Digest","Storage.BatchId","Storage.BatchStatus","Storage.BatchApproval","Storage.Overall","Storage.RequirementStatus","Verify.Evidence","Verify.HostEvidence","Plan.PlanId","Approval.ApprovalId","Receipt.ReceiptId","Errors.fail","Reason.InvalidInput","Errors.DomainKitError","Connect.Service","Provision.Service","Cleanup.Service","Verify.Service","Storage.Service","Providers.Service","Principal.Service","Reason.NotFound"],"sources":["../src/Server.ts"],"sourcesContent":["/**\n * domainkit/server — the whole lifecycle as one HttpApi group the host mounts.\n *\n * const Api = HttpApi.make(\"app\").add(Server.group)\n * const ApiLive = HttpApiBuilder.layer(Api).pipe(\n * Layer.provide([Server.layer(Api), DomainKitLive, IdentityLive]),\n * )\n *\n * The host provides `Identity` (request -> `Principal.Interface`). Everything else comes from\n * `DomainKit.layer`. Non-HttpApi hosts use `toWebHandler`. The group carries no prefix of its\n * own, so `group.prefix(\"/internal/dns\")` or a host router mount is all a different base path\n * takes; callback URLs follow the mount.\n */\nimport { Context, Effect, Layer, Option, Schema } from \"effect\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport * as HttpServer from \"effect/http/HttpServer\";\nimport * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport * as HttpApiEndpoint from \"effect/http-api/HttpApiEndpoint\";\nimport * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport * as HttpApiSchema from \"effect/http-api/HttpApiSchema\";\n\nimport * as Approval from \"./Approval.ts\";\nimport * as Cleanup from \"./Cleanup.ts\";\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Provision from \"./Provision.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Receipt from \"./Receipt.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Verify from \"./Verify.ts\";\n\n/**\n * What DomainKit already knows about the request when it asks the host who is making it. Only\n * `/callback/:provider` passes one, because it is the only route that points at a flow DomainKit\n * recorded earlier; every other route calls `principal` with the request alone.\n */\nexport interface IdentityContext {\n /** The route being served. */\n readonly endpoint: EndpointName;\n /** The flow this callback claims to finish, as DomainKit recorded it when the flow started. */\n readonly continuation: Storage.ContinuationHeader;\n}\n\n/** The only service a host must implement for the server. */\nexport interface IdentityService {\n /**\n * `context` is present only on the callback, where a host that keeps per-request tenancy can use\n * `context.continuation.ownerId` to pick the right tenant out of a session that holds several.\n * Ignoring it is safe everywhere: DomainKit checks the principal against the recorded flow on\n * that route regardless, so a host that answers with the wrong one is refused rather than\n * trusted.\n */\n readonly principal: (\n request: HttpServerRequest.HttpServerRequest,\n context?: IdentityContext,\n ) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;\n /**\n * Which routes this principal may reach, checked after `principal` on every request. Fail with\n * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches\n * every route, which is the right default for a host whose own middleware already gates the\n * mount.\n */\n readonly authorize?: (\n principal: Principal.Interface,\n endpoint: EndpointName,\n ) => Effect.Effect<void, Errors.DomainKitError>;\n}\n\nexport class Identity extends Context.Service<Identity, IdentityService>()(\n \"@domainkit/server/Identity\",\n) {}\n\n// ---------------------------------------------------------------------------------------------\n// Wire schemas (the transport contract `domainkit/client` and `@domainkit/react` consume)\n// ---------------------------------------------------------------------------------------------\n\nexport const ConnectionStatus = Schema.Literals([\"disconnected\", \"connected\", \"reconnect\"]);\nexport type ConnectionStatus = typeof ConnectionStatus.Type;\n\n/** One value a token method needs, so a form renders without knowing the provider. */\nexport const Field = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n secret: Schema.Boolean,\n});\nexport type Field = typeof Field.Type;\n\n/** How a provider can be connected; `fields` is `null` for the interactive methods. */\nexport const MethodDescriptor = Schema.Struct({\n kind: Storage.AuthMethod,\n label: Schema.String,\n docsUrl: Schema.NullOr(Schema.String),\n fields: Schema.NullOr(Schema.Array(Field)),\n});\nexport type MethodDescriptor = typeof MethodDescriptor.Type;\n\n/** Everything the UI needs about a domain, flattened; provider context never crosses the wire. */\n/** The attachment a domain holds, with the zone label the provider gave when it was created. */\nexport const Attachment = Schema.Struct({\n id: Schema.String,\n label: Schema.String,\n});\nexport type Attachment = typeof Attachment.Type;\n\nexport const Snapshot = Schema.Struct({\n domain: Schema.String,\n attachment: Schema.NullOr(Attachment),\n connectionId: Schema.NullOr(Schema.String),\n provider: Schema.NullOr(Schema.String),\n method: Schema.NullOr(Storage.AuthMethod),\n status: ConnectionStatus,\n lastReceiptId: Schema.NullOr(Schema.String),\n /** How many domains the connection serves, including this one; `0` without a connection. */\n connectionDomains: Schema.Number,\n reusable: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n method: Storage.AuthMethod,\n }),\n ),\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Snapshot = typeof Snapshot.Type;\n\n/**\n * How the client asks to connect. `values` is keyed by the provider's declared token fields, as\n * `Snapshot.providers[].methods[].fields` names them. Secrets are plaintext in transit and never\n * come back out.\n */\nexport class Token extends Schema.TaggedClass<Token>(\"@domainkit/server/Method/Token\")(\"Token\", {\n values: Schema.Record(Schema.String, Schema.String),\n}) {}\nexport class OAuth extends Schema.TaggedClass<OAuth>(\"@domainkit/server/Method/OAuth\")(\"OAuth\", {\n returnTo: Schema.optionalKey(Schema.String),\n}) {}\nexport class Integration extends Schema.TaggedClass<Integration>(\n \"@domainkit/server/Method/Integration\",\n)(\"Integration\", { returnTo: Schema.optionalKey(Schema.String) }) {}\nexport const Method = Schema.Union([Token, OAuth, Integration]);\nexport type Method = typeof Method.Type;\n\n/** A start without a domain connects the account alone; the customer picks a zone afterwards. */\nexport const StartPayload = Schema.Struct({\n domain: Schema.optionalKey(Schema.String),\n provider: Schema.String,\n method: Method,\n});\nexport type StartPayload = typeof StartPayload.Type;\n\n/** How the customer proves the account again. The provider is the connection's own. */\nexport const ReconnectPayload = Schema.Struct({ method: Method });\nexport type ReconnectPayload = typeof ReconnectPayload.Type;\n\n/** A zone the connection can serve, named for a customer to pick from. */\nexport class Candidate extends Schema.Class<Candidate>(\"@domainkit/server/Candidate\")({\n zone: Schema.String,\n label: Schema.String,\n}) {}\n\n/**\n * The connection the start produced. `label` names the account the way the customer will read it:\n * the attached zone's label, or the provider's name when the start carried no domain. `snapshot`\n * is the domain's state, and is null for a start that attached none.\n */\nexport class Connected extends Schema.TaggedClass<Connected>(\"@domainkit/server/Started/Connected\")(\n \"Connected\",\n {\n connectionId: Schema.String,\n provider: Schema.String,\n label: Schema.String,\n snapshot: Schema.NullOr(Snapshot),\n },\n) {}\nexport class Redirect extends Schema.TaggedClass<Redirect>(\"@domainkit/server/Started/Redirect\")(\n \"Redirect\",\n { authorizationUrl: Schema.String },\n) {}\nexport class SelectionRequired extends Schema.TaggedClass<SelectionRequired>(\n \"@domainkit/server/Started/SelectionRequired\",\n)(\"SelectionRequired\", { connectionId: Schema.String, candidates: Schema.Array(Candidate) }) {}\nexport const Started = Schema.Union([Connected, Redirect, SelectionRequired]);\nexport type Started = typeof Started.Type;\n\n/**\n * Which of the principal's connections already reaches a domain. The class names carry a\n * `Discovery` prefix because `Started` owns the unprefixed `SelectionRequired`; the wire tags are\n * the ones `Connect.Discovery` uses.\n */\nexport class DiscoveryResolved extends Schema.TaggedClass<DiscoveryResolved>(\n \"@domainkit/server/Discovery/Resolved\",\n)(\"Resolved\", { connectionId: Schema.String, zone: Schema.String, label: Schema.String }) {}\nexport class DiscoverySelectionRequired extends Schema.TaggedClass<DiscoverySelectionRequired>(\n \"@domainkit/server/Discovery/SelectionRequired\",\n)(\"SelectionRequired\", {\n candidates: Schema.Array(\n Schema.Struct({ connectionId: Schema.String, zone: Schema.String, label: Schema.String }),\n ),\n}) {}\n/** `host` names the registered provider whose nameservers serve the domain, by definition id. */\nexport class DiscoveryNotFound extends Schema.TaggedClass<DiscoveryNotFound>(\n \"@domainkit/server/Discovery/NotFound\",\n)(\"NotFound\", {\n nameservers: Schema.Array(Schema.String),\n host: Schema.NullOr(Schema.Struct({ provider: Schema.String })),\n}) {}\nexport const Discovery = Schema.Union([\n DiscoveryResolved,\n DiscoverySelectionRequired,\n DiscoveryNotFound,\n]);\nexport type Discovery = typeof Discovery.Type;\n\n/** One zone a connection reaches, named for a customer to pick from. */\nexport const Zone = Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n zone: Schema.String,\n label: Schema.String,\n nameservers: Schema.optionalKey(Schema.Array(Schema.String)),\n});\nexport type Zone = typeof Zone.Type;\n\n/**\n * Every zone the principal's connections reach, and where each connection stands. A connection\n * marked `reconnect` contributes no zones and needs the customer to authorize it again.\n */\nexport const Zones = Schema.Struct({\n zones: Schema.Array(Zone),\n connections: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n /** What the provider called the account at connect time; `null` when it named none. */\n label: Schema.NullOr(Schema.String),\n status: ConnectionStatus,\n }),\n ),\n /** Every provider a customer could connect, so a picker offers one without a second call. */\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Zones = typeof Zones.Type;\n\nexport const AttachPayload = Schema.Struct({\n domain: Schema.String,\n /** Which candidate zone to attach when `SelectionRequired` came back. */\n zone: Schema.optionalKey(Schema.String),\n});\nexport type AttachPayload = typeof AttachPayload.Type;\n\nexport const PlanPayload = Schema.Struct({\n requirements: Schema.Array(DnsRecord.Model),\n});\nexport type PlanPayload = typeof PlanPayload.Type;\n\nexport const ObservePayload = Schema.Struct({\n /** Records to observe instead of the attachment's receipt; required for an unattached domain. */\n requirements: Schema.optionalKey(Schema.Array(DnsRecord.Model)),\n});\nexport type ObservePayload = typeof ObservePayload.Type;\n\nexport const ApprovePayload = Schema.Struct({\n /** A subset of the plan's writes; omit to approve every write. */\n operationIds: Schema.optionalKey(Schema.Array(Plan.OperationId)),\n});\nexport type ApprovePayload = typeof ApprovePayload.Type;\n\nexport const RejectPayload = Schema.Struct({\n /** Why the customer declined, for the audit trail. */\n reason: Schema.optionalKey(Schema.String),\n});\nexport type RejectPayload = typeof RejectPayload.Type;\n\n/** One durable plan -> approval -> receipt lifecycle, as `GET /plans/:planId` returns it. */\nexport const Attempt = Schema.Struct({\n plan: Plan.Model,\n status: Storage.AttemptStatus,\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n});\nexport type Attempt = typeof Attempt.Type;\n\n/** What a batch's create and resume routes take: one domain and its requirements per item. */\nexport const BatchPayload = Schema.Struct({\n items: Schema.Array(\n Schema.Struct({ domain: Schema.String, requirements: Schema.Array(DnsRecord.Model) }),\n ),\n});\nexport type BatchPayload = typeof BatchPayload.Type;\n\n/** The digest the principal read, which the batch's current plans must still produce. */\nexport const BatchApprovePayload = Schema.Struct({ digest: Plan.Digest });\nexport type BatchApprovePayload = typeof BatchApprovePayload.Type;\n\n/** One domain's place in a batch, with whatever its attempt holds. */\nexport const BatchItem = Schema.Struct({\n attachmentId: Schema.String,\n position: Schema.Number,\n /** Null while the item has no plan; `planFailure` then says why. */\n plan: Schema.NullOr(Plan.Model),\n status: Schema.NullOr(Storage.AttemptStatus),\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n failure: Schema.NullOr(Schema.String),\n planFailure: Schema.NullOr(Schema.String),\n});\nexport type BatchItem = typeof BatchItem.Type;\n\n/** Many domains planned together, as every batch route returns them. */\nexport const Batch = Schema.Struct({\n id: Storage.BatchId,\n status: Storage.BatchStatus,\n /** What `POST /batches/:batchId/approvals` takes; null while any item is unplanned. */\n digest: Schema.NullOr(Plan.Digest),\n approval: Schema.NullOr(Storage.BatchApproval),\n rejection: Schema.NullOr(Storage.Rejection),\n createdAt: Schema.DateTimeUtcFromString,\n updatedAt: Schema.DateTimeUtcFromString,\n completedAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n items: Schema.Array(BatchItem),\n});\nexport type Batch = typeof Batch.Type;\n\n/** A batch without its plans, as the unfinished index returns them. */\nexport const BatchSummary = Schema.Struct({\n id: Storage.BatchId,\n status: Storage.BatchStatus,\n itemCount: Schema.Number,\n createdAt: Schema.DateTimeUtcFromString,\n updatedAt: Schema.DateTimeUtcFromString,\n completedAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type BatchSummary = typeof BatchSummary.Type;\n\n/** `Verify.Readiness` on the wire; timestamps encode as ISO strings. */\nexport const Readiness = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n overall: Storage.Overall,\n requirements: Schema.Array(\n Schema.Struct({\n /** `Verify.requirementKey(record)`, so a client pairs rows by content, not by position. */\n key: Schema.String,\n operationId: Schema.NullOr(Plan.OperationId),\n record: DnsRecord.Model,\n status: Storage.RequirementStatus,\n evidence: Schema.Array(Verify.Evidence),\n }),\n ),\n host: Schema.Array(Verify.HostEvidence),\n checkedAt: Schema.DateTimeUtcFromString,\n nextCheckAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type Readiness = typeof Readiness.Type;\n\nconst Redirected = HttpApiSchema.WithHeaders(HttpApiSchema.Empty(302), {\n location: Schema.String,\n});\n\n// ---------------------------------------------------------------------------------------------\n// Errors: one wire body, the status from `DomainKit.Error.httpStatus`\n// ---------------------------------------------------------------------------------------------\n\n/** `DomainKit.Error` narrowed to the reasons that answer with `status`. */\nconst errorAt = (status: number) =>\n Errors.DomainKitError.check(\n Schema.makeFilter((error: Errors.DomainKitError) => error.httpStatus === status),\n ).pipe(HttpApiSchema.status(status));\n\nconst errors = [400, 401, 403, 404, 409, 500, 501, 502, 503].map(errorAt);\n\n// ---------------------------------------------------------------------------------------------\n// The group\n// ---------------------------------------------------------------------------------------------\n\nexport const group = HttpApiGroup.make(\"domainkit\")\n .add(\n HttpApiEndpoint.get(\"inspect\", \"/domains/:domain\", {\n params: { domain: Schema.String },\n success: Snapshot,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"discover\", \"/domains/:domain/discovery\", {\n params: { domain: Schema.String },\n success: Discovery,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"zones\", \"/zones\", {\n query: { provider: Schema.optionalKey(Schema.String) },\n success: Zones,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"start\", \"/connections\", {\n payload: StartPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"callback\", \"/callback/:provider\", {\n params: { provider: Schema.String },\n // `Connect.complete` reads every callback parameter off the request URL, because a provider\n // may return `error`, `teamId`, or `configurationId` too. These two are declared so the\n // OpenAPI document names what a provider is expected to send back.\n query: { state: Schema.String, code: Schema.optionalKey(Schema.String) },\n success: Redirected,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reconnect\", \"/connections/:connectionId/reconnections\", {\n params: { connectionId: Schema.String },\n payload: ReconnectPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"attach\", \"/connections/:connectionId/attachments\", {\n params: { connectionId: Schema.String },\n payload: AttachPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"detach\", \"/attachments/:attachmentId\", {\n params: { attachmentId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"disconnect\", \"/connections/:connectionId\", {\n params: { connectionId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"createPlan\", \"/domains/:domain/plans\", {\n params: { domain: Schema.String },\n payload: PlanPayload,\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approve\", \"/plans/:planId/approvals\", {\n params: { planId: Plan.PlanId },\n payload: ApprovePayload,\n success: Approval.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reject\", \"/plans/:planId/rejections\", {\n params: { planId: Plan.PlanId },\n payload: RejectPayload,\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"apply\", \"/approvals/:approvalId/apply\", {\n params: { approvalId: Approval.ApprovalId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"plan\", \"/plans/:planId\", {\n params: { planId: Plan.PlanId },\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"receipt\", \"/receipts/:receiptId\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"observe\", \"/domains/:domain/observations\", {\n params: { domain: Schema.String },\n payload: ObservePayload,\n success: Readiness,\n error: errors,\n }),\n )\n .add(\n // The stored observation, or `null` when nothing has been observed for the domain yet. A\n // domain with no readiness is not a missing route, so it answers 200 with a null body and\n // leaves 404 to name a domain this owner cannot reach.\n HttpApiEndpoint.get(\"readiness\", \"/domains/:domain/readiness\", {\n params: { domain: Schema.String },\n success: Schema.NullOr(Readiness),\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"cleanupPlan\", \"/receipts/:receiptId/cleanup-plans\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n // `Idempotency-Key` is the batch's identity for this owner: a retried create answers with the\n // batch the first call made rather than planning the same domains twice.\n HttpApiEndpoint.post(\"createBatch\", \"/batches\", {\n headers: { \"idempotency-key\": Schema.String },\n payload: BatchPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n // The one listing there is: what this owner still owes a move on. A finished batch is read by\n // its own id.\n HttpApiEndpoint.get(\"batches\", \"/batches\", {\n query: { unfinished: Schema.Literal(\"true\") },\n success: Schema.Array(BatchSummary),\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"batch\", \"/batches/:batchId\", {\n params: { batchId: Storage.BatchId },\n success: Batch,\n error: errors,\n }),\n )\n .add(\n // Plan the items that still have none. The batch stores pointers, not requirements, so the\n // host supplies them again.\n HttpApiEndpoint.post(\"planBatch\", \"/batches/:batchId/plans\", {\n params: { batchId: Storage.BatchId },\n payload: BatchPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approveBatch\", \"/batches/:batchId/approvals\", {\n params: { batchId: Storage.BatchId },\n payload: BatchApprovePayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"rejectBatch\", \"/batches/:batchId/rejections\", {\n params: { batchId: Storage.BatchId },\n payload: RejectPayload,\n success: Batch,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"applyBatch\", \"/batches/:batchId/apply\", {\n params: { batchId: Storage.BatchId },\n success: Batch,\n error: errors,\n }),\n );\n// Cleanup approve, reject, and apply reuse those routes: the attempt knows its kind.\n\nexport type Group = typeof group;\n\n/** Every route in the group, by name; what `Identity.authorize` is asked about. */\nexport type EndpointName = HttpApiGroup.Endpoints<Group>[\"identifier\"];\n\nexport interface Options {\n /**\n * Where the callback route is reachable from the provider, without the `/callback/:provider`\n * suffix. Defaults to the incoming request's origin and mount path.\n */\n readonly callbackBaseUrl?: string;\n /** Where interactive flows return when the callback carries no `returnTo`. */\n readonly defaultReturnTo?: string;\n}\n\n/** Everything the handlers need: the lifecycle services, Storage, and the host's `Identity`. */\nexport type Services =\n | Provision.Service\n | Cleanup.Service\n | Connect.Service\n | Verify.Service\n | Providers.Service\n | Storage.Service\n | Identity;\n\n// ---------------------------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------------------------\n\nconst START_ROUTE = \"/connections\";\nconst reconnectRoute = (connectionId: string) =>\n `/connections/${encodeURIComponent(connectionId)}/reconnections`;\nconst callbackRoute = (provider: string) => `/callback/${encodeURIComponent(provider)}`;\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** The request as an absolute URL: the web original when there is one, else scheme plus `Host`. */\nconst absoluteUrl = (\n request: HttpServerRequest.HttpServerRequest,\n): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n try {\n return Effect.succeed(new URL(request.originalUrl));\n } catch {\n const url = HttpServerRequest.toURL(request);\n return Option.isNone(url)\n ? invalid(\"The request has no usable origin; set callbackBaseUrl\", \"callbackBaseUrl\")\n : Effect.succeed(url.value);\n }\n });\n\n/**\n * The absolute URL the provider redirects back to. `callbackBaseUrl` wins when the host set it,\n * because a proxy that rewrites `Host` leaves the request pointing at an origin the browser never\n * sees; otherwise it follows the prefix the group is mounted at, derived by stripping the handling\n * route's own path off the request.\n */\nconst callbackUrlFor = (input: {\n readonly request: HttpServerRequest.HttpServerRequest;\n readonly provider: string;\n readonly options: Options;\n /** The path of the route serving this request, stripped to find the mount. */\n readonly route: string;\n}): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n const suffix = `/callback/${encodeURIComponent(input.provider)}`;\n const configured = input.options.callbackBaseUrl;\n if (configured !== undefined) {\n return Effect.try({\n try: () => new URL(`${configured.replace(/\\/+$/, \"\")}${suffix}`),\n catch: () => callbackConfigurationError(`${configured} is not a URL`),\n });\n }\n return Effect.flatMap(absoluteUrl(input.request), (url) => {\n const path = url.pathname;\n if (!path.endsWith(input.route)) {\n return invalid(\n `The ${input.route} route is not mounted where it says; set callbackBaseUrl`,\n \"callbackBaseUrl\",\n );\n }\n return Effect.succeed(\n new URL(`${url.origin}${path.slice(0, path.length - input.route.length)}${suffix}`),\n );\n });\n });\n\n/**\n * One refusal for every way a callback can fail to match a flow: no such `state`, one that has\n * expired, one presented at another provider's route, and one presented by a session that did not\n * start it. The text is constant on purpose — a caller holding a leaked `state` must not be able\n * to tell \"no such flow\" from \"not your flow\".\n */\nconst noSuchFlow = Errors.fail(\n new Reason.InvalidInput({\n message: \"This callback does not match a connection you started\",\n field: \"state\",\n }),\n);\n\nconst callbackConfigurationError = (message: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field: \"callbackBaseUrl\" }),\n });\n\n/**\n * Where the callback may send the customer: a path under the callback's own base, or an absolute\n * URL on its origin. A destination that leaves the application is an open redirect, whoever\n * supplied it.\n */\nconst sameOrigin = (destination: string, callback: URL): string | null => {\n try {\n // Resolving against the callback is what makes this safe: the URL parser normalizes the forms\n // a browser would follow off-origin (`//host`, `/\\host`, `\\/host`, encoded hosts) into a real\n // origin, so comparing origins catches every one of them.\n const url = new URL(destination, callback);\n return url.origin === callback.origin ? url.toString() : null;\n } catch {\n return null;\n }\n};\n\nconst snapshotOf = (snapshot: Connect.Snapshot): Snapshot => ({\n domain: snapshot.domain,\n attachment:\n snapshot.attachment === null\n ? null\n : { id: snapshot.attachment.id, label: snapshot.attachment.label },\n connectionId: snapshot.connection?.id ?? null,\n provider: snapshot.authorization?.provider ?? null,\n method: snapshot.authorization?.method ?? null,\n status:\n snapshot.connection === null\n ? \"disconnected\"\n : snapshot.authorization?.revocation === \"active\"\n ? \"connected\"\n : \"reconnect\",\n lastReceiptId: snapshot.lastReceiptId,\n connectionDomains: snapshot.connectionDomains,\n reusable: snapshot.reusable.map(({ connection, provider, method }) => ({\n connectionId: connection.id,\n provider,\n method,\n })),\n providers: snapshot.providers.map(({ id, name, methods }) => ({ id, name, methods })),\n});\n\n/** The wire method as `Connect` takes it; interactive cases carry the caller's return destination. */\nconst methodOf = (method: Method): Connect.Method => {\n switch (method._tag) {\n case \"Token\":\n return Connect.Method.token(method.values);\n case \"OAuth\":\n return Connect.Method.oauth(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n case \"Integration\":\n return Connect.Method.integration(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n }\n};\n\nconst candidatesOf = (\n candidates: ReadonlyArray<{ readonly zone: string; readonly label: string }>,\n): ReadonlyArray<Candidate> => candidates.map(({ zone, label }) => new Candidate({ zone, label }));\n\n/**\n * Handlers for `group` inside the host's API. Every handler derives `Principal` from `Identity`\n * for the request it is serving and provides it to the lifecycle services.\n */\nexport const layer = <ApiId extends string, Groups extends HttpApiGroup.Constraint>(\n api: HttpApi.HttpApi<ApiId, Groups>,\n options: Options = {},\n): Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services> =>\n // The host's API carries its own groups beside this one; only `\"domainkit\"` is implemented here,\n // and `HttpApiBuilder.group` needs the group's own endpoint types to check the handlers.\n HttpApiBuilder.group(api as unknown as HttpApi.HttpApi<ApiId, Group>, \"domainkit\", (handlers) =>\n Effect.gen(function* () {\n const identity = yield* Identity;\n const connect = yield* Connect.Service;\n const provision = yield* Provision.Service;\n const cleanup = yield* Cleanup.Service;\n const verify = yield* Verify.Service;\n const storage = yield* Storage.Service;\n const providers = yield* Providers.Service;\n\n /**\n * Run a lifecycle effect as the principal the host derives from this request, once the host\n * has allowed that principal onto this route.\n */\n const as = <A>(\n endpoint: EndpointName,\n request: HttpServerRequest.HttpServerRequest,\n effect: Effect.Effect<A, Errors.DomainKitError, Principal.Service>,\n context?: IdentityContext,\n ): Effect.Effect<A, Errors.DomainKitError> =>\n Effect.flatMap(identity.principal(request, context), (principal) =>\n Effect.flatMap(identity.authorize?.(principal, endpoint) ?? Effect.void, () =>\n Effect.provideService(effect, Principal.Service, principal),\n ),\n );\n\n const snapshot = (domain: string) => Effect.map(connect.inspect(domain), snapshotOf);\n\n const discovered = (discovery: Connect.Discovery): Discovery => {\n switch (discovery._tag) {\n case \"Resolved\":\n return new DiscoveryResolved({\n connectionId: discovery.connectionId,\n zone: discovery.target.zone,\n label: discovery.target.label,\n });\n case \"SelectionRequired\":\n return new DiscoverySelectionRequired({\n candidates: discovery.candidates.map(({ connectionId, target }) => ({\n connectionId,\n zone: target.zone,\n label: target.label,\n })),\n });\n case \"NotFound\":\n return new DiscoveryNotFound({\n nameservers: discovery.nameservers,\n host: discovery.host,\n });\n }\n };\n\n /**\n * The connection a start or an attach produced. `label` is the zone the attachment named,\n * or the provider itself where the connection carries no domain yet.\n */\n const connected = (input: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n readonly domain: string | undefined;\n }): Effect.Effect<Connected, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(input.connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n return new Connected({\n connectionId: input.connection.id,\n provider: authorization.provider,\n label: input.attachment?.label ?? authorization.label ?? definition.name,\n snapshot: input.domain === undefined ? null : yield* snapshot(input.domain),\n });\n });\n\n const startedOf = (\n started: Connect.Started,\n domain: string | undefined,\n ): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> => {\n switch (started._tag) {\n case \"Connected\":\n return connected({\n attachment: started.attachment,\n connection: started.connection,\n domain,\n });\n case \"Redirect\":\n return Effect.succeed(new Redirect({ authorizationUrl: started.authorizationUrl }));\n case \"SelectionRequired\":\n return Effect.succeed(\n new SelectionRequired({\n connectionId: started.connection.id,\n candidates: candidatesOf(started.candidates),\n }),\n );\n }\n };\n\n /**\n * Attach with a zone the client picked out of a `SelectionRequired`. The provider target\n * behind a zone never crosses the wire, so resolve the candidates again and match by zone.\n */\n const attachAt = (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly zone: string | undefined;\n }): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const attached = (attachment: Storage.Attachment) =>\n connected({ attachment, connection, domain: input.domain });\n const first = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n });\n if (!(\"_tag\" in first)) return yield* attached(first);\n if (input.zone === undefined) {\n return new SelectionRequired({\n connectionId: input.connectionId,\n candidates: candidatesOf(first.candidates),\n });\n }\n const target = first.candidates.find(({ zone }) => zone === input.zone);\n if (target === undefined) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }));\n }\n const second = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n target,\n });\n return \"_tag\" in second\n ? yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }))\n : yield* attached(second);\n });\n\n /** Provisioning and cleanup share the approve and apply routes; the attempt knows its kind. */\n const kindOf = (attempt: Storage.Attempt) =>\n attempt.kind === \"cleanup\" ? cleanup : provision;\n\n return handlers\n .handle(\"inspect\", ({ params, request }) => as(\"inspect\", request, snapshot(params.domain)))\n .handle(\"discover\", ({ params, request }) =>\n as(\"discover\", request, Effect.map(connect.discover(params.domain), discovered)),\n )\n .handle(\"zones\", ({ query, request }) =>\n as(\n \"zones\",\n request,\n Effect.map(\n connect.zones(query.provider === undefined ? {} : { provider: query.provider }),\n (listing): Zones => ({\n zones: listing.zones.map(({ connectionId, provider, target }) => ({\n connectionId,\n provider,\n zone: target.zone,\n label: target.label,\n ...(target.nameservers === undefined ? {} : { nameservers: target.nameservers }),\n })),\n connections: listing.connections,\n providers: listing.providers,\n }),\n ),\n ),\n )\n .handle(\"start\", ({ payload, request }) =>\n as(\n \"start\",\n request,\n Effect.gen(function* () {\n // Token methods connect in one call; the interactive ones need somewhere to land.\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: payload.provider,\n options,\n route: START_ROUTE,\n })).toString();\n const started = yield* connect.start({\n provider: payload.provider,\n ...(payload.domain === undefined ? {} : { domain: payload.domain }),\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, payload.domain);\n }),\n ),\n )\n .handle(\"reconnect\", ({ params, payload, request }) =>\n as(\n \"reconnect\",\n request,\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(params.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: authorization.provider,\n options,\n route: reconnectRoute(params.connectionId),\n })).toString();\n const started = yield* connect.reconnect({\n connectionId: params.connectionId,\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, undefined);\n }),\n ),\n )\n .handle(\"callback\", ({ params, query, request }) =>\n Effect.gen(function* () {\n // The one route that establishes its identity from something other than the request.\n // A provider callback is a top-level navigation carrying a `state` the provider echoed\n // back and whatever credential the browser attaches by itself, so the flow DomainKit\n // recorded is the only authority on whose it is. Read its header first — a read, never\n // a spend, so a leaked `state` cannot burn the flow it names — tell the host which\n // flow is being finished, and then hold the host's answer against the record.\n const header = yield* storage.continuations\n .header(query.state)\n .pipe(\n Effect.catch((error) =>\n error.reason._tag === \"NotFound\" || error.reason._tag === \"Expired\"\n ? Effect.succeed(null)\n : Effect.fail(error),\n ),\n );\n const continuation =\n header !== null && header.provider === params.provider ? header : null;\n // The host authenticates even when there is nothing to finish, so the status cannot\n // tell a caller whether the `state` it holds names a flow at all.\n return yield* as(\n \"callback\",\n request,\n Effect.gen(function* () {\n // The owner comes from the record and the actor from the host's own session;\n // nothing in the query string contributes either. The scoped `get` below keeps\n // its owner filter, so a host that answers with the wrong principal still fails\n // closed even if this check were ever removed.\n const principal = yield* Principal.Service;\n if (\n continuation === null ||\n principal.ownerId !== continuation.ownerId ||\n principal.actorId !== continuation.actorId\n ) {\n return yield* noSuchFlow;\n }\n const url = yield* absoluteUrl(request);\n // Resolve the destination against the callback's public base, not the request: a\n // proxy that rewrites `Host` leaves the request on an origin the browser never\n // sees.\n const base = yield* callbackUrlFor({\n request,\n provider: params.provider,\n options,\n route: callbackRoute(params.provider),\n });\n // Where the customer lands comes from the continuation this owner started, never\n // from the provider's query string, and it is resolved before the callback is\n // spent: a server with nowhere to send them must not connect the provider and\n // then fail.\n // The host's authentication sits between the header read and this one, so the\n // flow can expire or be spent in that window. It refuses the same way it would\n // have a moment earlier, rather than changing shape because of when it happened.\n // This is also where the owner filter fails closed when a host resolves the wrong\n // principal, and that refusal must look like every other one.\n const flow = yield* storage.continuations\n .get(query.state)\n .pipe(\n Effect.catch((error) =>\n error.reason._tag === \"NotFound\" || error.reason._tag === \"Expired\"\n ? noSuchFlow\n : Effect.fail(error),\n ),\n );\n const requested = flow.returnTo ?? options.defaultReturnTo;\n if (requested === undefined) {\n return yield* invalid(\n \"The flow carried no returnTo and the server has no defaultReturnTo\",\n \"returnTo\",\n );\n }\n const destination = sameOrigin(requested, base);\n if (destination === null) {\n return yield* invalid(`${requested} leaves this application`, \"returnTo\");\n }\n yield* connect.complete({\n continuationId: query.state,\n callbackUrl: url.toString(),\n });\n return HttpApiSchema.withHeaders({\n body: undefined as void,\n headers: { location: destination },\n });\n }),\n continuation === null ? undefined : { endpoint: \"callback\", continuation },\n );\n }),\n )\n .handle(\"attach\", ({ params, payload, request }) =>\n as(\n \"attach\",\n request,\n attachAt({\n connectionId: params.connectionId,\n domain: payload.domain,\n zone: payload.zone,\n }),\n ),\n )\n .handle(\"detach\", ({ params, request }) =>\n as(\"detach\", request, connect.detach(params.attachmentId)),\n )\n .handle(\"disconnect\", ({ params, request }) =>\n as(\"disconnect\", request, connect.disconnect(params.connectionId)),\n )\n .handle(\"createPlan\", ({ params, payload, request }) =>\n as(\n \"createPlan\",\n request,\n provision.plan({ domain: params.domain, requirements: payload.requirements }),\n ),\n )\n .handle(\"approve\", ({ params, payload, request }) =>\n as(\n \"approve\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).approve(params.planId, {\n ...(payload.operationIds === undefined\n ? {}\n : { operationIds: payload.operationIds, allowPartial: true }),\n });\n }),\n ),\n )\n .handle(\"reject\", ({ params, payload, request }) =>\n as(\n \"reject\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).reject(params.planId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n });\n }),\n ),\n )\n .handle(\"apply\", ({ params, request }) =>\n as(\n \"apply\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.byApproval(params.approvalId);\n return yield* kindOf(attempt).apply(params.approvalId);\n }),\n ),\n )\n .handle(\"plan\", ({ params, request }) =>\n as(\n \"plan\",\n request,\n Effect.map(storage.attempts.get(params.planId), (attempt) => ({\n plan: attempt.plan,\n status: attempt.status,\n approval: attempt.approval,\n receipt: attempt.receipt,\n rejection: attempt.rejection,\n })),\n ),\n )\n .handle(\"receipt\", ({ params, request }) =>\n as(\n \"receipt\",\n request,\n Effect.flatMap(storage.attempts.byReceipt(params.receiptId), (attempt) =>\n attempt.receipt === null\n ? Errors.fail(new Reason.NotFound({ entity: \"receipt\", id: params.receiptId }))\n : Effect.succeed(attempt.receipt),\n ),\n ),\n )\n .handle(\"observe\", ({ params, payload, request }) =>\n as(\n \"observe\",\n request,\n verify.observe({\n domain: params.domain,\n ...(payload.requirements === undefined ? {} : { requirements: payload.requirements }),\n }),\n ),\n )\n .handle(\"readiness\", ({ params, request }) =>\n as(\"readiness\", request, verify.latest(params.domain)),\n )\n .handle(\"cleanupPlan\", ({ params, request }) =>\n as(\"cleanupPlan\", request, cleanup.plan({ receiptId: params.receiptId })),\n )\n .handle(\"createBatch\", ({ headers, payload, request }) =>\n as(\n \"createBatch\",\n request,\n provision.batch.create({\n items: payload.items,\n idempotencyKey: headers[\"idempotency-key\"],\n }),\n ),\n )\n .handle(\"batches\", ({ request }) =>\n as(\"batches\", request, provision.batch.list({ unfinished: true })),\n )\n .handle(\"batch\", ({ params, request }) =>\n as(\"batch\", request, provision.batch.get(params.batchId)),\n )\n .handle(\"planBatch\", ({ params, payload, request }) =>\n as(\n \"planBatch\",\n request,\n provision.batch.resumePlanning(params.batchId, { items: payload.items }),\n ),\n )\n .handle(\"approveBatch\", ({ params, payload, request }) =>\n as(\n \"approveBatch\",\n request,\n provision.batch.approve(params.batchId, { digest: payload.digest }),\n ),\n )\n .handle(\"rejectBatch\", ({ params, payload, request }) =>\n as(\n \"rejectBatch\",\n request,\n provision.batch.reject(params.batchId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n }),\n ),\n )\n .handle(\"applyBatch\", ({ params, request }) =>\n as(\"applyBatch\", request, provision.batch.apply(params.batchId)),\n );\n }),\n ) as Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services>;\n\n/** A standalone API for hosts not on HttpApi. Mount at any prefix. */\nexport const api: HttpApi.HttpApi<\"domainkit\", Group> = HttpApi.make(\"domainkit\").add(group);\n\nexport interface WebHandlerOptions extends Options {\n /** Mount every route under this path, e.g. `/api/domainkit`. */\n readonly prefix?: `/${string}`;\n}\n\n/** The Promise edge: a `fetch`-shaped handler over the group for hosts that are not Effect-native. */\nexport const toWebHandler = (\n services: Layer.Layer<Services, Errors.DomainKitError>,\n options: WebHandlerOptions = {},\n): {\n readonly handler: (request: Request) => Promise<Response>;\n readonly dispose: () => Promise<void>;\n} => {\n // `group.prefix` widens the endpoint paths in the type, which `Group` states unprefixed; the\n // handlers are keyed by endpoint identifier, so the routes still line up.\n const prefixed =\n options.prefix === undefined\n ? api\n : (HttpApi.make(\"domainkit\").add(group.prefix(options.prefix)) as unknown as HttpApi.HttpApi<\n \"domainkit\",\n Group\n >);\n const app = HttpApiBuilder.layer(prefixed).pipe(\n Layer.provide(layer(prefixed, options)),\n Layer.provide(services),\n Layer.provide(HttpServer.layerServices),\n );\n return HttpRouter.toWebHandler(app, { disableLogger: true });\n};\n"],"mappings":"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"}