domainkit 0.8.0 → 0.9.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.
Files changed (165) hide show
  1. package/README.md +213 -41
  2. package/dist/Approval.mjs +34 -0
  3. package/dist/Approval.mjs.map +1 -0
  4. package/dist/Cleanup.mjs +97 -0
  5. package/dist/Cleanup.mjs.map +1 -0
  6. package/dist/Cloudflare.mjs +225 -0
  7. package/dist/Cloudflare.mjs.map +1 -0
  8. package/dist/Connect.mjs +463 -0
  9. package/dist/Connect.mjs.map +1 -0
  10. package/dist/Custody.mjs +64 -0
  11. package/dist/Custody.mjs.map +1 -0
  12. package/dist/DnsRecord.mjs +213 -0
  13. package/dist/DnsRecord.mjs.map +1 -0
  14. package/dist/DomainKit.mjs +34 -0
  15. package/dist/DomainKit.mjs.map +1 -0
  16. package/dist/DomainName.mjs +100 -0
  17. package/dist/DomainName.mjs.map +1 -0
  18. package/dist/Plan.mjs +102 -0
  19. package/dist/Plan.mjs.map +1 -0
  20. package/dist/Principal.mjs +23 -0
  21. package/dist/Principal.mjs.map +1 -0
  22. package/dist/Provider.mjs +130 -0
  23. package/dist/Provider.mjs.map +1 -0
  24. package/dist/Providers.mjs +40 -0
  25. package/dist/Providers.mjs.map +1 -0
  26. package/dist/Provision.mjs +93 -0
  27. package/dist/Provision.mjs.map +1 -0
  28. package/dist/Reason.mjs +162 -0
  29. package/dist/Reason.mjs.map +1 -0
  30. package/dist/Receipt.mjs +71 -0
  31. package/dist/Receipt.mjs.map +1 -0
  32. package/dist/Resolver.mjs +75 -0
  33. package/dist/Resolver.mjs.map +1 -0
  34. package/dist/Server.mjs +516 -0
  35. package/dist/Server.mjs.map +1 -0
  36. package/dist/Storage.mjs +236 -0
  37. package/dist/Storage.mjs.map +1 -0
  38. package/dist/Testing.mjs +226 -0
  39. package/dist/Testing.mjs.map +1 -0
  40. package/dist/Transport.mjs +187 -0
  41. package/dist/Transport.mjs.map +1 -0
  42. package/dist/Vercel.mjs +116 -0
  43. package/dist/Vercel.mjs.map +1 -0
  44. package/dist/Verify.mjs +292 -0
  45. package/dist/Verify.mjs.map +1 -0
  46. package/dist/{rolldown-runtime-D7D4PA-g.mjs → _virtual/_rolldown/runtime.mjs} +1 -1
  47. package/dist/entry/client.mjs +2 -0
  48. package/dist/entry/server.mjs +2 -0
  49. package/dist/entry/testing.mjs +3 -0
  50. package/dist/index.mjs +33 -462
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/internal/aes.mjs +61 -0
  53. package/dist/internal/aes.mjs.map +1 -0
  54. package/dist/internal/attempts.mjs +284 -0
  55. package/dist/internal/attempts.mjs.map +1 -0
  56. package/dist/internal/cloudflare/client.mjs +100 -0
  57. package/dist/internal/cloudflare/client.mjs.map +1 -0
  58. package/dist/internal/cloudflare/protocol.mjs +59 -0
  59. package/dist/internal/cloudflare/protocol.mjs.map +1 -0
  60. package/dist/internal/cloudflare/records.mjs +156 -0
  61. package/dist/internal/cloudflare/records.mjs.map +1 -0
  62. package/dist/internal/config.mjs +13 -0
  63. package/dist/internal/config.mjs.map +1 -0
  64. package/dist/internal/conformance/provider.mjs +207 -0
  65. package/dist/internal/conformance/provider.mjs.map +1 -0
  66. package/dist/internal/conformance/storage.mjs +428 -0
  67. package/dist/internal/conformance/storage.mjs.map +1 -0
  68. package/dist/internal/digest.mjs +25 -0
  69. package/dist/internal/digest.mjs.map +1 -0
  70. package/dist/internal/doh.mjs +153 -0
  71. package/dist/internal/doh.mjs.map +1 -0
  72. package/dist/internal/error.mjs +69 -0
  73. package/dist/internal/error.mjs.map +1 -0
  74. package/dist/internal/http.mjs +90 -0
  75. package/dist/internal/http.mjs.map +1 -0
  76. package/dist/internal/ids.mjs +8 -0
  77. package/dist/internal/ids.mjs.map +1 -0
  78. package/dist/internal/oauth.mjs +113 -0
  79. package/dist/internal/oauth.mjs.map +1 -0
  80. package/dist/internal/planner.mjs +88 -0
  81. package/dist/internal/planner.mjs.map +1 -0
  82. package/dist/internal/provider-async.mjs +83 -0
  83. package/dist/internal/provider-async.mjs.map +1 -0
  84. package/dist/internal/storage-memory.mjs +302 -0
  85. package/dist/internal/storage-memory.mjs.map +1 -0
  86. package/dist/internal/vercel/client.mjs +122 -0
  87. package/dist/internal/vercel/client.mjs.map +1 -0
  88. package/dist/internal/vercel/protocol.mjs +80 -0
  89. package/dist/internal/vercel/protocol.mjs.map +1 -0
  90. package/dist/internal/vercel/records.mjs +151 -0
  91. package/dist/internal/vercel/records.mjs.map +1 -0
  92. package/dist/package.mjs +6 -0
  93. package/dist/package.mjs.map +1 -0
  94. package/dist/types/Approval.d.ts +26 -0
  95. package/dist/types/Cleanup.d.ts +41 -0
  96. package/dist/types/Cloudflare.d.ts +36 -0
  97. package/dist/types/Connect.d.ts +506 -0
  98. package/dist/types/Custody.d.ts +38 -0
  99. package/dist/types/DnsRecord.d.ts +158 -0
  100. package/dist/types/DomainKit.d.ts +31 -0
  101. package/dist/types/DomainName.d.ts +27 -0
  102. package/dist/types/Plan.d.ts +76 -0
  103. package/dist/types/Principal.d.ts +18 -0
  104. package/dist/types/Provider.d.ts +248 -0
  105. package/dist/types/Providers.d.ts +16 -0
  106. package/dist/types/Provision.d.ts +75 -0
  107. package/dist/types/Reason.d.ts +118 -0
  108. package/dist/types/Receipt.d.ts +55 -0
  109. package/dist/types/Resolver.d.ts +135 -0
  110. package/dist/types/Server.d.ts +6523 -0
  111. package/dist/types/Storage.d.ts +314 -0
  112. package/dist/types/Testing.d.ts +84 -0
  113. package/dist/types/Transport.d.ts +124 -0
  114. package/dist/types/Vercel.d.ts +25 -0
  115. package/dist/types/Verify.d.ts +109 -0
  116. package/dist/types/entry/client.d.ts +2 -0
  117. package/dist/types/entry/server.d.ts +2 -0
  118. package/dist/types/entry/testing.d.ts +3 -0
  119. package/dist/types/index.d.ts +21 -0
  120. package/dist/types/internal/aes.d.ts +8 -0
  121. package/dist/types/internal/attempts.d.ts +40 -0
  122. package/dist/types/internal/cloudflare/client.d.ts +23 -0
  123. package/dist/types/internal/cloudflare/protocol.d.ts +71 -0
  124. package/dist/types/internal/cloudflare/records.d.ts +8 -0
  125. package/dist/types/internal/config.d.ts +4 -0
  126. package/dist/types/internal/conformance/provider.d.ts +12 -0
  127. package/dist/types/internal/conformance/storage.d.ts +14 -0
  128. package/dist/types/internal/digest.d.ts +10 -0
  129. package/dist/types/internal/doh.d.ts +19 -0
  130. package/dist/types/internal/error.d.ts +24 -0
  131. package/dist/types/internal/http.d.ts +27 -0
  132. package/dist/types/internal/ids.d.ts +3 -0
  133. package/dist/types/internal/oauth.d.ts +58 -0
  134. package/dist/types/internal/planner.d.ts +22 -0
  135. package/dist/types/internal/provider-async.d.ts +78 -0
  136. package/dist/types/internal/storage-memory.d.ts +2 -0
  137. package/dist/types/internal/vercel/client.d.ts +29 -0
  138. package/dist/types/internal/vercel/protocol.d.ts +124 -0
  139. package/dist/types/internal/vercel/records.d.ts +7 -0
  140. package/package.json +88 -12
  141. package/dist/canonical-json-CQYKsZw7.mjs +0 -310
  142. package/dist/canonical-json-CQYKsZw7.mjs.map +0 -1
  143. package/dist/connect-D-X8T9W8.d.mts +0 -1597
  144. package/dist/connect-oI8hcMxc.mjs +0 -185
  145. package/dist/connect-oI8hcMxc.mjs.map +0 -1
  146. package/dist/connection-Dp6ZdSo8.d.mts +0 -186
  147. package/dist/connection-api-lqG1nBtD.d.mts +0 -26
  148. package/dist/index.d.mts +0 -1551
  149. package/dist/managed-dns-connections-C3A_GBaz.mjs +0 -99
  150. package/dist/managed-dns-connections-C3A_GBaz.mjs.map +0 -1
  151. package/dist/promise.d.mts +0 -378
  152. package/dist/promise.mjs +0 -598
  153. package/dist/promise.mjs.map +0 -1
  154. package/dist/resolver-BeGz1ukw.mjs +0 -921
  155. package/dist/resolver-BeGz1ukw.mjs.map +0 -1
  156. package/dist/resolver-xoSwyXDJ.d.mts +0 -3734
  157. package/dist/server.d.mts +0 -128
  158. package/dist/server.mjs +0 -319
  159. package/dist/server.mjs.map +0 -1
  160. package/dist/testing.d.mts +0 -62
  161. package/dist/testing.mjs +0 -507
  162. package/dist/testing.mjs.map +0 -1
  163. package/dist/verify-DvGPb5C9.mjs +0 -2402
  164. package/dist/verify-DvGPb5C9.mjs.map +0 -1
  165. package/dist/verify-FpunAcM7.d.mts +0 -1335
