domainkit 0.3.1 → 0.5.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,176 +1,10 @@
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 { At as Error$2, Dt as decode$7, Et as Schema$3, I as Error$3, L as Service$3, R as fromAsync$1, V as toAsync$4, Y as Opaque, bt as assertAttachment, i as toAsync$5, it as equals, kt as parse$1, mt as AuthorizationError, n as Service$2, nt as decode$8, pt as Service$1, t as Error$4, tt as data, ut as candidates, wt as project, x as authorize$1, xt as authorizationError } from "./resolver-tZU6gEVq.mjs";
3
3
  import { Clock, Context, Crypto, Data, Effect, Layer, Option, Schema, SchemaIssue, SchemaTransformation } from "effect";
4
4
  import * as oauth from "oauth4webapi";
5
5
  import * as DnsPacket from "@leichtgewicht/dns-packet";
6
6
  //#region package.json
7
- var version = "0.3.1";
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
- }
7
+ var version = "0.5.0";
174
8
  //#endregion
175
9
  //#region src/auth/connect.ts
176
10
  const Method$1 = Data.taggedEnum();
@@ -190,57 +24,84 @@ var Error$1 = class extends Schema.TaggedError()("ConnectionError", {
190
24
  "unknown"
191
25
  ])
192
26
  }) {};
