domainkit 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,372 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { I as Error$3, L as Service$3, Y as Opaque, gt as decode$8, ht as Schema$4, i as toAsync$3, it as equals, n as Service$2, nt as decode$9, pt as Service$1, t as Error$4, tt as data, ut as candidates, vt as parse$1, x as authorize$1, yt as Error$2 } from "./resolver-CvK7JoJM.mjs";
2
+ import { E as decode$5, O as parse$1, T as Schema$2, b as authorizationError, c as Evidence$1, f as AuthorizationError, k as Error$1, s as Service$1, u as codec, y as assertAttachment } from "./canonical-json-9x7gHIoS.mjs";
3
+ import { n as Method$1, t as Error$2 } from "./connect-ks3cpqBD.mjs";
4
+ import { $ as data, F as Service$3, I as fromAsync$1, P as Error$3, ct as candidates, et as decode$6, i as toAsync$5, n as Service$2, nt as equals, q as Opaque, t as Error$4, x as authorize$1, z as toAsync$4 } from "./resolver-DxJPsq6l.mjs";
3
5
  import { Clock, Context, Crypto, Data, Effect, Layer, Option, Schema, SchemaIssue, SchemaTransformation } from "effect";
4
6
  import * as oauth from "oauth4webapi";
5
7
  import * as DnsPacket from "@leichtgewicht/dns-packet";
6
8
  //#region package.json
7
- var version = "0.4.0";
8
- //#endregion
9
- //#region src/auth/provider-context.ts
10
- var provider_context_exports = /* @__PURE__ */ __exportAll({
11
- Envelope: () => Envelope,
12
- codec: () => codec,
13
- decode: () => decode$7,
14
- encode: () => encode$4
15
- });
16
- /** A provider-owned, versioned, non-secret context value persisted by the host. */
17
- const Envelope = Schema.Struct({
18
- value: Schema.Json,
19
- version: Schema.String
20
- });
21
- /** Build an adapter-owned context codec from a schema and stable version identifier. */
22
- function codec(version, schema) {
23
- return {
24
- decode: Effect.fn("ProviderContext.decode")((envelope) => envelope.version !== version ? Effect.fail(new Error$2({ message: `Unsupported provider context version: ${envelope.version}` })) : Schema.decodeUnknownEffect(schema)(envelope.value).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message })))),
25
- encode: Effect.fn("ProviderContext.encode")((value) => Schema.encodeEffect(schema)(value).pipe(Effect.flatMap((encoded) => Schema.decodeUnknownEffect(Schema.Json)(encoded)), Effect.map((encoded) => ({
26
- value: encoded,
27
- version
28
- })), Effect.mapError((cause) => new Error$2({ message: cause.message })))),
29
- version
30
- };
31
- }
32
- const decode$7 = Effect.fn("ProviderContext.Envelope.decode")((input) => Schema.decodeUnknownEffect(Envelope)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
33
- const encode$4 = Schema.encodeSync(Envelope);
34
- //#endregion
35
- //#region src/auth/authorization.ts
36
- var authorization_exports = /* @__PURE__ */ __exportAll({
37
- Capability: () => Capability,
38
- CapabilityEvidence: () => CapabilityEvidence,
39
- Evidence: () => Evidence$1,
40
- EvidenceSchema: () => EvidenceSchema,
41
- Revocation: () => Revocation,
42
- Schema: () => Schema$3,
43
- decode: () => decode$6,
44
- encode: () => encode$3,
45
- evidenceFor: () => evidenceFor$1,
46
- validate: () => validate$1
47
- });
48
- const Capability = Schema.Literals(["dns:read", "dns:write"]);
49
- const Evidence$1 = Data.taggedEnum();
50
- const EvidenceSchema = Schema.TaggedUnion({
51
- Declared: {},
52
- Exercised: { observedAt: Schema.DateFromString },
53
- Introspected: { observedAt: Schema.DateFromString }
54
- });
55
- const CapabilityEvidence = Schema.Struct({
56
- capability: Capability,
57
- evidence: EvidenceSchema
58
- });
59
- const Revocation = Schema.TaggedUnion({
60
- Active: {},
61
- Pending: { requestedAt: Schema.DateFromString }
62
- });
63
- /** A provider credential authorization shared by one or more owner connections. */
64
- const Schema$3 = Schema.Struct({
65
- authorizedById: Schema.String,
66
- capabilityEvidence: Schema.Array(CapabilityEvidence),
67
- createdAt: Schema.DateFromString,
68
- expiresAt: Schema.NullOr(Schema.DateFromString),
69
- id: Schema.String,
70
- method: Schema.Literals([
71
- "integration",
72
- "oauth2",
73
- "token"
74
- ]),
75
- providerAccountId: Schema.String,
76
- providerContext: Envelope,
77
- providerId: Schema.String,
78
- requiredCapabilities: Schema.Array(Capability),
79
- revocation: Revocation,
80
- scopes: Schema.Array(Schema.String)
81
- });
82
- const decode$6 = Effect.fn("ProviderAuthorization.decode")((input) => Schema.decodeUnknownEffect(Schema$3)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
83
- const validate$1 = Effect.fn("ProviderAuthorization.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$3))(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
84
- const encode$3 = Schema.encodeSync(Schema$3);
85
- function evidenceFor$1(authorization, capability) {
86
- return authorization.capabilityEvidence.find((item) => item.capability === capability)?.evidence;
87
- }
88
- //#endregion
89
- //#region src/auth/connection.ts
90
- const Grant = Schema.TaggedUnion({
91
- account: { excludedDomains: Schema.Array(Schema$4) },
92
- domains: { domains: Schema.Array(Schema$4) }
93
- });
94
- /** Returns whether a grant includes one parsed domain. */
95
- const coversDomain = (grant, domain) => grant._tag === "account" ? !grant.excludedDomains.includes(domain) : grant.domains.includes(domain);
96
- /** Expands a grant to include the requested domains. */
97
- const includeDomains = (current, requested) => {
98
- if (current === void 0) return requested;
99
- if (current._tag === "account" && requested._tag === "account") return {
100
- _tag: "account",
101
- excludedDomains: current.excludedDomains.filter((domain) => requested.excludedDomains.includes(domain))
102
- };
103
- if (current._tag === "account" && requested._tag === "domains") return {
104
- _tag: "account",
105
- excludedDomains: current.excludedDomains.filter((domain) => !requested.domains.includes(domain))
106
- };
107
- if (requested._tag === "account") {
108
- const includedDomains = current._tag === "domains" ? current.domains : [];
109
- return {
110
- _tag: "account",
111
- excludedDomains: requested.excludedDomains.filter((domain) => !includedDomains.includes(domain))
112
- };
113
- }
114
- if (current._tag === "account") return current;
115
- return {
116
- _tag: "domains",
117
- domains: [.../* @__PURE__ */ new Set([...current.domains, ...requested.domains])]
118
- };
119
- };
120
- /** Contracts a grant so it no longer includes one parsed domain. */
121
- const removeDomain$1 = (grant, domain) => grant._tag === "account" ? {
122
- _tag: "account",
123
- excludedDomains: [.../* @__PURE__ */ new Set([...grant.excludedDomains, domain])]
124
- } : {
125
- _tag: "domains",
126
- domains: grant.domains.filter((candidate) => candidate !== domain)
127
- };
128
- /** An owner-scoped grant bound to one provider authorization. */
129
- const Schema$2 = Schema.Struct({
130
- authorizationId: Schema.String,
131
- createdAt: Schema.DateFromString,
132
- grant: Grant,
133
- id: Schema.String,
134
- ownerId: Schema.String
135
- });
136
- const decode$5 = Effect.fn("Connection.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
137
- const validate = Effect.fn("Connection.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$2))(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
138
- const encode$2 = Schema.encodeSync(Schema$2);
139
- var AuthorizationError = class extends Schema.TaggedError()("AuthorizationError", {
140
- category: Schema.Literal("authorization"),
141
- message: Schema.String,
142
- operation: Schema.String,
143
- retry: Schema.Literals([
144
- "never",
145
- "after-user-action",
146
- "safe",
147
- "unknown"
148
- ])
149
- }) {};
150
- const authorizationError = (message, operation) => new AuthorizationError({
151
- category: "authorization",
152
- message,
153
- operation,
154
- retry: "after-user-action"
155
- });
156
- function assertGrant(connection, authorization, request) {
157
- if (connection.authorizationId !== authorization.id || authorization.providerId !== request.providerId || authorization.revocation._tag !== "Active") throw authorizationError("Connection does not grant this provider account", "Connection.assertGrant");
158
- if (authorization.expiresAt !== null) {
159
- if (Number.isNaN(authorization.expiresAt.getTime())) throw authorizationError("Connection expiration is invalid", "Connection.assertGrant");
160
- if (authorization.expiresAt <= (request.now ?? /* @__PURE__ */ new Date())) throw authorizationError("Connection has expired", "Connection.assertGrant");
161
- }
162
- if (!authorization.requiredCapabilities.includes(request.capability)) throw authorizationError(`Connection lacks the ${request.capability} capability`, "Connection.assertGrant");
163
- if (evidenceFor$1(authorization, request.capability) === void 0) throw authorizationError(`Connection has no evidence for the ${request.capability} capability`, "Connection.assertGrant");
164
- let domain;
165
- try {
166
- domain = parse$1(request.domain);
167
- } catch (cause) {
168
- if (cause instanceof Error$2) throw cause;
169
- throw new Error$2({ message: cause instanceof Error ? cause.message : String(cause) });
170
- }
171
- if (!coversDomain(connection.grant, domain)) throw authorizationError("Connection does not grant this domain", "Connection.assertGrant");
172
- return domain;
173
- }
174
- //#endregion
175
- //#region src/auth/connect.ts
176
- const Method$1 = Data.taggedEnum();
177
- const StartResult = Data.taggedEnum();
178
- var Error$1 = class extends Schema.TaggedError()("ConnectionError", {
179
- category: Schema.Literals([
180
- "authorization",
181
- "provider",
182
- "validation"
183
- ]),
184
- message: Schema.String,
185
- operation: Schema.String,
186
- retry: Schema.Literals([
187
- "never",
188
- "after-user-action",
189
- "safe",
190
- "unknown"
191
- ])
192
- }) {};
193
- const RemoveDomainResult = Data.taggedEnum();
194
- const persist = Effect.fn("Connection.persist")(function* (input) {
195
- const repository = yield* Service$1;
196
- const crypto = yield* Crypto.Crypto;
197
- const now = new Date(yield* Clock.currentTimeMillis);
198
- const missingCapability = input.requiredCapabilities.find((capability) => !input.authentication.capabilityEvidence.some((item) => item.capability === capability));
199
- if (missingCapability !== void 0) return yield* new Error$1({
200
- category: "authorization",
201
- message: `Provider authorization lacks evidence for ${missingCapability}`,
202
- operation: "Connection.persist",
203
- retry: "after-user-action"
204
- });
205
- const existing = yield* repository.findByProviderAccount(input.providerId, input.authentication.providerAccountId);
206
- const authorizationId = existing?.authorization.id ?? (yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
207
- category: "validation",
208
- message: cause.message,
209
- operation: "Connection.persist",
210
- retry: "safe"
211
- }))));
212
- const existingBinding = existing?.bindings.find((binding) => binding.ownerId === input.ownerId);
213
- const binding = {
214
- authorizationId,
215
- createdAt: existingBinding?.createdAt ?? now,
216
- grant: includeDomains(existingBinding?.grant, input.grant),
217
- id: existingBinding?.id ?? (yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
218
- category: "validation",
219
- message: cause.message,
220
- operation: "Connection.persist",
221
- retry: "safe"
222
- })))),
223
- ownerId: input.ownerId
224
- };
225
- const aggregate = {
226
- authorization: {
227
- authorizedById: input.authorizedById,
228
- capabilityEvidence: [...input.authentication.capabilityEvidence],
229
- createdAt: existing?.authorization.createdAt ?? now,
230
- expiresAt: input.authentication.expiresAt,
231
- id: authorizationId,
232
- method: input.method,
233
- providerAccountId: input.authentication.providerAccountId,
234
- providerContext: input.authentication.providerContext,
235
- providerId: input.providerId,
236
- requiredCapabilities: [...input.requiredCapabilities],
237
- revocation: { _tag: "Active" },
238
- scopes: [...input.authentication.scopes]
239
- },
240
- bindings: [...existing?.bindings.filter((candidate) => candidate.id !== binding.id) ?? [], binding],
241
- credential: input.authentication.credential
242
- };
243
- return yield* repository.connect(aggregate);
244
- });
245
- const start$1 = Effect.fn("Connection.start")(function* (input) {
246
- if (input.method._tag === "Interactive") {
247
- const crypto = yield* Crypto.Crypto;
248
- const now = yield* Clock.currentTimeMillis;
249
- const continuationId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
250
- category: "validation",
251
- message: cause.message,
252
- operation: "Connection.start",
253
- retry: "safe"
254
- })));
255
- const started = yield* input.method.flow.start(continuationId);
256
- yield* input.method.continuations.put({
257
- authorizedById: input.authorizedById,
258
- expiresAt: new Date(now + (input.method.ttlMs ?? 6e5)),
259
- grant: input.grant,
260
- id: continuationId,
261
- method: input.method.flow.method,
262
- ownerId: input.ownerId,
263
- payload: started.payload,
264
- providerId: input.method.flow.providerId,
265
- requiredCapabilities: [...input.method.flow.requiredCapabilities]
266
- });
267
- return StartResult.Redirect({
268
- authorizationUrl: started.authorizationUrl,
269
- continuationId
270
- });
271
- }
272
- const authentication = yield* input.method.authenticate(input.method.token);
273
- const aggregate = yield* persist({
274
- authentication,
275
- authorizedById: input.authorizedById,
276
- grant: input.grant,
277
- method: "token",
278
- ownerId: input.ownerId,
279
- providerId: input.method.providerId,
280
- requiredCapabilities: input.method.requiredCapabilities
281
- });
282
- return StartResult.Connected({ aggregate });
283
- });
284
- const complete$1 = Effect.fn("Connection.complete")(function* (input) {
285
- const continuation = yield* input.continuations.consume(input.continuationId, new Date(yield* Clock.currentTimeMillis));
286
- if (continuation === null) return yield* new Error$1({
287
- category: "authorization",
288
- message: "Connection continuation is expired, unknown, or already consumed",
289
- operation: "Connection.complete",
290
- retry: "after-user-action"
291
- });
292
- if (continuation.providerId !== input.flow.providerId || continuation.method !== input.flow.method) return yield* new Error$1({
293
- category: "authorization",
294
- message: "Connection continuation does not match the provider flow",
295
- operation: "Connection.complete",
296
- retry: "never"
297
- });
298
- const authentication = yield* input.flow.complete(continuation.payload, input.callbackUrl);
299
- return yield* persist({
300
- authentication,
301
- authorizedById: continuation.authorizedById,
302
- grant: continuation.grant,
303
- method: continuation.method,
304
- ownerId: continuation.ownerId,
305
- providerId: continuation.providerId,
306
- requiredCapabilities: continuation.requiredCapabilities
307
- });
308
- });
309
- /** Extends one owner binding without repeating provider authentication. */
310
- const extend = Effect.fn("Connection.extend")(function* (input) {
311
- const repository = yield* Service$1;
312
- const aggregate = yield* repository.getByConnectionId(input.connectionId);
313
- const binding = aggregate?.bindings.find(({ id }) => id === input.connectionId);
314
- if (aggregate === null || binding === void 0 || binding.ownerId !== input.ownerId) return yield* new Error$1({
315
- category: "authorization",
316
- message: "Connection does not belong to this owner",
317
- operation: "Connection.extend",
318
- retry: "never"
319
- });
320
- if (aggregate.authorization.revocation._tag !== "Active") return yield* new Error$1({
321
- category: "authorization",
322
- message: "Provider authorization is awaiting revocation",
323
- operation: "Connection.extend",
324
- retry: "after-user-action"
325
- });
326
- const now = new Date(yield* Clock.currentTimeMillis);
327
- if (aggregate.authorization.expiresAt !== null && (Number.isNaN(aggregate.authorization.expiresAt.getTime()) || aggregate.authorization.expiresAt <= now)) return yield* new Error$1({
328
- category: "authorization",
329
- message: "Provider authorization has expired",
330
- operation: "Connection.extend",
331
- retry: "after-user-action"
332
- });
333
- return yield* repository.bind(aggregate.authorization.id, {
334
- ...binding,
335
- grant: includeDomains(binding.grant, input.grant)
336
- });
337
- });
338
- /** Removes one domain from an owner binding without revoking provider authorization. */
339
- const removeDomain = Effect.fn("Connection.removeDomain")(function* (input) {
340
- const repository = yield* Service$1;
341
- const aggregate = yield* repository.getByConnectionId(input.connectionId);
342
- const binding = aggregate?.bindings.find(({ id }) => id === input.connectionId);
343
- if (aggregate === null || binding === void 0 || binding.ownerId !== input.ownerId) return yield* new Error$1({
344
- category: "authorization",
345
- message: "Connection does not belong to this owner",
346
- operation: "Connection.removeDomain",
347
- retry: "never"
348
- });
349
- const domain = yield* decode$8(input.domain).pipe(Effect.mapError((cause) => new Error$1({
350
- category: "validation",
351
- message: cause.message,
352
- operation: "Connection.removeDomain",
353
- retry: "never"
354
- })));
355
- const updated = yield* repository.modifyBinding(input.connectionId, (current) => {
356
- if (!coversDomain(current.grant, domain)) return {
357
- binding: current,
358
- value: false
359
- };
360
- return {
361
- binding: {
362
- ...current,
363
- grant: removeDomain$1(current.grant, domain)
364
- },
365
- value: true
366
- };
367
- });
368
- return updated.value ? RemoveDomainResult.Removed({ aggregate: updated.aggregate }) : RemoveDomainResult.AlreadyRemoved({ aggregate: updated.aggregate });
369
- });
9
+ var version = "0.6.0";
370
10
  //#endregion