@@ -0,0 +1,463 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { candidates, decode } from "./DomainName.mjs";
3
+ import { Forbidden, InvalidInput, NotFound, Reconnect, Unauthenticated } from "./Reason.mjs";
4
+ import { DomainKitError, decode as decode$1, fail } from "./internal/error.mjs";
5
+ import { Service as Service$1 } from "./Custody.mjs";
6
+ import { fresh } from "./internal/ids.mjs";
7
+ import { pkce } from "./internal/oauth.mjs";
8
+ import { Service as Service$2 } from "./Principal.mjs";
9
+ import { Resolution, decodeContext, describeMethods, encodeContext } from "./Provider.mjs";
10
+ import { Service as Service$3 } from "./Providers.mjs";
11
+ import { Service as Service$4 } from "./Resolver.mjs";
12
+ import { Attachment, Authorization, Continuation, Credential, Service as Service$5 } from "./Storage.mjs";
13
+ import { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from "effect";
14
+ //#region src/Connect.ts
15
+ /**
16
+ * Connect a provider, attach domains to it, and keep the credential alive. Interactive methods
17
+ * (OAuth, integration) return a redirect and complete on callback; token methods connect in one
18
+ * call. Reuse of an existing connection for a new domain is `attach`.
19
+ */
20
+ var Connect_exports = /* @__PURE__ */ __exportAll({
21
+ Discovery: () => Discovery,
22
+ Method: () => Method,
23
+ Policy: () => Policy,
24
+ Selection: () => Selection,
25
+ Service: () => Service,
26
+ Started: () => Started,
27
+ attach: () => attach,
28
+ complete: () => complete,
29
+ defaults: () => defaults,
30
+ detach: () => detach,
31
+ disconnect: () => disconnect,
32
+ discover: () => discover,
33
+ inspect: () => inspect,
34
+ layer: () => layer,
35
+ make: () => make,
36
+ refresh: () => refresh,
37
+ session: () => session,
38
+ start: () => start
39
+ });
40
+ const MethodEnum = Data.taggedEnum();
41
+ /** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */
42
+ const Method = {
43
+ ...MethodEnum,
44
+ token: (values) => MethodEnum.Token({ values: typeof values === "string" ? { token: Redacted.make(values) } : Object.fromEntries(Object.entries(values).map(([name, value]) => [name, typeof value === "string" ? Redacted.make(value) : value])) }),
45
+ oauth: (options = {}) => MethodEnum.OAuth(options),
46
+ integration: (options = {}) => MethodEnum.Integration(options)
47
+ };
48
+ const Started = Data.taggedEnum();
49
+ const Selection = Data.taggedEnum();
50
+ const Discovery = Data.taggedEnum();
51
+ var Service = class extends Context.Service()("@domainkit/Connect") {};
52
+ const defaults = {
53
+ allowReuse: () => Effect.succeed(true),
54
+ refreshBeforeMs: 6e5,
55
+ continuationTtlMs: 9e5
56
+ };
57
+ var Policy = class extends Context.Reference("@domainkit/Connect/Policy", { defaultValue: () => defaults }) {};
58
+ const Payload = Schema.Struct({
59
+ method: Schema.Literals(["oauth", "integration"]),
60
+ codeVerifier: Schema.NullOr(Schema.String),
61
+ callbackUrl: Schema.String,
62
+ domain: Schema.NullOr(Schema.String)
63
+ });
64
+ const Target = Schema.Struct({
65
+ zone: Schema.String,
66
+ context: Schema.Unknown,
67
+ label: Schema.String
68
+ });
69
+ const invalid = (message, field) => fail(new InvalidInput({
70
+ message,
71
+ ...field === void 0 ? {} : { field }
72
+ }));
73
+ const make = Effect.gen(function* () {
74
+ const storage = yield* Service$5;
75
+ const custody = yield* Service$1;
76
+ const providers = yield* Service$3;
77
+ const resolver = yield* Service$4;
78
+ const seal = (issued) => Effect.gen(function* () {
79
+ const ciphertext = yield* custody.seal(issued.secret);
80
+ return new Credential({
81
+ ciphertext,
82
+ expiresAt: issued.expiresAt,
83
+ rotatedAt: yield* DateTime.now
84
+ });
85
+ });
86
+ /** Plaintext credential with the context decoded from its stored envelope. */
87
+ const open = (authorization) => Effect.gen(function* () {
88
+ const definition = yield* providers.get(authorization.provider);
89
+ const row = yield* storage.authorizations.credential(authorization.id);
90
+ return {
91
+ secret: yield* custody.open(row.ciphertext),
92
+ context: yield* decodeContext(definition, authorization.context)
93
+ };
94
+ });
95
+ const refresherFor = (definition, method) => method === "oauth" ? definition.auth.oauth?.refresh : method === "integration" ? definition.auth.integration?.refresh : void 0;
96
+ const revokerFor = (definition, method) => method === "oauth" ? definition.auth.oauth?.revoke : method === "integration" ? definition.auth.integration?.revoke : void 0;
97
+ const revokeAt = (authorization) => Effect.gen(function* () {
98
+ const definition = yield* providers.get(authorization.provider);
99
+ const revoke = revokerFor(definition, authorization.method);
100
+ if (revoke === void 0) return;
101
+ yield* revoke(yield* open(authorization));
102
+ });
103
+ /** Finish revocations a crash interrupted; best effort, never blocks the caller. */
104
+ const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);
105
+ const refresh = (connectionId) => Effect.gen(function* () {
106
+ const policy = yield* Policy;
107
+ const connection = yield* storage.connections.get(connectionId);
108
+ const authorization = yield* storage.authorizations.get(connection.authorizationId);
109
+ const definition = yield* providers.get(authorization.provider);
110
+ const refresher = refresherFor(definition, authorization.method);
111
+ const dueAt = (row, now) => row.expiresAt !== null && DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <= policy.refreshBeforeMs;
112
+ const expired = (row, now) => row.expiresAt !== null && DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);
113
+ const current = yield* storage.authorizations.credential(authorization.id);
114
+ const now = yield* DateTime.now;
115
+ if (!dueAt(current, now)) return "current";
116
+ if (refresher === void 0) return expired(current, now) ? "reconnect" : "current";
117
+ return yield* storage.withLock(`refresh:${authorization.id}`, Effect.gen(function* () {
118
+ const live = yield* storage.authorizations.get(authorization.id).pipe(Effect.map(Option.some), Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.succeed(Option.none())));
119
+ if (Option.isNone(live) || live.value.revocation !== "active") return "reconnect";
120
+ const latest = yield* storage.authorizations.credential(authorization.id);
121
+ if (!dueAt(latest, yield* DateTime.now)) return "current";
122
+ const secret = yield* custody.open(latest.ciphertext);
123
+ const context = yield* decodeContext(definition, live.value.context);
124
+ const issued = yield* refresher({
125
+ secret,
126
+ context
127
+ });
128
+ yield* storage.authorizations.rotate(authorization.id, yield* seal(issued)).pipe(Effect.tapError((error) => error.reason._tag === "NotFound" ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(Effect.ignore) : Effect.void));
129
+ return "refreshed";
130
+ })).pipe(Effect.catchIf((error) => error.reason._tag === "Busy" && !expired(current, now), () => Effect.succeed("current")), Effect.catchIf((error) => error.reason._tag === "Unauthenticated", () => Effect.succeed("reconnect")));
131
+ });
132
+ const sessionFor = (connection) => Effect.gen(function* () {
133
+ const authorization = yield* storage.authorizations.get(connection.authorizationId);
134
+ const reconnect = fail(new Reconnect({
135
+ provider: authorization.provider,
136
+ connectionId: connection.id
137
+ }));
138
+ if (authorization.revocation !== "active") return yield* reconnect;
139
+ if ((yield* refresh(connection.id)) === "reconnect") return yield* reconnect;
140
+ return (yield* providers.get(authorization.provider)).session(yield* open(authorization));
141
+ });
142
+ const connectWith = (input) => Effect.gen(function* () {
143
+ const principal = yield* Service$2;
144
+ const session = input.definition.session(input.issued);
145
+ const held = yield* session.capabilities();
146
+ const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));
147
+ if (missing.length > 0) return yield* fail(new Forbidden({ message: `${input.definition.name} credential lacks ${missing.join(", ")}` }));
148
+ const authorization = new Authorization({
149
+ id: yield* fresh("auth"),
150
+ ownerId: principal.ownerId,
151
+ provider: input.definition.id,
152
+ method: input.method,
153
+ capabilities: held,
154
+ context: yield* encodeContext(input.definition, input.issued.context),
155
+ revocation: "active",
156
+ createdBy: principal.actorId,
157
+ createdAt: yield* DateTime.now
158
+ });
159
+ yield* storage.authorizations.upsert({
160
+ authorization,
161
+ credential: yield* seal(input.issued)
162
+ });
163
+ return {
164
+ connection: yield* storage.connections.create(authorization.id),
165
+ session
166
+ };
167
+ });
168
+ const attachWith = (input) => Effect.gen(function* () {
169
+ const domain = yield* decode(input.domain);
170
+ const existing = yield* storage.attachments.byDomain(domain);
171
+ if (Option.isSome(existing)) {
172
+ if (existing.value.connectionId === input.connection.id) return existing.value;
173
+ return yield* invalid(`${domain} is already attached to another connection`, "domain");
174
+ }
175
+ const resolution = input.target === void 0 ? yield* input.session.resolveTarget(domain) : Resolution.Resolved({ target: input.target });
176
+ switch (resolution._tag) {
177
+ case "SelectionRequired": return Selection.SelectionRequired({ candidates: resolution.candidates });
178
+ case "NotFound": return yield* fail(new NotFound({
179
+ entity: "zone",
180
+ id: domain
181
+ }));
182
+ case "Resolved": return yield* storage.attachments.create({
183
+ connectionId: input.connection.id,
184
+ domain,
185
+ zone: resolution.target.zone,
186
+ target: {
187
+ ...resolution.target,
188
+ context: yield* encodeContext(input.definition, resolution.target.context)
189
+ }
190
+ });
191
+ }
192
+ });
193
+ const started = (connection, attached) => attached === null ? Started.Connected({
194
+ connection,
195
+ attachment: null
196
+ }) : attached instanceof Attachment ? Started.Connected({
197
+ connection,
198
+ attachment: attached
199
+ }) : Started.SelectionRequired({
200
+ connection,
201
+ candidates: attached.candidates
202
+ });
203
+ /** Authoritative nameservers for the closest zone that answers an NS query. */
204
+ const authoritative = (candidates) => Effect.gen(function* () {
205
+ for (const zone of candidates) {
206
+ const names = (yield* resolver.resolve(zone, "NS")).flatMap((outcome) => outcome._tag === "Answered" ? outcome.answer.records.flatMap((record) => record._tag === "NS" ? [record.nameserver] : []) : []);
207
+ if (names.length > 0) return [...new Set(names)].sort();
208
+ }
209
+ return [];
210
+ });
211
+ const discover = (input) => Effect.gen(function* () {
212
+ const domain = yield* decode(input);
213
+ const candidates$1 = candidates(domain);
214
+ const nameservers = yield* authoritative(candidates$1);
215
+ const reachable = [];
216
+ for (const connection of yield* storage.connections.list()) {
217
+ const targets = yield* sessionFor(connection).pipe(Effect.flatMap((session) => session.listTargets()), Effect.catch(() => Effect.succeed([])));
218
+ for (const target of targets) if (candidates$1.includes(target.zone)) reachable.push({
219
+ connectionId: connection.id,
220
+ target
221
+ });
222
+ }
223
+ const normalize = (name) => name.toLowerCase().replace(/\.$/, "");
224
+ for (const zone of candidates$1) {
225
+ const matches = reachable.filter(({ target }) => target.zone === zone).sort((left, right) => left.connectionId.localeCompare(right.connectionId));
226
+ if (matches.length === 0) continue;
227
+ const [only] = matches;
228
+ if (matches.length === 1 && only !== void 0) return Discovery.Resolved(only);
229
+ const decisive = matches.filter(({ target }) => {
230
+ const configured = new Set((target.nameservers ?? []).map(normalize));
231
+ return nameservers.length > 0 && nameservers.every((name) => configured.has(name));
232
+ });
233
+ const [winner] = decisive;
234
+ if (decisive.length === 1 && winner !== void 0) return Discovery.Resolved(winner);
235
+ return Discovery.SelectionRequired({ candidates: matches });
236
+ }
237
+ return Discovery.NotFound({ nameservers });
238
+ });
239
+ const inspect = (input) => Effect.gen(function* () {
240
+ const domain = yield* decode(input);
241
+ const policy = yield* Policy;
242
+ const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));
243
+ const connection = attachment === null ? null : yield* storage.connections.get(attachment.connectionId);
244
+ const authorization = connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);
245
+ const latest = attachment === null ? Option.none() : yield* storage.attempts.latest(attachment.id, "provisioning");
246
+ const reusable = [];
247
+ for (const candidate of yield* storage.connections.list()) {
248
+ if (candidate.id === connection?.id) continue;
249
+ if (!(yield* policy.allowReuse({
250
+ connection: candidate,
251
+ domain
252
+ }))) continue;
253
+ const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);
254
+ if (candidateAuthorization.revocation !== "active") continue;
255
+ reusable.push({
256
+ connection: candidate,
257
+ provider: candidateAuthorization.provider,
258
+ method: candidateAuthorization.method
259
+ });
260
+ }
261
+ return {
262
+ domain,
263
+ attachment,
264
+ connection,
265
+ authorization: authorization === null ? null : {
266
+ id: authorization.id,
267
+ provider: authorization.provider,
268
+ method: authorization.method,
269
+ capabilities: authorization.capabilities,
270
+ revocation: authorization.revocation
271
+ },
272
+ lastReceiptId: Option.isSome(latest) ? latest.value.receipt?.id ?? null : null,
273
+ reusable,
274
+ providers: providers.list().map((definition) => ({
275
+ id: definition.id,
276
+ name: definition.name,
277
+ methods: describeMethods(definition)
278
+ }))
279
+ };
280
+ });
281
+ const start = (input) => Effect.gen(function* () {
282
+ yield* recover;
283
+ const principal = yield* Service$2;
284
+ const policy = yield* Policy;
285
+ const definition = yield* providers.get(input.provider);
286
+ if (input.domain !== void 0) yield* decode(input.domain);
287
+ switch (input.method._tag) {
288
+ case "Token": {
289
+ const token = definition.auth.token;
290
+ if (token === void 0) return yield* invalid(`${definition.name} does not accept tokens`, "method");
291
+ const issued = yield* token.authenticate(input.method.values);
292
+ const { connection, session } = yield* connectWith({
293
+ definition,
294
+ method: "token",
295
+ issued,
296
+ requiredCapabilities: token.requiredCapabilities
297
+ });
298
+ const attached = input.domain === void 0 ? null : yield* attachWith({
299
+ definition,
300
+ connection,
301
+ session,
302
+ domain: input.domain
303
+ });
304
+ return started(connection, attached);
305
+ }
306
+ case "OAuth":
307
+ case "Integration": {
308
+ const interactive = input.method._tag === "OAuth" ? definition.auth.oauth : definition.auth.integration;
309
+ if (interactive === void 0) return yield* invalid(`${definition.name} does not offer ${input.method._tag.toLowerCase()} connections`, "method");
310
+ if (input.callbackUrl === void 0) return yield* invalid("Interactive connections need a callbackUrl", "callbackUrl");
311
+ const continuationId = yield* fresh("cont");
312
+ const pkce$1 = input.method._tag === "OAuth" ? yield* pkce() : null;
313
+ const redirect = input.method._tag === "OAuth" ? yield* interactive.start({
314
+ state: continuationId,
315
+ callbackUrl: input.callbackUrl,
316
+ codeChallenge: pkce$1?.codeChallenge ?? ""
317
+ }) : yield* interactive.start({
318
+ state: continuationId,
319
+ callbackUrl: input.callbackUrl
320
+ });
321
+ yield* storage.continuations.put(new Continuation({
322
+ id: continuationId,
323
+ ownerId: principal.ownerId,
324
+ actorId: principal.actorId,
325
+ provider: definition.id,
326
+ payload: Schema.encodeSync(Payload)({
327
+ method: input.method._tag === "OAuth" ? "oauth" : "integration",
328
+ codeVerifier: pkce$1?.codeVerifier ?? null,
329
+ callbackUrl: input.callbackUrl,
330
+ domain: input.domain ?? null
331
+ }),
332
+ returnTo: input.method.returnTo ?? null,
333
+ expiresAt: DateTime.addDuration(yield* DateTime.now, Duration.millis(policy.continuationTtlMs))
334
+ }));
335
+ return Started.Redirect({
336
+ authorizationUrl: redirect.authorizationUrl,
337
+ continuationId
338
+ });
339
+ }
340
+ }
341
+ });
342
+ const complete = (input) => Effect.gen(function* () {
343
+ const callback = yield* Effect.try({
344
+ try: () => new URL(input.callbackUrl),
345
+ catch: () => new DomainKitError({ reason: new InvalidInput({
346
+ message: "callbackUrl is not a URL",
347
+ field: "callbackUrl"
348
+ }) })
349
+ });
350
+ const params = Object.fromEntries(callback.searchParams);
351
+ const unauthenticated = (message) => fail(new Unauthenticated({ message }));
352
+ if (params.error !== void 0) return yield* unauthenticated(`Provider returned ${params.error}`);
353
+ if (params.state !== input.continuationId) return yield* unauthenticated("Callback state does not match the continuation");
354
+ const code = params.code;
355
+ if (code === void 0) return yield* unauthenticated("Callback has no code");
356
+ return yield* storage.withLock(`continuation:${input.continuationId}`, Effect.gen(function* () {
357
+ const continuation = yield* storage.continuations.get(input.continuationId);
358
+ const payload = yield* decode$1(Payload, continuation.payload, "continuation");
359
+ const definition = yield* providers.get(continuation.provider);
360
+ const issued = payload.method === "oauth" ? yield* Effect.gen(function* () {
361
+ const oauth = definition.auth.oauth;
362
+ if (oauth === void 0 || payload.codeVerifier === null) return yield* invalid(`${definition.name} does not offer oauth connections`, "method");
363
+ return yield* oauth.complete({
364
+ code,
365
+ callbackUrl: payload.callbackUrl,
366
+ codeVerifier: payload.codeVerifier,
367
+ params
368
+ });
369
+ }) : yield* Effect.gen(function* () {
370
+ const integration = definition.auth.integration;
371
+ if (integration === void 0) return yield* invalid(`${definition.name} does not offer integration connections`, "method");
372
+ return yield* integration.complete({
373
+ code,
374
+ callbackUrl: payload.callbackUrl,
375
+ params
376
+ });
377
+ });
378
+ const { connection, session } = yield* connectWith({
379
+ definition,
380
+ method: payload.method,
381
+ issued,
382
+ requiredCapabilities: []
383
+ });
384
+ const attached = payload.domain === null ? null : yield* attachWith({
385
+ definition,
386
+ connection,
387
+ session,
388
+ domain: payload.domain
389
+ });
390
+ yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);
391
+ return started(connection, attached);
392
+ }));
393
+ });
394
+ const attach = (input) => Effect.gen(function* () {
395
+ const connection = yield* storage.connections.get(input.connectionId);
396
+ const authorization = yield* storage.authorizations.get(connection.authorizationId);
397
+ const definition = yield* providers.get(authorization.provider);
398
+ const session = yield* sessionFor(connection);
399
+ return yield* attachWith({
400
+ definition,
401
+ connection,
402
+ session,
403
+ domain: input.domain,
404
+ ...input.target === void 0 ? {} : { target: input.target }
405
+ });
406
+ });
407
+ const disconnect = (connectionId) => Effect.gen(function* () {
408
+ yield* recover;
409
+ const connection = yield* storage.connections.get(connectionId);
410
+ const attachments = yield* storage.attachments.list(connectionId);
411
+ yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));
412
+ yield* storage.connections.remove(connectionId);
413
+ if ((yield* storage.connections.list()).some((candidate) => candidate.authorizationId === connection.authorizationId)) return;
414
+ const authorization = yield* storage.authorizations.get(connection.authorizationId);
415
+ yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));
416
+ });
417
+ const session = (attachmentId) => Effect.gen(function* () {
418
+ const attachment = yield* storage.attachments.get(attachmentId);
419
+ const connection = yield* storage.connections.get(attachment.connectionId);
420
+ const authorization = yield* storage.authorizations.get(connection.authorizationId);
421
+ const definition = yield* providers.get(authorization.provider);
422
+ const stored = yield* decode$1(Target, attachment.target, "target");
423
+ const target = {
424
+ ...stored,
425
+ context: yield* decodeContext(definition, stored.context)
426
+ };
427
+ const built = yield* sessionFor(connection);
428
+ if (!(yield* built.listTargets()).some((candidate) => candidate.zone === target.zone)) return yield* fail(new NotFound({
429
+ entity: "zone",
430
+ id: target.zone
431
+ }));
432
+ return {
433
+ session: built,
434
+ target
435
+ };
436
+ });
437
+ return {
438
+ inspect,
439
+ discover,
440
+ start,
441
+ complete,
442
+ attach,
443
+ detach: (attachmentId) => storage.attachments.remove(attachmentId),
444
+ disconnect,
445
+ refresh,
446
+ session
447
+ };
448
+ });
449
+ const layer = Layer.effect(Service)(make);
450
+ const accessor = (pick) => (...args) => Effect.flatMap(Service, (service) => pick(service)(...args));
451
+ const inspect = accessor((service) => service.inspect);
452
+ const discover = accessor((service) => service.discover);
453
+ const start = accessor((service) => service.start);
454
+ const complete = accessor((service) => service.complete);
455
+ const attach = accessor((service) => service.attach);
456
+ const detach = accessor((service) => service.detach);
457
+ const disconnect = accessor((service) => service.disconnect);
458
+ const refresh = accessor((service) => service.refresh);
459
+ const session = accessor((service) => service.session);
460
+ //#endregion
461
+ export { Connect_exports, Discovery, Method, Policy, Selection, Service, Started, attach, complete, defaults, detach, disconnect, discover, inspect, layer, make, refresh, session, start };
462
+
463
+ //# sourceMappingURL=Connect.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Connect.mjs","names":["Errors.fail","Reason.InvalidInput","Storage.Service","Custody.Service","Providers.Service","Resolver.Service","Storage.Credential","Provider.decodeContext","Reason.Reconnect","Principal.Service","Reason.Forbidden","Storage.Authorization","Provider.encodeContext","DomainName.decode","Reason.NotFound","Storage.Attachment","candidates","DomainName.candidates","Provider.describeMethods","pkce","OAuth.pkce","Storage.Continuation","Errors.DomainKitError","Reason.Unauthenticated","Errors.decode"],"sources":["../src/Connect.ts"],"sourcesContent":["/**\n * Connect a provider, attach domains to it, and keep the credential alive. Interactive methods\n * (OAuth, integration) return a redirect and complete on callback; token methods connect in one\n * call. Reuse of an existing connection for a new domain is `attach`.\n */\nimport { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from \"effect\";\n\nimport * as Custody from \"./Custody.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { fresh } from \"./internal/ids.ts\";\nimport * as OAuth from \"./internal/oauth.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport type Method = Data.TaggedEnum<{\n /** Values keyed by the provider's declared `auth.token.fields`. */\n Token: { readonly values: Provider.TokenValues };\n OAuth: { readonly returnTo?: string };\n Integration: { readonly returnTo?: string };\n}>;\nconst MethodEnum = Data.taggedEnum<Method>();\n/** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */\nexport const Method = {\n ...MethodEnum,\n token: (\n values: string | Readonly<Record<string, Redacted.Redacted<string> | string | undefined>>,\n ): Method =>\n MethodEnum.Token({\n values:\n typeof values === \"string\"\n ? { token: Redacted.make(values) }\n : Object.fromEntries(\n Object.entries(values).map(([name, value]) => [\n name,\n typeof value === \"string\" ? Redacted.make(value) : value,\n ]),\n ),\n }),\n oauth: (options: { readonly returnTo?: string } = {}): Method => MethodEnum.OAuth(options),\n integration: (options: { readonly returnTo?: string } = {}): Method =>\n MethodEnum.Integration(options),\n} as const;\n\n/** Everything the UI needs about a domain's provider state. */\nexport interface Snapshot {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly connection: Storage.Connection | null;\n readonly authorization: Pick<\n Storage.Authorization,\n \"id\" | \"provider\" | \"method\" | \"capabilities\" | \"revocation\"\n > | null;\n readonly lastReceiptId: string | null;\n /** Other connections of this owner that could serve this domain. */\n readonly reusable: ReadonlyArray<{\n readonly connection: Storage.Connection;\n readonly provider: string;\n readonly method: Storage.AuthMethod;\n }>;\n readonly providers: ReadonlyArray<{\n readonly id: string;\n readonly name: string;\n readonly methods: ReadonlyArray<Provider.MethodDescriptor>;\n }>;\n}\n\nexport type Started = Data.TaggedEnum<{\n Connected: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n };\n Redirect: { readonly authorizationUrl: string; readonly continuationId: string };\n SelectionRequired: {\n readonly connection: Storage.Connection;\n readonly candidates: ReadonlyArray<Provider.Target>;\n };\n}>;\nexport const Started = Data.taggedEnum<Started>();\n\n/** `attach` either attached the domain or needs the caller to pick a zone. */\nexport type Selection = Data.TaggedEnum<{\n SelectionRequired: { readonly candidates: ReadonlyArray<Provider.Target> };\n}>;\nexport const Selection = Data.taggedEnum<Selection>();\n\nexport type Attached = Storage.Attachment | Selection;\n\nexport type Refreshed = \"current\" | \"refreshed\" | \"reconnect\";\n\n/** Which of the principal's connections serves a domain, from nameserver evidence and zone lists. */\nexport type Discovery = Data.TaggedEnum<{\n Resolved: { readonly connectionId: string; readonly target: Provider.Target };\n SelectionRequired: {\n readonly candidates: ReadonlyArray<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }>;\n };\n NotFound: { readonly nameservers: ReadonlyArray<string> };\n}>;\nexport const Discovery = Data.taggedEnum<Discovery>();\n\nexport interface Interface {\n readonly inspect: (domain: string) => Fx<Snapshot>;\n /**\n * Resolve the domain's authoritative nameservers, then match them against the zones each of\n * the principal's connections can reach: the closest zone wins, a decisive nameserver match\n * breaks ties, and anything else needs a selection.\n */\n readonly discover: (domain: string) => Fx<Discovery>;\n /** Connect a provider and, when `domain` is given, attach it. Token methods finish here. */\n readonly start: (input: {\n readonly provider: string;\n readonly method: Method;\n readonly domain?: string;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /** Finish an interactive connection from the provider callback (the full callback URL). */\n readonly complete: (input: {\n readonly continuationId: string;\n readonly callbackUrl: string;\n }) => Fx<Started>;\n readonly attach: (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly target?: Provider.Target;\n }) => Fx<Attached>;\n /** Records stay in DNS; DomainKit forgets the domain. Use `Cleanup` first to remove records. */\n readonly detach: (attachmentId: string) => Fx<void>;\n /** Detach every domain and revoke the credential at the provider when the method supports it. */\n readonly disconnect: (connectionId: string) => Fx<void>;\n /** Refresh a near-expiry credential, single-flighted. Called automatically before provider use. */\n readonly refresh: (connectionId: string) => Fx<Refreshed>;\n /** Live provider session for an attachment; used by Provision, Cleanup, Verify. */\n readonly session: (\n attachmentId: string,\n ) => Fx<{ readonly session: Provider.Session; readonly target: Provider.Target }>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Connect\") {}\n\n/** Which owner-scoped reuse is allowed. Default: any connection of the same owner may serve any of its domains. */\nexport interface PolicyShape {\n readonly allowReuse: (input: {\n readonly connection: Storage.Connection;\n readonly domain: string;\n }) => Effect.Effect<boolean, never, Principal.Service>;\n /** Refresh this long before expiry. Default 10 minutes. */\n readonly refreshBeforeMs: number;\n /** Continuation lifetime. Default 15 minutes. */\n readonly continuationTtlMs: number;\n}\nexport const defaults: PolicyShape = {\n allowReuse: () => Effect.succeed(true),\n refreshBeforeMs: 10 * 60_000,\n continuationTtlMs: 15 * 60_000,\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Connect/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n method: Schema.Literals([\"oauth\", \"integration\"]),\n codeVerifier: Schema.NullOr(Schema.String),\n callbackUrl: Schema.String,\n domain: Schema.NullOr(Schema.String),\n});\n\nconst Target = Schema.Struct({\n zone: Schema.String,\n context: Schema.Unknown,\n label: Schema.String,\n});\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const custody = yield* Custody.Service;\n const providers = yield* Providers.Service;\n const resolver = yield* Resolver.Service;\n\n const seal = (issued: Provider.IssuedCredential) =>\n Effect.gen(function* () {\n const ciphertext = yield* custody.seal(issued.secret);\n return new Storage.Credential({\n ciphertext,\n expiresAt: issued.expiresAt,\n rotatedAt: yield* DateTime.now,\n });\n });\n\n /** Plaintext credential with the context decoded from its stored envelope. */\n const open = (authorization: Storage.Authorization): Fx<Provider.Credential> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const row = yield* storage.authorizations.credential(authorization.id);\n const secret = yield* custody.open(row.ciphertext);\n const context = yield* Provider.decodeContext(definition, authorization.context);\n return { secret, context };\n });\n\n const refresherFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.refresh\n : method === \"integration\"\n ? definition.auth.integration?.refresh\n : undefined;\n\n const revokerFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.revoke\n : method === \"integration\"\n ? definition.auth.integration?.revoke\n : undefined;\n\n const revokeAt = (authorization: Storage.Authorization): Fx<void> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const revoke = revokerFor(definition, authorization.method);\n if (revoke === undefined) return;\n const credential = yield* open(authorization);\n yield* revoke(credential);\n });\n\n /** Finish revocations a crash interrupted; best effort, never blocks the caller. */\n const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);\n\n const refresh: Interface[\"refresh\"] = (connectionId) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const connection = yield* storage.connections.get(connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const refresher = refresherFor(definition, authorization.method);\n const dueAt = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <=\n policy.refreshBeforeMs;\n const expired = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);\n const current = yield* storage.authorizations.credential(authorization.id);\n const now = yield* DateTime.now;\n if (!dueAt(current, now)) return \"current\" as const;\n if (refresher === undefined)\n return expired(current, now) ? (\"reconnect\" as const) : (\"current\" as const);\n return yield* storage\n .withLock(\n `refresh:${authorization.id}`,\n Effect.gen(function* () {\n // Disconnect's two-phase revocation does not take this lock: re-read the row here\n // so a revocation that started meanwhile skips the refresh instead of racing it.\n const live = yield* storage.authorizations.get(authorization.id).pipe(\n Effect.map(Option.some),\n Effect.catchIf(\n (error) => error.reason._tag === \"NotFound\",\n () => Effect.succeed(Option.none<Storage.Authorization>()),\n ),\n );\n if (Option.isNone(live) || live.value.revocation !== \"active\") {\n return \"reconnect\" as const;\n }\n const latest = yield* storage.authorizations.credential(authorization.id);\n if (!dueAt(latest, yield* DateTime.now)) return \"current\" as const;\n const secret = yield* custody.open(latest.ciphertext);\n const context = yield* Provider.decodeContext(definition, live.value.context);\n const issued = yield* refresher({ secret, context });\n // A revocation that completed while the provider was issuing leaves no row to\n // rotate; revoke the fresh credential so it does not stay live without a home.\n yield* storage.authorizations\n .rotate(authorization.id, yield* seal(issued))\n .pipe(\n Effect.tapError((error) =>\n error.reason._tag === \"NotFound\"\n ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(\n Effect.ignore,\n )\n : Effect.void,\n ),\n );\n return \"refreshed\" as const;\n }),\n )\n .pipe(\n Effect.catchIf(\n (error) => error.reason._tag === \"Busy\" && !expired(current, now),\n () => Effect.succeed(\"current\" as const),\n ),\n Effect.catchIf(\n (error) => error.reason._tag === \"Unauthenticated\",\n () => Effect.succeed(\"reconnect\" as const),\n ),\n );\n });\n\n const sessionFor = (connection: Storage.Connection): Fx<Provider.Session> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const reconnect = Errors.fail(\n new Reason.Reconnect({\n provider: authorization.provider,\n connectionId: connection.id,\n }),\n );\n if (authorization.revocation !== \"active\") return yield* reconnect;\n if ((yield* refresh(connection.id)) === \"reconnect\") return yield* reconnect;\n const definition = yield* providers.get(authorization.provider);\n return definition.session(yield* open(authorization));\n });\n\n const connectWith = (input: {\n readonly definition: Provider.Definition;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<{ readonly connection: Storage.Connection; readonly session: Provider.Session }> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: yield* fresh(\"auth\"),\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n });\n const connection = yield* storage.connections.create(authorization.id);\n return { connection, session };\n });\n\n const attachWith = (input: {\n readonly definition: Provider.Definition;\n readonly connection: Storage.Connection;\n readonly session: Provider.Session;\n readonly domain: string;\n readonly target?: Provider.Target;\n }): Fx<Attached> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input.domain);\n const existing = yield* storage.attachments.byDomain(domain);\n if (Option.isSome(existing)) {\n if (existing.value.connectionId === input.connection.id) return existing.value;\n return yield* invalid(`${domain} is already attached to another connection`, \"domain\");\n }\n const resolution: Provider.Resolution =\n input.target === undefined\n ? yield* input.session.resolveTarget(domain)\n : Provider.Resolution.Resolved({ target: input.target });\n switch (resolution._tag) {\n case \"SelectionRequired\":\n return Selection.SelectionRequired({ candidates: resolution.candidates });\n case \"NotFound\":\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: domain }));\n case \"Resolved\":\n return yield* storage.attachments.create({\n connectionId: input.connection.id,\n domain,\n zone: resolution.target.zone,\n target: {\n ...resolution.target,\n context: yield* Provider.encodeContext(input.definition, resolution.target.context),\n },\n });\n }\n });\n\n const started = (connection: Storage.Connection, attached: Attached | null): Started =>\n attached === null\n ? Started.Connected({ connection, attachment: null })\n : attached instanceof Storage.Attachment\n ? Started.Connected({ connection, attachment: attached })\n : Started.SelectionRequired({ connection, candidates: attached.candidates });\n\n /** Authoritative nameservers for the closest zone that answers an NS query. */\n const authoritative = (candidates: ReadonlyArray<DomainName.Model>) =>\n Effect.gen(function* () {\n for (const zone of candidates) {\n const outcomes = yield* resolver.resolve(zone, \"NS\");\n const names = outcomes.flatMap((outcome) =>\n outcome._tag === \"Answered\"\n ? outcome.answer.records.flatMap((record) =>\n record._tag === \"NS\" ? [record.nameserver] : [],\n )\n : [],\n );\n if (names.length > 0) return [...new Set(names)].sort();\n }\n return [] as ReadonlyArray<string>;\n });\n\n const discover: Interface[\"discover\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const candidates = DomainName.candidates(domain);\n const nameservers = yield* authoritative(candidates);\n const reachable: Array<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }> = [];\n for (const connection of yield* storage.connections.list()) {\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.catch(() => Effect.succeed([] as ReadonlyArray<Provider.Target>)),\n );\n for (const target of targets) {\n if (candidates.includes(target.zone as DomainName.Model)) {\n reachable.push({ connectionId: connection.id, target });\n }\n }\n }\n const normalize = (name: string) => name.toLowerCase().replace(/\\.$/, \"\");\n for (const zone of candidates) {\n const matches = reachable\n .filter(({ target }) => target.zone === zone)\n .sort((left, right) => left.connectionId.localeCompare(right.connectionId));\n if (matches.length === 0) continue;\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Discovery.Resolved(only);\n const decisive = matches.filter(({ target }) => {\n const configured = new Set((target.nameservers ?? []).map(normalize));\n return nameservers.length > 0 && nameservers.every((name) => configured.has(name));\n });\n const [winner] = decisive;\n if (decisive.length === 1 && winner !== undefined) return Discovery.Resolved(winner);\n return Discovery.SelectionRequired({ candidates: matches });\n }\n return Discovery.NotFound({ nameservers });\n });\n\n const inspect: Interface[\"inspect\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const policy = yield* Policy;\n const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));\n const connection =\n attachment === null ? null : yield* storage.connections.get(attachment.connectionId);\n const authorization =\n connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);\n const latest =\n attachment === null\n ? Option.none()\n : yield* storage.attempts.latest(attachment.id, \"provisioning\");\n const reusable: Array<Snapshot[\"reusable\"][number]> = [];\n for (const candidate of yield* storage.connections.list()) {\n if (candidate.id === connection?.id) continue;\n if (!(yield* policy.allowReuse({ connection: candidate, domain }))) continue;\n const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);\n if (candidateAuthorization.revocation !== \"active\") continue;\n reusable.push({\n connection: candidate,\n provider: candidateAuthorization.provider,\n method: candidateAuthorization.method,\n });\n }\n return {\n domain,\n attachment,\n connection,\n authorization:\n authorization === null\n ? null\n : {\n id: authorization.id,\n provider: authorization.provider,\n method: authorization.method,\n capabilities: authorization.capabilities,\n revocation: authorization.revocation,\n },\n lastReceiptId: Option.isSome(latest) ? (latest.value.receipt?.id ?? null) : null,\n reusable,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const start: Interface[\"start\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const definition = yield* providers.get(input.provider);\n if (input.domain !== undefined) yield* DomainName.decode(input.domain);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n const { connection, session } = yield* connectWith({\n definition,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n const attached =\n input.domain === undefined\n ? null\n : yield* attachWith({ definition, connection, session, domain: input.domain });\n return started(connection, attached);\n }\n case \"OAuth\":\n case \"Integration\": {\n const interactive =\n input.method._tag === \"OAuth\" ? definition.auth.oauth : definition.auth.integration;\n if (interactive === undefined) {\n return yield* invalid(\n `${definition.name} does not offer ${input.method._tag.toLowerCase()} connections`,\n \"method\",\n );\n }\n if (input.callbackUrl === undefined) {\n return yield* invalid(\"Interactive connections need a callbackUrl\", \"callbackUrl\");\n }\n const continuationId = yield* fresh(\"cont\");\n const pkce = input.method._tag === \"OAuth\" ? yield* OAuth.pkce() : null;\n const redirect =\n input.method._tag === \"OAuth\"\n ? yield* (interactive as Provider.OAuthAuth).start({\n state: continuationId,\n callbackUrl: input.callbackUrl,\n codeChallenge: pkce?.codeChallenge ?? \"\",\n })\n : yield* (interactive as Provider.IntegrationAuth).start({\n state: continuationId,\n callbackUrl: input.callbackUrl,\n });\n yield* storage.continuations.put(\n new Storage.Continuation({\n id: continuationId,\n ownerId: principal.ownerId,\n actorId: principal.actorId,\n provider: definition.id,\n payload: Schema.encodeSync(Payload)({\n method: input.method._tag === \"OAuth\" ? \"oauth\" : \"integration\",\n codeVerifier: pkce?.codeVerifier ?? null,\n callbackUrl: input.callbackUrl,\n domain: input.domain ?? null,\n }),\n returnTo: input.method.returnTo ?? null,\n expiresAt: DateTime.addDuration(\n yield* DateTime.now,\n Duration.millis(policy.continuationTtlMs),\n ),\n }),\n );\n return Started.Redirect({\n authorizationUrl: redirect.authorizationUrl,\n continuationId,\n });\n }\n }\n });\n\n const complete: Interface[\"complete\"] = (input) =>\n Effect.gen(function* () {\n // The continuation is spent only after the credential and connection are persisted, so a\n // failure before that (bad callback, provider outage, storage outage) leaves the flow\n // retryable. Concurrent callbacks for the same continuation serialize on a lock; the\n // loser sees Busy or NotFound. A provider code is single-use, so a retry after the\n // provider already redeemed it fails Unauthenticated and the customer starts over.\n const callback = yield* Effect.try({\n try: () => new URL(input.callbackUrl),\n catch: () =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({\n message: \"callbackUrl is not a URL\",\n field: \"callbackUrl\",\n }),\n }),\n });\n const params = Object.fromEntries(callback.searchParams);\n const unauthenticated = (message: string) =>\n Errors.fail(new Reason.Unauthenticated({ message }));\n if (params.error !== undefined) {\n return yield* unauthenticated(`Provider returned ${params.error}`);\n }\n if (params.state !== input.continuationId) {\n return yield* unauthenticated(\"Callback state does not match the continuation\");\n }\n const code = params.code;\n if (code === undefined) return yield* unauthenticated(\"Callback has no code\");\n return yield* storage.withLock(\n `continuation:${input.continuationId}`,\n Effect.gen(function* () {\n const continuation = yield* storage.continuations.get(input.continuationId);\n const payload = yield* Errors.decode(Payload, continuation.payload, \"continuation\");\n const definition = yield* providers.get(continuation.provider);\n const issued =\n payload.method === \"oauth\"\n ? yield* Effect.gen(function* () {\n const oauth = definition.auth.oauth;\n if (oauth === undefined || payload.codeVerifier === null) {\n return yield* invalid(\n `${definition.name} does not offer oauth connections`,\n \"method\",\n );\n }\n return yield* oauth.complete({\n code,\n callbackUrl: payload.callbackUrl,\n codeVerifier: payload.codeVerifier,\n params,\n });\n })\n : yield* Effect.gen(function* () {\n const integration = definition.auth.integration;\n if (integration === undefined) {\n return yield* invalid(\n `${definition.name} does not offer integration connections`,\n \"method\",\n );\n }\n return yield* integration.complete({\n code,\n callbackUrl: payload.callbackUrl,\n params,\n });\n });\n const { connection, session } = yield* connectWith({\n definition,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n const attached =\n payload.domain === null\n ? null\n : yield* attachWith({ definition, connection, session, domain: payload.domain });\n // The connection is durable at this point. Whatever happens to the continuation\n // now (expired, already spent, or a storage failure while spending it) no longer\n // changes the outcome: the row expires on its own and a replayed callback fails at\n // the provider, so the persisted result is returned.\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return started(connection, attached);\n }),\n );\n });\n\n const attach: Interface[\"attach\"] = (input) =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const session = yield* sessionFor(connection);\n return yield* attachWith({\n definition,\n connection,\n session,\n domain: input.domain,\n ...(input.target === undefined ? {} : { target: input.target }),\n });\n });\n\n const disconnect: Interface[\"disconnect\"] = (connectionId) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(connectionId);\n const attachments = yield* storage.attachments.list(connectionId);\n yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));\n yield* storage.connections.remove(connectionId);\n const remaining = (yield* storage.connections.list()).some(\n (candidate) => candidate.authorizationId === connection.authorizationId,\n );\n if (remaining) return;\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));\n });\n\n const session: Interface[\"session\"] = (attachmentId) =>\n Effect.gen(function* () {\n const attachment = yield* storage.attachments.get(attachmentId);\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const stored = yield* Errors.decode(Target, attachment.target, \"target\");\n const target: Provider.Target = {\n ...stored,\n context: yield* Provider.decodeContext(definition, stored.context),\n };\n const built = yield* sessionFor(connection);\n // The zone must still be reachable by this credential: it may have moved account, been\n // deleted, or become a kind the provider cannot host records in.\n const reachable = yield* built.listTargets();\n if (!reachable.some((candidate) => candidate.zone === target.zone)) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }));\n }\n return { session: built, target };\n });\n\n return {\n inspect,\n discover,\n start,\n complete,\n attach,\n detach: (attachmentId) => storage.attachments.remove(attachmentId),\n disconnect,\n refresh,\n session,\n };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const inspect = accessor((service) => service.inspect);\nexport const discover = accessor((service) => service.discover);\nexport const start = accessor((service) => service.start);\nexport const complete = accessor((service) => service.complete);\nexport const attach = accessor((service) => service.attach);\nexport const detach = accessor((service) => service.detach);\nexport const disconnect = accessor((service) => service.disconnect);\nexport const refresh = accessor((service) => service.refresh);\nexport const session = accessor((service) => service.session);\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { CryptoFailed, InvalidInput } from "./Reason.mjs";
3
+ import { DomainKitError, fail, isDomainKitError } from "./internal/error.mjs";
4
+ import { decodeKey, generateKey as generateKey$1, importKey, open, seal } from "./internal/aes.mjs";
5
+ import { Config, Context, Effect, Layer, Redacted } from "effect";
6
+ //#region src/Custody.ts
7
+ /**
8
+ * Seals provider credentials before Storage writes them. The default is AES-256-GCM through
9
+ * WebCrypto from one configured key; hosts with a KMS provide their own. There is no plaintext
10
+ * mode.
11
+ *
12
+ * Envelope format: `v1.<iv>.<ciphertext>` with base64url segments and a 12-byte IV. Rotating
13
+ * the key requires re-sealing stored credentials; `open` fails with `CryptoFailed` on a mismatch.
14
+ */
15
+ var Custody_exports = /* @__PURE__ */ __exportAll({
16
+ Service: () => Service,
17
+ fromAsync: () => fromAsync,
18
+ generateKey: () => generateKey,
19
+ layer: () => layer,
20
+ layerConfig: () => layerConfig,
21
+ layerFromAsync: () => layerFromAsync,
22
+ make: () => make
23
+ });
24
+ var Service = class extends Context.Service()("@domainkit/Custody") {};
25
+ const make = (options) => Effect.gen(function* () {
26
+ const raw = decodeKey(Redacted.value(options.key));
27
+ if (raw === null) return yield* fail(new InvalidInput({
28
+ message: "Custody key must be 32 bytes encoded as base64 or hex",
29
+ field: "key"
30
+ }));
31
+ const key = yield* importKey(raw);
32
+ return {
33
+ seal: (plaintext) => seal(key, plaintext),
34
+ open: (ciphertext) => open(key, ciphertext)
35
+ };
36
+ });
37
+ const layer = (options) => Layer.effect(Service)(make(options));
38
+ /** Same options, each field a `Config`. Defaults to `DOMAINKIT_CUSTODY_KEY`. */
39
+ const layerConfig = (options = {}) => Layer.effect(Service)(Effect.gen(function* () {
40
+ const key = yield* (options.key ?? Config.redacted("DOMAINKIT_CUSTODY_KEY")).pipe(Effect.mapError((cause) => new DomainKitError({ reason: new InvalidInput({
41
+ message: cause.message,
42
+ field: "key"
43
+ }) })));
44
+ return yield* make({ key });
45
+ }));
46
+ /** A fresh random key in the accepted encoding; for tests, playgrounds, and first-time setup. */
47
+ const generateKey = generateKey$1;
48
+ const cryptoFailed = (operation) => (cause) => isDomainKitError(cause) ? cause : new DomainKitError({ reason: new CryptoFailed({ operation }) });
49
+ /** Wrap a Promise-shaped KMS client; rejections become `CryptoFailed`. */
50
+ const fromAsync = (service) => ({
51
+ seal: (plaintext) => Effect.tryPromise({
52
+ try: () => service.seal(Redacted.value(plaintext)),
53
+ catch: cryptoFailed("seal")
54
+ }),
55
+ open: (ciphertext) => Effect.tryPromise({
56
+ try: () => service.open(ciphertext),
57
+ catch: cryptoFailed("open")
58
+ }).pipe(Effect.map((value) => Redacted.make(value)))
59
+ });
60
+ const layerFromAsync = (service) => Layer.succeed(Service)(fromAsync(service));
61
+ //#endregion
62
+ export { Custody_exports, Service, fromAsync, generateKey, layer, layerConfig, layerFromAsync, make };
63
+
64
+ //# sourceMappingURL=Custody.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Custody.mjs","names":["Aes.decodeKey","Errors.fail","Reason.InvalidInput","Aes.importKey","Aes.seal","Aes.open","Errors.DomainKitError","Aes.generateKey","Errors.isDomainKitError","Reason.CryptoFailed"],"sources":["../src/Custody.ts"],"sourcesContent":["/**\n * Seals provider credentials before Storage writes them. The default is AES-256-GCM through\n * WebCrypto from one configured key; hosts with a KMS provide their own. There is no plaintext\n * mode.\n *\n * Envelope format: `v1.<iv>.<ciphertext>` with base64url segments and a 12-byte IV. Rotating\n * the key requires re-sealing stored credentials; `open` fails with `CryptoFailed` on a mismatch.\n */\nimport { Config, Context, Effect, Layer, Redacted } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Aes from \"./internal/aes.ts\";\n\nexport interface Interface {\n readonly seal: (\n plaintext: Redacted.Redacted<string>,\n ) => Effect.Effect<string, Errors.DomainKitError>;\n readonly open: (\n ciphertext: string,\n ) => Effect.Effect<Redacted.Redacted<string>, Errors.DomainKitError>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Custody\") {}\n\nexport interface Options {\n /** 32 bytes, base64 or hex. Rotating it requires re-sealing stored credentials. */\n readonly key: Redacted.Redacted<string>;\n}\n\nexport const make = (options: Options): Effect.Effect<Interface, Errors.DomainKitError> =>\n Effect.gen(function* () {\n const raw = Aes.decodeKey(Redacted.value(options.key));\n if (raw === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Custody key must be 32 bytes encoded as base64 or hex\",\n field: \"key\",\n }),\n );\n }\n const key = yield* Aes.importKey(raw);\n return {\n seal: (plaintext) => Aes.seal(key, plaintext),\n open: (ciphertext) => Aes.open(key, ciphertext),\n };\n });\n\nexport const layer = (options: Options): Layer.Layer<Service, Errors.DomainKitError> =>\n Layer.effect(Service)(make(options));\n\n/** Same options, each field a `Config`. Defaults to `DOMAINKIT_CUSTODY_KEY`. */\nexport const layerConfig = (\n options: { readonly key?: Config.Config<Redacted.Redacted<string>> } = {},\n): Layer.Layer<Service, Errors.DomainKitError> =>\n Layer.effect(Service)(\n Effect.gen(function* () {\n const key = yield* (options.key ?? Config.redacted(\"DOMAINKIT_CUSTODY_KEY\")).pipe(\n Effect.mapError(\n (cause) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message: cause.message, field: \"key\" }),\n }),\n ),\n );\n return yield* make({ key });\n }),\n );\n\n/** A fresh random key in the accepted encoding; for tests, playgrounds, and first-time setup. */\nexport const generateKey = Aes.generateKey;\n\nexport interface AsyncInterface {\n readonly seal: (plaintext: string) => Promise<string>;\n readonly open: (ciphertext: string) => Promise<string>;\n}\n\nconst cryptoFailed = (operation: \"seal\" | \"open\") => (cause: unknown) =>\n Errors.isDomainKitError(cause)\n ? cause\n : new Errors.DomainKitError({ reason: new Reason.CryptoFailed({ operation }) });\n\n/** Wrap a Promise-shaped KMS client; rejections become `CryptoFailed`. */\nexport const fromAsync = (service: AsyncInterface): Interface => ({\n seal: (plaintext) =>\n Effect.tryPromise({\n try: () => service.seal(Redacted.value(plaintext)),\n catch: cryptoFailed(\"seal\"),\n }),\n open: (ciphertext) =>\n Effect.tryPromise({ try: () => service.open(ciphertext), catch: cryptoFailed(\"open\") }).pipe(\n Effect.map((value) => Redacted.make(value)),\n ),\n});\n\nexport const layerFromAsync = (service: AsyncInterface): Layer.Layer<Service> =>\n Layer.succeed(Service)(fromAsync(service));\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,UAAb,cAA6B,QAAQ,QAA4B,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAO1F,MAAa,QAAQ,YACnB,OAAO,IAAI,aAAa;CACtB,MAAM,MAAMA,UAAc,SAAS,MAAM,QAAQ,GAAG,CAAC;CACrD,IAAI,QAAQ,MACV,OAAO,OAAOC,KACZ,IAAIC,aAAoB;EACtB,SAAS;EACT,OAAO;CACT,CAAC,CACH;CAEF,MAAM,MAAM,OAAOC,UAAc,GAAG;CACpC,OAAO;EACL,OAAO,cAAcC,KAAS,KAAK,SAAS;EAC5C,OAAO,eAAeC,KAAS,KAAK,UAAU;CAChD;AACF,CAAC;AAEH,MAAa,SAAS,YACpB,MAAM,OAAO,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC;;AAGrC,MAAa,eACX,UAAuE,CAAC,MAExE,MAAM,OAAO,OAAO,CAAC,CACnB,OAAO,IAAI,aAAa;CACtB,MAAM,MAAM,QAAQ,QAAQ,OAAO,OAAO,SAAS,uBAAuB,EAAA,CAAG,KAC3E,OAAO,UACJ,UACC,IAAIC,eAAsB,EACxB,QAAQ,IAAIJ,aAAoB;EAAE,SAAS,MAAM;EAAS,OAAO;CAAM,CAAC,EAC1E,CAAC,CACL,CACF;CACA,OAAO,OAAO,KAAK,EAAE,IAAI,CAAC;AAC5B,CAAC,CACH;;AAGF,MAAa,cAAcK;AAO3B,MAAM,gBAAgB,eAAgC,UACpDC,iBAAwB,KAAK,IACzB,QACA,IAAIF,eAAsB,EAAE,QAAQ,IAAIG,aAAoB,EAAE,UAAU,CAAC,EAAE,CAAC;;AAGlF,MAAa,aAAa,aAAwC;CAChE,OAAO,cACL,OAAO,WAAW;EAChB,WAAW,QAAQ,KAAK,SAAS,MAAM,SAAS,CAAC;EACjD,OAAO,aAAa,MAAM;CAC5B,CAAC;CACH,OAAO,eACL,OAAO,WAAW;EAAE,WAAW,QAAQ,KAAK,UAAU;EAAG,OAAO,aAAa,MAAM;CAAE,CAAC,CAAC,CAAC,KACtF,OAAO,KAAK,UAAU,SAAS,KAAK,KAAK,CAAC,CAC5C;AACJ;AAEA,MAAa,kBAAkB,YAC7B,MAAM,QAAQ,OAAO,CAAC,CAAC,UAAU,OAAO,CAAC"}