193
- const RemoveDomainResult = Data.taggedEnum();
194
- const persist = Effect.fn("Connection.persist")(function* (input) {
27
+ const missingCapability = (authentication, requiredCapabilities) => requiredCapabilities.find((capability) => !authentication.capabilityEvidence.some((item) => item.capability === capability));
28
+ const freshId = Effect.fn("Connection.freshId")(function* () {
29
+ return yield* (yield* Crypto.Crypto).randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
30
+ category: "validation",
31
+ message: cause.message,
32
+ operation: "Connection.connect",
33
+ retry: "safe"
34
+ })));
35
+ });
36
+ /** Persist one authenticated provider connection, optionally reusing a proven authorization. */
37
+ const connect = Effect.fn("Connection.connect")(function* (input) {
195
38
  const repository = yield* Service$1;
196
- const crypto = yield* Crypto.Crypto;
197
39
  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({
40
+ const missing = missingCapability(input.authentication, input.requiredCapabilities);
41
+ if (missing !== void 0) return yield* new Error$1({
200
42
  category: "authorization",
201
- message: `Provider authorization lacks evidence for ${missingCapability}`,
202
- operation: "Connection.persist",
43
+ message: `Provider authorization lacks evidence for ${missing}`,
44
+ operation: "Connection.connect",
203
45
  retry: "after-user-action"
204
46
  });
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
47
+ const existing = input.authorizationId === void 0 ? null : yield* repository.get(input.authorizationId);
48
+ if (input.authorizationId !== void 0 && existing === null) return yield* new Error$1({
49
+ category: "authorization",
50
+ message: "Provider authorization does not exist",
51
+ operation: "Connection.connect",
52
+ retry: "after-user-action"
53
+ });
54
+ if (existing?.authorization.revocation._tag === "Pending") return yield* new Error$1({
55
+ category: "authorization",
56
+ message: "Provider authorization is awaiting revocation recovery",
57
+ operation: "Connection.connect",
58
+ retry: "after-user-action"
59
+ });
60
+ if (existing !== null && (existing.authorization.providerId !== input.providerId || existing.authorization.method !== input.method)) return yield* new Error$1({
61
+ category: "authorization",
62
+ message: "Provider proof does not match the existing authorization",
63
+ operation: "Connection.connect",
64
+ retry: "never"
65
+ });
66
+ const authorizationId = existing?.authorization.id ?? (yield* freshId());
67
+ const authorization = existing === null ? {
68
+ authorizedById: input.authorizedById,
69
+ capabilityEvidence: [...input.authentication.capabilityEvidence],
70
+ createdAt: now,
71
+ expiresAt: input.authentication.expiresAt,
72
+ id: authorizationId,
73
+ method: input.method,
74
+ providerContext: input.authentication.providerContext,
75
+ providerId: input.providerId,
76
+ requiredCapabilities: [...input.requiredCapabilities],
77
+ revocation: { _tag: "Active" },
78
+ scopes: [...input.authentication.scopes]
79
+ } : {
80
+ ...existing.authorization,
81
+ capabilityEvidence: mergeEvidence(existing.authorization.capabilityEvidence, input.authentication.capabilityEvidence),
82
+ expiresAt: input.authentication.expiresAt,
83
+ method: input.method,
84
+ providerContext: input.authentication.providerContext,
85
+ requiredCapabilities: [...input.requiredCapabilities],
86
+ revocation: { _tag: "Active" },
87
+ scopes: [...input.authentication.scopes]
224
88
  };
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
89
+ const existingConnection = yield* repository.findConnection(input.ownerId, authorizationId);
90
+ const connection = {
91
+ authorizationId,
92
+ createdAt: existingConnection?.createdAt ?? now,
93
+ id: existingConnection?.id ?? (yield* freshId()),
94
+ method: input.method,
95
+ ownerId: input.ownerId,
96
+ providerId: input.providerId
242
97
  };
243
- return yield* repository.connect(aggregate);
98
+ return yield* repository.connect({
99
+ authorization,
100
+ connection,
101
+ credential: input.authentication.credential,
102
+ ...existing === null ? {} : { expectedAuthorizationId: existing.authorization.id },
103
+ ...existingConnection === null ? {} : { expectedConnectionId: existingConnection.id }
104
+ });
244
105
  });
245
106
  const start$1 = Effect.fn("Connection.start")(function* (input) {
246
107
  if (input.method._tag === "Interactive") {
@@ -255,8 +116,8 @@ const start$1 = Effect.fn("Connection.start")(function* (input) {
255
116
  const started = yield* input.method.flow.start(continuationId);
256
117
  yield* input.method.continuations.put({
257
118
  authorizedById: input.authorizedById,
119
+ ...input.authorizationId === void 0 ? {} : { authorizationId: input.authorizationId },
258
120
  expiresAt: new Date(now + (input.method.ttlMs ?? 6e5)),
259
- grant: input.grant,
260
121
  id: continuationId,
261
122
  method: input.method.flow.method,
262
123
  ownerId: input.ownerId,
@@ -270,16 +131,23 @@ const start$1 = Effect.fn("Connection.start")(function* (input) {
270
131
  });
271
132
  }
272
133
  const authentication = yield* input.method.authenticate(input.method.token);
273
- const aggregate = yield* persist({
134
+ const aggregate = yield* connect({
274
135
  authentication,
275
136
  authorizedById: input.authorizedById,
276
- grant: input.grant,
137
+ ...input.authorizationId === void 0 ? {} : { authorizationId: input.authorizationId },
277
138
  method: "token",
278
139
  ownerId: input.ownerId,
279
140
  providerId: input.method.providerId,
280
141
  requiredCapabilities: input.method.requiredCapabilities
281
142
  });
282
- return StartResult.Connected({ aggregate });
143
+ const storedConnection = aggregate.connections.find(({ ownerId }) => ownerId === input.ownerId);
144
+ if (storedConnection === void 0) return yield* new Error$1({
145
+ category: "validation",
146
+ message: "Connected provider authorization returned no organization connection",
147
+ operation: "Connection.start",
148
+ retry: "safe"
149
+ });
150
+ return StartResult.Connected({ connection: project(storedConnection, aggregate.authorization) });
283
151
  });
284
152
  const complete$1 = Effect.fn("Connection.complete")(function* (input) {
285
153
  const continuation = yield* input.continuations.consume(input.continuationId, new Date(yield* Clock.currentTimeMillis));
@@ -296,77 +164,29 @@ const complete$1 = Effect.fn("Connection.complete")(function* (input) {
296
164
  retry: "never"
297
165
  });
298
166
  const authentication = yield* input.flow.complete(continuation.payload, input.callbackUrl);
299
- return yield* persist({
167
+ const aggregate = yield* connect({
300
168
  authentication,
301
169
  authorizedById: continuation.authorizedById,
302
- grant: continuation.grant,
170
+ ...continuation.authorizationId === void 0 ? {} : { authorizationId: continuation.authorizationId },
303
171
  method: continuation.method,
304
172
  ownerId: continuation.ownerId,
305
173
  providerId: continuation.providerId,
306
174
  requiredCapabilities: continuation.requiredCapabilities
307
175
  });
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({
176
+ const storedConnection = aggregate.connections.find(({ ownerId }) => ownerId === continuation.ownerId);
177
+ if (storedConnection === void 0) return yield* new Error$1({
350
178
  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
- };
179
+ message: "Connected provider authorization returned no organization connection",
180
+ operation: "Connection.complete",
181
+ retry: "safe"
367
182
  });
368
- return updated.value ? RemoveDomainResult.Removed({ aggregate: updated.aggregate }) : RemoveDomainResult.AlreadyRemoved({ aggregate: updated.aggregate });
183
+ return project(storedConnection, aggregate.authorization);
369
184
  });
185
+ function mergeEvidence(current, incoming) {
186
+ const byCapability = new Map(current.map((item) => [item.capability, item]));
187
+ for (const item of incoming) byCapability.set(item.capability, item);
188
+ return [...byCapability.values()];
189
+ }
370
190
  //#endregion
371
191
  //#region src/auth/diagnostic.ts
372
192
  var diagnostic_exports = /* @__PURE__ */ __exportAll({
@@ -407,8 +227,8 @@ function from(error) {
407
227
  //#region src/auth/manifest.ts
408
228
  var manifest_exports = /* @__PURE__ */ __exportAll({
409
229
  Method: () => Method,
410
- Schema: () => Schema$1,
411
- decode: () => decode$4
230
+ Schema: () => Schema$2,
231
+ decode: () => decode$6
412
232
  });
413
233
  const Method = Schema.TaggedUnion({
414
234
  integration: {
@@ -436,11 +256,37 @@ const Method = Schema.TaggedUnion({
436
256
  instructionsUrl: Schema.String
437
257
  }
438
258
  });
439
- const Schema$1 = Schema.Struct({
259
+ const Schema$2 = Schema.Struct({
440
260
  methods: Schema.Array(Method),
441
261
  providerId: Schema.String
442
262
  });
443
- const decode$4 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
263
+ const decode$6 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
264
+ //#endregion
265
+ //#region src/auth/provider-context.ts
266
+ var provider_context_exports = /* @__PURE__ */ __exportAll({
267
+ Envelope: () => Envelope,
268
+ codec: () => codec,
269
+ decode: () => decode$5,
270
+ encode: () => encode$3
271
+ });
272
+ /** A provider-owned, versioned, non-secret context value persisted by the host. */
273
+ const Envelope = Schema.Struct({
274
+ value: Schema.Json,
275
+ version: Schema.String
276
+ });
277
+ /** Build an adapter-owned context codec from a schema and stable version identifier. */
278
+ function codec(version, schema) {
279
+ return {
280
+ 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 })))),
281
+ encode: Effect.fn("ProviderContext.encode")((value) => Schema.encodeEffect(schema)(value).pipe(Effect.flatMap((encoded) => Schema.decodeUnknownEffect(Schema.Json)(encoded)), Effect.map((encoded) => ({
282
+ value: encoded,
283
+ version
284
+ })), Effect.mapError((cause) => new Error$2({ message: cause.message })))),
285
+ version
286
+ };
287
+ }
288
+ const decode$5 = Effect.fn("ProviderContext.Envelope.decode")((input) => Schema.decodeUnknownEffect(Envelope)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
289
+ const encode$3 = Schema.encodeSync(Envelope);
444
290
  //#endregion
445
291
  //#region src/auth/secret.ts
446
292
  var secret_exports = /* @__PURE__ */ __exportAll({
@@ -471,26 +317,26 @@ const make$6 = Value.from;
471
317
  //#endregion
472
318
  //#region src/discovery/selection.ts
473
319
  var selection_exports = /* @__PURE__ */ __exportAll({
474
- Evidence: () => Evidence,
320
+ Evidence: () => Evidence$1,
475
321
  Selection: () => Selection,
476
322
  select: () => select
477
323
  });
478
- const Evidence = Schema.Struct({
324
+ const Evidence$1 = Schema.Struct({
479
325
  accountId: Schema.String,
480
326
  connectionId: Schema.String,
481
327
  decisiveNameserverMatch: Schema.Boolean,
482
- matchedNameservers: Schema.Array(Schema$4),
328
+ matchedNameservers: Schema.Array(Schema$3),
483
329
  providerId: Schema.String,
484
- zone: Schema$4
330
+ zone: Schema$3
485
331
  });
486
332
  const Selection = Schema.TaggedUnion({
487
333
  selected: {
488
- candidate: Evidence,
489
- evidence: Schema.Array(Evidence),
334
+ candidate: Evidence$1,
335
+ evidence: Schema.Array(Evidence$1),
490
336
  reason: Schema.Literals(["explicit", "unique-nameserver-match"])
491
337
  },
492
338
  manual: {
493
- evidence: Schema.Array(Evidence),
339
+ evidence: Schema.Array(Evidence$1),
494
340
  reason: Schema.Literals(["ambiguous", "unsupported"])
495
341
  }
496
342
  });
@@ -499,13 +345,16 @@ function select(input) {
499
345
  const zoneCandidates = new Set(candidates(domain));
500
346
  const authoritative = nameserverSet(input.authoritativeNameservers);
501
347
  const eligible = input.connectedZones.filter((connected) => {
502
- if (!zoneCandidates.has(parse$1(connected.zone))) return false;
348
+ if (!zoneCandidates.has(connected.attachment.target.zoneName)) return false;
503
349
  try {
504
- assertGrant(connected.connection, connected.authorization, {
350
+ assertAttachment({
351
+ attachment: connected.attachment,
505
352
  capability: "dns:read",
353
+ connection: connected.connection,
506
354
  domain,
507
355
  ...input.now === void 0 ? {} : { now: input.now },
508
- providerId: connected.providerId
356
+ providerId: connected.connection.providerId,
357
+ authorization: connected.authorization
509
358
  });
510
359
  return true;
511
360
  } catch {
@@ -515,8 +364,8 @@ function select(input) {
515
364
  const evidence = eligible.map((connected) => evidenceFor(connected, authoritative));
516
365
  if (input.explicit !== void 0) {
517
366
  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");
367
+ const index = eligible.findIndex((connected) => connected.connection.providerId === input.explicit?.providerId && connected.attachment.target.accountId === input.explicit.accountId && connected.attachment.target.zoneName === explicitZone);
368
+ if (index < 0) throw authorizationError("Explicit provider zone is not connected for this domain", "ProviderDiscovery.select");
520
369
  const candidate = evidence[index];
521
370
  if (candidate === void 0) throw new Error("Eligible provider evidence is missing");
522
371
  return {
@@ -543,32 +392,78 @@ function evidenceFor(connected, authoritative) {
543
392
  const configured = new Set(nameserverSet(connected.nameservers));
544
393
  const matchedNameservers = authoritative.filter((nameserver) => configured.has(nameserver));
545
394
  return {
546
- accountId: connected.authorization.providerAccountId,
395
+ accountId: connected.attachment.target.accountId,
547
396
  connectionId: connected.connection.id,
548
397
  decisiveNameserverMatch: authoritative.length > 0 && matchedNameservers.length === authoritative.length,
549
398
  matchedNameservers,
550
- providerId: connected.providerId,
551
- zone: parse$1(connected.zone)
399
+ providerId: connected.connection.providerId,
400
+ zone: connected.attachment.target.zoneName
552
401
  };
553
402
  }
554
403
  function nameserverSet(nameservers) {
555
404
  return [...new Set(nameservers.map(parse$1))].sort();
556
405
  }
557
406
  //#endregion
407
+ //#region src/provider/session.ts
408
+ var session_exports = /* @__PURE__ */ __exportAll({
409
+ Resolution: () => Resolution,
410
+ fromAsync: () => fromAsync,
411
+ toAsync: () => toAsync$3
412
+ });
413
+ const Resolution = Data.taggedEnum();
414
+ /** Adapts a Promise-shaped session to the Effect-native session boundary. */
415
+ const fromAsync = (session) => ({
416
+ providerId: session.providerId,
417
+ listTargets: (input) => Effect.tryPromise({
418
+ try: () => session.listTargets(input),
419
+ catch: (cause) => sessionFailure(session.providerId, "listTargets", cause)
420
+ }),
421
+ resolveTarget: (domain) => Effect.tryPromise({
422
+ try: () => session.resolveTarget(domain),
423
+ catch: (cause) => sessionFailure(session.providerId, "resolveTarget", cause)
424
+ }),
425
+ forTarget: (target) => Effect.tryPromise({
426
+ try: () => session.forTarget(target),
427
+ catch: (cause) => sessionFailure(session.providerId, "forTarget", cause)
428
+ }).pipe(Effect.map(fromAsync$1))
429
+ });
430
+ /** Adapts an Effect-native session to the Promise facade. */
431
+ const toAsync$3 = (session) => ({
432
+ providerId: session.providerId,
433
+ listTargets: (input) => Effect.runPromise(session.listTargets(input)),
434
+ resolveTarget: (domain) => Effect.runPromise(session.resolveTarget(domain)),
435
+ forTarget: (target) => Effect.runPromise(session.forTarget(target)).then(toAsync$4)
436
+ });
437
+ function sessionFailure(providerId, operation, cause) {
438
+ return cause instanceof Error$3 ? cause : new Error$3({
439
+ message: cause instanceof globalThis.Error ? cause.message : String(cause),
440
+ operation: `ProviderSession.${operation}`,
441
+ providerId,
442
+ reason: "transport"
443
+ });
444
+ }
445
+ //#endregion
558
446
  //#region src/plan/connection-authorization.ts
559
447
  var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize });
560
448
  const authorize = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
449
+ const aggregate = yield* (yield* Service$1).getByConnectionId(input.connection.id);
450
+ const storedConnection = aggregate?.connections.find(({ id }) => id === input.connection.id);
451
+ const storedAttachment = aggregate?.attachments.find(({ id }) => id === input.attachment.id);
452
+ 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
453
  const domain = yield* Effect.try({
562
- try: () => assertGrant(input.connection, input.authorization, {
454
+ try: () => assertAttachment({
455
+ attachment: storedAttachment,
563
456
  capability: "dns:write",
457
+ authorization: aggregate.authorization,
458
+ connection: storedConnection,
564
459
  domain: input.domain,
565
460
  providerId: input.plan.providerId
566
461
  }),
567
- catch: (cause) => cause instanceof AuthorizationError || cause instanceof Error$2 ? cause : authorizationError("Connection grant is invalid", "ConnectionAuthorization.authorize")
462
+ catch: (cause) => cause instanceof AuthorizationError || cause instanceof Error$2 ? cause : authorizationError("Domain attachment is invalid", "ConnectionAuthorization.authorize")
568
463
  });
569
464
  const authorization = yield* authorize$1(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
570
465
  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");
466
+ 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
467
  return authorization;
573
468
  });
574
469
  //#endregion
@@ -598,7 +493,7 @@ const start = Effect.fn("AuthorizationCode.start")(function* (input) {
598
493
  });
599
494
  const complete = Effect.fn("AuthorizationCode.complete")(function* (input) {
600
495
  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");
496
+ if (input.callbackUrl.searchParams.get("state") !== payload.state) return yield* failure$6("Authorization callback state does not match its continuation");
602
497
  const server = input.method.authorizationServer;
603
498
  const client = { client_id: input.client.clientId };
604
499
  const authentication = yield* clientAuthentication(input.method, input.client);
@@ -632,8 +527,8 @@ function decodePayload(payload) {
632
527
  function clientAuthentication(method, client) {
633
528
  switch (method.clientAuth) {
634
529
  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()));
530
+ case "client_secret_basic": return client.clientSecret === void 0 ? failure$6("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretBasic(client.clientSecret.expose()));
531
+ case "client_secret_post": return client.clientSecret === void 0 ? failure$6("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretPost(client.clientSecret.expose()));
637
532
  }
638
533
  }
639
534
  function requestOptions(fetchImplementation, signal) {
@@ -645,7 +540,7 @@ function requestOptions(fetchImplementation, signal) {
645
540
  })
646
541
  };
647
542
  }
648
- const failure$5 = (message) => Effect.fail(new Error$1({
543
+ const failure$6 = (message) => Effect.fail(new Error$1({
649
544
  category: "authorization",
650
545
  message,
651
546
  operation: "AuthorizationCode",
@@ -673,6 +568,44 @@ function base64Url(bytes) {
673
568
  return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
674
569
  }
675
570
  //#endregion
571
+ //#region src/auth/authorization.ts
572
+ const Capability = Schema.Literals(["dns:read", "dns:write"]);
573
+ const Evidence = Data.taggedEnum();
574
+ const EvidenceSchema = Schema.TaggedUnion({
575
+ Declared: {},
576
+ Exercised: { observedAt: Schema.DateFromString },
577
+ Introspected: { observedAt: Schema.DateFromString }
578
+ });
579
+ const CapabilityEvidence = Schema.Struct({
580
+ capability: Capability,
581
+ evidence: EvidenceSchema
582
+ });
583
+ const Revocation = Schema.TaggedUnion({
584
+ Active: {},
585
+ Pending: { requestedAt: Schema.DateFromString }
586
+ });
587
+ /** A provider credential authorization shared by one or more organization connections. */
588
+ const Schema$1 = Schema.Struct({
589
+ authorizedById: Schema.String,
590
+ capabilityEvidence: Schema.Array(CapabilityEvidence),
591
+ createdAt: Schema.DateFromString,
592
+ expiresAt: Schema.NullOr(Schema.DateFromString),
593
+ id: Schema.String,
594
+ method: Schema.Literals([
595
+ "integration",
596
+ "oauth2",
597
+ "token"
598
+ ]),
599
+ providerContext: Envelope,
600
+ providerId: Schema.String,
601
+ requiredCapabilities: Schema.Array(Capability),
602
+ revocation: Revocation,
603
+ scopes: Schema.Array(Schema.String)
604
+ });
605
+ Effect.fn("ProviderAuthorization.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
606
+ Effect.fn("ProviderAuthorization.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$1))(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
607
+ Schema.encodeSync(Schema$1);
608
+ //#endregion
676
609
  //#region src/providers/cloudflare/protocol.ts
677
610
  const ErrorDetail$1 = Schema.Struct({
678
611
  code: Schema.Number,
@@ -703,7 +636,13 @@ const Zone = Schema.Struct({
703
636
  id: Schema.String,
704
637
  name: Schema.String,
705
638
  name_servers: Schema.Array(Schema.String),
706
- status: Schema.optionalKey(Schema.String)
639
+ status: Schema.optionalKey(Schema.String),
640
+ type: Schema.optionalKey(Schema.Literals([
641
+ "full",
642
+ "partial",
643
+ "secondary",
644
+ "internal"
645
+ ]))
707
646
  });
708
647
  const Record$1 = Schema.Struct({
709
648
  content: Schema.optionalKey(Schema.String),
@@ -763,7 +702,7 @@ const Data$1 = Schema.Struct({
763
702
  weight: Schema.optionalKey(Schema.Number)
764
703
  });
765
704
  const decode$3 = Effect.fn("CloudflareRecords.decode")((record) => Effect.gen(function* () {
766
- const name = yield* decode$8(record.name).pipe(Effect.match({
705
+ const name = yield* decode$7(record.name).pipe(Effect.match({
767
706
  onFailure: () => null,
768
707
  onSuccess: (value) => value
769
708
  }));
@@ -778,46 +717,46 @@ const decode$3 = Effect.fn("CloudflareRecords.decode")((record) => Effect.gen(fu
778
717
  policy: record.type === "CNAME" ? "exclusive" : "append",
779
718
  ttl: record.ttl === 1 ? null : record.ttl
780
719
  };
781
- const content = () => record.content === void 0 ? Effect.fail(failure$4("decodeRecord", `Cloudflare ${record.type} record has no content`)) : Effect.succeed(record.content);
720
+ const content = () => record.content === void 0 ? Effect.fail(failure$5("decodeRecord", `Cloudflare ${record.type} record has no content`)) : Effect.succeed(record.content);
782
721
  switch (record.type) {
783
722
  case "A":
784
- case "AAAA": return yield* decode$9({
723
+ case "AAAA": return yield* decode$8({
785
724
  ...common,
786
725
  _tag: record.type,
787
726
  address: yield* content()
788
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
727
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
789
728
  case "CNAME":
790
- case "NS": return yield* decode$9({
729
+ case "NS": return yield* decode$8({
791
730
  ...common,
792
731
  _tag: record.type,
793
732
  target: yield* content()
794
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
795
- case "TXT": return yield* decode$9({
733
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
734
+ case "TXT": return yield* decode$8({
796
735
  ...common,
797
736
  _tag: "TXT",
798
737
  value: yield* content()
799
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
800
- case "MX": return yield* decode$9({
738
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
739
+ case "MX": return yield* decode$8({
801
740
  ...common,
802
741
  _tag: "MX",
803
742
  exchange: yield* content(),
804
743
  priority: record.priority
805
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
744
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
806
745
  case "CAA": {
807
746
  const data = yield* decodeData(record);
808
- return yield* decode$9({
747
+ return yield* decode$8({
809
748
  ...common,
810
749
  _tag: "CAA",
811
750
  ...data
812
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
751
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
813
752
  }
814
753
  case "SRV": {
815
754
  const data = yield* decodeData(record);
816
- return yield* decode$9({
755
+ return yield* decode$8({
817
756
  ...common,
818
757
  _tag: "SRV",
819
758
  ...data
820
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
759
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message)));
821
760
  }
822
761
  default: return yield* decodeOpaque$1(record);
823
762
  }
@@ -827,7 +766,7 @@ const decodeOpaque$1 = Effect.fn("CloudflareRecords.decodeOpaque")((record) => S
827
766
  name: record.name,
828
767
  providerRecordId: record.id,
829
768
  providerType: record.type
830
- }).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message))));
769
+ }).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message))));
831
770
  function encode$1(record) {
832
771
  const common = {
833
772
  name: record.name,
@@ -874,8 +813,8 @@ function encode$1(record) {
874
813
  };
875
814
  }
876
815
  }
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 = {}) {
816
+ const decodeData = Effect.fn("CloudflareRecords.decodeData")((record) => Schema.decodeUnknownEffect(Data$1)(record.data).pipe(Effect.mapError((cause) => failure$5("decodeRecord", cause.message))));
817
+ function failure$5(operation, message, fields = {}) {
879
818
  return new Error$3({
880
819
  message,
881
820
  operation,
@@ -915,14 +854,14 @@ const makeTransport = (options) => {
915
854
  ...init?.headers
916
855
  }
917
856
  }),
918
- catch: () => failure$3(operation, "Cloudflare request failed", { reason: "transport" })
857
+ catch: () => failure$4(operation, "Cloudflare request failed", { reason: "transport" })
919
858
  });
920
859
  return {
921
860
  body: yield* Effect.tryPromise({
922
861
  try: () => response.json(),
923
862
  catch: () => {
924
863
  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}`, {
864
+ return failure$4(operation, response.ok ? "Cloudflare returned a non-JSON response" : `Cloudflare request failed with HTTP ${response.status}`, {
926
865
  reason: response.ok ? "response" : reason$1(response.status),
927
866
  ...retryAfterMs === void 0 ? {} : { retryAfterMs },
928
867
  status: response.status
@@ -960,12 +899,12 @@ const makeTransport = (options) => {
960
899
  const { body, response } = yield* request(path, "validateToken");
961
900
  yield* ensureSuccess(body, "validateToken", response);
962
901
  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}`, {
902
+ if (envelope.result.status !== "active") return yield* Effect.fail(failure$4("validateToken", `Cloudflare token is ${envelope.result.status}`, {
964
903
  reason: "authentication",
965
904
  status: response.status
966
905
  }));
967
906
  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", {
907
+ if (expiresAt !== null && Number.isNaN(expiresAt.valueOf())) return yield* Effect.fail(failure$4("validateToken", "Cloudflare token expiry is not a valid timestamp", {
969
908
  reason: "response",
970
909
  status: response.status
971
910
  }));
@@ -981,9 +920,9 @@ const discoverAccount = Effect.fn("CloudflareClient.discoverAccount")((options)
981
920
  const match = matches[0];
982
921
  if (matches.length === 0) continue;
983
922
  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" }));
923
+ return yield* Effect.fail(failure$4("discoverAccount", `Cloudflare returned multiple accessible zones named ${candidate}`, { reason: "conflict" }));
985
924
  }
986
- return yield* Effect.fail(failure$3("discoverAccount", `Cloudflare did not expose an accessible zone for ${options.domain}`, { reason: "not_found" }));
925
+ return yield* Effect.fail(failure$4("discoverAccount", `Cloudflare did not expose an accessible zone for ${options.domain}`, { reason: "not_found" }));
987
926
  }));
988
927
  const discoverAuthorizationAccount = discoverAccount;
989
928
  const validateDomainToken = Effect.fn("CloudflareClient.validateDomainToken")((options) => Effect.gen(function* () {
@@ -999,12 +938,27 @@ const validateDomainToken = Effect.fn("CloudflareClient.validateDomainToken")((o
999
938
  }));
1000
939
  /** Creates an Effect-native Cloudflare client without owning the credential lifecycle. */
1001
940
  function make$5(options) {
941
+ return makeClient$1(options);
942
+ }
943
+ function makeClient$1(options) {
1002
944
  const { allZones, inspectToken, request } = makeTransport(options);
1003
945
  const resolveZone = Effect.fn("CloudflareClient.resolveZone")((name) => Effect.gen(function* () {
946
+ if (options.target !== void 0) {
947
+ 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" }));
948
+ return {
949
+ account: {
950
+ id: options.target.accountId,
951
+ name: options.target.accountId
952
+ },
953
+ id: options.target.zoneId,
954
+ name: options.target.zoneName,
955
+ name_servers: options.target.evidence?.nameservers ?? []
956
+ };
957
+ }
1004
958
  const matches = yield* allZones(options.accountId, name);
1005
959
  const match = matches[0];
1006
960
  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" }));
961
+ 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
962
  }));
1009
963
  const listRecords = Effect.fn("CloudflareClient.listRecords")((zoneName) => Effect.gen(function* () {
1010
964
  const zone = yield* resolveZone(zoneName);
@@ -1041,28 +995,72 @@ function make$5(options) {
1041
995
  const envelope = yield* decode$2(RecordEnvelope, body, "getRecord", response);
1042
996
  return yield* decode$3(envelope.result);
1043
997
  }));
998
+ const deleteRecord = Effect.fn("CloudflareClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
999
+ const zone = yield* resolveZone(zoneName);
1000
+ const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records/${encodeURIComponent(providerRecordId)}`, "deleteRecord", { method: "DELETE" });
1001
+ yield* ensureSuccess(body, "deleteRecord", response);
1002
+ }));
1003
+ const listAccounts = Effect.fn("CloudflareClient.listAccounts")(() => allZones().pipe(Effect.map((zones) => {
1004
+ const accounts = /* @__PURE__ */ new Map();
1005
+ for (const zone of zones) accounts.set(zone.account.id, zone.account);
1006
+ return [...accounts.values()].sort((left, right) => left.name.localeCompare(right.name));
1007
+ })));
1008
+ const listZones = Effect.fn("CloudflareClient.listZones")((input = {}) => allZones(input.accountId ?? options.accountId, input.name).pipe(Effect.flatMap((zones) => Effect.forEach(zones, projectZone$1))));
1009
+ const listTargets = Effect.fn("CloudflareClient.listTargets")((input = {}) => Effect.gen(function* () {
1010
+ const names = input.domain === void 0 ? void 0 : new Set(candidates(input.domain).map((name) => String(name)));
1011
+ const zones = yield* allZones(input.accountId ?? options.accountId);
1012
+ return yield* Effect.forEach(zones.filter((zone) => zone.type !== "internal" && (names === void 0 || names.has(zone.name))), targetFromZone);
1013
+ }));
1044
1014
  return {
1045
1015
  id: "cloudflare",
1016
+ providerId: "cloudflare",
1046
1017
  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
- })),
1018
+ deleteRecord,
1052
1019
  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
- }))),
1020
+ listAccounts,
1058
1021
  listRecords,
1059
- listZones: Effect.fn("CloudflareClient.listZones")((input = {}) => allZones(input.accountId ?? options.accountId, input.name).pipe(Effect.flatMap((zones) => Effect.forEach(zones, projectZone$1)))),
1022
+ listZones,
1023
+ listTargets,
1024
+ resolveTarget: Effect.fn("CloudflareClient.resolveTarget")((domain) => Effect.gen(function* () {
1025
+ const candidates$2 = yield* listTargets({ domain });
1026
+ for (const zoneName of candidates(domain)) {
1027
+ const matches = candidates$2.filter((target) => target.zoneName === zoneName);
1028
+ if (matches.length === 1) {
1029
+ const target = matches[0];
1030
+ if (target !== void 0) return Resolution.Resolved({ target });
1031
+ }
1032
+ if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });
1033
+ }
1034
+ return Resolution.NotFound({ domain });
1035
+ })),
1036
+ forTarget: Effect.fn("CloudflareClient.forTarget")((target) => Effect.gen(function* () {
1037
+ if (target.accountKind !== null && target.accountKind !== "account") return yield* Effect.fail(failure$4("forTarget", "Cloudflare targets must use the account account kind", { reason: "request" }));
1038
+ if (target.evidence?.zoneType === "internal") return yield* Effect.fail(failure$4("forTarget", "Cloudflare internal zones cannot host managed DNS records", { reason: "unsupported" }));
1039
+ const selected = (yield* listTargets({
1040
+ accountId: target.accountId,
1041
+ domain: target.zoneName
1042
+ })).find((candidate) => candidate.accountId === target.accountId && candidate.zoneId === target.zoneId && candidate.zoneName === target.zoneName);
1043
+ if (selected === void 0) return yield* Effect.fail(failure$4("forTarget", `Cloudflare target ${target.zoneName} is not visible to this credential`, { reason: "authorization" }));
1044
+ if (selected.evidence?.zoneType === "internal") return yield* Effect.fail(failure$4("forTarget", "Cloudflare internal zones cannot host managed DNS records", { reason: "unsupported" }));
1045
+ const client = makeClient$1({
1046
+ ...options,
1047
+ accountId: selected.accountId,
1048
+ target: selected
1049
+ });
1050
+ return {
1051
+ id: client.id,
1052
+ createRecord: client.createRecord,
1053
+ deleteRecord: client.deleteRecord,
1054
+ getRecord: client.getRecord,
1055
+ listRecords: client.listRecords
1056
+ };
1057
+ })),
1060
1058
  validateToken: Effect.fn("CloudflareClient.validateToken")(() => Effect.gen(function* () {
1061
- const path = options.tokenKind === "account" ? `/accounts/${encodeURIComponent(options.accountId)}/tokens/verify` : "/user/tokens/verify";
1059
+ 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
1060
  const expiresAt = yield* inspectToken(path);
1063
1061
  yield* allZones(options.accountId);
1064
1062
  return {
1065
- accountId: options.accountId,
1063
+ accountId: options.accountId ?? "",
1066
1064
  capabilities: options.capabilities,
1067
1065
  expiresAt,
1068
1066
  scopes: []
@@ -1070,9 +1068,25 @@ function make$5(options) {
1070
1068
  }))
1071
1069
  };
1072
1070
  }
1071
+ const targetFromZone = Effect.fn("CloudflareClient.targetFromZone")((zone) => Effect.gen(function* () {
1072
+ const name = yield* decode$7(zone.name).pipe(Effect.mapError((cause) => failure$4("listTargets", cause.message, { reason: "response" })));
1073
+ const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$7(nameserver).pipe(Effect.mapError((cause) => failure$4("listTargets", cause.message, { reason: "response" }))));
1074
+ return {
1075
+ accountId: zone.account.id,
1076
+ accountKind: "account",
1077
+ evidence: {
1078
+ accountName: zone.account.name,
1079
+ nameservers,
1080
+ status: zone.status ?? "unknown",
1081
+ zoneType: zone.type ?? "unknown"
1082
+ },
1083
+ zoneId: zone.id,
1084
+ zoneName: name
1085
+ };
1086
+ }));
1073
1087
  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" }))));
1088
+ const name = yield* decode$7(zone.name).pipe(Effect.mapError((cause) => failure$4("listZones", cause.message, { reason: "response" })));
1089
+ const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$7(nameserver).pipe(Effect.mapError((cause) => failure$4("listZones", cause.message, { reason: "response" }))));
1076
1090
  return {
1077
1091
  accountId: zone.account.id,
1078
1092
  id: zone.id,
@@ -1082,20 +1096,20 @@ const projectZone$1 = Effect.fn("CloudflareClient.projectZone")((zone) => Effect
1082
1096
  };
1083
1097
  }));
1084
1098
  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}`, {
1099
+ return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$4(operation, `Cloudflare response did not match its API contract: ${cause.message}`, {
1086
1100
  reason: "response",
1087
1101
  status: response.status
1088
1102
  })));
1089
1103
  }
1090
1104
  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", {
1105
+ return Schema.decodeUnknownEffect(BaseEnvelope)(input).pipe(Effect.mapError(() => failure$4(operation, "Cloudflare response did not contain a valid API envelope", {
1092
1106
  reason: "response",
1093
1107
  status: response.status
1094
1108
  })), Effect.flatMap((envelope) => {
1095
1109
  if (response.ok && envelope.success) return Effect.void;
1096
1110
  const detail = envelope.errors[0];
1097
1111
  const retryAfterMs = retryAfter(response.headers.get("retry-after"));
1098
- return Effect.fail(failure$3(operation, detail?.message ?? `Cloudflare request failed with HTTP ${response.status}`, {
1112
+ return Effect.fail(failure$4(operation, detail?.message ?? `Cloudflare request failed with HTTP ${response.status}`, {
1099
1113
  ...detail === void 0 ? {} : { code: detail.code },
1100
1114
  reason: reason$1(response.status, detail?.code),
1101
1115
  ...retryAfterMs === void 0 ? {} : { retryAfterMs },
@@ -1119,7 +1133,7 @@ function retryAfter(value) {
1119
1133
  const date = Date.parse(value);
1120
1134
  return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
1121
1135
  }
1122
- function failure$3(operation, message, fields = {}) {
1136
+ function failure$4(operation, message, fields = {}) {
1123
1137
  return new Error$3({
1124
1138
  ...fields,
1125
1139
  message,
@@ -1135,6 +1149,7 @@ var auth_exports$1 = /* @__PURE__ */ __exportAll({
1135
1149
  oauthAuthentication: () => oauthAuthentication,
1136
1150
  oauthFlow: () => oauthFlow,
1137
1151
  oauthMethod: () => oauthMethod,
1152
+ restore: () => restore$1,
1138
1153
  subjectResolver: () => subjectResolver,
1139
1154
  tokenConnectionMethod: () => tokenConnectionMethod$1,
1140
1155
  tokenMethod: () => tokenMethod$1,
@@ -1146,7 +1161,10 @@ const authorizationServer = {
1146
1161
  revocation_endpoint: "https://dash.cloudflare.com/oauth2/revoke",
1147
1162
  token_endpoint: "https://dash.cloudflare.com/oauth2/token"
1148
1163
  };
1149
- const contextCodec$1 = codec("cloudflare.v1", Schema.Struct({ tokenKind: Schema.Literals(["account", "user"]) }));
1164
+ const contextCodec$1 = codec("cloudflare.v1", Schema.Struct({
1165
+ accountId: Schema.optionalKey(Schema.String),
1166
+ tokenKind: Schema.Literals(["account", "user"])
1167
+ }));
1150
1168
  /** Describes Cloudflare's caller-created API-token authorization method. */
1151
1169
  function tokenMethod$1(capabilities) {
1152
1170
  return {
@@ -1172,7 +1190,7 @@ function manifest$1(options) {
1172
1190
  providerId: "cloudflare"
1173
1191
  };
1174
1192
  }
1175
- /** Resolves the Cloudflare account selected by a completed OAuth grant. */
1193
+ /** Resolves the Cloudflare account selected by a completed OAuth authorization. */
1176
1194
  function subjectResolver(options) {
1177
1195
  return (tokens, accessToken) => Effect.gen(function* () {
1178
1196
  const accountId = options.accountId ?? (yield* discoverAuthorizationAccount({
@@ -1224,7 +1242,7 @@ function tokenConnectionMethod$1(options) {
1224
1242
  return {
1225
1243
  capabilityEvidence: validated.capabilities.map((capability) => ({
1226
1244
  capability,
1227
- evidence: Evidence$1.Introspected({ observedAt })
1245
+ evidence: Evidence.Introspected({ observedAt })
1228
1246
  })),
1229
1247
  credential: {
1230
1248
  accessToken: token,
@@ -1233,7 +1251,10 @@ function tokenConnectionMethod$1(options) {
1233
1251
  },
1234
1252
  expiresAt: validated.expiresAt,
1235
1253
  providerAccountId: validated.accountId,
1236
- providerContext: yield* contextCodec$1.encode({ tokenKind: options.tokenKind ?? "user" }),
1254
+ providerContext: yield* contextCodec$1.encode(options.tokenKind === "account" ? {
1255
+ accountId: validated.accountId,
1256
+ tokenKind: "account"
1257
+ } : { tokenKind: "user" }),
1237
1258
  scopes: [...validated.scopes]
1238
1259
  };
1239
1260
  }),
@@ -1242,14 +1263,14 @@ function tokenConnectionMethod$1(options) {
1242
1263
  token: options.token
1243
1264
  });
1244
1265
  }
1245
- /** Converts a completed Cloudflare OAuth grant into the canonical connection authentication. */
1266
+ /** Converts a completed Cloudflare OAuth authorization into canonical connection authentication. */
1246
1267
  const oauthAuthentication = Effect.fn("CloudflareAuth.oauthAuthentication")(function* (options) {
1247
1268
  const resolved = yield* subjectResolver(options)(options.tokens, options.accessToken);
1248
1269
  const observedAt = new Date(yield* Clock.currentTimeMillis);
1249
1270
  return {
1250
1271
  capabilityEvidence: options.capabilities.map((capability) => ({
1251
1272
  capability,
1252
- evidence: Evidence$1.Introspected({ observedAt })
1273
+ evidence: Evidence.Introspected({ observedAt })
1253
1274
  })),
1254
1275
  credential: {
1255
1276
  accessToken: options.accessToken,
@@ -1262,6 +1283,26 @@ const oauthAuthentication = Effect.fn("CloudflareAuth.oauthAuthentication")(func
1262
1283
  scopes: (options.tokens.scope ?? "").split(" ").filter(Boolean)
1263
1284
  };
1264
1285
  });
1286
+ /** Restore a credential-scoped Cloudflare session from persisted authorization state. */
1287
+ function restore$1(options) {
1288
+ return Effect.gen(function* () {
1289
+ if (options.authorization.providerId !== "cloudflare") return yield* Effect.fail(failure$3("restore", "Provider authorization belongs to another provider", "authorization"));
1290
+ if (options.authorization.method === "integration") return yield* Effect.fail(failure$3("restore", "Cloudflare does not support integration credentials", "unsupported"));
1291
+ if (options.authorization.revocation._tag !== "Active") return yield* Effect.fail(failure$3("restore", "Cloudflare authorization is pending revocation", "authorization"));
1292
+ const missingCapability = options.authorization.requiredCapabilities.find((capability) => !options.authorization.capabilityEvidence.some((item) => item.capability === capability));
1293
+ if (missingCapability !== void 0) return yield* Effect.fail(failure$3("restore", `Cloudflare authorization lacks evidence for ${missingCapability}`, "authorization"));
1294
+ if (options.authorization.expiresAt !== null && (Number.isNaN(options.authorization.expiresAt.valueOf()) || options.authorization.expiresAt <= /* @__PURE__ */ new Date())) return yield* Effect.fail(failure$3("restore", "Cloudflare authorization has expired", "authentication"));
1295
+ const context = yield* contextCodec$1.decode(options.authorization.providerContext).pipe(Effect.mapError((cause) => failure$3("restore", cause.message, "response")));
1296
+ return make$5({
1297
+ ...context.accountId === void 0 ? {} : { accountId: context.accountId },
1298
+ ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
1299
+ capabilities: options.authorization.capabilityEvidence.map(({ capability }) => capability),
1300
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
1301
+ token: options.credential.accessToken,
1302
+ tokenKind: context.tokenKind
1303
+ });
1304
+ });
1305
+ }
1265
1306
  /** Creates Cloudflare's OAuth implementation of the common interactive connection capability. */
1266
1307
  function oauthFlow(options) {
1267
1308
  const method = oauthMethod({
@@ -1299,6 +1340,14 @@ function oauthFlow(options) {
1299
1340
  })
1300
1341
  };
1301
1342
  }
1343
+ function failure$3(operation, message, reason) {
1344
+ return new Error$3({
1345
+ message,
1346
+ operation,
1347
+ providerId: "cloudflare",
1348
+ reason
1349
+ });
1350
+ }
1302
1351
  //#endregion
1303
1352
  //#region src/providers/vercel/protocol.ts
1304
1353
  const Pagination = Schema.Struct({
@@ -1388,7 +1437,7 @@ const IntegrationToken = Schema.Struct({
1388
1437
  //#region src/providers/vercel/records.ts
1389
1438
  const decode$1 = Effect.fn("VercelRecords.decode")((zone, record) => Effect.gen(function* () {
1390
1439
  const absolute = absoluteNameCandidate(zone, record.name);
1391
- const name = yield* decode$8(absolute).pipe(Effect.match({
1440
+ const name = yield* decode$7(absolute).pipe(Effect.match({
1392
1441
  onFailure: () => null,
1393
1442
  onSuccess: (value) => value
1394
1443
  }));
@@ -1403,7 +1452,7 @@ const decode$1 = Effect.fn("VercelRecords.decode")((zone, record) => Effect.gen(
1403
1452
  policy: record.type === "CNAME" ? "exclusive" : "append",
1404
1453
  ttl: record.ttl ?? 60
1405
1454
  };
1406
- const fromRecord = (input) => decode$9(input).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message)));
1455
+ const fromRecord = (input) => decode$8(input).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message)));
1407
1456
  switch (record.type) {
1408
1457
  case "A":
1409
1458
  case "AAAA": return yield* fromRecord({
@@ -1544,6 +1593,9 @@ function discovery(client) {
1544
1593
  }
1545
1594
  /** Creates an Effect-native Vercel client without owning the credential lifecycle. */
1546
1595
  function make$4(options) {
1596
+ return makeClient(options);
1597
+ }
1598
+ function makeClient(options) {
1547
1599
  const fetch = options.fetch ?? globalThis.fetch;
1548
1600
  const baseUrl = (options.baseUrl ?? "https://api.vercel.com").replace(/\/$/, "");
1549
1601
  const withContext = (path, values = {}) => {
@@ -1615,9 +1667,13 @@ function make$4(options) {
1615
1667
  return envelope.domain;
1616
1668
  }).pipe(Effect.catchTag("ProviderError", (cause) => cause.reason === "not_found" ? Effect.succeed(null) : Effect.fail(cause))));
1617
1669
  const resolveZone = Effect.fn("VercelClient.resolveZone")((name) => Effect.gen(function* () {
1670
+ if (options.target !== void 0) {
1671
+ 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" }));
1672
+ return options.target.zoneName;
1673
+ }
1618
1674
  const domain = yield* discoverZone(name);
1619
1675
  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}`)));
1676
+ return yield* decode$7(domain.name).pipe(Effect.mapError((cause) => failure$1("resolveZone", `Vercel returned an invalid storage zone: ${cause.message}`)));
1621
1677
  }));
1622
1678
  const listRecords = Effect.fn("VercelClient.listRecords")((zoneName) => Effect.gen(function* () {
1623
1679
  const storageZone = yield* resolveZone(zoneName);
@@ -1661,34 +1717,84 @@ function make$4(options) {
1661
1717
  until = next;
1662
1718
  }
1663
1719
  }));
1720
+ const deleteRecord = Effect.fn("VercelClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
1721
+ const storageZone = yield* resolveZone(zoneName);
1722
+ yield* request(withContext(`/v2/domains/${encodeURIComponent(storageZone)}/records/${encodeURIComponent(providerRecordId)}`), "deleteRecord", { method: "DELETE" });
1723
+ }));
1724
+ const listAccounts = Effect.fn("VercelClient.listAccounts")(() => Effect.gen(function* () {
1725
+ const userResult = yield* request(new URL(`${baseUrl}/v2/user`), "listAccounts");
1726
+ const { user } = yield* decode(UserEnvelope, userResult.body, "listAccounts", userResult.response);
1727
+ const teams = yield* allTeams();
1728
+ return [{
1729
+ id: user.id,
1730
+ name: user.name ?? user.username,
1731
+ type: "personal"
1732
+ }, ...teams.map((team) => ({
1733
+ id: team.id,
1734
+ name: team.name ?? team.slug,
1735
+ type: "team"
1736
+ }))];
1737
+ }));
1738
+ const listZones = Effect.fn("VercelClient.listZones")((input = {}) => Effect.gen(function* () {
1739
+ const domains = (yield* allDomains()).filter((domain) => hasDnsStorage(domain) && (input.name === void 0 || domain.name === input.name));
1740
+ if (domains.length > 0 || input.name === void 0) return yield* Effect.forEach(domains, projectZone);
1741
+ const discovered = yield* discoverZone(input.name);
1742
+ return discovered === null ? [] : [yield* projectZone(discovered)];
1743
+ }));
1744
+ const listTargets = Effect.fn("VercelClient.listTargets")((input = {}) => Effect.gen(function* () {
1745
+ const names = input.domain === void 0 ? void 0 : new Set(candidates(input.domain).map((name) => String(name)));
1746
+ 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));
1747
+ if (domains.length > 0 || input.domain === void 0) return yield* Effect.forEach(domains, targetFromDomain);
1748
+ for (const name of candidates(input.domain)) {
1749
+ const discovered = yield* discoverZone(name);
1750
+ if (discovered !== null && (input.accountId === void 0 || (discovered.teamId ?? discovered.userId) === input.accountId)) return [yield* targetFromDomain(discovered)];
1751
+ }
1752
+ return [];
1753
+ }));
1664
1754
  return {
1665
1755
  id: "vercel",
1756
+ providerId: "vercel",
1666
1757
  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
- })),
1758
+ deleteRecord,
1671
1759
  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
- })),
1760
+ listAccounts,
1686
1761
  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)];
1762
+ listZones,
1763
+ listTargets,
1764
+ resolveTarget: Effect.fn("VercelClient.resolveTarget")((domain) => Effect.gen(function* () {
1765
+ const candidates$1 = yield* listTargets({ domain });
1766
+ for (const zoneName of candidates(domain)) {
1767
+ const matches = candidates$1.filter((target) => target.zoneName === zoneName);
1768
+ if (matches.length === 1) {
1769
+ const target = matches[0];
1770
+ if (target !== void 0) return Resolution.Resolved({ target });
1771
+ }
1772
+ if (matches.length > 1) return Resolution.SelectionRequired({ candidates: matches });
1773
+ }
1774
+ return Resolution.NotFound({ domain });
1775
+ })),
1776
+ forTarget: Effect.fn("VercelClient.forTarget")((target) => Effect.gen(function* () {
1777
+ if (target.accountKind === "team") {
1778
+ 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" }));
1779
+ } 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" }));
1780
+ else if (target.accountKind === "account") return yield* Effect.fail(failure$1("forTarget", "Vercel does not support account-kind targets", { reason: "unsupported" }));
1781
+ else if (target.accountKind === null) return yield* Effect.fail(failure$1("forTarget", "Vercel targets must identify a personal or team account", { reason: "request" }));
1782
+ const selected = (yield* listTargets({
1783
+ accountId: target.accountId,
1784
+ domain: target.zoneName
1785
+ })).find((candidate) => candidate.accountId === target.accountId && candidate.accountKind === target.accountKind && candidate.zoneId === target.zoneId && candidate.zoneName === target.zoneName);
1786
+ if (selected === void 0) return yield* Effect.fail(failure$1("forTarget", `Vercel target ${target.zoneName} is not visible to this installation`, { reason: "authorization" }));
1787
+ const client = makeClient({
1788
+ ...options,
1789
+ target: selected
1790
+ });
1791
+ return {
1792
+ id: client.id,
1793
+ createRecord: client.createRecord,
1794
+ deleteRecord: client.deleteRecord,
1795
+ getRecord: client.getRecord,
1796
+ listRecords: client.listRecords
1797
+ };
1692
1798
  })),
1693
1799
  validateToken: Effect.fn("VercelClient.validateToken")(() => Effect.gen(function* () {
1694
1800
  let accountId;
@@ -1713,8 +1819,8 @@ function hasDnsStorage(domain, configuredServiceType = domain.serviceType) {
1713
1819
  return configuredServiceType === "zeit.world" || domain.zone === true || domain.intendedNameservers.length > 0;
1714
1820
  }
1715
1821
  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" }))));
1822
+ const name = yield* decode$7(domain.name).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" })));
1823
+ const nameservers = yield* Effect.forEach(domain.intendedNameservers, (nameserver) => decode$7(nameserver).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" }))));
1718
1824
  return {
1719
1825
  accountId: domain.teamId ?? domain.userId,
1720
1826
  id: domain.id,
@@ -1723,6 +1829,21 @@ const projectZone = Effect.fn("VercelClient.projectZone")((domain) => Effect.gen
1723
1829
  status: domain.verified ? "active" : "pending"
1724
1830
  };
1725
1831
  }));
1832
+ const targetFromDomain = Effect.fn("VercelClient.targetFromDomain")((domain) => Effect.gen(function* () {
1833
+ const zoneName = yield* decode$7(domain.name).pipe(Effect.mapError((cause) => failure$1("listTargets", cause.message, { reason: "response" })));
1834
+ const nameservers = yield* Effect.forEach(domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers, (nameserver) => decode$7(nameserver).pipe(Effect.mapError((cause) => failure$1("listTargets", cause.message, { reason: "response" }))));
1835
+ return {
1836
+ accountId: domain.teamId ?? domain.userId,
1837
+ accountKind: domain.teamId === null ? "personal" : "team",
1838
+ evidence: {
1839
+ nameservers,
1840
+ status: domain.verified ? "active" : "pending",
1841
+ zoneType: domain.serviceType
1842
+ },
1843
+ zoneId: domain.id,
1844
+ zoneName
1845
+ };
1846
+ }));
1726
1847
  function decode(schema, input, operation, response) {
1727
1848
  return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$1(operation, `Vercel response did not match its API contract: ${cause.message}`, {
1728
1849
  reason: "response",
@@ -1770,6 +1891,7 @@ var auth_exports = /* @__PURE__ */ __exportAll({
1770
1891
  integrationFlow: () => integrationFlow,
1771
1892
  integrationMethod: () => integrationMethod,
1772
1893
  manifest: () => manifest,
1894
+ restore: () => restore,
1773
1895
  tokenConnectionMethod: () => tokenConnectionMethod,
1774
1896
  tokenMethod: () => tokenMethod
1775
1897
  });
@@ -1794,7 +1916,7 @@ function integrationMethod(options) {
1794
1916
  _tag: "integration",
1795
1917
  capabilities: [...options.capabilities],
1796
1918
  installUrl: `https://vercel.com/integrations/${encodeURIComponent(options.slug)}/new`,
1797
- tokenEndpoint: "https://api.vercel.com/oauth/access_token"
1919
+ tokenEndpoint: "https://api.vercel.com/v2/oauth/access_token"
1798
1920
  };
1799
1921
  }
1800
1922
  /** Creates the Vercel token and integration authorization manifest. */
@@ -1815,7 +1937,7 @@ const exchangeCode = Effect.fn("VercelAuth.exchangeCode")((options) => Effect.ge
1815
1937
  redirect_uri: options.redirectUri
1816
1938
  });
1817
1939
  const response = yield* Effect.tryPromise({
1818
- try: () => fetch(`${baseUrl}/oauth/access_token`, {
1940
+ try: () => fetch(`${baseUrl}/v2/oauth/access_token`, {
1819
1941
  body,
1820
1942
  headers: {
1821
1943
  Accept: "application/json",
@@ -1840,7 +1962,7 @@ const exchangeCode = Effect.fn("VercelAuth.exchangeCode")((options) => Effect.ge
1840
1962
  _tag: "team",
1841
1963
  teamId: token.team_id
1842
1964
  },
1843
- installationId: token.installation_id ?? null,
1965
+ installationId: token.installation_id ?? options.configurationId ?? null,
1844
1966
  userId: token.user_id
1845
1967
  };
1846
1968
  }));
@@ -1857,7 +1979,7 @@ function tokenConnectionMethod(options) {
1857
1979
  return {
1858
1980
  capabilityEvidence: validated.capabilities.map((capability) => ({
1859
1981
  capability,
1860
- evidence: Evidence$1.Introspected({ observedAt })
1982
+ evidence: Evidence.Introspected({ observedAt })
1861
1983
  })),
1862
1984
  credential: {
1863
1985
  accessToken: token,
@@ -1889,7 +2011,7 @@ const integrationAuthentication = Effect.fn("VercelAuth.integrationAuthenticatio
1889
2011
  return {
1890
2012
  capabilityEvidence: options.capabilities.map((capability) => ({
1891
2013
  capability,
1892
- evidence: Evidence$1.Introspected({ observedAt })
2014
+ evidence: Evidence.Introspected({ observedAt })
1893
2015
  })),
1894
2016
  credential: {
1895
2017
  accessToken: exchanged.accessToken,
@@ -1909,6 +2031,27 @@ const integrationAuthentication = Effect.fn("VercelAuth.integrationAuthenticatio
1909
2031
  scopes: []
1910
2032
  };
1911
2033
  });
2034
+ /** Restore a credential-scoped Vercel session from persisted authorization state. */
2035
+ function restore(options) {
2036
+ return Effect.gen(function* () {
2037
+ if (options.authorization.providerId !== "vercel") return yield* Effect.fail(failure("restore", "Provider authorization belongs to another provider", "authorization"));
2038
+ if (options.authorization.revocation._tag !== "Active") return yield* Effect.fail(failure("restore", "Vercel authorization is pending revocation", "authorization"));
2039
+ const missingCapability = options.authorization.requiredCapabilities.find((capability) => !options.authorization.capabilityEvidence.some((item) => item.capability === capability));
2040
+ if (missingCapability !== void 0) return yield* Effect.fail(failure("restore", `Vercel authorization lacks evidence for ${missingCapability}`, "authorization"));
2041
+ if (options.authorization.expiresAt !== null && (Number.isNaN(options.authorization.expiresAt.valueOf()) || options.authorization.expiresAt <= /* @__PURE__ */ new Date())) return yield* Effect.fail(failure("restore", "Vercel authorization has expired", "authentication"));
2042
+ const context = yield* contextCodec.decode(options.authorization.providerContext).pipe(Effect.mapError((cause) => failure("restore", cause.message, "response")));
2043
+ return make$4({
2044
+ ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
2045
+ capabilities: options.authorization.capabilityEvidence.map(({ capability }) => capability),
2046
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
2047
+ context: context._tag === "team" ? {
2048
+ _tag: "team",
2049
+ teamId: context.teamId
2050
+ } : { _tag: "personal" },
2051
+ token: options.credential.accessToken
2052
+ });
2053
+ });
2054
+ }
1912
2055
  /** Creates Vercel Integration's implementation of the common interactive connection capability. */
1913
2056
  function integrationFlow(options) {
1914
2057
  const method = integrationMethod(options);
@@ -1922,21 +2065,39 @@ function integrationFlow(options) {
1922
2065
  retry: "after-user-action"
1923
2066
  })));
1924
2067
  const code = callbackUrl.searchParams.get("code");
1925
- if (callbackUrl.searchParams.get("state") !== continuation.state || code === null) return yield* new Error$1({
2068
+ const configurationId = callbackUrl.searchParams.get("configurationId");
2069
+ if (callbackUrl.searchParams.get("state") !== continuation.state || code === null || configurationId === null) return yield* new Error$1({
1926
2070
  category: "authorization",
1927
2071
  message: "Vercel Integration callback does not match its continuation",
1928
2072
  operation: "VercelAuth.integrationFlow.complete",
1929
2073
  retry: "after-user-action"
1930
2074
  });
1931
- return yield* integrationAuthentication({
2075
+ const authentication = yield* integrationAuthentication({
1932
2076
  ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
1933
2077
  capabilities: options.capabilities,
1934
2078
  clientId: options.clientId,
1935
2079
  clientSecret: options.clientSecret,
1936
2080
  code: make$6(code),
2081
+ configurationId,
1937
2082
  ...options.fetch === void 0 ? {} : { fetch: options.fetch },
1938
2083
  redirectUri: options.redirectUri
1939
2084
  });
2085
+ const callbackTeamId = callbackUrl.searchParams.get("teamId");
2086
+ if (callbackTeamId !== null) {
2087
+ const context = yield* contextCodec.decode(authentication.providerContext).pipe(Effect.mapError((cause) => new Error$1({
2088
+ category: "provider",
2089
+ message: cause.message,
2090
+ operation: "VercelAuth.integrationFlow.complete",
2091
+ retry: "unknown"
2092
+ })));
2093
+ if (context._tag !== "team" || context.teamId !== callbackTeamId) return yield* new Error$1({
2094
+ category: "authorization",
2095
+ message: "Vercel Integration callback team does not match the exchanged installation",
2096
+ operation: "VercelAuth.integrationFlow.complete",
2097
+ retry: "after-user-action"
2098
+ });
2099
+ }
2100
+ return authentication;
1940
2101
  }),
1941
2102
  method: "integration",
1942
2103
  providerId: "vercel",
@@ -2051,7 +2212,7 @@ function make$3(options) {
2051
2212
  }).pipe(Effect.flatMap((message) => decodeResolution(query, message)))) };
2052
2213
  }
2053
2214
  const layer$2 = (options) => Layer.succeed(Service$2, make$3(options));
2054
- const toAsync$2 = (options) => toAsync$3(make$3(options));
2215
+ const toAsync$2 = (options) => toAsync$5(make$3(options));
2055
2216
  function decodeResolution(query, message) {
2056
2217
  return Effect.try({
2057
2218
  try: () => DnsPacket.decode(message),
@@ -2109,7 +2270,7 @@ function decodeAnswer(answer) {
2109
2270
  })
2110
2271
  });
2111
2272
  if (decoded === void 0) return null;
2112
- const name = yield* decode$8(answer.name);
2273
+ const name = yield* decode$7(answer.name);
2113
2274
  const data = yield* Effect.try({
2114
2275
  try: () => parse(decoded.type, decoded.data),
2115
2276
  catch: (cause) => new Error$4({
@@ -2249,7 +2410,7 @@ function make$2(options = {}) {
2249
2410
  });
2250
2411
  }
2251
2412
  const layer$1 = (options = {}) => Layer.succeed(Service$2, make$2(options));
2252
- const toAsync$1 = (options = {}) => toAsync$3(make$2(options));
2413
+ const toAsync$1 = (options = {}) => toAsync$5(make$2(options));
2253
2414
  //#endregion
2254
2415
  //#region src/verification/google-doh.ts
2255
2416
  var google_doh_exports = /* @__PURE__ */ __exportAll({
@@ -2265,7 +2426,7 @@ function make$1(options = {}) {
2265
2426
  });
2266
2427
  }
2267
2428
  const layer = (options = {}) => Layer.succeed(Service$2, make$1(options));
2268
- const toAsync = (options = {}) => toAsync$3(make$1(options));
2429
+ const toAsync = (options = {}) => toAsync$5(make$1(options));
2269
2430
  //#endregion
2270
2431
  //#region src/verification/resolver-pool.ts
2271
2432
  var resolver_pool_exports = /* @__PURE__ */ __exportAll({
@@ -2415,6 +2576,6 @@ function recordValue(requirement) {
2415
2576
  }
2416
2577
  }
2417
2578
  //#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 };
2579
+ export { manifest_exports as $, discovery as A, tokenValidator as B, integrationFlow as C, tokenConnectionMethod as D, restore as E, oauthMethod as F, connection_authorization_exports as G, discovery$1 as H, restore$1 as I, secret_exports as J, session_exports as K, subjectResolver as L, auth_exports$1 as M, manifest$1 as N, tokenMethod as O, oauthFlow as P, decode$6 as Q, tokenConnectionMethod$1 as R, exchangeCode as S, manifest as T, make$5 as U, client_exports$1 as V, authorize as W, Method as X, provider_context_exports as Y, Schema$2 as Z, toAsync$1 as _, Result as a, start$1 as at, 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, complete$1 as it, make$4 as j, client_exports as k, Policy as l, google_doh_exports as m, ProviderObservation as n, Method$1 as nt, observe as o, version as ot, resolver_pool_exports as p, selection_exports as q, PublicDns as r, StartResult as rt, verify_exports as s, Provider as t, Error$1 as tt, Service as u, doh_exports as v, integrationMethod as w, auth_exports as x, toAsync$2 as y, tokenMethod$1 as z };
2419
2580
 
2420
- //# sourceMappingURL=verify-BoWy70ew.mjs.map
2581
+ //# sourceMappingURL=verify-B2fPyjde.mjs.map