371
11
  //#region src/auth/diagnostic.ts
372
12
  var diagnostic_exports = /* @__PURE__ */ __exportAll({
@@ -440,7 +80,7 @@ const Schema$1 = Schema.Struct({
440
80
  methods: Schema.Array(Method),
441
81
  providerId: Schema.String
442
82
  });
443
- const decode$4 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
83
+ const decode$4 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
444
84
  //#endregion
445
85
  //#region src/auth/secret.ts
446
86
  var secret_exports = /* @__PURE__ */ __exportAll({
@@ -479,9 +119,9 @@ const Evidence = Schema.Struct({
479
119
  accountId: Schema.String,
480
120
  connectionId: Schema.String,
481
121
  decisiveNameserverMatch: Schema.Boolean,
482
- matchedNameservers: Schema.Array(Schema$4),
122
+ matchedNameservers: Schema.Array(Schema$2),
483
123
  providerId: Schema.String,
484
- zone: Schema$4
124
+ zone: Schema$2
485
125
  });
486
126
  const Selection = Schema.TaggedUnion({
487
127
  selected: {
@@ -499,13 +139,16 @@ function select(input) {
499
139
  const zoneCandidates = new Set(candidates(domain));
500
140
  const authoritative = nameserverSet(input.authoritativeNameservers);
501
141
  const eligible = input.connectedZones.filter((connected) => {
502
- if (!zoneCandidates.has(parse$1(connected.zone))) return false;
142
+ if (!zoneCandidates.has(connected.attachment.target.zoneName)) return false;
503
143
  try {
504
- assertGrant(connected.connection, connected.authorization, {
144
+ assertAttachment({
145
+ attachment: connected.attachment,
505
146
  capability: "dns:read",
147
+ connection: connected.connection,
506
148
  domain,
507
149
  ...input.now === void 0 ? {} : { now: input.now },
508
- providerId: connected.providerId
150
+ providerId: connected.connection.providerId,
151
+ authorization: connected.authorization
509
152
  });
510
153
  return true;
511
154
  } catch {
@@ -515,8 +158,8 @@ function select(input) {
515
158
  const evidence = eligible.map((connected) => evidenceFor(connected, authoritative));
516
159
  if (input.explicit !== void 0) {
517
160
  const explicitZone = parse$1(input.explicit.zone);
518
- const index = eligible.findIndex((connected) => connected.providerId === input.explicit?.providerId && connected.authorization.providerAccountId === input.explicit.accountId && parse$1(connected.zone) === explicitZone);
519
- if (index < 0) throw authorizationError("Explicit provider zone is not connected or granted", "ProviderDiscovery.select");
161
+ const index = eligible.findIndex((connected) => connected.connection.providerId === input.explicit?.providerId && connected.attachment.target.accountId === input.explicit.accountId && connected.attachment.target.zoneName === explicitZone);
162
+ if (index < 0) throw authorizationError("Explicit provider zone is not connected for this domain", "ProviderDiscovery.select");
520
163
  const candidate = evidence[index];
521
164
  if (candidate === void 0) throw new Error("Eligible provider evidence is missing");
522
165
  return {
@@ -543,32 +186,79 @@ function evidenceFor(connected, authoritative) {
543
186
  const configured = new Set(nameserverSet(connected.nameservers));
544
187
  const matchedNameservers = authoritative.filter((nameserver) => configured.has(nameserver));
545
188
  return {
546
- accountId: connected.authorization.providerAccountId,
189
+ accountId: connected.attachment.target.accountId,
547
190
  connectionId: connected.connection.id,
548
191
  decisiveNameserverMatch: authoritative.length > 0 && matchedNameservers.length === authoritative.length,
549
192
  matchedNameservers,
550
- providerId: connected.providerId,
551
- zone: parse$1(connected.zone)
193
+ providerId: connected.connection.providerId,
194
+ zone: connected.attachment.target.zoneName
552
195
  };
553
196
  }
554
197
  function nameserverSet(nameservers) {
555
198
  return [...new Set(nameservers.map(parse$1))].sort();
556
199
  }
557
200
  //#endregion
201
+ //#region src/provider/session.ts
202
+ var session_exports = /* @__PURE__ */ __exportAll({
203
+ Resolution: () => Resolution,
204
+ fromAsync: () => fromAsync,
205
+ toAsync: () => toAsync$3
206
+ });
207
+ const Resolution = Data.taggedEnum();
208
+ /** Adapts a Promise-shaped session to the Effect-native session boundary. */
209
+ const fromAsync = (session) => ({
210
+ providerId: session.providerId,
211
+ listTargets: (input) => Effect.tryPromise({
212
+ try: () => session.listTargets(input),
213
+ catch: (cause) => sessionFailure(session.providerId, "listTargets", cause)
214
+ }),
215
+ resolveTarget: (domain) => Effect.tryPromise({
216
+ try: () => session.resolveTarget(domain),
217
+ catch: (cause) => sessionFailure(session.providerId, "resolveTarget", cause)
218
+ }),
219
+ forTarget: (target) => Effect.tryPromise({
220
+ try: () => session.forTarget(target),
221
+ catch: (cause) => sessionFailure(session.providerId, "forTarget", cause)
222
+ }).pipe(Effect.map(fromAsync$1))
223
+ });
224
+ /** Adapts an Effect-native session to the Promise facade. */
225
+ const toAsync$3 = (session) => ({
226
+ providerId: session.providerId,
227
+ listTargets: (input) => Effect.runPromise(session.listTargets(input)),
228
+ resolveTarget: (domain) => Effect.runPromise(session.resolveTarget(domain)),
229
+ forTarget: (target) => Effect.runPromise(session.forTarget(target)).then(toAsync$4)
230
+ });
231
+ function sessionFailure(providerId, operation, cause) {
232
+ return cause instanceof Error$3 ? cause : new Error$3({
233
+ message: cause instanceof globalThis.Error ? cause.message : String(cause),
234
+ operation: `ProviderSession.${operation}`,
235
+ providerId,
236
+ reason: "transport"
237
+ });
238
+ }
239
+ //#endregion
558
240
  //#region src/plan/connection-authorization.ts
559
241
  var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize });
560
242
  const authorize = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
243
+ const aggregate = yield* (yield* Service$1).getByConnectionId(input.connection.id);
244
+ const storedConnection = aggregate?.connections.find(({ id }) => id === input.connection.id);
245
+ const storedAttachment = aggregate?.attachments.find(({ id }) => id === input.attachment.id);
246
+ if (aggregate === null || storedConnection === void 0 || storedAttachment === void 0 || storedConnection.ownerId !== input.connection.ownerId || storedConnection.providerId !== input.connection.providerId || storedConnection.method !== aggregate.authorization.method || storedConnection.authorizationId !== input.authorization.id || storedAttachment.connectionId !== storedConnection.id) return yield* authorizationError("Domain attachment is not owned by the active provider connection", "ConnectionAuthorization.authorize");
561
247
  const domain = yield* Effect.try({
562
- try: () => assertGrant(input.connection, input.authorization, {
248
+ try: () => assertAttachment({
249
+ attachment: storedAttachment,
563
250
  capability: "dns:write",
251
+ authorization: aggregate.authorization,
252
+ connection: storedConnection,
253
+ credential: aggregate.credential,
564
254
  domain: input.domain,
565
255
  providerId: input.plan.providerId
566
256
  }),
567
- catch: (cause) => cause instanceof AuthorizationError || cause instanceof Error$2 ? cause : authorizationError("Connection grant is invalid", "ConnectionAuthorization.authorize")
257
+ catch: (cause) => cause instanceof AuthorizationError || cause instanceof Error$1 ? cause : authorizationError("Domain attachment is invalid", "ConnectionAuthorization.authorize")
568
258
  });
569
259
  const authorization = yield* authorize$1(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
570
260
  const selected = new Set(authorization.operationIds);
571
- if (input.plan.operations.filter((operation) => operation._tag === "create" && selected.has(operation.id) && operation.requirement.name !== domain && !operation.requirement.name.endsWith(`.${domain}`)).length > 0) return yield* authorizationError(`Plan operations are outside the granted domain ${domain}`, "ConnectionAuthorization.authorize");
261
+ if (input.plan.operations.filter((operation) => operation._tag === "create" && selected.has(operation.id) && operation.requirement.name !== domain && !operation.requirement.name.endsWith(`.${domain}`)).length > 0) return yield* authorizationError(`Plan operations are outside the attached domain ${domain}`, "ConnectionAuthorization.authorize");
572
262
  return authorization;
573
263
  });
574
264
  //#endregion
@@ -598,17 +288,17 @@ const start = Effect.fn("AuthorizationCode.start")(function* (input) {
598
288
  });
599
289
  const complete = Effect.fn("AuthorizationCode.complete")(function* (input) {
600
290
  const payload = yield* decodePayload(input.payload);
601
- if (input.callbackUrl.searchParams.get("state") !== payload.state) return yield* failure$5("Authorization callback state does not match its continuation");
291
+ if (input.callbackUrl.searchParams.get("state") !== payload.state) return yield* failure$6("Authorization callback state does not match its continuation");
602
292
  const server = input.method.authorizationServer;
603
293
  const client = { client_id: input.client.clientId };
604
- const authentication = yield* clientAuthentication(input.method, input.client);
294
+ const authentication = yield* clientAuthentication$1(input.method, input.client);
605
295
  return yield* Effect.tryPromise({
606
296
  try: async (signal) => {
607
297
  const callback = oauth.validateAuthResponse(server, client, input.callbackUrl, payload.state);
608
298
  const response = await oauth.authorizationCodeGrantRequest(server, client, authentication, callback, payload.redirectUri, payload.codeVerifier, requestOptions(input.fetch, signal));
609
299
  return oauth.processAuthorizationCodeResponse(server, client, response);
610
300
  },
611
- catch: (cause) => new Error$1({
301
+ catch: (cause) => new Error$2({
612
302
  category: "provider",
613
303
  message: cause instanceof globalThis.Error ? cause.name : "OAuth provider request failed",
614
304
  operation: "AuthorizationCode.complete",
@@ -622,18 +312,18 @@ const PayloadSchema = Schema.Struct({
622
312
  state: Schema.String
623
313
  });
624
314
  function decodePayload(payload) {
625
- return Schema.decodeUnknownEffect(Schema.fromJsonString(PayloadSchema))(payload.expose()).pipe(Effect.mapError(() => new Error$1({
315
+ return Schema.decodeUnknownEffect(Schema.fromJsonString(PayloadSchema))(payload.expose()).pipe(Effect.mapError(() => new Error$2({
626
316
  category: "authorization",
627
317
  message: "OAuth continuation payload is invalid",
628
318
  operation: "AuthorizationCode",
629
319
  retry: "after-user-action"
630
320
  })));
631
321
  }
632
- function clientAuthentication(method, client) {
322
+ function clientAuthentication$1(method, client) {
633
323
  switch (method.clientAuth) {
634
324
  case "none": return Effect.succeed(oauth.None());
635
- case "client_secret_basic": return client.clientSecret === void 0 ? failure$5("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretBasic(client.clientSecret.expose()));
636
- case "client_secret_post": return client.clientSecret === void 0 ? failure$5("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretPost(client.clientSecret.expose()));
325
+ case "client_secret_basic": return client.clientSecret === void 0 ? failure$6("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretBasic(client.clientSecret.expose()));
326
+ case "client_secret_post": return client.clientSecret === void 0 ? failure$6("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretPost(client.clientSecret.expose()));
637
327
  }
638
328
  }
639
329
  function requestOptions(fetchImplementation, signal) {
@@ -645,14 +335,14 @@ function requestOptions(fetchImplementation, signal) {
645
335
  })
646
336
  };
647
337
  }
648
- const failure$5 = (message) => Effect.fail(new Error$1({
338
+ const failure$6 = (message) => Effect.fail(new Error$2({
649
339
  category: "authorization",
650
340
  message,
651
341
  operation: "AuthorizationCode",
652
342
  retry: "after-user-action"
653
343
  }));
654
344
  function randomBytes(crypto, size) {
655
- return crypto.randomBytes(size).pipe(Effect.mapError((cause) => new Error$1({
345
+ return crypto.randomBytes(size).pipe(Effect.mapError((cause) => new Error$2({
656
346
  category: "validation",
657
347
  message: cause.message,
658
348
  operation: "AuthorizationCode.start",
@@ -660,7 +350,7 @@ function randomBytes(crypto, size) {
660
350
  })));
661
351
  }
662
352
  function digest(crypto, value) {
663
- return crypto.digest("SHA-256", new TextEncoder().encode(value)).pipe(Effect.mapError((cause) => new Error$1({
353
+ return crypto.digest("SHA-256", new TextEncoder().encode(value)).pipe(Effect.mapError((cause) => new Error$2({
664
354
  category: "validation",
665
355
  message: cause.message,
666
356
  operation: "AuthorizationCode.start",
@@ -703,7 +393,13 @@ const Zone = Schema.Struct({
703
393
  id: Schema.String,
704
394
  name: Schema.String,
705
395
  name_servers: Schema.Array(Schema.String),
706
- status: Schema.optionalKey(Schema.String)
396
+ status: Schema.optionalKey(Schema.String),
397
+ type: Schema.optionalKey(Schema.Literals([
398
+ "full",
399
+ "partial",
400
+ "secondary",
401
+ "internal"
402
+ ]))
707
403
  });
708
404
  const Record$1 = Schema.Struct({
709
405
  content: Schema.optionalKey(Schema.String),
@@ -763,7 +459,7 @@ const Data$1 = Schema.Struct({
763
459
  weight: Schema.optionalKey(Schema.Number)
764
460
  });
765
461
  const decode$3 = Effect.fn("CloudflareRecords.decode")((record) => Effect.gen(function* () {
766
- const name = yield* decode$8(record.name).pipe(Effect.match({
462
+ const name = yield* decode$5(record.name).pipe(Effect.match({
767
463
  onFailure: () => null,
768
464
  onSuccess: (value) => value
769
465
  }));
@@ -778,46 +474,46 @@ const decode$3 = Effect.fn("CloudflareRecords.decode")((record) => Effect.gen(fu
778
474
  policy: record.type === "CNAME" ? "exclusive" : "append",
779
475
  ttl: record.ttl === 1 ? null : record.ttl
780
476
  };
781
- const content = () => record.content === void 0 ? Effect.fail(failure$4("decodeRecord", `Cloudflare ${record.type} record has no content`)) : Effect.succeed(record.content);
477
+ const content = () => record.content === void 0 ? Effect.fail(failure$5("decodeRecord", `Cloudflare ${record.type} record has no content`)) : Effect.succeed(record.content);
782
478
  switch (record.type) {
783
479
  case "A":
784
- case "AAAA": return yield* decode$9({
480
+ case "AAAA": return yield* decode$6({
785
481
  ...common,
786
482
  _tag: record.type,
787
483
  address: yield* content()
788
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
484
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
789
485
  case "CNAME":
790
- case "NS": return yield* decode$9({
486
+ case "NS": return yield* decode$6({
791
487
  ...common,
792
488
  _tag: record.type,
793
489
  target: yield* content()
794
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
795
- case "TXT": return yield* decode$9({
490
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
491
+ case "TXT": return yield* decode$6({
796
492
  ...common,
797
493
  _tag: "TXT",
798
494
  value: yield* content()
799
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
800
- case "MX": return yield* decode$9({
495
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
496
+ case "MX": return yield* decode$6({
801
497
  ...common,
802
498
  _tag: "MX",
803
499
  exchange: yield* content(),
804
500
  priority: record.priority
805
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
501
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
806
502
  case "CAA": {
807
503
  const data = yield* decodeData(record);
808
- return yield* decode$9({
504
+ return yield* decode$6({
809
505
  ...common,
810
506
  _tag: "CAA",
811
507
  ...data
812
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
508
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
813
509
  }
814
510
  case "SRV": {
815
511
  const data = yield* decodeData(record);
816
- return yield* decode$9({
512
+ return yield* decode$6({
817
513
  ...common,
818
514
  _tag: "SRV",
819
515
  ...data
820
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
516
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
821
517
  }
822
518
  default: return yield* decodeOpaque$1(record);
823
519
  }
@@ -827,7 +523,7 @@ const decodeOpaque$1 = Effect.fn("CloudflareRecords.decodeOpaque")((record) => S
827
523
  name: record.name,
828
524
  providerRecordId: record.id,
829
525
  providerType: record.type
830
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message))));
526
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message))));
831
527
  function encode$1(record) {
832
528
  const common = {
833
529
  name: record.name,
@@ -874,8 +570,8 @@ function encode$1(record) {
874
570
  };
875
571
  }
876
572
  }
877
- const decodeData = Effect.fn("CloudflareRecords.decodeData")((record) => Schema.decodeUnknownEffect(Data$1)(record.data).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message))));
878
- function failure$4(operation, message, fields = {}) {
573
+ const decodeData = Effect.fn("CloudflareRecords.decodeData")((record) => Schema.decodeUnknownEffect(Data$1)(record.data).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message))));
574
+ function failure$5(operation, message, fields = {}) {
879
575
  return new Error$3({
880
576
  message,
881
577
  operation,
@@ -915,14 +611,14 @@ const makeTransport = (options) => {
915
611
  ...init?.headers
916
612
  }
917
613
  }),
918
- catch: () => failure$3(operation, "Cloudflare request failed", { reason: "transport" })
614
+ catch: () => failure$4(operation, "Cloudflare request failed", { reason: "transport" })
919
615
  });
920
616
  return {
921
617
  body: yield* Effect.tryPromise({
922
618
  try: () => response.json(),
923
619
  catch: () => {
924
620
  const retryAfterMs = retryAfter(response.headers.get("retry-after"));
925
- return failure$3(operation, response.ok ? "Cloudflare returned a non-JSON response" : `Cloudflare request failed with HTTP ${response.status}`, {
621
+ return failure$4(operation, response.ok ? "Cloudflare returned a non-JSON response" : `Cloudflare request failed with HTTP ${response.status}`, {
926
622
  reason: response.ok ? "response" : reason$1(response.status),
927
623
  ...retryAfterMs === void 0 ? {} : { retryAfterMs },
928
624
  status: response.status
@@ -960,12 +656,12 @@ const makeTransport = (options) => {
960
656
  const { body, response } = yield* request(path, "validateToken");
961
657
  yield* ensureSuccess(body, "validateToken", response);
962
658
  const envelope = yield* decode$2(TokenEnvelope, body, "validateToken", response);
963
- if (envelope.result.status !== "active") return yield* Effect.fail(failure$3("validateToken", `Cloudflare token is ${envelope.result.status}`, {
659
+ if (envelope.result.status !== "active") return yield* Effect.fail(failure$4("validateToken", `Cloudflare token is ${envelope.result.status}`, {
964
660
  reason: "authentication",
965
661
  status: response.status
966
662
  }));
967
663
  const expiresAt = envelope.result.expires_on === void 0 ? null : new Date(envelope.result.expires_on);
968
- if (expiresAt !== null && Number.isNaN(expiresAt.valueOf())) return yield* Effect.fail(failure$3("validateToken", "Cloudflare token expiry is not a valid timestamp", {
664
+ if (expiresAt !== null && Number.isNaN(expiresAt.valueOf())) return yield* Effect.fail(failure$4("validateToken", "Cloudflare token expiry is not a valid timestamp", {
969
665
  reason: "response",
970
666
  status: response.status
971
667
  }));
@@ -981,9 +677,9 @@ const discoverAccount = Effect.fn("CloudflareClient.discoverAccount")((options)
981
677
  const match = matches[0];
982
678
  if (matches.length === 0) continue;
983
679
  if (matches.length === 1 && match !== void 0) return match.account;
984
- return yield* Effect.fail(failure$3("discoverAccount", `Cloudflare returned multiple accessible zones named ${candidate}`, { reason: "conflict" }));
680
+ return yield* Effect.fail(failure$4("discoverAccount", `Cloudflare returned multiple accessible zones named ${candidate}`, { reason: "conflict" }));
985
681
  }
986
- return yield* Effect.fail(failure$3("discoverAccount", `Cloudflare did not expose an accessible zone for ${options.domain}`, { reason: "not_found" }));
682
+ return yield* Effect.fail(failure$4("discoverAccount", `Cloudflare did not expose an accessible zone for ${options.domain}`, { reason: "not_found" }));
987
683
  }));
988
684
  const discoverAuthorizationAccount = discoverAccount;
989
685
  const validateDomainToken = Effect.fn("CloudflareClient.validateDomainToken")((options) => Effect.gen(function* () {
@@ -999,12 +695,27 @@ const validateDomainToken = Effect.fn("CloudflareClient.validateDomainToken")((o
999
695
  }));
1000
696
  /** Creates an Effect-native Cloudflare client without owning the credential lifecycle. */
1001
697
  function make$5(options) {
698
+ return makeClient$1(options);
699
+ }
700
+ function makeClient$1(options) {
1002
701
  const { allZones, inspectToken, request } = makeTransport(options);
1003
702
  const resolveZone = Effect.fn("CloudflareClient.resolveZone")((name) => Effect.gen(function* () {
703
+ if (options.target !== void 0) {
704
+ if (name !== options.target.zoneName && !name.endsWith(`.${options.target.zoneName}`)) return yield* Effect.fail(failure$4("resolveZone", `Cloudflare target ${options.target.zoneName} does not cover ${name}`, { reason: "not_found" }));
705
+ return {
706
+ account: {
707
+ id: options.target.accountId,
708
+ name: options.target.accountId
709
+ },
710
+ id: options.target.zoneId,
711
+ name: options.target.zoneName,
712
+ name_servers: options.target.evidence?.nameservers ?? []
713
+ };
714
+ }
1004
715
  const matches = yield* allZones(options.accountId, name);
1005
716
  const match = matches[0];
1006
717
  if (matches.length === 1 && match !== void 0) return match;
1007
- return yield* Effect.fail(failure$3("resolveZone", matches.length === 0 ? `Cloudflare zone ${name} was not found in account ${options.accountId}` : `Cloudflare returned multiple zones named ${name} in account ${options.accountId}`, { reason: matches.length === 0 ? "not_found" : "response" }));
718
+ return yield* Effect.fail(failure$4("resolveZone", matches.length === 0 ? options.accountId === void 0 ? `Cloudflare zone ${name} was not found in the authorized accounts` : `Cloudflare zone ${name} was not found in account ${options.accountId}` : options.accountId === void 0 ? `Cloudflare returned multiple zones named ${name} across authorized accounts` : `Cloudflare returned multiple zones named ${name} in account ${options.accountId}`, { reason: matches.length === 0 ? "not_found" : "response" }));
1008
719
  }));
1009
720
  const listRecords = Effect.fn("CloudflareClient.listRecords")((zoneName) => Effect.gen(function* () {
1010
721
  const zone = yield* resolveZone(zoneName);
@@ -1041,28 +752,72 @@ function make$5(options) {
1041
752
  const envelope = yield* decode$2(RecordEnvelope, body, "getRecord", response);
1042
753
  return yield* decode$3(envelope.result);
1043
754
  }));
755
+ const deleteRecord = Effect.fn("CloudflareClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
756
+ const zone = yield* resolveZone(zoneName);
757
+ const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records/${encodeURIComponent(providerRecordId)}`, "deleteRecord", { method: "DELETE" });
758
+ yield* ensureSuccess(body, "deleteRecord", response);
759
+ }));
760
+ const listAccounts = Effect.fn("CloudflareClient.listAccounts")(() => allZones().pipe(Effect.map((zones) => {
761
+ const accounts = /* @__PURE__ */ new Map();
762
+ for (const zone of zones) accounts.set(zone.account.id, zone.account);
763
+ return [...accounts.values()].sort((left, right) => left.name.localeCompare(right.name));
764
+ })));
765
+ const listZones = Effect.fn("CloudflareClient.listZones")((input = {}) => allZones(input.accountId ?? options.accountId, input.name).pipe(Effect.flatMap((zones) => Effect.forEach(zones, projectZone$1))));
766
+ const listTargets = Effect.fn("CloudflareClient.listTargets")((input = {}) => Effect.gen(function* () {
767
+ const names = input.domain === void 0 ? void 0 : new Set(candidates(input.domain).map((name) => String(name)));
768
+ const zones = yield* allZones(input.accountId ?? options.accountId);
769
+ return yield* Effect.forEach(zones.filter((zone) => zone.type !== "internal" && (names === void 0 || names.has(zone.name))), targetFromZone);
770
+ }));
1044
771
  return {
1045
772
  id: "cloudflare",
773
+ providerId: "cloudflare",
1046
774
  createRecord,
1047
- deleteRecord: Effect.fn("CloudflareClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
1048
- const zone = yield* resolveZone(zoneName);
1049
- const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records/${encodeURIComponent(providerRecordId)}`, "deleteRecord", { method: "DELETE" });
1050
- yield* ensureSuccess(body, "deleteRecord", response);
1051
- })),
775
+ deleteRecord,
1052
776
  getRecord,
1053
- listAccounts: Effect.fn("CloudflareClient.listAccounts")(() => allZones().pipe(Effect.map((zones) => {
1054
- const accounts = /* @__PURE__ */ new Map();
1055
- for (const zone of zones) accounts.set(zone.account.id, zone.account);
1056
- return [...accounts.values()].sort((left, right) => left.name.localeCompare(right.name));
1057
- }))),
777
+ listAccounts,
1058
778
  listRecords,
1059
- listZones: Effect.fn("CloudflareClient.listZones")((input = {}) => allZones(input.accountId ?? options.accountId, input.name).pipe(Effect.flatMap((zones) => Effect.forEach(zones, projectZone$1)))),
779
+ listZones,
780
+ listTargets,
781
+ resolveTarget: Effect.fn("CloudflareClient.resolveTarget")((domain) => Effect.gen(function* () {
782
+ const candidates$2 = yield* listTargets({ domain });
783
+ for (const zoneName of candidates(domain)) {
784
+ const matches = candidates$2.filter((target) => target.zoneName === zoneName);
785
+ if (matches.length === 1) {
786
+ const target = matches[0];
787
+ if (target !== void 0) return Resolution.Resolved({ target });
788
+ }
789
+ if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });
790
+ }
791
+ return Resolution.NotFound({ domain });
792
+ })),
793
+ forTarget: Effect.fn("CloudflareClient.forTarget")((target) => Effect.gen(function* () {
794
+ if (target.accountKind !== null && target.accountKind !== "account") return yield* Effect.fail(failure$4("forTarget", "Cloudflare targets must use the account account kind", { reason: "request" }));
795
+ if (target.evidence?.zoneType === "internal") return yield* Effect.fail(failure$4("forTarget", "Cloudflare internal zones cannot host managed DNS records", { reason: "unsupported" }));
796
+ const selected = (yield* listTargets({
797
+ accountId: target.accountId,
798
+ domain: target.zoneName
799
+ })).find((candidate) => candidate.accountId === target.accountId && candidate.zoneId === target.zoneId && candidate.zoneName === target.zoneName);
800
+ if (selected === void 0) return yield* Effect.fail(failure$4("forTarget", `Cloudflare target ${target.zoneName} is not visible to this credential`, { reason: "authorization" }));
801
+ if (selected.evidence?.zoneType === "internal") return yield* Effect.fail(failure$4("forTarget", "Cloudflare internal zones cannot host managed DNS records", { reason: "unsupported" }));
802
+ const client = makeClient$1({
803
+ ...options,
804
+ accountId: selected.accountId,
805
+ target: selected
806
+ });
807
+ return {
808
+ id: client.id,
809
+ createRecord: client.createRecord,
810
+ deleteRecord: client.deleteRecord,
811
+ getRecord: client.getRecord,
812
+ listRecords: client.listRecords
813
+ };
814
+ })),
1060
815
  validateToken: Effect.fn("CloudflareClient.validateToken")(() => Effect.gen(function* () {
1061
- const path = options.tokenKind === "account" ? `/accounts/${encodeURIComponent(options.accountId)}/tokens/verify` : "/user/tokens/verify";
816
+ const path = options.tokenKind === "account" ? options.accountId === void 0 ? yield* Effect.fail(failure$4("validateToken", "Cloudflare account tokens require an account ID", { reason: "request" })) : `/accounts/${encodeURIComponent(options.accountId)}/tokens/verify` : "/user/tokens/verify";
1062
817
  const expiresAt = yield* inspectToken(path);
1063
818
  yield* allZones(options.accountId);
1064
819
  return {
1065
- accountId: options.accountId,
820
+ accountId: options.accountId ?? "",
1066
821
  capabilities: options.capabilities,
1067
822
  expiresAt,
1068
823
  scopes: []
@@ -1070,9 +825,25 @@ function make$5(options) {
1070
825
  }))
1071
826
  };
1072
827
  }
828
+ const targetFromZone = Effect.fn("CloudflareClient.targetFromZone")((zone) => Effect.gen(function* () {
829
+ const name = yield* decode$5(zone.name).pipe(Effect.mapError((cause) => failure$4("listTargets", cause.message, { reason: "response" })));
830
+ const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$5(nameserver).pipe(Effect.mapError((cause) => failure$4("listTargets", cause.message, { reason: "response" }))));
831
+ return {
832
+ accountId: zone.account.id,
833
+ accountKind: "account",
834
+ evidence: {
835
+ accountName: zone.account.name,
836
+ nameservers,
837
+ status: zone.status ?? "unknown",
838
+ zoneType: zone.type ?? "unknown"
839
+ },
840
+ zoneId: zone.id,
841
+ zoneName: name
842
+ };
843
+ }));
1073
844
  const projectZone$1 = Effect.fn("CloudflareClient.projectZone")((zone) => Effect.gen(function* () {
1074
- const name = yield* decode$8(zone.name).pipe(Effect.mapError((cause) => failure$3("listZones", cause.message, { reason: "response" })));
1075
- const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$8(nameserver).pipe(Effect.mapError((cause) => failure$3("listZones", cause.message, { reason: "response" }))));
845
+ const name = yield* decode$5(zone.name).pipe(Effect.mapError((cause) => failure$4("listZones", cause.message, { reason: "response" })));
846
+ const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$5(nameserver).pipe(Effect.mapError((cause) => failure$4("listZones", cause.message, { reason: "response" }))));
1076
847
  return {
1077
848
  accountId: zone.account.id,
1078
849
  id: zone.id,
@@ -1082,20 +853,20 @@ const projectZone$1 = Effect.fn("CloudflareClient.projectZone")((zone) => Effect
1082
853
  };
1083
854
  }));
1084
855
  function decode$2(schema, input, operation, response) {
1085
- return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$3(operation, `Cloudflare response did not match its API contract: ${cause.message}`, {
856
+ return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$4(operation, `Cloudflare response did not match its API contract: ${cause.message}`, {
1086
857
  reason: "response",
1087
858
  status: response.status
1088
859
  })));
1089
860
  }
1090
861
  function ensureSuccess(input, operation, response) {
1091
- return Schema.decodeUnknownEffect(BaseEnvelope)(input).pipe(Effect.mapError(() => failure$3(operation, "Cloudflare response did not contain a valid API envelope", {
862
+ return Schema.decodeUnknownEffect(BaseEnvelope)(input).pipe(Effect.mapError(() => failure$4(operation, "Cloudflare response did not contain a valid API envelope", {
1092
863
  reason: "response",
1093
864
  status: response.status
1094
865
  })), Effect.flatMap((envelope) => {
1095
866
  if (response.ok && envelope.success) return Effect.void;
1096
867
  const detail = envelope.errors[0];
1097
868
  const retryAfterMs = retryAfter(response.headers.get("retry-after"));
1098
- return Effect.fail(failure$3(operation, detail?.message ?? `Cloudflare request failed with HTTP ${response.status}`, {
869
+ return Effect.fail(failure$4(operation, detail?.message ?? `Cloudflare request failed with HTTP ${response.status}`, {
1099
870
  ...detail === void 0 ? {} : { code: detail.code },
1100
871
  reason: reason$1(response.status, detail?.code),
1101
872
  ...retryAfterMs === void 0 ? {} : { retryAfterMs },
@@ -1119,7 +890,7 @@ function retryAfter(value) {
1119
890
  const date = Date.parse(value);
1120
891
  return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
1121
892
  }
1122
- function failure$3(operation, message, fields = {}) {
893
+ function failure$4(operation, message, fields = {}) {
1123
894
  return new Error$3({
1124
895
  ...fields,
1125
896
  message,
@@ -1135,6 +906,8 @@ var auth_exports$1 = /* @__PURE__ */ __exportAll({
1135
906
  oauthAuthentication: () => oauthAuthentication,
1136
907
  oauthFlow: () => oauthFlow,
1137
908
  oauthMethod: () => oauthMethod,
909
+ refreshCredential: () => refreshCredential,
910
+ restore: () => restore$1,
1138
911
  subjectResolver: () => subjectResolver,
1139
912
  tokenConnectionMethod: () => tokenConnectionMethod$1,
1140
913
  tokenMethod: () => tokenMethod$1,
@@ -1146,7 +919,11 @@ const authorizationServer = {
1146
919
  revocation_endpoint: "https://dash.cloudflare.com/oauth2/revoke",
1147
920
  token_endpoint: "https://dash.cloudflare.com/oauth2/token"
1148
921
  };
1149
- const contextCodec$1 = codec("cloudflare.v1", Schema.Struct({ tokenKind: Schema.Literals(["account", "user"]) }));
922
+ const Context$2 = Schema.Struct({
923
+ accountId: Schema.optionalKey(Schema.String),
924
+ tokenKind: Schema.Literals(["account", "user"])
925
+ });
926
+ const contextCodec$1 = codec("cloudflare.v1", Context$2);
1150
927
  /** Describes Cloudflare's caller-created API-token authorization method. */
1151
928
  function tokenMethod$1(capabilities) {
1152
929
  return {
@@ -1172,7 +949,7 @@ function manifest$1(options) {
1172
949
  providerId: "cloudflare"
1173
950
  };
1174
951
  }
1175
- /** Resolves the Cloudflare account selected by a completed OAuth grant. */
952
+ /** Resolves the Cloudflare account selected by a completed OAuth authorization. */
1176
953
  function subjectResolver(options) {
1177
954
  return (tokens, accessToken) => Effect.gen(function* () {
1178
955
  const accountId = options.accountId ?? (yield* discoverAuthorizationAccount({
@@ -1228,12 +1005,15 @@ function tokenConnectionMethod$1(options) {
1228
1005
  })),
1229
1006
  credential: {
1230
1007
  accessToken: token,
1008
+ expiresAt: validated.expiresAt,
1231
1009
  refreshToken: null,
1232
1010
  tokenType: "bearer"
1233
1011
  },
1234
- expiresAt: validated.expiresAt,
1235
1012
  providerAccountId: validated.accountId,
1236
- providerContext: yield* contextCodec$1.encode({ tokenKind: options.tokenKind ?? "user" }),
1013
+ providerContext: yield* contextCodec$1.encode(options.tokenKind === "account" ? {
1014
+ accountId: validated.accountId,
1015
+ tokenKind: "account"
1016
+ } : { tokenKind: "user" }),
1237
1017
  scopes: [...validated.scopes]
1238
1018
  };
1239
1019
  }),
@@ -1242,7 +1022,7 @@ function tokenConnectionMethod$1(options) {
1242
1022
  token: options.token
1243
1023
  });
1244
1024
  }
1245
- /** Converts a completed Cloudflare OAuth grant into the canonical connection authentication. */
1025
+ /** Converts a completed Cloudflare OAuth authorization into canonical connection authentication. */
1246
1026
  const oauthAuthentication = Effect.fn("CloudflareAuth.oauthAuthentication")(function* (options) {
1247
1027
  const resolved = yield* subjectResolver(options)(options.tokens, options.accessToken);
1248
1028
  const observedAt = new Date(yield* Clock.currentTimeMillis);
@@ -1253,15 +1033,76 @@ const oauthAuthentication = Effect.fn("CloudflareAuth.oauthAuthentication")(func
1253
1033
  })),
1254
1034
  credential: {
1255
1035
  accessToken: options.accessToken,
1036
+ expiresAt: resolved.expiresAt,
1256
1037
  refreshToken: options.tokens.refresh_token === void 0 ? null : Value.from(options.tokens.refresh_token),
1257
1038
  tokenType: options.tokens.token_type
1258
1039
  },
1259
- expiresAt: resolved.expiresAt,
1260
1040
  providerAccountId: resolved.accountId,
1261
1041
  providerContext: yield* contextCodec$1.encode({ tokenKind: "user" }),
1262
1042
  scopes: (options.tokens.scope ?? "").split(" ").filter(Boolean)
1263
1043
  };
1264
1044
  });
1045
+ /** Restore a credential-scoped Cloudflare session from persisted authorization state. */
1046
+ function restore$1(options) {
1047
+ return Effect.gen(function* () {
1048
+ if (options.authorization.providerId !== "cloudflare") return yield* Effect.fail(failure$3("restore", "Provider authorization belongs to another provider", "authorization"));
1049
+ if (options.authorization.method === "integration") return yield* Effect.fail(failure$3("restore", "Cloudflare does not support integration credentials", "unsupported"));
1050
+ if (options.authorization.revocation._tag !== "Active") return yield* Effect.fail(failure$3("restore", "Cloudflare authorization is pending revocation", "authorization"));
1051
+ const missingCapability = options.authorization.requiredCapabilities.find((capability) => !options.authorization.capabilityEvidence.some((item) => item.capability === capability));
1052
+ if (missingCapability !== void 0) return yield* Effect.fail(failure$3("restore", `Cloudflare authorization lacks evidence for ${missingCapability}`, "authorization"));
1053
+ if (options.credential.expiresAt !== null && (Number.isNaN(options.credential.expiresAt.valueOf()) || options.credential.expiresAt <= /* @__PURE__ */ new Date())) return yield* Effect.fail(failure$3("restore", "Cloudflare authorization has expired", "authentication"));
1054
+ const context = yield* contextCodec$1.decode(options.authorization.providerContext).pipe(Effect.mapError((cause) => failure$3("restore", cause.message, "response")));
1055
+ return make$5({
1056
+ ...context.accountId === void 0 ? {} : { accountId: context.accountId },
1057
+ ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
1058
+ capabilities: options.authorization.capabilityEvidence.map(({ capability }) => capability),
1059
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
1060
+ token: options.credential.accessToken,
1061
+ tokenKind: context.tokenKind
1062
+ });
1063
+ });
1064
+ }
1065
+ /** Exchanges a Cloudflare refresh token for a fresh provider credential. */
1066
+ const refreshCredential = Effect.fn("CloudflareAuth.refreshCredential")(function* (options) {
1067
+ if (options.credential.refreshToken === null) return yield* new Error$2({
1068
+ category: "authorization",
1069
+ message: "Cloudflare credential has no refresh token",
1070
+ operation: "CloudflareAuth.refreshCredential",
1071
+ retry: "after-user-action"
1072
+ });
1073
+ const refreshToken = options.credential.refreshToken;
1074
+ const client = { client_id: options.client.clientId };
1075
+ const authentication = yield* Effect.try({
1076
+ try: () => clientAuthentication(options.clientAuth, options.client),
1077
+ catch: () => new Error$2({
1078
+ category: "authorization",
1079
+ message: "Cloudflare OAuth client authentication is invalid",
1080
+ operation: "CloudflareAuth.refreshCredential",
1081
+ retry: "never"
1082
+ })
1083
+ });
1084
+ const fetch = options.fetch;
1085
+ const tokens = yield* Effect.tryPromise({
1086
+ try: async (signal) => {
1087
+ const request = await oauth.refreshTokenGrantRequest(authorizationServer, client, authentication, refreshToken.expose(), fetch === void 0 ? { signal } : {
1088
+ signal,
1089
+ [oauth.customFetch]: (url, init) => fetch(url, {
1090
+ ...init,
1091
+ body: init.body
1092
+ })
1093
+ });
1094
+ return oauth.processRefreshTokenResponse(authorizationServer, client, request);
1095
+ },
1096
+ catch: (cause) => refreshFailure(cause)
1097
+ });
1098
+ const now = yield* Clock.currentTimeMillis;
1099
+ return {
1100
+ accessToken: Value.from(tokens.access_token),
1101
+ expiresAt: typeof tokens.expires_in === "number" ? new Date(now + tokens.expires_in * 1e3) : null,
1102
+ refreshToken: tokens.refresh_token === void 0 ? refreshToken : Value.from(tokens.refresh_token),
1103
+ tokenType: tokens.token_type
1104
+ };
1105
+ });
1265
1106
  /** Creates Cloudflare's OAuth implementation of the common interactive connection capability. */
1266
1107
  function oauthFlow(options) {
1267
1108
  const method = oauthMethod({
@@ -1299,6 +1140,28 @@ function oauthFlow(options) {
1299
1140
  })
1300
1141
  };
1301
1142
  }
1143
+ function failure$3(operation, message, reason) {
1144
+ return new Error$3({
1145
+ message,
1146
+ operation,
1147
+ providerId: "cloudflare",
1148
+ reason
1149
+ });
1150
+ }
1151
+ function clientAuthentication(method, client) {
1152
+ if (method === "none") return oauth.None();
1153
+ if (client.clientSecret === void 0) throw new Error("Client secret is required");
1154
+ return method === "client_secret_basic" ? oauth.ClientSecretBasic(client.clientSecret.expose()) : oauth.ClientSecretPost(client.clientSecret.expose());
1155
+ }
1156
+ function refreshFailure(cause) {
1157
+ if (cause instanceof oauth.ResponseBodyError && ["invalid_client", "invalid_grant"].includes(cause.error)) return new Error$2({
1158
+ category: "authorization",
1159
+ message: "Cloudflare authorization can no longer be refreshed",
1160
+ operation: "CloudflareAuth.refreshCredential",
1161
+ retry: "after-user-action"
1162
+ });
1163
+ return failure$3("refreshCredential", cause instanceof Error ? cause.message : "Cloudflare token refresh failed", cause instanceof oauth.ResponseBodyError ? "response" : "transport");
1164
+ }
1302
1165
  //#endregion
1303
1166
  //#region src/providers/vercel/protocol.ts
1304
1167
  const Pagination = Schema.Struct({
@@ -1388,7 +1251,7 @@ const IntegrationToken = Schema.Struct({
1388
1251
  //#region src/providers/vercel/records.ts
1389
1252
  const decode$1 = Effect.fn("VercelRecords.decode")((zone, record) => Effect.gen(function* () {
1390
1253
  const absolute = absoluteNameCandidate(zone, record.name);
1391
- const name = yield* decode$8(absolute).pipe(Effect.match({
1254
+ const name = yield* decode$5(absolute).pipe(Effect.match({
1392
1255
  onFailure: () => null,
1393
1256
  onSuccess: (value) => value
1394
1257
  }));
@@ -1403,7 +1266,7 @@ const decode$1 = Effect.fn("VercelRecords.decode")((zone, record) => Effect.gen(
1403
1266
  policy: record.type === "CNAME" ? "exclusive" : "append",
1404
1267
  ttl: record.ttl ?? 60
1405
1268
  };
1406
- const fromRecord = (input) => decode$9(input).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message)));
1269
+ const fromRecord = (input) => decode$6(input).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message)));
1407
1270
  switch (record.type) {
1408
1271
  case "A":
1409
1272
  case "AAAA": return yield* fromRecord({
@@ -1544,6 +1407,9 @@ function discovery(client) {
1544
1407
  }
1545
1408
  /** Creates an Effect-native Vercel client without owning the credential lifecycle. */
1546
1409
  function make$4(options) {
1410
+ return makeClient(options);
1411
+ }
1412
+ function makeClient(options) {
1547
1413
  const fetch = options.fetch ?? globalThis.fetch;
1548
1414
  const baseUrl = (options.baseUrl ?? "https://api.vercel.com").replace(/\/$/, "");
1549
1415
  const withContext = (path, values = {}) => {
@@ -1615,9 +1481,13 @@ function make$4(options) {
1615
1481
  return envelope.domain;
1616
1482
  }).pipe(Effect.catchTag("ProviderError", (cause) => cause.reason === "not_found" ? Effect.succeed(null) : Effect.fail(cause))));
1617
1483
  const resolveZone = Effect.fn("VercelClient.resolveZone")((name) => Effect.gen(function* () {
1484
+ if (options.target !== void 0) {
1485
+ if (name !== options.target.zoneName && !name.endsWith(`.${options.target.zoneName}`)) return yield* Effect.fail(failure$1("resolveZone", `Vercel target ${options.target.zoneName} does not cover ${name}`, { reason: "not_found" }));
1486
+ return options.target.zoneName;
1487
+ }
1618
1488
  const domain = yield* discoverZone(name);
1619
1489
  if (domain === null) return yield* Effect.fail(failure$1("resolveZone", `Vercel zone ${name} is not configured for authoritative DNS in the selected account`, { reason: "not_found" }));
1620
- return yield* decode$8(domain.name).pipe(Effect.mapError((cause) => failure$1("resolveZone", `Vercel returned an invalid storage zone: ${cause.message}`)));
1490
+ return yield* decode$5(domain.name).pipe(Effect.mapError((cause) => failure$1("resolveZone", `Vercel returned an invalid storage zone: ${cause.message}`)));
1621
1491
  }));
1622
1492
  const listRecords = Effect.fn("VercelClient.listRecords")((zoneName) => Effect.gen(function* () {
1623
1493
  const storageZone = yield* resolveZone(zoneName);
@@ -1661,34 +1531,84 @@ function make$4(options) {
1661
1531
  until = next;
1662
1532
  }
1663
1533
  }));
1534
+ const deleteRecord = Effect.fn("VercelClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
1535
+ const storageZone = yield* resolveZone(zoneName);
1536
+ yield* request(withContext(`/v2/domains/${encodeURIComponent(storageZone)}/records/${encodeURIComponent(providerRecordId)}`), "deleteRecord", { method: "DELETE" });
1537
+ }));
1538
+ const listAccounts = Effect.fn("VercelClient.listAccounts")(() => Effect.gen(function* () {
1539
+ const userResult = yield* request(new URL(`${baseUrl}/v2/user`), "listAccounts");
1540
+ const { user } = yield* decode(UserEnvelope, userResult.body, "listAccounts", userResult.response);
1541
+ const teams = yield* allTeams();
1542
+ return [{
1543
+ id: user.id,
1544
+ name: user.name ?? user.username,
1545
+ type: "personal"
1546
+ }, ...teams.map((team) => ({
1547
+ id: team.id,
1548
+ name: team.name ?? team.slug,
1549
+ type: "team"
1550
+ }))];
1551
+ }));
1552
+ const listZones = Effect.fn("VercelClient.listZones")((input = {}) => Effect.gen(function* () {
1553
+ const domains = (yield* allDomains()).filter((domain) => hasDnsStorage(domain) && (input.name === void 0 || domain.name === input.name));
1554
+ if (domains.length > 0 || input.name === void 0) return yield* Effect.forEach(domains, projectZone);
1555
+ const discovered = yield* discoverZone(input.name);
1556
+ return discovered === null ? [] : [yield* projectZone(discovered)];
1557
+ }));
1558
+ const listTargets = Effect.fn("VercelClient.listTargets")((input = {}) => Effect.gen(function* () {
1559
+ const names = input.domain === void 0 ? void 0 : new Set(candidates(input.domain).map((name) => String(name)));
1560
+ const domains = (yield* allDomains()).filter((domain) => hasDnsStorage(domain) && (names === void 0 || names.has(domain.name)) && (input.accountId === void 0 || (domain.teamId ?? domain.userId) === input.accountId));
1561
+ if (domains.length > 0 || input.domain === void 0) return yield* Effect.forEach(domains, targetFromDomain);
1562
+ for (const name of candidates(input.domain)) {
1563
+ const discovered = yield* discoverZone(name);
1564
+ if (discovered !== null && (input.accountId === void 0 || (discovered.teamId ?? discovered.userId) === input.accountId)) return [yield* targetFromDomain(discovered)];
1565
+ }
1566
+ return [];
1567
+ }));
1664
1568
  return {
1665
1569
  id: "vercel",
1570
+ providerId: "vercel",
1666
1571
  createRecord,
1667
- deleteRecord: Effect.fn("VercelClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
1668
- const storageZone = yield* resolveZone(zoneName);
1669
- yield* request(withContext(`/v2/domains/${encodeURIComponent(storageZone)}/records/${encodeURIComponent(providerRecordId)}`), "deleteRecord", { method: "DELETE" });
1670
- })),
1572
+ deleteRecord,
1671
1573
  getRecord,
1672
- listAccounts: Effect.fn("VercelClient.listAccounts")(() => Effect.gen(function* () {
1673
- const userResult = yield* request(new URL(`${baseUrl}/v2/user`), "listAccounts");
1674
- const { user } = yield* decode(UserEnvelope, userResult.body, "listAccounts", userResult.response);
1675
- const teams = yield* allTeams();
1676
- return [{
1677
- id: user.id,
1678
- name: user.name ?? user.username,
1679
- type: "personal"
1680
- }, ...teams.map((team) => ({
1681
- id: team.id,
1682
- name: team.name ?? team.slug,
1683
- type: "team"
1684
- }))];
1685
- })),
1574
+ listAccounts,
1686
1575
  listRecords,
1687
- listZones: Effect.fn("VercelClient.listZones")((input = {}) => Effect.gen(function* () {
1688
- const domains = (yield* allDomains()).filter((domain) => hasDnsStorage(domain) && (input.name === void 0 || domain.name === input.name));
1689
- if (domains.length > 0 || input.name === void 0) return yield* Effect.forEach(domains, projectZone);
1690
- const discovered = yield* discoverZone(input.name);
1691
- return discovered === null ? [] : [yield* projectZone(discovered)];
1576
+ listZones,
1577
+ listTargets,
1578
+ resolveTarget: Effect.fn("VercelClient.resolveTarget")((domain) => Effect.gen(function* () {
1579
+ const candidates$1 = yield* listTargets({ domain });
1580
+ for (const zoneName of candidates(domain)) {
1581
+ const matches = candidates$1.filter((target) => target.zoneName === zoneName);
1582
+ if (matches.length === 1) {
1583
+ const target = matches[0];
1584
+ if (target !== void 0) return Resolution.Resolved({ target });
1585
+ }
1586
+ if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });
1587
+ }
1588
+ return Resolution.NotFound({ domain });
1589
+ })),
1590
+ forTarget: Effect.fn("VercelClient.forTarget")((target) => Effect.gen(function* () {
1591
+ if (target.accountKind === "team") {
1592
+ if (options.context._tag !== "team" || options.context.teamId !== target.accountId) return yield* Effect.fail(failure$1("forTarget", "Vercel team target is outside the selected installation", { reason: "authorization" }));
1593
+ } else if (target.accountKind === "personal" && options.context._tag !== "personal") return yield* Effect.fail(failure$1("forTarget", "Vercel personal target is outside the selected installation", { reason: "authorization" }));
1594
+ else if (target.accountKind === "account") return yield* Effect.fail(failure$1("forTarget", "Vercel does not support account-kind targets", { reason: "unsupported" }));
1595
+ else if (target.accountKind === null) return yield* Effect.fail(failure$1("forTarget", "Vercel targets must identify a personal or team account", { reason: "request" }));
1596
+ const selected = (yield* listTargets({
1597
+ accountId: target.accountId,
1598
+ domain: target.zoneName
1599
+ })).find((candidate) => candidate.accountId === target.accountId && candidate.accountKind === target.accountKind && candidate.zoneId === target.zoneId && candidate.zoneName === target.zoneName);
1600
+ if (selected === void 0) return yield* Effect.fail(failure$1("forTarget", `Vercel target ${target.zoneName} is not visible to this installation`, { reason: "authorization" }));
1601
+ const client = makeClient({
1602
+ ...options,
1603
+ target: selected
1604
+ });
1605
+ return {
1606
+ id: client.id,
1607
+ createRecord: client.createRecord,
1608
+ deleteRecord: client.deleteRecord,
1609
+ getRecord: client.getRecord,
1610
+ listRecords: client.listRecords
1611
+ };
1692
1612
  })),
1693
1613
  validateToken: Effect.fn("VercelClient.validateToken")(() => Effect.gen(function* () {
1694
1614
  let accountId;
@@ -1713,8 +1633,8 @@ function hasDnsStorage(domain, configuredServiceType = domain.serviceType) {
1713
1633
  return configuredServiceType === "zeit.world" || domain.zone === true || domain.intendedNameservers.length > 0;
1714
1634
  }
1715
1635
  const projectZone = Effect.fn("VercelClient.projectZone")((domain) => Effect.gen(function* () {
1716
- const name = yield* decode$8(domain.name).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" })));
1717
- const nameservers = yield* Effect.forEach(domain.intendedNameservers, (nameserver) => decode$8(nameserver).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" }))));
1636
+ const name = yield* decode$5(domain.name).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" })));
1637
+ const nameservers = yield* Effect.forEach(domain.intendedNameservers, (nameserver) => decode$5(nameserver).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" }))));
1718
1638
  return {
1719
1639
  accountId: domain.teamId ?? domain.userId,
1720
1640
  id: domain.id,
@@ -1723,6 +1643,21 @@ const projectZone = Effect.fn("VercelClient.projectZone")((domain) => Effect.gen
1723
1643
  status: domain.verified ? "active" : "pending"
1724
1644
  };
1725
1645
  }));
1646
+ const targetFromDomain = Effect.fn("VercelClient.targetFromDomain")((domain) => Effect.gen(function* () {
1647
+ const zoneName = yield* decode$5(domain.name).pipe(Effect.mapError((cause) => failure$1("listTargets", cause.message, { reason: "response" })));
1648
+ const nameservers = yield* Effect.forEach(domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers, (nameserver) => decode$5(nameserver).pipe(Effect.mapError((cause) => failure$1("listTargets", cause.message, { reason: "response" }))));
1649
+ return {
1650
+ accountId: domain.teamId ?? domain.userId,
1651
+ accountKind: domain.teamId === null ? "personal" : "team",
1652
+ evidence: {
1653
+ nameservers,
1654
+ status: domain.verified ? "active" : "pending",
1655
+ zoneType: domain.serviceType
1656
+ },
1657
+ zoneId: domain.id,
1658
+ zoneName
1659
+ };
1660
+ }));
1726
1661
  function decode(schema, input, operation, response) {
1727
1662
  return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$1(operation, `Vercel response did not match its API contract: ${cause.message}`, {
1728
1663
  reason: "response",
@@ -1770,16 +1705,18 @@ var auth_exports = /* @__PURE__ */ __exportAll({
1770
1705
  integrationFlow: () => integrationFlow,
1771
1706
  integrationMethod: () => integrationMethod,
1772
1707
  manifest: () => manifest,
1708
+ restore: () => restore,
1773
1709
  tokenConnectionMethod: () => tokenConnectionMethod,
1774
1710
  tokenMethod: () => tokenMethod
1775
1711
  });
1776
- const contextCodec = codec("vercel.v1", Schema.TaggedUnion({
1712
+ const Context$1 = Schema.TaggedUnion({
1777
1713
  personal: { installationId: Schema.NullOr(Schema.String) },
1778
1714
  team: {
1779
1715
  installationId: Schema.NullOr(Schema.String),
1780
1716
  teamId: Schema.String
1781
1717
  }
1782
- }));
1718
+ });
1719
+ const contextCodec = codec("vercel.v1", Context$1);
1783
1720
  /** Describes Vercel's caller-created personal access-token method. */
1784
1721
  function tokenMethod(capabilities) {
1785
1722
  return {
@@ -1794,7 +1731,7 @@ function integrationMethod(options) {
1794
1731
  _tag: "integration",
1795
1732
  capabilities: [...options.capabilities],
1796
1733
  installUrl: `https://vercel.com/integrations/${encodeURIComponent(options.slug)}/new`,
1797
- tokenEndpoint: "https://api.vercel.com/oauth/access_token"
1734
+ tokenEndpoint: "https://api.vercel.com/v2/oauth/access_token"
1798
1735
  };
1799
1736
  }
1800
1737
  /** Creates the Vercel token and integration authorization manifest. */
@@ -1815,7 +1752,7 @@ const exchangeCode = Effect.fn("VercelAuth.exchangeCode")((options) => Effect.ge
1815
1752
  redirect_uri: options.redirectUri
1816
1753
  });
1817
1754
  const response = yield* Effect.tryPromise({
1818
- try: () => fetch(`${baseUrl}/oauth/access_token`, {
1755
+ try: () => fetch(`${baseUrl}/v2/oauth/access_token`, {
1819
1756
  body,
1820
1757
  headers: {
1821
1758
  Accept: "application/json",
@@ -1840,7 +1777,7 @@ const exchangeCode = Effect.fn("VercelAuth.exchangeCode")((options) => Effect.ge
1840
1777
  _tag: "team",
1841
1778
  teamId: token.team_id
1842
1779
  },
1843
- installationId: token.installation_id ?? null,
1780
+ installationId: token.installation_id ?? options.configurationId ?? null,
1844
1781
  userId: token.user_id
1845
1782
  };
1846
1783
  }));
@@ -1861,10 +1798,10 @@ function tokenConnectionMethod(options) {
1861
1798
  })),
1862
1799
  credential: {
1863
1800
  accessToken: token,
1801
+ expiresAt: validated.expiresAt,
1864
1802
  refreshToken: null,
1865
1803
  tokenType: "bearer"
1866
1804
  },
1867
- expiresAt: validated.expiresAt,
1868
1805
  providerAccountId: validated.accountId,
1869
1806
  providerContext: yield* contextCodec.encode(options.context._tag === "team" ? {
1870
1807
  _tag: "team",
@@ -1893,10 +1830,10 @@ const integrationAuthentication = Effect.fn("VercelAuth.integrationAuthenticatio
1893
1830
  })),
1894
1831
  credential: {
1895
1832
  accessToken: exchanged.accessToken,
1833
+ expiresAt: null,
1896
1834
  refreshToken: null,
1897
1835
  tokenType: "bearer"
1898
1836
  },
1899
- expiresAt: null,
1900
1837
  providerAccountId: exchanged.context._tag === "team" ? exchanged.context.teamId : exchanged.userId,
1901
1838
  providerContext: yield* contextCodec.encode(exchanged.context._tag === "team" ? {
1902
1839
  _tag: "team",
@@ -1909,34 +1846,73 @@ const integrationAuthentication = Effect.fn("VercelAuth.integrationAuthenticatio
1909
1846
  scopes: []
1910
1847
  };
1911
1848
  });
1849
+ /** Restore a credential-scoped Vercel session from persisted authorization state. */
1850
+ function restore(options) {
1851
+ return Effect.gen(function* () {
1852
+ if (options.authorization.providerId !== "vercel") return yield* Effect.fail(failure("restore", "Provider authorization belongs to another provider", "authorization"));
1853
+ if (options.authorization.revocation._tag !== "Active") return yield* Effect.fail(failure("restore", "Vercel authorization is pending revocation", "authorization"));
1854
+ const missingCapability = options.authorization.requiredCapabilities.find((capability) => !options.authorization.capabilityEvidence.some((item) => item.capability === capability));
1855
+ if (missingCapability !== void 0) return yield* Effect.fail(failure("restore", `Vercel authorization lacks evidence for ${missingCapability}`, "authorization"));
1856
+ if (options.credential.expiresAt !== null && (Number.isNaN(options.credential.expiresAt.valueOf()) || options.credential.expiresAt <= /* @__PURE__ */ new Date())) return yield* Effect.fail(failure("restore", "Vercel authorization has expired", "authentication"));
1857
+ const context = yield* contextCodec.decode(options.authorization.providerContext).pipe(Effect.mapError((cause) => failure("restore", cause.message, "response")));
1858
+ return make$4({
1859
+ ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
1860
+ capabilities: options.authorization.capabilityEvidence.map(({ capability }) => capability),
1861
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
1862
+ context: context._tag === "team" ? {
1863
+ _tag: "team",
1864
+ teamId: context.teamId
1865
+ } : { _tag: "personal" },
1866
+ token: options.credential.accessToken
1867
+ });
1868
+ });
1869
+ }
1912
1870
  /** Creates Vercel Integration's implementation of the common interactive connection capability. */
1913
1871
  function integrationFlow(options) {
1914
1872
  const method = integrationMethod(options);
1915
1873
  const Payload = Schema.fromJsonString(Schema.Struct({ state: Schema.String }));
1916
1874
  return {
1917
1875
  complete: Effect.fn("VercelAuth.integrationFlow.complete")(function* (payload, callbackUrl) {
1918
- const continuation = yield* Schema.decodeUnknownEffect(Payload)(payload.expose()).pipe(Effect.mapError(() => new Error$1({
1876
+ const continuation = yield* Schema.decodeUnknownEffect(Payload)(payload.expose()).pipe(Effect.mapError(() => new Error$2({
1919
1877
  category: "authorization",
1920
1878
  message: "Vercel Integration continuation payload is invalid",
1921
1879
  operation: "VercelAuth.integrationFlow.complete",
1922
1880
  retry: "after-user-action"
1923
1881
  })));
1924
1882
  const code = callbackUrl.searchParams.get("code");
1925
- if (callbackUrl.searchParams.get("state") !== continuation.state || code === null) return yield* new Error$1({
1883
+ const configurationId = callbackUrl.searchParams.get("configurationId");
1884
+ if (callbackUrl.searchParams.get("state") !== continuation.state || code === null || configurationId === null) return yield* new Error$2({
1926
1885
  category: "authorization",
1927
1886
  message: "Vercel Integration callback does not match its continuation",
1928
1887
  operation: "VercelAuth.integrationFlow.complete",
1929
1888
  retry: "after-user-action"
1930
1889
  });
1931
- return yield* integrationAuthentication({
1890
+ const authentication = yield* integrationAuthentication({
1932
1891
  ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
1933
1892
  capabilities: options.capabilities,
1934
1893
  clientId: options.clientId,
1935
1894
  clientSecret: options.clientSecret,
1936
1895
  code: make$6(code),
1896
+ configurationId,
1937
1897
  ...options.fetch === void 0 ? {} : { fetch: options.fetch },
1938
1898
  redirectUri: options.redirectUri
1939
1899
  });
1900
+ const callbackTeamId = callbackUrl.searchParams.get("teamId");
1901
+ if (callbackTeamId !== null) {
1902
+ const context = yield* contextCodec.decode(authentication.providerContext).pipe(Effect.mapError((cause) => new Error$2({
1903
+ category: "provider",
1904
+ message: cause.message,
1905
+ operation: "VercelAuth.integrationFlow.complete",
1906
+ retry: "unknown"
1907
+ })));
1908
+ if (context._tag !== "team" || context.teamId !== callbackTeamId) return yield* new Error$2({
1909
+ category: "authorization",
1910
+ message: "Vercel Integration callback team does not match the exchanged installation",
1911
+ operation: "VercelAuth.integrationFlow.complete",
1912
+ retry: "after-user-action"
1913
+ });
1914
+ }
1915
+ return authentication;
1940
1916
  }),
1941
1917
  method: "integration",
1942
1918
  providerId: "vercel",
@@ -2006,7 +1982,7 @@ function parse(type, input) {
2006
1982
  try {
2007
1983
  return Schema.decodeUnknownSync(schema)(input);
2008
1984
  } catch (cause) {
2009
- throw new Error$2({ message: cause instanceof Error ? cause.message : String(cause) });
1985
+ throw new Error$1({ message: cause instanceof Error ? cause.message : String(cause) });
2010
1986
  }
2011
1987
  }
2012
1988
  function tokenizedDomain(length, domainIndex) {
@@ -2051,7 +2027,7 @@ function make$3(options) {
2051
2027
  }).pipe(Effect.flatMap((message) => decodeResolution(query, message)))) };
2052
2028
  }
2053
2029
  const layer$2 = (options) => Layer.succeed(Service$2, make$3(options));
2054
- const toAsync$2 = (options) => toAsync$3(make$3(options));
2030
+ const toAsync$2 = (options) => toAsync$5(make$3(options));
2055
2031
  function decodeResolution(query, message) {
2056
2032
  return Effect.try({
2057
2033
  try: () => DnsPacket.decode(message),
@@ -2109,7 +2085,7 @@ function decodeAnswer(answer) {
2109
2085
  })
2110
2086
  });
2111
2087
  if (decoded === void 0) return null;
2112
- const name = yield* decode$8(answer.name);
2088
+ const name = yield* decode$5(answer.name);
2113
2089
  const data = yield* Effect.try({
2114
2090
  try: () => parse(decoded.type, decoded.data),
2115
2091
  catch: (cause) => new Error$4({
@@ -2249,7 +2225,7 @@ function make$2(options = {}) {
2249
2225
  });
2250
2226
  }
2251
2227
  const layer$1 = (options = {}) => Layer.succeed(Service$2, make$2(options));
2252
- const toAsync$1 = (options = {}) => toAsync$3(make$2(options));
2228
+ const toAsync$1 = (options = {}) => toAsync$5(make$2(options));
2253
2229
  //#endregion
2254
2230
  //#region src/verification/google-doh.ts
2255
2231
  var google_doh_exports = /* @__PURE__ */ __exportAll({
@@ -2265,7 +2241,7 @@ function make$1(options = {}) {
2265
2241
  });
2266
2242
  }
2267
2243
  const layer = (options = {}) => Layer.succeed(Service$2, make$1(options));
2268
- const toAsync = (options = {}) => toAsync$3(make$1(options));
2244
+ const toAsync = (options = {}) => toAsync$5(make$1(options));
2269
2245
  //#endregion
2270
2246
  //#region src/verification/resolver-pool.ts
2271
2247
  var resolver_pool_exports = /* @__PURE__ */ __exportAll({
@@ -2366,13 +2342,13 @@ function observeProvider(provider, zone, requirement) {
2366
2342
  }
2367
2343
  function observePublicDns(requirement, policy) {
2368
2344
  return Effect.gen(function* () {
2369
- const validatedPolicy = yield* Schema.decodeUnknownEffect(Policy.Schema)(policy).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message })));
2345
+ const validatedPolicy = yield* Schema.decodeUnknownEffect(Policy.Schema)(policy).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message })));
2370
2346
  const configuredPool = yield* Effect.serviceOption(Service);
2371
2347
  const evidence = yield* Option.getOrElse(configuredPool, () => defaultMake()).observe({
2372
2348
  name: requirement.name,
2373
2349
  type: requirement._tag
2374
2350
  });
2375
- if (validatedPolicy._tag === "Quorum" && validatedPolicy.minimum > evidence.length) return yield* new Error$2({ message: `Quorum minimum ${validatedPolicy.minimum} exceeds the ${evidence.length} configured resolvers` });
2351
+ if (validatedPolicy._tag === "Quorum" && validatedPolicy.minimum > evidence.length) return yield* new Error$1({ message: `Quorum minimum ${validatedPolicy.minimum} exceeds the ${evidence.length} configured resolvers` });
2376
2352
  return evaluatePublicDns(requirement, validatedPolicy, evidence);
2377
2353
  });
2378
2354
  }
@@ -2415,6 +2391,6 @@ function recordValue(requirement) {
2415
2391
  }
2416
2392
  }
2417
2393
  //#endregion
2418
- export { RemoveDomainResult as $, make$4 as A, discovery$1 as B, integrationFlow as C, encode$3 as Ct, tokenMethod as D, tokenConnectionMethod as E, subjectResolver as F, secret_exports as G, authorize as H, tokenConnectionMethod$1 as I, decode$4 as J, Method as K, tokenMethod$1 as L, manifest$1 as M, oauthFlow as N, client_exports as O, oauthMethod as P, Method$1 as Q, tokenValidator as R, exchangeCode as S, decode$6 as St, manifest as T, version as Tt, connection_authorization_exports as U, make$5 as V, selection_exports as W, diagnostic_exports as X, manifest_exports as Y, Error$1 as Z, toAsync$1 as _, CapabilityEvidence as _t, Result as a, AuthorizationError as at, dns_data_exports as b, Schema$3 as bt, Observation as c, assertGrant as ct, defaultMake as d, decode$5 as dt, StartResult as et, make as f, encode$2 as ft, cloudflare_doh_exports as g, Capability as gt, toAsync as h, validate as ht, PublicDnsObservation as i, start$1 as it, auth_exports$1 as j, discovery as k, Policy as l, authorizationError as lt, google_doh_exports as m, removeDomain$1 as mt, ProviderObservation as n, extend as nt, observe as o, Grant as ot, resolver_pool_exports as p, includeDomains as pt, Schema$1 as q, PublicDns as r, removeDomain as rt, verify_exports as s, Schema$2 as st, Provider as t, complete$1 as tt, Service as u, coversDomain as ut, doh_exports as v, Evidence$1 as vt, integrationMethod as w, provider_context_exports as wt, auth_exports as x, authorization_exports as xt, toAsync$2 as y, EvidenceSchema as yt, client_exports$1 as z };
2394
+ export { manifest_exports as $, discovery as A, tokenMethod$1 as B, integrationFlow as C, tokenConnectionMethod as D, restore as E, oauthMethod as F, authorize as G, client_exports$1 as H, refreshCredential as I, selection_exports as J, connection_authorization_exports as K, restore$1 as L, auth_exports$1 as M, manifest$1 as N, tokenMethod as O, oauthFlow as P, decode$4 as Q, subjectResolver as R, exchangeCode as S, manifest as T, discovery$1 as U, tokenValidator as V, make$5 as W, Method as X, secret_exports as Y, Schema$1 as Z, toAsync$1 as _, Result as a, dns_data_exports as b, Observation as c, defaultMake as d, diagnostic_exports as et, make as f, cloudflare_doh_exports as g, toAsync as h, PublicDnsObservation as i, make$4 as j, client_exports as k, Policy as l, google_doh_exports as m, ProviderObservation as n, observe as o, resolver_pool_exports as p, session_exports as q, PublicDns as r, verify_exports as s, Provider as t, version as tt, Service as u, doh_exports as v, integrationMethod as w, auth_exports as x, toAsync$2 as y, tokenConnectionMethod$1 as z };
2419
2395
 
2420
- //# sourceMappingURL=verify-BUB4N5zs.mjs.map
2396
+ //# sourceMappingURL=verify-DAtUAEeg.mjs.map