domainkit 0.9.1 → 0.10.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 +1 @@
1
- {"version":3,"file":"Server.mjs","names":["Storage.AuthMethod","DnsRecord.Model","Plan.OperationId","Plan.Model","Storage.AttemptStatus","Approval.Model","Receipt.Model","Storage.Rejection","Storage.Overall","Storage.RequirementStatus","Verify.Evidence","Verify.HostEvidence","Plan.PlanId","Approval.ApprovalId","Receipt.ReceiptId","Errors.fail","Reason.InvalidInput","Errors.DomainKitError","Connect.Service","Provision.Service","Cleanup.Service","Verify.Service","Storage.Service","Principal.Service","Reason.NotFound"],"sources":["../src/Server.ts"],"sourcesContent":["/**\n * domainkit/server — the whole lifecycle as one HttpApi group the host mounts.\n *\n * const Api = HttpApi.make(\"app\").add(Server.group)\n * const ApiLive = HttpApiBuilder.layer(Api).pipe(\n * Layer.provide([Server.layer(Api), DomainKitLive, IdentityLive]),\n * )\n *\n * The host provides `Identity` (request -> `Principal.Interface`). Everything else comes from\n * `DomainKit.layer`. Non-HttpApi hosts use `toWebHandler`. The group carries no prefix of its\n * own, so `group.prefix(\"/internal/dns\")` or a host router mount is all a different base path\n * takes; callback URLs follow the mount.\n */\nimport { Context, Effect, Layer, Option, Schema } from \"effect\";\nimport * as HttpRouter from \"effect/unstable/http/HttpRouter\";\nimport * as HttpServer from \"effect/unstable/http/HttpServer\";\nimport * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport * as HttpApi from \"effect/unstable/httpapi/HttpApi\";\nimport * as HttpApiBuilder from \"effect/unstable/httpapi/HttpApiBuilder\";\nimport * as HttpApiEndpoint from \"effect/unstable/httpapi/HttpApiEndpoint\";\nimport * as HttpApiGroup from \"effect/unstable/httpapi/HttpApiGroup\";\nimport * as HttpApiSchema from \"effect/unstable/httpapi/HttpApiSchema\";\n\nimport * as Approval from \"./Approval.ts\";\nimport * as Cleanup from \"./Cleanup.ts\";\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Provision from \"./Provision.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Receipt from \"./Receipt.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Verify from \"./Verify.ts\";\n\n/** The only service a host must implement for the server. */\nexport interface IdentityService {\n readonly principal: (\n request: HttpServerRequest.HttpServerRequest,\n ) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;\n /**\n * Which routes this principal may reach, checked after `principal` on every request. Fail with\n * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches\n * every route, which is the right default for a host whose own middleware already gates the\n * mount.\n */\n readonly authorize?: (\n principal: Principal.Interface,\n endpoint: EndpointName,\n ) => Effect.Effect<void, Errors.DomainKitError>;\n}\n\nexport class Identity extends Context.Service<Identity, IdentityService>()(\n \"@domainkit/server/Identity\",\n) {}\n\n// ---------------------------------------------------------------------------------------------\n// Wire schemas (the transport contract `domainkit/client` and `@domainkit/react` consume)\n// ---------------------------------------------------------------------------------------------\n\nexport const ConnectionStatus = Schema.Literals([\"disconnected\", \"connected\", \"reconnect\"]);\nexport type ConnectionStatus = typeof ConnectionStatus.Type;\n\n/** One value a token method needs, so a form renders without knowing the provider. */\nexport const Field = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n secret: Schema.Boolean,\n});\nexport type Field = typeof Field.Type;\n\n/** How a provider can be connected; `fields` is `null` for the interactive methods. */\nexport const MethodDescriptor = Schema.Struct({\n kind: Storage.AuthMethod,\n label: Schema.String,\n docsUrl: Schema.NullOr(Schema.String),\n fields: Schema.NullOr(Schema.Array(Field)),\n});\nexport type MethodDescriptor = typeof MethodDescriptor.Type;\n\n/** Everything the UI needs about a domain, flattened; provider context never crosses the wire. */\nexport const Snapshot = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n connectionId: Schema.NullOr(Schema.String),\n provider: Schema.NullOr(Schema.String),\n method: Schema.NullOr(Storage.AuthMethod),\n status: ConnectionStatus,\n lastReceiptId: Schema.NullOr(Schema.String),\n reusable: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n method: Storage.AuthMethod,\n }),\n ),\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Snapshot = typeof Snapshot.Type;\n\n/**\n * How the client asks to connect. `values` is keyed by the provider's declared token fields, as\n * `Snapshot.providers[].methods[].fields` names them. Secrets are plaintext in transit and never\n * come back out.\n */\nexport class Token extends Schema.TaggedClass<Token>(\"@domainkit/server/Method/Token\")(\"Token\", {\n values: Schema.Record(Schema.String, Schema.String),\n}) {}\nexport class OAuth extends Schema.TaggedClass<OAuth>(\"@domainkit/server/Method/OAuth\")(\"OAuth\", {\n returnTo: Schema.optionalKey(Schema.String),\n}) {}\nexport class Integration extends Schema.TaggedClass<Integration>(\n \"@domainkit/server/Method/Integration\",\n)(\"Integration\", { returnTo: Schema.optionalKey(Schema.String) }) {}\nexport const Method = Schema.Union([Token, OAuth, Integration]);\nexport type Method = typeof Method.Type;\n\nexport const StartPayload = Schema.Struct({\n domain: Schema.String,\n provider: Schema.String,\n method: Method,\n});\nexport type StartPayload = typeof StartPayload.Type;\n\n/** A zone the connection can serve, named for a customer to pick from. */\nexport class Candidate extends Schema.Class<Candidate>(\"@domainkit/server/Candidate\")({\n zone: Schema.String,\n label: Schema.String,\n}) {}\n\nexport class Connected extends Schema.TaggedClass<Connected>(\"@domainkit/server/Started/Connected\")(\n \"Connected\",\n { snapshot: Snapshot },\n) {}\nexport class Redirect extends Schema.TaggedClass<Redirect>(\"@domainkit/server/Started/Redirect\")(\n \"Redirect\",\n { authorizationUrl: Schema.String },\n) {}\nexport class SelectionRequired extends Schema.TaggedClass<SelectionRequired>(\n \"@domainkit/server/Started/SelectionRequired\",\n)(\"SelectionRequired\", { connectionId: Schema.String, candidates: Schema.Array(Candidate) }) {}\nexport const Started = Schema.Union([Connected, Redirect, SelectionRequired]);\nexport type Started = typeof Started.Type;\n\n/**\n * Which of the principal's connections already reaches a domain. The class names carry a\n * `Discovery` prefix because `Started` owns the unprefixed `SelectionRequired`; the wire tags are\n * the ones `Connect.Discovery` uses.\n */\nexport class DiscoveryResolved extends Schema.TaggedClass<DiscoveryResolved>(\n \"@domainkit/server/Discovery/Resolved\",\n)(\"Resolved\", { connectionId: Schema.String, zone: Schema.String, label: Schema.String }) {}\nexport class DiscoverySelectionRequired extends Schema.TaggedClass<DiscoverySelectionRequired>(\n \"@domainkit/server/Discovery/SelectionRequired\",\n)(\"SelectionRequired\", {\n candidates: Schema.Array(\n Schema.Struct({ connectionId: Schema.String, zone: Schema.String, label: Schema.String }),\n ),\n}) {}\nexport class DiscoveryNotFound extends Schema.TaggedClass<DiscoveryNotFound>(\n \"@domainkit/server/Discovery/NotFound\",\n)(\"NotFound\", { nameservers: Schema.Array(Schema.String) }) {}\nexport const Discovery = Schema.Union([\n DiscoveryResolved,\n DiscoverySelectionRequired,\n DiscoveryNotFound,\n]);\nexport type Discovery = typeof Discovery.Type;\n\nexport const AttachPayload = Schema.Struct({\n domain: Schema.String,\n /** Which candidate zone to attach when `SelectionRequired` came back. */\n zone: Schema.optionalKey(Schema.String),\n});\nexport type AttachPayload = typeof AttachPayload.Type;\n\nexport const PlanPayload = Schema.Struct({\n requirements: Schema.Array(DnsRecord.Model),\n});\nexport type PlanPayload = typeof PlanPayload.Type;\n\nexport const ObservePayload = Schema.Struct({\n /** Records to observe instead of the attachment's receipt; required for an unattached domain. */\n requirements: Schema.optionalKey(Schema.Array(DnsRecord.Model)),\n});\nexport type ObservePayload = typeof ObservePayload.Type;\n\nexport const ApprovePayload = Schema.Struct({\n /** A subset of the plan's writes; omit to approve every write. */\n operationIds: Schema.optionalKey(Schema.Array(Plan.OperationId)),\n});\nexport type ApprovePayload = typeof ApprovePayload.Type;\n\nexport const RejectPayload = Schema.Struct({\n /** Why the customer declined, for the audit trail. */\n reason: Schema.optionalKey(Schema.String),\n});\nexport type RejectPayload = typeof RejectPayload.Type;\n\n/** One durable plan -> approval -> receipt lifecycle, as `GET /plans/:planId` returns it. */\nexport const Attempt = Schema.Struct({\n plan: Plan.Model,\n status: Storage.AttemptStatus,\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n});\nexport type Attempt = typeof Attempt.Type;\n\n/** `Verify.Readiness` on the wire; timestamps encode as ISO strings. */\nexport const Readiness = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n overall: Storage.Overall,\n requirements: Schema.Array(\n Schema.Struct({\n operationId: Schema.NullOr(Plan.OperationId),\n record: DnsRecord.Model,\n status: Storage.RequirementStatus,\n evidence: Schema.Array(Verify.Evidence),\n }),\n ),\n host: Schema.Array(Verify.HostEvidence),\n checkedAt: Schema.DateTimeUtcFromString,\n nextCheckAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type Readiness = typeof Readiness.Type;\n\nconst Redirected = HttpApiSchema.WithHeaders(HttpApiSchema.Empty(302), {\n location: Schema.String,\n});\n\n// ---------------------------------------------------------------------------------------------\n// Errors: one wire body, the status from `DomainKit.Error.httpStatus`\n// ---------------------------------------------------------------------------------------------\n\n/** `DomainKit.Error` narrowed to the reasons that answer with `status`. */\nconst errorAt = (status: number) =>\n Errors.DomainKitError.check(\n Schema.makeFilter((error: Errors.DomainKitError) => error.httpStatus === status),\n ).pipe(HttpApiSchema.status(status));\n\nconst errors = [400, 401, 403, 404, 409, 500, 501, 502, 503].map(errorAt);\n\n// ---------------------------------------------------------------------------------------------\n// The group\n// ---------------------------------------------------------------------------------------------\n\nexport const group = HttpApiGroup.make(\"domainkit\")\n .add(\n HttpApiEndpoint.get(\"inspect\", \"/domains/:domain\", {\n params: { domain: Schema.String },\n success: Snapshot,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"discover\", \"/domains/:domain/discovery\", {\n params: { domain: Schema.String },\n success: Discovery,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"start\", \"/connections\", {\n payload: StartPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"callback\", \"/callback/:provider\", {\n params: { provider: Schema.String },\n // `Connect.complete` reads every callback parameter off the request URL, because a provider\n // may return `error`, `teamId`, or `configurationId` too. These two are declared so the\n // OpenAPI document names what a provider is expected to send back.\n query: { state: Schema.String, code: Schema.optionalKey(Schema.String) },\n success: Redirected,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"attach\", \"/connections/:connectionId/attachments\", {\n params: { connectionId: Schema.String },\n payload: AttachPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"detach\", \"/attachments/:attachmentId\", {\n params: { attachmentId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"disconnect\", \"/connections/:connectionId\", {\n params: { connectionId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"createPlan\", \"/domains/:domain/plans\", {\n params: { domain: Schema.String },\n payload: PlanPayload,\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approve\", \"/plans/:planId/approvals\", {\n params: { planId: Plan.PlanId },\n payload: ApprovePayload,\n success: Approval.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reject\", \"/plans/:planId/rejections\", {\n params: { planId: Plan.PlanId },\n payload: RejectPayload,\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"apply\", \"/approvals/:approvalId/apply\", {\n params: { approvalId: Approval.ApprovalId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"plan\", \"/plans/:planId\", {\n params: { planId: Plan.PlanId },\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"receipt\", \"/receipts/:receiptId\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"observe\", \"/domains/:domain/observations\", {\n params: { domain: Schema.String },\n payload: ObservePayload,\n success: Readiness,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"cleanupPlan\", \"/receipts/:receiptId/cleanup-plans\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Plan.Model,\n error: errors,\n }),\n );\n// Cleanup approve, reject, and apply reuse those routes: the attempt knows its kind.\n\nexport type Group = typeof group;\n\n/** Every route in the group, by name; what `Identity.authorize` is asked about. */\nexport type EndpointName = HttpApiGroup.Endpoints<Group>[\"identifier\"];\n\nexport interface Options {\n /**\n * Where the callback route is reachable from the provider, without the `/callback/:provider`\n * suffix. Defaults to the incoming request's origin and mount path.\n */\n readonly callbackBaseUrl?: string;\n /** Where interactive flows return when the callback carries no `returnTo`. */\n readonly defaultReturnTo?: string;\n}\n\n/** Everything the handlers need: the lifecycle services, Storage, and the host's `Identity`. */\nexport type Services =\n | Provision.Service\n | Cleanup.Service\n | Connect.Service\n | Verify.Service\n | Providers.Service\n | Storage.Service\n | Identity;\n\n// ---------------------------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------------------------\n\nconst START_ROUTE = \"/connections\";\nconst callbackRoute = (provider: string) => `/callback/${encodeURIComponent(provider)}`;\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** The request as an absolute URL: the web original when there is one, else scheme plus `Host`. */\nconst absoluteUrl = (\n request: HttpServerRequest.HttpServerRequest,\n): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n try {\n return Effect.succeed(new URL(request.originalUrl));\n } catch {\n const url = HttpServerRequest.toURL(request);\n return Option.isNone(url)\n ? invalid(\"The request has no usable origin; set callbackBaseUrl\", \"callbackBaseUrl\")\n : Effect.succeed(url.value);\n }\n });\n\n/**\n * The absolute URL the provider redirects back to. `callbackBaseUrl` wins when the host set it,\n * because a proxy that rewrites `Host` leaves the request pointing at an origin the browser never\n * sees; otherwise it follows the prefix the group is mounted at, derived by stripping the handling\n * route's own path off the request.\n */\nconst callbackUrlFor = (input: {\n readonly request: HttpServerRequest.HttpServerRequest;\n readonly provider: string;\n readonly options: Options;\n /** The path of the route serving this request, stripped to find the mount. */\n readonly route: string;\n}): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n const suffix = `/callback/${encodeURIComponent(input.provider)}`;\n const configured = input.options.callbackBaseUrl;\n if (configured !== undefined) {\n return Effect.try({\n try: () => new URL(`${configured.replace(/\\/+$/, \"\")}${suffix}`),\n catch: () => callbackConfigurationError(`${configured} is not a URL`),\n });\n }\n return Effect.flatMap(absoluteUrl(input.request), (url) => {\n const path = url.pathname;\n if (!path.endsWith(input.route)) {\n return invalid(\n `The ${input.route} route is not mounted where it says; set callbackBaseUrl`,\n \"callbackBaseUrl\",\n );\n }\n return Effect.succeed(\n new URL(`${url.origin}${path.slice(0, path.length - input.route.length)}${suffix}`),\n );\n });\n });\n\nconst callbackConfigurationError = (message: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field: \"callbackBaseUrl\" }),\n });\n\n/**\n * Where the callback may send the customer: a path under the callback's own base, or an absolute\n * URL on its origin. A destination that leaves the application is an open redirect, whoever\n * supplied it.\n */\nconst sameOrigin = (destination: string, callback: URL): string | null => {\n try {\n // Resolving against the callback is what makes this safe: the URL parser normalizes the forms\n // a browser would follow off-origin (`//host`, `/\\host`, `\\/host`, encoded hosts) into a real\n // origin, so comparing origins catches every one of them.\n const url = new URL(destination, callback);\n return url.origin === callback.origin ? url.toString() : null;\n } catch {\n return null;\n }\n};\n\nconst snapshotOf = (snapshot: Connect.Snapshot): Snapshot => ({\n domain: snapshot.domain,\n attachmentId: snapshot.attachment?.id ?? null,\n connectionId: snapshot.connection?.id ?? null,\n provider: snapshot.authorization?.provider ?? null,\n method: snapshot.authorization?.method ?? null,\n status:\n snapshot.connection === null\n ? \"disconnected\"\n : snapshot.authorization?.revocation === \"active\"\n ? \"connected\"\n : \"reconnect\",\n lastReceiptId: snapshot.lastReceiptId,\n reusable: snapshot.reusable.map(({ connection, provider, method }) => ({\n connectionId: connection.id,\n provider,\n method,\n })),\n providers: snapshot.providers.map(({ id, name, methods }) => ({ id, name, methods })),\n});\n\n/** The wire method as `Connect` takes it; interactive cases carry the caller's return destination. */\nconst methodOf = (method: Method): Connect.Method => {\n switch (method._tag) {\n case \"Token\":\n return Connect.Method.token(method.values);\n case \"OAuth\":\n return Connect.Method.oauth(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n case \"Integration\":\n return Connect.Method.integration(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n }\n};\n\nconst candidatesOf = (\n candidates: ReadonlyArray<{ readonly zone: string; readonly label: string }>,\n): ReadonlyArray<Candidate> => candidates.map(({ zone, label }) => new Candidate({ zone, label }));\n\n/**\n * Handlers for `group` inside the host's API. Every handler derives `Principal` from `Identity`\n * for the request it is serving and provides it to the lifecycle services.\n */\nexport const layer = <ApiId extends string, Groups extends HttpApiGroup.Constraint>(\n api: HttpApi.HttpApi<ApiId, Groups>,\n options: Options = {},\n): Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services> =>\n // The host's API carries its own groups beside this one; only `\"domainkit\"` is implemented here,\n // and `HttpApiBuilder.group` needs the group's own endpoint types to check the handlers.\n HttpApiBuilder.group(api as unknown as HttpApi.HttpApi<ApiId, Group>, \"domainkit\", (handlers) =>\n Effect.gen(function* () {\n const identity = yield* Identity;\n const connect = yield* Connect.Service;\n const provision = yield* Provision.Service;\n const cleanup = yield* Cleanup.Service;\n const verify = yield* Verify.Service;\n const storage = yield* Storage.Service;\n\n /**\n * Run a lifecycle effect as the principal the host derives from this request, once the host\n * has allowed that principal onto this route.\n */\n const as = <A>(\n endpoint: EndpointName,\n request: HttpServerRequest.HttpServerRequest,\n effect: Effect.Effect<A, Errors.DomainKitError, Principal.Service>,\n ): Effect.Effect<A, Errors.DomainKitError> =>\n Effect.flatMap(identity.principal(request), (principal) =>\n Effect.flatMap(identity.authorize?.(principal, endpoint) ?? Effect.void, () =>\n Effect.provideService(effect, Principal.Service, principal),\n ),\n );\n\n const snapshot = (domain: string) => Effect.map(connect.inspect(domain), snapshotOf);\n\n const discovered = (discovery: Connect.Discovery): Discovery => {\n switch (discovery._tag) {\n case \"Resolved\":\n return new DiscoveryResolved({\n connectionId: discovery.connectionId,\n zone: discovery.target.zone,\n label: discovery.target.label,\n });\n case \"SelectionRequired\":\n return new DiscoverySelectionRequired({\n candidates: discovery.candidates.map(({ connectionId, target }) => ({\n connectionId,\n zone: target.zone,\n label: target.label,\n })),\n });\n case \"NotFound\":\n return new DiscoveryNotFound({ nameservers: discovery.nameservers });\n }\n };\n\n const connected = (domain: string) =>\n Effect.map(snapshot(domain), (value) => new Connected({ snapshot: value }));\n\n const startedOf = (\n started: Connect.Started,\n domain: string,\n ): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> => {\n switch (started._tag) {\n case \"Connected\":\n return connected(domain);\n case \"Redirect\":\n return Effect.succeed(new Redirect({ authorizationUrl: started.authorizationUrl }));\n case \"SelectionRequired\":\n return Effect.succeed(\n new SelectionRequired({\n connectionId: started.connection.id,\n candidates: candidatesOf(started.candidates),\n }),\n );\n }\n };\n\n /**\n * Attach with a zone the client picked out of a `SelectionRequired`. The provider target\n * behind a zone never crosses the wire, so resolve the candidates again and match by zone.\n */\n const attachAt = (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly zone: string | undefined;\n }): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const first = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n });\n if (!(\"_tag\" in first)) return yield* connected(input.domain);\n if (input.zone === undefined) {\n return new SelectionRequired({\n connectionId: input.connectionId,\n candidates: candidatesOf(first.candidates),\n });\n }\n const target = first.candidates.find(({ zone }) => zone === input.zone);\n if (target === undefined) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }));\n }\n yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n target,\n });\n return yield* connected(input.domain);\n });\n\n /** Provisioning and cleanup share the approve and apply routes; the attempt knows its kind. */\n const kindOf = (attempt: Storage.Attempt) =>\n attempt.kind === \"cleanup\" ? cleanup : provision;\n\n return handlers\n .handle(\"inspect\", ({ params, request }) => as(\"inspect\", request, snapshot(params.domain)))\n .handle(\"discover\", ({ params, request }) =>\n as(\"discover\", request, Effect.map(connect.discover(params.domain), discovered)),\n )\n .handle(\"start\", ({ payload, request }) =>\n as(\n \"start\",\n request,\n Effect.gen(function* () {\n // Token methods connect in one call; the interactive ones need somewhere to land.\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: payload.provider,\n options,\n route: START_ROUTE,\n })).toString();\n const started = yield* connect.start({\n provider: payload.provider,\n domain: payload.domain,\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, payload.domain);\n }),\n ),\n )\n .handle(\"callback\", ({ params, query, request }) =>\n as(\n \"callback\",\n request,\n Effect.gen(function* () {\n const url = yield* absoluteUrl(request);\n // Resolve the destination against the callback's public base, not the request: a\n // proxy that rewrites `Host` leaves the request on an origin the browser never sees.\n const base = yield* callbackUrlFor({\n request,\n provider: params.provider,\n options,\n route: callbackRoute(params.provider),\n });\n // Where the customer lands comes from the continuation this owner started, never\n // from the provider's query string, and it is resolved before the callback is spent:\n // a server with nowhere to send them must not connect the provider and then fail.\n const continuation = yield* storage.continuations.get(query.state);\n const requested = continuation.returnTo ?? options.defaultReturnTo;\n if (requested === undefined) {\n return yield* invalid(\n \"The flow carried no returnTo and the server has no defaultReturnTo\",\n \"returnTo\",\n );\n }\n const destination = sameOrigin(requested, base);\n if (destination === null) {\n return yield* invalid(`${requested} leaves this application`, \"returnTo\");\n }\n yield* connect.complete({\n continuationId: query.state,\n callbackUrl: url.toString(),\n });\n return HttpApiSchema.withHeaders({\n body: undefined as void,\n headers: { location: destination },\n });\n }),\n ),\n )\n .handle(\"attach\", ({ params, payload, request }) =>\n as(\n \"attach\",\n request,\n attachAt({\n connectionId: params.connectionId,\n domain: payload.domain,\n zone: payload.zone,\n }),\n ),\n )\n .handle(\"detach\", ({ params, request }) =>\n as(\"detach\", request, connect.detach(params.attachmentId)),\n )\n .handle(\"disconnect\", ({ params, request }) =>\n as(\"disconnect\", request, connect.disconnect(params.connectionId)),\n )\n .handle(\"createPlan\", ({ params, payload, request }) =>\n as(\n \"createPlan\",\n request,\n provision.plan({ domain: params.domain, requirements: payload.requirements }),\n ),\n )\n .handle(\"approve\", ({ params, payload, request }) =>\n as(\n \"approve\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).approve(params.planId, {\n ...(payload.operationIds === undefined\n ? {}\n : { operationIds: payload.operationIds, allowPartial: true }),\n });\n }),\n ),\n )\n .handle(\"reject\", ({ params, payload, request }) =>\n as(\n \"reject\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).reject(params.planId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n });\n }),\n ),\n )\n .handle(\"apply\", ({ params, request }) =>\n as(\n \"apply\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.byApproval(params.approvalId);\n return yield* kindOf(attempt).apply(params.approvalId);\n }),\n ),\n )\n .handle(\"plan\", ({ params, request }) =>\n as(\n \"plan\",\n request,\n Effect.map(storage.attempts.get(params.planId), (attempt) => ({\n plan: attempt.plan,\n status: attempt.status,\n approval: attempt.approval,\n receipt: attempt.receipt,\n rejection: attempt.rejection,\n })),\n ),\n )\n .handle(\"receipt\", ({ params, request }) =>\n as(\n \"receipt\",\n request,\n Effect.flatMap(storage.attempts.byReceipt(params.receiptId), (attempt) =>\n attempt.receipt === null\n ? Errors.fail(new Reason.NotFound({ entity: \"receipt\", id: params.receiptId }))\n : Effect.succeed(attempt.receipt),\n ),\n ),\n )\n .handle(\"observe\", ({ params, payload, request }) =>\n as(\n \"observe\",\n request,\n verify.observe({\n domain: params.domain,\n ...(payload.requirements === undefined ? {} : { requirements: payload.requirements }),\n }),\n ),\n )\n .handle(\"cleanupPlan\", ({ params, request }) =>\n as(\"cleanupPlan\", request, cleanup.plan({ receiptId: params.receiptId })),\n );\n }),\n ) as Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services>;\n\n/** A standalone API for hosts not on HttpApi. Mount at any prefix. */\nexport const api: HttpApi.HttpApi<\"domainkit\", Group> = HttpApi.make(\"domainkit\").add(group);\n\nexport interface WebHandlerOptions extends Options {\n /** Mount every route under this path, e.g. `/api/domainkit`. */\n readonly prefix?: `/${string}`;\n}\n\n/** The Promise edge: a `fetch`-shaped handler over the group for hosts that are not Effect-native. */\nexport const toWebHandler = (\n services: Layer.Layer<Services, Errors.DomainKitError>,\n options: WebHandlerOptions = {},\n): {\n readonly handler: (request: Request) => Promise<Response>;\n readonly dispose: () => Promise<void>;\n} => {\n // `group.prefix` widens the endpoint paths in the type, which `Group` states unprefixed; the\n // handlers are keyed by endpoint identifier, so the routes still line up.\n const prefixed =\n options.prefix === undefined\n ? api\n : (HttpApi.make(\"domainkit\").add(group.prefix(options.prefix)) as unknown as HttpApi.HttpApi<\n \"domainkit\",\n Group\n >);\n const app = HttpApiBuilder.layer(prefixed).pipe(\n Layer.provide(layer(prefixed, options)),\n Layer.provide(services),\n Layer.provide(HttpServer.layerServices),\n );\n return HttpRouter.toWebHandler(app, { disableLogger: true });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Server.mjs","names":["Storage.AuthMethod","DnsRecord.Model","Plan.OperationId","Plan.Model","Storage.AttemptStatus","Approval.Model","Receipt.Model","Storage.Rejection","Storage.Overall","Storage.RequirementStatus","Verify.Evidence","Verify.HostEvidence","Plan.PlanId","Approval.ApprovalId","Receipt.ReceiptId","Errors.fail","Reason.InvalidInput","Errors.DomainKitError","Connect.Service","Provision.Service","Cleanup.Service","Verify.Service","Storage.Service","Principal.Service","Reason.NotFound"],"sources":["../src/Server.ts"],"sourcesContent":["/**\n * domainkit/server — the whole lifecycle as one HttpApi group the host mounts.\n *\n * const Api = HttpApi.make(\"app\").add(Server.group)\n * const ApiLive = HttpApiBuilder.layer(Api).pipe(\n * Layer.provide([Server.layer(Api), DomainKitLive, IdentityLive]),\n * )\n *\n * The host provides `Identity` (request -> `Principal.Interface`). Everything else comes from\n * `DomainKit.layer`. Non-HttpApi hosts use `toWebHandler`. The group carries no prefix of its\n * own, so `group.prefix(\"/internal/dns\")` or a host router mount is all a different base path\n * takes; callback URLs follow the mount.\n */\nimport { Context, Effect, Layer, Option, Schema } from \"effect\";\nimport * as HttpRouter from \"effect/unstable/http/HttpRouter\";\nimport * as HttpServer from \"effect/unstable/http/HttpServer\";\nimport * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport * as HttpApi from \"effect/unstable/httpapi/HttpApi\";\nimport * as HttpApiBuilder from \"effect/unstable/httpapi/HttpApiBuilder\";\nimport * as HttpApiEndpoint from \"effect/unstable/httpapi/HttpApiEndpoint\";\nimport * as HttpApiGroup from \"effect/unstable/httpapi/HttpApiGroup\";\nimport * as HttpApiSchema from \"effect/unstable/httpapi/HttpApiSchema\";\n\nimport * as Approval from \"./Approval.ts\";\nimport * as Cleanup from \"./Cleanup.ts\";\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Provision from \"./Provision.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as Receipt from \"./Receipt.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Verify from \"./Verify.ts\";\n\n/** The only service a host must implement for the server. */\nexport interface IdentityService {\n readonly principal: (\n request: HttpServerRequest.HttpServerRequest,\n ) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;\n /**\n * Which routes this principal may reach, checked after `principal` on every request. Fail with\n * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches\n * every route, which is the right default for a host whose own middleware already gates the\n * mount.\n */\n readonly authorize?: (\n principal: Principal.Interface,\n endpoint: EndpointName,\n ) => Effect.Effect<void, Errors.DomainKitError>;\n}\n\nexport class Identity extends Context.Service<Identity, IdentityService>()(\n \"@domainkit/server/Identity\",\n) {}\n\n// ---------------------------------------------------------------------------------------------\n// Wire schemas (the transport contract `domainkit/client` and `@domainkit/react` consume)\n// ---------------------------------------------------------------------------------------------\n\nexport const ConnectionStatus = Schema.Literals([\"disconnected\", \"connected\", \"reconnect\"]);\nexport type ConnectionStatus = typeof ConnectionStatus.Type;\n\n/** One value a token method needs, so a form renders without knowing the provider. */\nexport const Field = Schema.Struct({\n name: Schema.String,\n required: Schema.Boolean,\n secret: Schema.Boolean,\n});\nexport type Field = typeof Field.Type;\n\n/** How a provider can be connected; `fields` is `null` for the interactive methods. */\nexport const MethodDescriptor = Schema.Struct({\n kind: Storage.AuthMethod,\n label: Schema.String,\n docsUrl: Schema.NullOr(Schema.String),\n fields: Schema.NullOr(Schema.Array(Field)),\n});\nexport type MethodDescriptor = typeof MethodDescriptor.Type;\n\n/** Everything the UI needs about a domain, flattened; provider context never crosses the wire. */\nexport const Snapshot = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n connectionId: Schema.NullOr(Schema.String),\n provider: Schema.NullOr(Schema.String),\n method: Schema.NullOr(Storage.AuthMethod),\n status: ConnectionStatus,\n lastReceiptId: Schema.NullOr(Schema.String),\n reusable: Schema.Array(\n Schema.Struct({\n connectionId: Schema.String,\n provider: Schema.String,\n method: Storage.AuthMethod,\n }),\n ),\n providers: Schema.Array(\n Schema.Struct({\n id: Schema.String,\n name: Schema.String,\n methods: Schema.Array(MethodDescriptor),\n }),\n ),\n});\nexport type Snapshot = typeof Snapshot.Type;\n\n/**\n * How the client asks to connect. `values` is keyed by the provider's declared token fields, as\n * `Snapshot.providers[].methods[].fields` names them. Secrets are plaintext in transit and never\n * come back out.\n */\nexport class Token extends Schema.TaggedClass<Token>(\"@domainkit/server/Method/Token\")(\"Token\", {\n values: Schema.Record(Schema.String, Schema.String),\n}) {}\nexport class OAuth extends Schema.TaggedClass<OAuth>(\"@domainkit/server/Method/OAuth\")(\"OAuth\", {\n returnTo: Schema.optionalKey(Schema.String),\n}) {}\nexport class Integration extends Schema.TaggedClass<Integration>(\n \"@domainkit/server/Method/Integration\",\n)(\"Integration\", { returnTo: Schema.optionalKey(Schema.String) }) {}\nexport const Method = Schema.Union([Token, OAuth, Integration]);\nexport type Method = typeof Method.Type;\n\nexport const StartPayload = Schema.Struct({\n domain: Schema.String,\n provider: Schema.String,\n method: Method,\n});\nexport type StartPayload = typeof StartPayload.Type;\n\n/** A zone the connection can serve, named for a customer to pick from. */\nexport class Candidate extends Schema.Class<Candidate>(\"@domainkit/server/Candidate\")({\n zone: Schema.String,\n label: Schema.String,\n}) {}\n\nexport class Connected extends Schema.TaggedClass<Connected>(\"@domainkit/server/Started/Connected\")(\n \"Connected\",\n { snapshot: Snapshot },\n) {}\nexport class Redirect extends Schema.TaggedClass<Redirect>(\"@domainkit/server/Started/Redirect\")(\n \"Redirect\",\n { authorizationUrl: Schema.String },\n) {}\nexport class SelectionRequired extends Schema.TaggedClass<SelectionRequired>(\n \"@domainkit/server/Started/SelectionRequired\",\n)(\"SelectionRequired\", { connectionId: Schema.String, candidates: Schema.Array(Candidate) }) {}\nexport const Started = Schema.Union([Connected, Redirect, SelectionRequired]);\nexport type Started = typeof Started.Type;\n\n/**\n * Which of the principal's connections already reaches a domain. The class names carry a\n * `Discovery` prefix because `Started` owns the unprefixed `SelectionRequired`; the wire tags are\n * the ones `Connect.Discovery` uses.\n */\nexport class DiscoveryResolved extends Schema.TaggedClass<DiscoveryResolved>(\n \"@domainkit/server/Discovery/Resolved\",\n)(\"Resolved\", { connectionId: Schema.String, zone: Schema.String, label: Schema.String }) {}\nexport class DiscoverySelectionRequired extends Schema.TaggedClass<DiscoverySelectionRequired>(\n \"@domainkit/server/Discovery/SelectionRequired\",\n)(\"SelectionRequired\", {\n candidates: Schema.Array(\n Schema.Struct({ connectionId: Schema.String, zone: Schema.String, label: Schema.String }),\n ),\n}) {}\n/** `host` names the registered provider whose nameservers serve the domain, by definition id. */\nexport class DiscoveryNotFound extends Schema.TaggedClass<DiscoveryNotFound>(\n \"@domainkit/server/Discovery/NotFound\",\n)(\"NotFound\", {\n nameservers: Schema.Array(Schema.String),\n host: Schema.NullOr(Schema.Struct({ provider: Schema.String })),\n}) {}\nexport const Discovery = Schema.Union([\n DiscoveryResolved,\n DiscoverySelectionRequired,\n DiscoveryNotFound,\n]);\nexport type Discovery = typeof Discovery.Type;\n\nexport const AttachPayload = Schema.Struct({\n domain: Schema.String,\n /** Which candidate zone to attach when `SelectionRequired` came back. */\n zone: Schema.optionalKey(Schema.String),\n});\nexport type AttachPayload = typeof AttachPayload.Type;\n\nexport const PlanPayload = Schema.Struct({\n requirements: Schema.Array(DnsRecord.Model),\n});\nexport type PlanPayload = typeof PlanPayload.Type;\n\nexport const ObservePayload = Schema.Struct({\n /** Records to observe instead of the attachment's receipt; required for an unattached domain. */\n requirements: Schema.optionalKey(Schema.Array(DnsRecord.Model)),\n});\nexport type ObservePayload = typeof ObservePayload.Type;\n\nexport const ApprovePayload = Schema.Struct({\n /** A subset of the plan's writes; omit to approve every write. */\n operationIds: Schema.optionalKey(Schema.Array(Plan.OperationId)),\n});\nexport type ApprovePayload = typeof ApprovePayload.Type;\n\nexport const RejectPayload = Schema.Struct({\n /** Why the customer declined, for the audit trail. */\n reason: Schema.optionalKey(Schema.String),\n});\nexport type RejectPayload = typeof RejectPayload.Type;\n\n/** One durable plan -> approval -> receipt lifecycle, as `GET /plans/:planId` returns it. */\nexport const Attempt = Schema.Struct({\n plan: Plan.Model,\n status: Storage.AttemptStatus,\n approval: Schema.NullOr(Approval.Model),\n receipt: Schema.NullOr(Receipt.Model),\n rejection: Schema.NullOr(Storage.Rejection),\n});\nexport type Attempt = typeof Attempt.Type;\n\n/** `Verify.Readiness` on the wire; timestamps encode as ISO strings. */\nexport const Readiness = Schema.Struct({\n domain: Schema.String,\n attachmentId: Schema.NullOr(Schema.String),\n overall: Storage.Overall,\n requirements: Schema.Array(\n Schema.Struct({\n operationId: Schema.NullOr(Plan.OperationId),\n record: DnsRecord.Model,\n status: Storage.RequirementStatus,\n evidence: Schema.Array(Verify.Evidence),\n }),\n ),\n host: Schema.Array(Verify.HostEvidence),\n checkedAt: Schema.DateTimeUtcFromString,\n nextCheckAt: Schema.NullOr(Schema.DateTimeUtcFromString),\n});\nexport type Readiness = typeof Readiness.Type;\n\nconst Redirected = HttpApiSchema.WithHeaders(HttpApiSchema.Empty(302), {\n location: Schema.String,\n});\n\n// ---------------------------------------------------------------------------------------------\n// Errors: one wire body, the status from `DomainKit.Error.httpStatus`\n// ---------------------------------------------------------------------------------------------\n\n/** `DomainKit.Error` narrowed to the reasons that answer with `status`. */\nconst errorAt = (status: number) =>\n Errors.DomainKitError.check(\n Schema.makeFilter((error: Errors.DomainKitError) => error.httpStatus === status),\n ).pipe(HttpApiSchema.status(status));\n\nconst errors = [400, 401, 403, 404, 409, 500, 501, 502, 503].map(errorAt);\n\n// ---------------------------------------------------------------------------------------------\n// The group\n// ---------------------------------------------------------------------------------------------\n\nexport const group = HttpApiGroup.make(\"domainkit\")\n .add(\n HttpApiEndpoint.get(\"inspect\", \"/domains/:domain\", {\n params: { domain: Schema.String },\n success: Snapshot,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"discover\", \"/domains/:domain/discovery\", {\n params: { domain: Schema.String },\n success: Discovery,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"start\", \"/connections\", {\n payload: StartPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"callback\", \"/callback/:provider\", {\n params: { provider: Schema.String },\n // `Connect.complete` reads every callback parameter off the request URL, because a provider\n // may return `error`, `teamId`, or `configurationId` too. These two are declared so the\n // OpenAPI document names what a provider is expected to send back.\n query: { state: Schema.String, code: Schema.optionalKey(Schema.String) },\n success: Redirected,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"attach\", \"/connections/:connectionId/attachments\", {\n params: { connectionId: Schema.String },\n payload: AttachPayload,\n success: Started,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"detach\", \"/attachments/:attachmentId\", {\n params: { attachmentId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.delete(\"disconnect\", \"/connections/:connectionId\", {\n params: { connectionId: Schema.String },\n success: Schema.Void,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"createPlan\", \"/domains/:domain/plans\", {\n params: { domain: Schema.String },\n payload: PlanPayload,\n success: Plan.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"approve\", \"/plans/:planId/approvals\", {\n params: { planId: Plan.PlanId },\n payload: ApprovePayload,\n success: Approval.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"reject\", \"/plans/:planId/rejections\", {\n params: { planId: Plan.PlanId },\n payload: RejectPayload,\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"apply\", \"/approvals/:approvalId/apply\", {\n params: { approvalId: Approval.ApprovalId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"plan\", \"/plans/:planId\", {\n params: { planId: Plan.PlanId },\n success: Attempt,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.get(\"receipt\", \"/receipts/:receiptId\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Receipt.Model,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"observe\", \"/domains/:domain/observations\", {\n params: { domain: Schema.String },\n payload: ObservePayload,\n success: Readiness,\n error: errors,\n }),\n )\n .add(\n HttpApiEndpoint.post(\"cleanupPlan\", \"/receipts/:receiptId/cleanup-plans\", {\n params: { receiptId: Receipt.ReceiptId },\n success: Plan.Model,\n error: errors,\n }),\n );\n// Cleanup approve, reject, and apply reuse those routes: the attempt knows its kind.\n\nexport type Group = typeof group;\n\n/** Every route in the group, by name; what `Identity.authorize` is asked about. */\nexport type EndpointName = HttpApiGroup.Endpoints<Group>[\"identifier\"];\n\nexport interface Options {\n /**\n * Where the callback route is reachable from the provider, without the `/callback/:provider`\n * suffix. Defaults to the incoming request's origin and mount path.\n */\n readonly callbackBaseUrl?: string;\n /** Where interactive flows return when the callback carries no `returnTo`. */\n readonly defaultReturnTo?: string;\n}\n\n/** Everything the handlers need: the lifecycle services, Storage, and the host's `Identity`. */\nexport type Services =\n | Provision.Service\n | Cleanup.Service\n | Connect.Service\n | Verify.Service\n | Providers.Service\n | Storage.Service\n | Identity;\n\n// ---------------------------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------------------------\n\nconst START_ROUTE = \"/connections\";\nconst callbackRoute = (provider: string) => `/callback/${encodeURIComponent(provider)}`;\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\n/** The request as an absolute URL: the web original when there is one, else scheme plus `Host`. */\nconst absoluteUrl = (\n request: HttpServerRequest.HttpServerRequest,\n): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n try {\n return Effect.succeed(new URL(request.originalUrl));\n } catch {\n const url = HttpServerRequest.toURL(request);\n return Option.isNone(url)\n ? invalid(\"The request has no usable origin; set callbackBaseUrl\", \"callbackBaseUrl\")\n : Effect.succeed(url.value);\n }\n });\n\n/**\n * The absolute URL the provider redirects back to. `callbackBaseUrl` wins when the host set it,\n * because a proxy that rewrites `Host` leaves the request pointing at an origin the browser never\n * sees; otherwise it follows the prefix the group is mounted at, derived by stripping the handling\n * route's own path off the request.\n */\nconst callbackUrlFor = (input: {\n readonly request: HttpServerRequest.HttpServerRequest;\n readonly provider: string;\n readonly options: Options;\n /** The path of the route serving this request, stripped to find the mount. */\n readonly route: string;\n}): Effect.Effect<URL, Errors.DomainKitError> =>\n Effect.suspend(() => {\n const suffix = `/callback/${encodeURIComponent(input.provider)}`;\n const configured = input.options.callbackBaseUrl;\n if (configured !== undefined) {\n return Effect.try({\n try: () => new URL(`${configured.replace(/\\/+$/, \"\")}${suffix}`),\n catch: () => callbackConfigurationError(`${configured} is not a URL`),\n });\n }\n return Effect.flatMap(absoluteUrl(input.request), (url) => {\n const path = url.pathname;\n if (!path.endsWith(input.route)) {\n return invalid(\n `The ${input.route} route is not mounted where it says; set callbackBaseUrl`,\n \"callbackBaseUrl\",\n );\n }\n return Effect.succeed(\n new URL(`${url.origin}${path.slice(0, path.length - input.route.length)}${suffix}`),\n );\n });\n });\n\nconst callbackConfigurationError = (message: string) =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({ message, field: \"callbackBaseUrl\" }),\n });\n\n/**\n * Where the callback may send the customer: a path under the callback's own base, or an absolute\n * URL on its origin. A destination that leaves the application is an open redirect, whoever\n * supplied it.\n */\nconst sameOrigin = (destination: string, callback: URL): string | null => {\n try {\n // Resolving against the callback is what makes this safe: the URL parser normalizes the forms\n // a browser would follow off-origin (`//host`, `/\\host`, `\\/host`, encoded hosts) into a real\n // origin, so comparing origins catches every one of them.\n const url = new URL(destination, callback);\n return url.origin === callback.origin ? url.toString() : null;\n } catch {\n return null;\n }\n};\n\nconst snapshotOf = (snapshot: Connect.Snapshot): Snapshot => ({\n domain: snapshot.domain,\n attachmentId: snapshot.attachment?.id ?? null,\n connectionId: snapshot.connection?.id ?? null,\n provider: snapshot.authorization?.provider ?? null,\n method: snapshot.authorization?.method ?? null,\n status:\n snapshot.connection === null\n ? \"disconnected\"\n : snapshot.authorization?.revocation === \"active\"\n ? \"connected\"\n : \"reconnect\",\n lastReceiptId: snapshot.lastReceiptId,\n reusable: snapshot.reusable.map(({ connection, provider, method }) => ({\n connectionId: connection.id,\n provider,\n method,\n })),\n providers: snapshot.providers.map(({ id, name, methods }) => ({ id, name, methods })),\n});\n\n/** The wire method as `Connect` takes it; interactive cases carry the caller's return destination. */\nconst methodOf = (method: Method): Connect.Method => {\n switch (method._tag) {\n case \"Token\":\n return Connect.Method.token(method.values);\n case \"OAuth\":\n return Connect.Method.oauth(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n case \"Integration\":\n return Connect.Method.integration(\n method.returnTo === undefined ? {} : { returnTo: method.returnTo },\n );\n }\n};\n\nconst candidatesOf = (\n candidates: ReadonlyArray<{ readonly zone: string; readonly label: string }>,\n): ReadonlyArray<Candidate> => candidates.map(({ zone, label }) => new Candidate({ zone, label }));\n\n/**\n * Handlers for `group` inside the host's API. Every handler derives `Principal` from `Identity`\n * for the request it is serving and provides it to the lifecycle services.\n */\nexport const layer = <ApiId extends string, Groups extends HttpApiGroup.Constraint>(\n api: HttpApi.HttpApi<ApiId, Groups>,\n options: Options = {},\n): Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services> =>\n // The host's API carries its own groups beside this one; only `\"domainkit\"` is implemented here,\n // and `HttpApiBuilder.group` needs the group's own endpoint types to check the handlers.\n HttpApiBuilder.group(api as unknown as HttpApi.HttpApi<ApiId, Group>, \"domainkit\", (handlers) =>\n Effect.gen(function* () {\n const identity = yield* Identity;\n const connect = yield* Connect.Service;\n const provision = yield* Provision.Service;\n const cleanup = yield* Cleanup.Service;\n const verify = yield* Verify.Service;\n const storage = yield* Storage.Service;\n\n /**\n * Run a lifecycle effect as the principal the host derives from this request, once the host\n * has allowed that principal onto this route.\n */\n const as = <A>(\n endpoint: EndpointName,\n request: HttpServerRequest.HttpServerRequest,\n effect: Effect.Effect<A, Errors.DomainKitError, Principal.Service>,\n ): Effect.Effect<A, Errors.DomainKitError> =>\n Effect.flatMap(identity.principal(request), (principal) =>\n Effect.flatMap(identity.authorize?.(principal, endpoint) ?? Effect.void, () =>\n Effect.provideService(effect, Principal.Service, principal),\n ),\n );\n\n const snapshot = (domain: string) => Effect.map(connect.inspect(domain), snapshotOf);\n\n const discovered = (discovery: Connect.Discovery): Discovery => {\n switch (discovery._tag) {\n case \"Resolved\":\n return new DiscoveryResolved({\n connectionId: discovery.connectionId,\n zone: discovery.target.zone,\n label: discovery.target.label,\n });\n case \"SelectionRequired\":\n return new DiscoverySelectionRequired({\n candidates: discovery.candidates.map(({ connectionId, target }) => ({\n connectionId,\n zone: target.zone,\n label: target.label,\n })),\n });\n case \"NotFound\":\n return new DiscoveryNotFound({\n nameservers: discovery.nameservers,\n host: discovery.host,\n });\n }\n };\n\n const connected = (domain: string) =>\n Effect.map(snapshot(domain), (value) => new Connected({ snapshot: value }));\n\n const startedOf = (\n started: Connect.Started,\n domain: string,\n ): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> => {\n switch (started._tag) {\n case \"Connected\":\n return connected(domain);\n case \"Redirect\":\n return Effect.succeed(new Redirect({ authorizationUrl: started.authorizationUrl }));\n case \"SelectionRequired\":\n return Effect.succeed(\n new SelectionRequired({\n connectionId: started.connection.id,\n candidates: candidatesOf(started.candidates),\n }),\n );\n }\n };\n\n /**\n * Attach with a zone the client picked out of a `SelectionRequired`. The provider target\n * behind a zone never crosses the wire, so resolve the candidates again and match by zone.\n */\n const attachAt = (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly zone: string | undefined;\n }): Effect.Effect<Started, Errors.DomainKitError, Principal.Service> =>\n Effect.gen(function* () {\n const first = yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n });\n if (!(\"_tag\" in first)) return yield* connected(input.domain);\n if (input.zone === undefined) {\n return new SelectionRequired({\n connectionId: input.connectionId,\n candidates: candidatesOf(first.candidates),\n });\n }\n const target = first.candidates.find(({ zone }) => zone === input.zone);\n if (target === undefined) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: input.zone }));\n }\n yield* connect.attach({\n connectionId: input.connectionId,\n domain: input.domain,\n target,\n });\n return yield* connected(input.domain);\n });\n\n /** Provisioning and cleanup share the approve and apply routes; the attempt knows its kind. */\n const kindOf = (attempt: Storage.Attempt) =>\n attempt.kind === \"cleanup\" ? cleanup : provision;\n\n return handlers\n .handle(\"inspect\", ({ params, request }) => as(\"inspect\", request, snapshot(params.domain)))\n .handle(\"discover\", ({ params, request }) =>\n as(\"discover\", request, Effect.map(connect.discover(params.domain), discovered)),\n )\n .handle(\"start\", ({ payload, request }) =>\n as(\n \"start\",\n request,\n Effect.gen(function* () {\n // Token methods connect in one call; the interactive ones need somewhere to land.\n const callbackUrl =\n payload.method._tag === \"Token\"\n ? undefined\n : (yield* callbackUrlFor({\n request,\n provider: payload.provider,\n options,\n route: START_ROUTE,\n })).toString();\n const started = yield* connect.start({\n provider: payload.provider,\n domain: payload.domain,\n method: methodOf(payload.method),\n ...(callbackUrl === undefined ? {} : { callbackUrl }),\n });\n return yield* startedOf(started, payload.domain);\n }),\n ),\n )\n .handle(\"callback\", ({ params, query, request }) =>\n as(\n \"callback\",\n request,\n Effect.gen(function* () {\n const url = yield* absoluteUrl(request);\n // Resolve the destination against the callback's public base, not the request: a\n // proxy that rewrites `Host` leaves the request on an origin the browser never sees.\n const base = yield* callbackUrlFor({\n request,\n provider: params.provider,\n options,\n route: callbackRoute(params.provider),\n });\n // Where the customer lands comes from the continuation this owner started, never\n // from the provider's query string, and it is resolved before the callback is spent:\n // a server with nowhere to send them must not connect the provider and then fail.\n const continuation = yield* storage.continuations.get(query.state);\n const requested = continuation.returnTo ?? options.defaultReturnTo;\n if (requested === undefined) {\n return yield* invalid(\n \"The flow carried no returnTo and the server has no defaultReturnTo\",\n \"returnTo\",\n );\n }\n const destination = sameOrigin(requested, base);\n if (destination === null) {\n return yield* invalid(`${requested} leaves this application`, \"returnTo\");\n }\n yield* connect.complete({\n continuationId: query.state,\n callbackUrl: url.toString(),\n });\n return HttpApiSchema.withHeaders({\n body: undefined as void,\n headers: { location: destination },\n });\n }),\n ),\n )\n .handle(\"attach\", ({ params, payload, request }) =>\n as(\n \"attach\",\n request,\n attachAt({\n connectionId: params.connectionId,\n domain: payload.domain,\n zone: payload.zone,\n }),\n ),\n )\n .handle(\"detach\", ({ params, request }) =>\n as(\"detach\", request, connect.detach(params.attachmentId)),\n )\n .handle(\"disconnect\", ({ params, request }) =>\n as(\"disconnect\", request, connect.disconnect(params.connectionId)),\n )\n .handle(\"createPlan\", ({ params, payload, request }) =>\n as(\n \"createPlan\",\n request,\n provision.plan({ domain: params.domain, requirements: payload.requirements }),\n ),\n )\n .handle(\"approve\", ({ params, payload, request }) =>\n as(\n \"approve\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).approve(params.planId, {\n ...(payload.operationIds === undefined\n ? {}\n : { operationIds: payload.operationIds, allowPartial: true }),\n });\n }),\n ),\n )\n .handle(\"reject\", ({ params, payload, request }) =>\n as(\n \"reject\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.get(params.planId);\n return yield* kindOf(attempt).reject(params.planId, {\n ...(payload.reason === undefined ? {} : { reason: payload.reason }),\n });\n }),\n ),\n )\n .handle(\"apply\", ({ params, request }) =>\n as(\n \"apply\",\n request,\n Effect.gen(function* () {\n const attempt = yield* storage.attempts.byApproval(params.approvalId);\n return yield* kindOf(attempt).apply(params.approvalId);\n }),\n ),\n )\n .handle(\"plan\", ({ params, request }) =>\n as(\n \"plan\",\n request,\n Effect.map(storage.attempts.get(params.planId), (attempt) => ({\n plan: attempt.plan,\n status: attempt.status,\n approval: attempt.approval,\n receipt: attempt.receipt,\n rejection: attempt.rejection,\n })),\n ),\n )\n .handle(\"receipt\", ({ params, request }) =>\n as(\n \"receipt\",\n request,\n Effect.flatMap(storage.attempts.byReceipt(params.receiptId), (attempt) =>\n attempt.receipt === null\n ? Errors.fail(new Reason.NotFound({ entity: \"receipt\", id: params.receiptId }))\n : Effect.succeed(attempt.receipt),\n ),\n ),\n )\n .handle(\"observe\", ({ params, payload, request }) =>\n as(\n \"observe\",\n request,\n verify.observe({\n domain: params.domain,\n ...(payload.requirements === undefined ? {} : { requirements: payload.requirements }),\n }),\n ),\n )\n .handle(\"cleanupPlan\", ({ params, request }) =>\n as(\"cleanupPlan\", request, cleanup.plan({ receiptId: params.receiptId })),\n );\n }),\n ) as Layer.Layer<HttpApiGroup.Service<ApiId, \"domainkit\">, never, Services>;\n\n/** A standalone API for hosts not on HttpApi. Mount at any prefix. */\nexport const api: HttpApi.HttpApi<\"domainkit\", Group> = HttpApi.make(\"domainkit\").add(group);\n\nexport interface WebHandlerOptions extends Options {\n /** Mount every route under this path, e.g. `/api/domainkit`. */\n readonly prefix?: `/${string}`;\n}\n\n/** The Promise edge: a `fetch`-shaped handler over the group for hosts that are not Effect-native. */\nexport const toWebHandler = (\n services: Layer.Layer<Services, Errors.DomainKitError>,\n options: WebHandlerOptions = {},\n): {\n readonly handler: (request: Request) => Promise<Response>;\n readonly dispose: () => Promise<void>;\n} => {\n // `group.prefix` widens the endpoint paths in the type, which `Group` states unprefixed; the\n // handlers are keyed by endpoint identifier, so the routes still line up.\n const prefixed =\n options.prefix === undefined\n ? api\n : (HttpApi.make(\"domainkit\").add(group.prefix(options.prefix)) as unknown as HttpApi.HttpApi<\n \"domainkit\",\n Group\n >);\n const app = HttpApiBuilder.layer(prefixed).pipe(\n Layer.provide(layer(prefixed, options)),\n Layer.provide(services),\n Layer.provide(HttpServer.layerServices),\n );\n return HttpRouter.toWebHandler(app, { disableLogger: true });\n};\n"],"mappings":"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"}
package/dist/Testing.mjs CHANGED
@@ -90,10 +90,28 @@ const provider = (options = {}) => {
90
90
  label: zone.name,
91
91
  nameservers: zone.nameservers
92
92
  }));
93
+ const oauth = {
94
+ label: "Sign in (fake)",
95
+ scopes: ["dns"],
96
+ start: (input) => Effect.succeed({ authorizationUrl: `${input.callbackUrl}?${new URLSearchParams({
97
+ state: input.state,
98
+ code: "fake-code"
99
+ })}` }),
100
+ complete: (input) => input.code === "fake-code" ? Effect.map(DateTime.now, (now) => ({
101
+ ...issue("oauth"),
102
+ expiresAt: DateTime.add(now, { hours: 1 })
103
+ })) : fail(new Unauthenticated({ message: "fake provider rejected the code" })),
104
+ refresh: (credential) => Redacted.value(credential.secret).startsWith("revoked") ? fail(new Unauthenticated({ message: "fake refresh token was revoked" })) : Effect.map(DateTime.now, (now) => ({
105
+ ...issue("oauth"),
106
+ expiresAt: DateTime.add(now, { hours: 1 })
107
+ })),
108
+ revoke: () => Effect.sync(() => void (revokedCount += 1))
109
+ };
93
110
  return {
94
111
  ...make$1({
95
112
  id,
96
113
  name: `Fake ${id}`,
114
+ ...options.nameserverSuffixes === void 0 ? {} : { nameservers: options.nameserverSuffixes },
97
115
  context: Context$1,
98
116
  contextVersion: "fake.v1",
99
117
  auth: {
@@ -107,23 +125,7 @@ const provider = (options = {}) => {
107
125
  expiresAt: null
108
126
  })
109
127
  }),
110
- ...options.oauth === true ? { oauth: {
111
- label: "Sign in (fake)",
112
- scopes: ["dns"],
113
- start: (input) => Effect.succeed({ authorizationUrl: `${input.callbackUrl}?${new URLSearchParams({
114
- state: input.state,
115
- code: "fake-code"
116
- })}` }),
117
- complete: (input) => input.code === "fake-code" ? Effect.map(DateTime.now, (now) => ({
118
- ...issue("oauth"),
119
- expiresAt: DateTime.add(now, { hours: 1 })
120
- })) : fail(new Unauthenticated({ message: "fake provider rejected the code" })),
121
- refresh: (credential) => Redacted.value(credential.secret).startsWith("revoked") ? fail(new Unauthenticated({ message: "fake refresh token was revoked" })) : Effect.map(DateTime.now, (now) => ({
122
- ...issue("oauth"),
123
- expiresAt: DateTime.add(now, { hours: 1 })
124
- })),
125
- revoke: () => Effect.sync(() => void (revokedCount += 1))
126
- } } : {}
128
+ ...options.oauth === true ? { oauth } : {}
127
129
  },
128
130
  session: () => ({
129
131
  capabilities: () => Effect.succeed(["dns:read", "dns:write"]),
@@ -217,7 +219,7 @@ const transport = (options = {}) => {
217
219
  const conformance = {
218
220
  /** Runs every Storage invariant (tenant isolation, leases, exactly-once continuations, revocation recovery). */
219
221
  storage: storage$1,
220
- /** Runs create/readback/cleanup, exact-noop, conflict, stale-plan, partial-apply against a real provider definition. */
222
+ /** Runs create/readback/cleanup, exact-noop, conflict, stale-plan, partial-apply, and rejected-token against a real provider definition. */
221
223
  provider: provider$1
222
224
  };
223
225
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"Testing.mjs","names":["Storage.layerMemory","Principal.make","Context","DomainName.fromStringUnsafe","Errors.fail","Reason.NotFound","Provider.make","Provider.tokenAuth","Reason.Unauthenticated","DomainName.decode","Provider.resolveAmong","Reason.ProviderUnavailable","DomainName.isWithin","Reason.ProviderRejected","DnsRecord.ns","Resolver.Service","DomainName.fromString","Server.toWebHandler","DomainKit.layerMemory","Server.Identity","Transport.fromFetch","Transport.capabilities","storageCases","providerCases"],"sources":["../src/Testing.ts"],"sourcesContent":["/**\n * domainkit/testing — fakes and conformance runners so hosts test against the seam instead of\n * stubbing global fetch.\n */\nimport { DateTime, Effect, Layer, Redacted, Schema } from \"effect\";\n\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as DomainKit from \"./DomainKit.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { provider as providerCases } from \"./internal/conformance/provider.ts\";\nimport { storage as storageCases } from \"./internal/conformance/storage.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Server from \"./Server.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Transport from \"./Transport.ts\";\n\n/** In-memory Storage. Same as `Storage.layerMemory`; re-exported for discoverability. */\nexport const storage: Layer.Layer<Storage.Service> = Storage.layerMemory;\n\nexport const principal: Principal.Interface = Principal.make({\n ownerId: \"org_test\",\n actorId: \"user_test\",\n});\n\nexport interface FakeProviderOptions {\n readonly id?: string;\n /** Zones the fake credential can reach. Default: `example.com`. */\n readonly zones?: ReadonlyArray<string>;\n /** Pre-existing records, to produce `Noop` and `Conflict` operations. */\n readonly records?: ReadonlyArray<{ readonly zone: string; readonly record: DnsRecord.Observed }>;\n /** Offer OAuth in addition to tokens; the fake redirects to `callbackUrl` immediately. */\n readonly oauth?: boolean;\n /** Fail the write at this zero-based index, to exercise partial receipts. */\n readonly failWrite?: (index: number) => boolean;\n /** Nameservers per zone; default `ns1.<zone>`, `ns2.<zone>`. `resolver()` answers NS queries from them. */\n readonly nameservers?: Readonly<Record<string, ReadonlyArray<string>>>;\n}\n\nexport interface FakeProvider extends Provider.Definition<FakeContext> {\n readonly records: (zone: string) => ReadonlyArray<DnsRecord.Observed>;\n /** Every credential the fake issued, oldest first. */\n readonly issued: () => ReadonlyArray<string>;\n /** How many credentials were revoked at the fake. */\n readonly revoked: () => number;\n}\n\ninterface Zone {\n readonly name: string;\n readonly nameservers: ReadonlyArray<string>;\n readonly rows: Array<{ readonly id: string; readonly record: DnsRecord.Observed }>;\n}\n\n/** Every fake zone created in this process, so `resolver()` can answer from them. */\nconst registry = new Set<Zone>();\n\n/** Account context carries `account`; target context carries `zone`. One schema covers both. */\nconst Context = Schema.Struct({\n account: Schema.optionalKey(Schema.String),\n zone: Schema.optionalKey(Schema.String),\n});\ntype FakeContext = typeof Context.Type;\nconst TargetContext = Schema.Struct({ zone: Schema.String });\n\n/** A provider definition with a token method (and optional OAuth) over in-memory zones. */\nexport const provider = (options: FakeProviderOptions = {}): FakeProvider => {\n const id = options.id ?? \"fake\";\n const zones = new Map<string, Zone>();\n let nextId = 1;\n let writes = 0;\n let revokedCount = 0;\n const issued: Array<string> = [];\n for (const name of options.zones ?? [\"example.com\"]) {\n const normalized = DomainName.fromStringUnsafe(name);\n const zone: Zone = {\n name: normalized,\n nameservers: options.nameservers?.[name] ??\n options.nameservers?.[normalized] ?? [`ns1.${normalized}`, `ns2.${normalized}`],\n rows: [],\n };\n zones.set(zone.name, zone);\n registry.add(zone);\n }\n for (const { zone: name, record } of options.records ?? []) {\n const zone = zones.get(DomainName.fromStringUnsafe(name));\n if (zone === undefined) throw new Error(`Fake provider ${id} has no zone ${name}`);\n zone.rows.push({ id: `${id}-${nextId++}`, record });\n }\n\n const issue = (prefix: string): Provider.IssuedCredential => {\n const secret = `${prefix}-${issued.length + 1}`;\n issued.push(secret);\n return { secret: Redacted.make(secret), context: { account: id }, expiresAt: null };\n };\n\n const zoneOf = (target: Provider.Target) =>\n Effect.suspend(() => {\n const decoded = Schema.decodeUnknownOption(TargetContext)(target.context);\n const zone = decoded._tag === \"Some\" ? zones.get(decoded.value.zone) : undefined;\n return zone === undefined\n ? Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }))\n : Effect.succeed(zone);\n });\n\n const targets = () =>\n [...zones.values()].map((zone): Provider.Target => ({\n zone: zone.name,\n context: { zone: zone.name },\n label: zone.name,\n nameservers: zone.nameservers,\n }));\n\n const oauth: Provider.OAuthAuth = {\n label: \"Sign in (fake)\",\n scopes: [\"dns\"],\n start: (input) =>\n Effect.succeed({\n authorizationUrl: `${input.callbackUrl}?${new URLSearchParams({\n state: input.state,\n code: \"fake-code\",\n })}`,\n }),\n complete: (input) =>\n input.code === \"fake-code\"\n ? Effect.map(DateTime.now, (now) => ({\n ...issue(\"oauth\"),\n expiresAt: DateTime.add(now, { hours: 1 }),\n }))\n : Errors.fail(new Reason.Unauthenticated({ message: \"fake provider rejected the code\" })),\n refresh: (credential) =>\n Redacted.value(credential.secret).startsWith(\"revoked\")\n ? Errors.fail(new Reason.Unauthenticated({ message: \"fake refresh token was revoked\" }))\n : Effect.map(DateTime.now, (now) => ({\n ...issue(\"oauth\"),\n expiresAt: DateTime.add(now, { hours: 1 }),\n })),\n revoke: () => Effect.sync(() => void (revokedCount += 1)),\n };\n\n const definition = Provider.make<FakeContext>({\n id,\n name: `Fake ${id}`,\n context: Context,\n contextVersion: \"fake.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Token (fake)\",\n requiredCapabilities: [\"dns:read\", \"dns:write\"],\n fields: Schema.Struct({ token: Schema.RedactedFromValue(Schema.String) }),\n authenticate: ({ token }) =>\n Redacted.value(token).length === 0\n ? Errors.fail(\n new Reason.Unauthenticated({\n message: \"fake provider rejected an empty token\",\n }),\n )\n : Effect.succeed({ secret: token, context: { account: id }, expiresAt: null }),\n }),\n ...(options.oauth === true ? { oauth } : {}),\n },\n session: () => ({\n capabilities: () => Effect.succeed([\"dns:read\", \"dns:write\"]),\n listTargets: () => Effect.succeed(targets()),\n resolveTarget: (domain) =>\n Effect.map(DomainName.decode(domain), (name) => Provider.resolveAmong(name, targets())),\n dns: (target) => ({\n list: () =>\n Effect.map(zoneOf(target), (zone) =>\n zone.rows.map(({ id: providerRecordId, record }) => ({ record, providerRecordId })),\n ),\n create: (_zone, record) =>\n Effect.gen(function* () {\n const zone = yield* zoneOf(target);\n const index = writes;\n writes += 1;\n if (options.failWrite?.(index) === true) {\n return yield* Errors.fail(\n new Reason.ProviderUnavailable({\n provider: id,\n message: `fake provider failed write ${index}`,\n }),\n );\n }\n if (!DomainName.isWithin(record.name, zone.name)) {\n return yield* Errors.fail(\n new Reason.ProviderRejected({\n provider: id,\n message: `${record.name} is outside ${zone.name}`,\n }),\n );\n }\n const providerRecordId = `${id}-${nextId++}`;\n zone.rows.push({ id: providerRecordId, record });\n return { providerRecordId };\n }),\n get: (_zone, providerRecordId) =>\n Effect.map(\n zoneOf(target),\n (zone) => zone.rows.find((row) => row.id === providerRecordId)?.record ?? null,\n ),\n delete: (_zone, providerRecordId) =>\n Effect.map(zoneOf(target), (zone) => {\n const index = zone.rows.findIndex((row) => row.id === providerRecordId);\n if (index >= 0) zone.rows.splice(index, 1);\n }),\n }),\n }),\n });\n\n return {\n ...definition,\n records: (zone) =>\n zones.get(DomainName.fromStringUnsafe(zone))?.rows.map(({ record }) => record) ?? [],\n issued: () => issued,\n revoked: () => revokedCount,\n };\n};\n\n/** NS records for every fake zone named `name`, so `resolver()` can answer authority queries. */\nconst fakeNameservers = (name: string): ReadonlyArray<DnsRecord.Observed> =>\n [...registry]\n .filter((zone) => zone.name === name)\n .flatMap((zone) =>\n zone.nameservers.map((nameserver) => DnsRecord.ns({ name: zone.name, nameserver })),\n );\n\n/** Records every fake provider currently holds for `name`, across all fake zones. */\nexport const fakeRecords = (name: string): ReadonlyArray<DnsRecord.Observed> =>\n [...registry]\n .flatMap((zone) => zone.rows.map(({ record }) => record))\n .filter((record) => record.name === name);\n\n/**\n * A resolver answering from the fake providers' zones, or from an explicit table. Every answer\n * is attributed to the resolver named `fake`.\n */\nexport const resolver = (\n answers?: ReadonlyArray<{\n readonly name: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n }>,\n): Layer.Layer<Resolver.Service> =>\n Layer.succeed(Resolver.Service)({\n resolve: (name, type) =>\n Effect.sync(() => {\n const normalized = DomainName.fromString(name);\n const lookup = normalized._tag === \"Some\" ? normalized.value : name;\n const table =\n answers === undefined\n ? [...fakeRecords(lookup), ...fakeNameservers(lookup)]\n : answers.filter((entry) => entry.name === lookup).flatMap((entry) => entry.records);\n const records = table.filter(\n (record) => (record._tag === \"Opaque\" ? record.type : record._tag) === type,\n );\n return [\n Resolver.Outcome.Answered({\n answer: { resolver: \"fake\", records, negative: records.length === 0, ttl: 60 },\n }),\n ];\n }),\n });\n\nexport interface TransportOptions {\n /** Which groups the fake server exposes, so a UI can be tested against a partial host. */\n readonly capabilities?: ReadonlyArray<Transport.Capability>;\n readonly provider?: FakeProviderOptions;\n}\n\nexport interface RecordedCall {\n /** `connection.inspect`, `provisioning.approve`, ... */\n readonly method: string;\n /** The call's only argument, or the argument list when the method takes more than one. */\n readonly input: unknown;\n}\n\ntype TransportMethod = (\n ...args: ReadonlyArray<never>\n) => Effect.Effect<unknown, Errors.DomainKitError>;\n\nexport interface RecordingTransport extends Transport.Interface {\n readonly calls: ReadonlyArray<RecordedCall>;\n}\n\n/**\n * A transport over an in-memory `domainkit/server` and memory Storage, recording every call. What\n * `@domainkit/react` tests render against, instead of stubbing global `fetch`.\n */\nexport const transport = (options: TransportOptions = {}): RecordingTransport => {\n const fake = provider(options.provider);\n // The handler's layer holds memory Storage and a throwaway custody key, so there is nothing to\n // release; a test that wants the server disposed builds `Server.toWebHandler` itself.\n const { handler } = Server.toWebHandler(\n DomainKit.layerMemory({ providers: [fake], resolver: resolver() }).pipe(\n Layer.merge(Layer.succeed(Server.Identity)({ principal: () => Effect.succeed(principal) })),\n ),\n );\n const live = Transport.fromFetch(\"http://domainkit.test\", {\n fetch: (input, init) => handler(new Request(input, init)),\n ...(options.capabilities === undefined ? {} : { capabilities: options.capabilities }),\n });\n const calls: Array<RecordedCall> = [];\n const recorded = Object.fromEntries(\n Transport.capabilities(live).map((capability) => [\n capability,\n Object.fromEntries(\n Object.entries(live[capability] as unknown as Record<string, TransportMethod>).map(\n ([name, method]) => [\n name,\n (...args: ReadonlyArray<never>) =>\n Effect.suspend(() => {\n calls.push({\n method: `${capability}.${name}`,\n input: args.length > 1 ? args : args[0],\n });\n return method(...args);\n }),\n ],\n ),\n ),\n ]),\n ) as Transport.Interface;\n return { ...recorded, calls };\n};\n\nexport type { Case as ProviderCase } from \"./internal/conformance/provider.ts\";\nexport type { Case as StorageCase } from \"./internal/conformance/storage.ts\";\n\nexport const conformance = {\n /** Runs every Storage invariant (tenant isolation, leases, exactly-once continuations, revocation recovery). */\n storage: storageCases,\n /** Runs create/readback/cleanup, exact-noop, conflict, stale-plan, partial-apply against a real provider definition. */\n provider: providerCases,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Testing.mjs","names":["Storage.layerMemory","Principal.make","Context","DomainName.fromStringUnsafe","Errors.fail","Reason.NotFound","Reason.Unauthenticated","Provider.make","Provider.tokenAuth","DomainName.decode","Provider.resolveAmong","Reason.ProviderUnavailable","DomainName.isWithin","Reason.ProviderRejected","DnsRecord.ns","Resolver.Service","DomainName.fromString","Server.toWebHandler","DomainKit.layerMemory","Server.Identity","Transport.fromFetch","Transport.capabilities","storageCases","providerCases"],"sources":["../src/Testing.ts"],"sourcesContent":["/**\n * domainkit/testing — fakes and conformance runners so hosts test against the seam instead of\n * stubbing global fetch.\n */\nimport { DateTime, Effect, Layer, Redacted, Schema } from \"effect\";\n\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as DomainKit from \"./DomainKit.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { provider as providerCases } from \"./internal/conformance/provider.ts\";\nimport { storage as storageCases } from \"./internal/conformance/storage.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Server from \"./Server.ts\";\nimport * as Storage from \"./Storage.ts\";\nimport * as Transport from \"./Transport.ts\";\n\n/** In-memory Storage. Same as `Storage.layerMemory`; re-exported for discoverability. */\nexport const storage: Layer.Layer<Storage.Service> = Storage.layerMemory;\n\nexport const principal: Principal.Interface = Principal.make({\n ownerId: \"org_test\",\n actorId: \"user_test\",\n});\n\nexport interface FakeProviderOptions {\n readonly id?: string;\n /** Zones the fake credential can reach. Default: `example.com`. */\n readonly zones?: ReadonlyArray<string>;\n /** Pre-existing records, to produce `Noop` and `Conflict` operations. */\n readonly records?: ReadonlyArray<{ readonly zone: string; readonly record: DnsRecord.Observed }>;\n /** Offer OAuth in addition to tokens; the fake redirects to `callbackUrl` immediately. */\n readonly oauth?: boolean;\n /** Fail the write at this zero-based index, to exercise partial receipts. */\n readonly failWrite?: (index: number) => boolean;\n /** Nameservers per zone; default `ns1.<zone>`, `ns2.<zone>`. `resolver()` answers NS queries from them. */\n readonly nameservers?: Readonly<Record<string, ReadonlyArray<string>>>;\n /** Suffixes the fake declares as `Definition.nameservers`, so `Connect.discover` can name it as a host. */\n readonly nameserverSuffixes?: ReadonlyArray<string>;\n}\n\nexport interface FakeProvider extends Provider.Definition<FakeContext> {\n readonly records: (zone: string) => ReadonlyArray<DnsRecord.Observed>;\n /** Every credential the fake issued, oldest first. */\n readonly issued: () => ReadonlyArray<string>;\n /** How many credentials were revoked at the fake. */\n readonly revoked: () => number;\n}\n\ninterface Zone {\n readonly name: string;\n readonly nameservers: ReadonlyArray<string>;\n readonly rows: Array<{ readonly id: string; readonly record: DnsRecord.Observed }>;\n}\n\n/** Every fake zone created in this process, so `resolver()` can answer from them. */\nconst registry = new Set<Zone>();\n\n/** Account context carries `account`; target context carries `zone`. One schema covers both. */\nconst Context = Schema.Struct({\n account: Schema.optionalKey(Schema.String),\n zone: Schema.optionalKey(Schema.String),\n});\ntype FakeContext = typeof Context.Type;\nconst TargetContext = Schema.Struct({ zone: Schema.String });\n\n/** A provider definition with a token method (and optional OAuth) over in-memory zones. */\nexport const provider = (options: FakeProviderOptions = {}): FakeProvider => {\n const id = options.id ?? \"fake\";\n const zones = new Map<string, Zone>();\n let nextId = 1;\n let writes = 0;\n let revokedCount = 0;\n const issued: Array<string> = [];\n for (const name of options.zones ?? [\"example.com\"]) {\n const normalized = DomainName.fromStringUnsafe(name);\n const zone: Zone = {\n name: normalized,\n nameservers: options.nameservers?.[name] ??\n options.nameservers?.[normalized] ?? [`ns1.${normalized}`, `ns2.${normalized}`],\n rows: [],\n };\n zones.set(zone.name, zone);\n registry.add(zone);\n }\n for (const { zone: name, record } of options.records ?? []) {\n const zone = zones.get(DomainName.fromStringUnsafe(name));\n if (zone === undefined) throw new Error(`Fake provider ${id} has no zone ${name}`);\n zone.rows.push({ id: `${id}-${nextId++}`, record });\n }\n\n const issue = (prefix: string): Provider.IssuedCredential => {\n const secret = `${prefix}-${issued.length + 1}`;\n issued.push(secret);\n return { secret: Redacted.make(secret), context: { account: id }, expiresAt: null };\n };\n\n const zoneOf = (target: Provider.Target) =>\n Effect.suspend(() => {\n const decoded = Schema.decodeUnknownOption(TargetContext)(target.context);\n const zone = decoded._tag === \"Some\" ? zones.get(decoded.value.zone) : undefined;\n return zone === undefined\n ? Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }))\n : Effect.succeed(zone);\n });\n\n const targets = () =>\n [...zones.values()].map((zone): Provider.Target => ({\n zone: zone.name,\n context: { zone: zone.name },\n label: zone.name,\n nameservers: zone.nameservers,\n }));\n\n const oauth: Provider.OAuthAuth = {\n label: \"Sign in (fake)\",\n scopes: [\"dns\"],\n start: (input) =>\n Effect.succeed({\n authorizationUrl: `${input.callbackUrl}?${new URLSearchParams({\n state: input.state,\n code: \"fake-code\",\n })}`,\n }),\n complete: (input) =>\n input.code === \"fake-code\"\n ? Effect.map(DateTime.now, (now) => ({\n ...issue(\"oauth\"),\n expiresAt: DateTime.add(now, { hours: 1 }),\n }))\n : Errors.fail(new Reason.Unauthenticated({ message: \"fake provider rejected the code\" })),\n refresh: (credential) =>\n Redacted.value(credential.secret).startsWith(\"revoked\")\n ? Errors.fail(new Reason.Unauthenticated({ message: \"fake refresh token was revoked\" }))\n : Effect.map(DateTime.now, (now) => ({\n ...issue(\"oauth\"),\n expiresAt: DateTime.add(now, { hours: 1 }),\n })),\n revoke: () => Effect.sync(() => void (revokedCount += 1)),\n };\n\n const definition = Provider.make<FakeContext>({\n id,\n name: `Fake ${id}`,\n ...(options.nameserverSuffixes === undefined\n ? {}\n : { nameservers: options.nameserverSuffixes }),\n context: Context,\n contextVersion: \"fake.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Token (fake)\",\n requiredCapabilities: [\"dns:read\", \"dns:write\"],\n fields: Schema.Struct({ token: Schema.RedactedFromValue(Schema.String) }),\n authenticate: ({ token }) =>\n Redacted.value(token).length === 0\n ? Errors.fail(\n new Reason.Unauthenticated({\n message: \"fake provider rejected an empty token\",\n }),\n )\n : Effect.succeed({ secret: token, context: { account: id }, expiresAt: null }),\n }),\n ...(options.oauth === true ? { oauth } : {}),\n },\n session: () => ({\n capabilities: () => Effect.succeed([\"dns:read\", \"dns:write\"]),\n listTargets: () => Effect.succeed(targets()),\n resolveTarget: (domain) =>\n Effect.map(DomainName.decode(domain), (name) => Provider.resolveAmong(name, targets())),\n dns: (target) => ({\n list: () =>\n Effect.map(zoneOf(target), (zone) =>\n zone.rows.map(({ id: providerRecordId, record }) => ({ record, providerRecordId })),\n ),\n create: (_zone, record) =>\n Effect.gen(function* () {\n const zone = yield* zoneOf(target);\n const index = writes;\n writes += 1;\n if (options.failWrite?.(index) === true) {\n return yield* Errors.fail(\n new Reason.ProviderUnavailable({\n provider: id,\n message: `fake provider failed write ${index}`,\n }),\n );\n }\n if (!DomainName.isWithin(record.name, zone.name)) {\n return yield* Errors.fail(\n new Reason.ProviderRejected({\n provider: id,\n message: `${record.name} is outside ${zone.name}`,\n }),\n );\n }\n const providerRecordId = `${id}-${nextId++}`;\n zone.rows.push({ id: providerRecordId, record });\n return { providerRecordId };\n }),\n get: (_zone, providerRecordId) =>\n Effect.map(\n zoneOf(target),\n (zone) => zone.rows.find((row) => row.id === providerRecordId)?.record ?? null,\n ),\n delete: (_zone, providerRecordId) =>\n Effect.map(zoneOf(target), (zone) => {\n const index = zone.rows.findIndex((row) => row.id === providerRecordId);\n if (index >= 0) zone.rows.splice(index, 1);\n }),\n }),\n }),\n });\n\n return {\n ...definition,\n records: (zone) =>\n zones.get(DomainName.fromStringUnsafe(zone))?.rows.map(({ record }) => record) ?? [],\n issued: () => issued,\n revoked: () => revokedCount,\n };\n};\n\n/** NS records for every fake zone named `name`, so `resolver()` can answer authority queries. */\nconst fakeNameservers = (name: string): ReadonlyArray<DnsRecord.Observed> =>\n [...registry]\n .filter((zone) => zone.name === name)\n .flatMap((zone) =>\n zone.nameservers.map((nameserver) => DnsRecord.ns({ name: zone.name, nameserver })),\n );\n\n/** Records every fake provider currently holds for `name`, across all fake zones. */\nexport const fakeRecords = (name: string): ReadonlyArray<DnsRecord.Observed> =>\n [...registry]\n .flatMap((zone) => zone.rows.map(({ record }) => record))\n .filter((record) => record.name === name);\n\n/**\n * A resolver answering from the fake providers' zones, or from an explicit table. Every answer\n * is attributed to the resolver named `fake`.\n */\nexport const resolver = (\n answers?: ReadonlyArray<{\n readonly name: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n }>,\n): Layer.Layer<Resolver.Service> =>\n Layer.succeed(Resolver.Service)({\n resolve: (name, type) =>\n Effect.sync(() => {\n const normalized = DomainName.fromString(name);\n const lookup = normalized._tag === \"Some\" ? normalized.value : name;\n const table =\n answers === undefined\n ? [...fakeRecords(lookup), ...fakeNameservers(lookup)]\n : answers.filter((entry) => entry.name === lookup).flatMap((entry) => entry.records);\n const records = table.filter(\n (record) => (record._tag === \"Opaque\" ? record.type : record._tag) === type,\n );\n return [\n Resolver.Outcome.Answered({\n answer: { resolver: \"fake\", records, negative: records.length === 0, ttl: 60 },\n }),\n ];\n }),\n });\n\nexport interface TransportOptions {\n /** Which groups the fake server exposes, so a UI can be tested against a partial host. */\n readonly capabilities?: ReadonlyArray<Transport.Capability>;\n readonly provider?: FakeProviderOptions;\n}\n\nexport interface RecordedCall {\n /** `connection.inspect`, `provisioning.approve`, ... */\n readonly method: string;\n /** The call's only argument, or the argument list when the method takes more than one. */\n readonly input: unknown;\n}\n\ntype TransportMethod = (\n ...args: ReadonlyArray<never>\n) => Effect.Effect<unknown, Errors.DomainKitError>;\n\nexport interface RecordingTransport extends Transport.Interface {\n readonly calls: ReadonlyArray<RecordedCall>;\n}\n\n/**\n * A transport over an in-memory `domainkit/server` and memory Storage, recording every call. What\n * `@domainkit/react` tests render against, instead of stubbing global `fetch`.\n */\nexport const transport = (options: TransportOptions = {}): RecordingTransport => {\n const fake = provider(options.provider);\n // The handler's layer holds memory Storage and a throwaway custody key, so there is nothing to\n // release; a test that wants the server disposed builds `Server.toWebHandler` itself.\n const { handler } = Server.toWebHandler(\n DomainKit.layerMemory({ providers: [fake], resolver: resolver() }).pipe(\n Layer.merge(Layer.succeed(Server.Identity)({ principal: () => Effect.succeed(principal) })),\n ),\n );\n const live = Transport.fromFetch(\"http://domainkit.test\", {\n fetch: (input, init) => handler(new Request(input, init)),\n ...(options.capabilities === undefined ? {} : { capabilities: options.capabilities }),\n });\n const calls: Array<RecordedCall> = [];\n const recorded = Object.fromEntries(\n Transport.capabilities(live).map((capability) => [\n capability,\n Object.fromEntries(\n Object.entries(live[capability] as unknown as Record<string, TransportMethod>).map(\n ([name, method]) => [\n name,\n (...args: ReadonlyArray<never>) =>\n Effect.suspend(() => {\n calls.push({\n method: `${capability}.${name}`,\n input: args.length > 1 ? args : args[0],\n });\n return method(...args);\n }),\n ],\n ),\n ),\n ]),\n ) as Transport.Interface;\n return { ...recorded, calls };\n};\n\nexport type { Case as ProviderCase } from \"./internal/conformance/provider.ts\";\nexport type { Case as StorageCase } from \"./internal/conformance/storage.ts\";\n\nexport const conformance = {\n /** Runs every Storage invariant (tenant isolation, leases, exactly-once continuations, revocation recovery). */\n storage: storageCases,\n /** Runs create/readback/cleanup, exact-noop, conflict, stale-plan, partial-apply, and rejected-token against a real provider definition. */\n provider: providerCases,\n};\n"],"mappings":"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"}
package/dist/Vercel.mjs CHANGED
@@ -3,6 +3,7 @@ import { decode } from "./DomainName.mjs";
3
3
  import { Unauthenticated } from "./Reason.mjs";
4
4
  import { fail } from "./internal/error.mjs";
5
5
  import { make, resolveAmong, tokenAuth } from "./Provider.mjs";
6
+ import { rejectedToken } from "./internal/http.mjs";
6
7
  import { resolve } from "./internal/config.mjs";
7
8
  import { dns, exchangeCode, teams, user, zones } from "./internal/vercel/client.mjs";
8
9
  import { Effect, Redacted, Schema } from "effect";
@@ -68,6 +69,7 @@ const provider = (options = {}) => {
68
69
  return make({
69
70
  id: "vercel",
70
71
  name: "Vercel",
72
+ nameservers: ["vercel-dns.com"],
71
73
  context: TeamContext,
72
74
  contextVersion: "vercel.v1",
73
75
  auth: {
@@ -80,7 +82,7 @@ const provider = (options = {}) => {
80
82
  secret: token,
81
83
  context: { teamId: teamId ?? null },
82
84
  expiresAt: null
83
- })))
85
+ })), rejectedToken)
84
86
  }),
85
87
  ...integration === void 0 ? {} : { integration }
86
88
  },
@@ -1 +1 @@
1
- {"version":3,"file":"Vercel.mjs","names":["Client.exchangeCode","Errors.fail","Reason.Unauthenticated","Provider.make","Provider.tokenAuth","Client.user","Client.zones","teams","Client.teams","DomainName.decode","Provider.resolveAmong","Client.dns"],"sources":["../src/Vercel.ts"],"sourcesContent":["/** Vercel: personal tokens and marketplace integrations. */\nimport { type Config, Effect, Redacted, Schema } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { resolve } from \"./internal/config.ts\";\nimport type { Fetch } from \"./internal/http.ts\";\nimport * as Client from \"./internal/vercel/client.ts\";\nimport type * as Protocol from \"./internal/vercel/protocol.ts\";\nimport * as Provider from \"./Provider.ts\";\n\n/** `teamId` is `null` for a personal account; targets carry the team that owns the zone. */\nexport const TeamContext = Schema.Struct({ teamId: Schema.NullOr(Schema.String) });\nexport type TeamContext = typeof TeamContext.Type;\n\n/** Token-method input: the access token, and a team id to scope the connection to one team. */\nexport const TokenFields = Schema.Struct({\n token: Schema.RedactedFromValue(Schema.String),\n teamId: Schema.optionalKey(Schema.String),\n});\n\nexport interface Options {\n /** Omit to offer tokens only. */\n readonly integration?: {\n readonly clientId: string | Config.Config<string>;\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n readonly slug: string;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nconst capabilities = [\"dns:read\", \"dns:write\"] as const;\n\nexport const provider = (options: Options = {}): Provider.Definition<TeamContext> => {\n const fetch = options.fetch ?? globalThis.fetch;\n const baseUrl = (options.baseUrl ?? \"https://api.vercel.com\").replace(/\\/$/, \"\");\n const client = (token: string): Client.Options => ({ token, fetch, baseUrl });\n\n const targetOf = (domain: Protocol.Domain, label: string): Provider.Target => ({\n zone: domain.name,\n context: { teamId: domain.teamId } satisfies TeamContext,\n label: `${domain.name} (${label})`,\n nameservers: domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers,\n });\n\n const integration =\n options.integration === undefined ? undefined : integrationAuth(options.integration);\n\n function integrationAuth(\n settings: NonNullable<Options[\"integration\"]>,\n ): Provider.IntegrationAuth {\n return {\n label: \"Install the Vercel integration\",\n start: (input) => {\n const authorizationUrl = new URL(\n `https://vercel.com/integrations/${encodeURIComponent(settings.slug)}/new`,\n );\n authorizationUrl.searchParams.set(\"source\", \"external\");\n authorizationUrl.searchParams.set(\"state\", input.state);\n return Effect.succeed({ authorizationUrl: authorizationUrl.toString() });\n },\n complete: (input) =>\n Effect.gen(function* () {\n const clientId = yield* resolve(settings.clientId, \"integration.clientId\");\n const clientSecret = yield* resolve(settings.clientSecret, \"integration.clientSecret\");\n const token = yield* Client.exchangeCode({\n options: client(\"\"),\n clientId,\n clientSecret: Redacted.value(clientSecret),\n code: input.code,\n callbackUrl: input.callbackUrl,\n });\n const callbackTeam = input.params.teamId;\n if (callbackTeam !== undefined && callbackTeam !== token.team_id) {\n return yield* Errors.fail(\n new Reason.Unauthenticated({\n message: \"Vercel integration callback team does not match the installed team\",\n }),\n );\n }\n return {\n secret: Redacted.make(token.access_token),\n context: { teamId: token.team_id } satisfies TeamContext,\n expiresAt: null,\n };\n }),\n };\n }\n\n return Provider.make<TeamContext>({\n id: \"vercel\",\n name: \"Vercel\",\n context: TeamContext,\n contextVersion: \"vercel.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Access token\",\n docsUrl: \"https://vercel.com/account/settings/tokens\",\n requiredCapabilities: capabilities,\n fields: TokenFields,\n authenticate: ({ token, teamId }) =>\n Client.user(client(Redacted.value(token))).pipe(\n Effect.map(() => ({\n secret: token,\n context: { teamId: teamId ?? null } satisfies TeamContext,\n expiresAt: null,\n })),\n ),\n }),\n ...(integration === undefined ? {} : { integration }),\n },\n session: (credential) => {\n const clientOptions = client(Redacted.value(credential.secret));\n const context = Schema.decodeUnknownOption(TeamContext)(credential.context);\n const teamId = context._tag === \"Some\" ? context.value.teamId : null;\n const listTargets = (): Effect.Effect<\n ReadonlyArray<Provider.Target>,\n Errors.DomainKitError\n > =>\n teamId !== null\n ? Client.zones(clientOptions, teamId).pipe(\n Effect.map((domains) => domains.map((domain) => targetOf(domain, teamId))),\n )\n : Effect.gen(function* () {\n const personal = yield* Client.zones(clientOptions, null);\n const teams = yield* Client.teams(clientOptions).pipe(\n Effect.catchIf(\n (error) => error.reason._tag !== \"ProviderUnavailable\",\n () => Effect.succeed([]),\n ),\n );\n const perTeam = yield* Effect.forEach(teams, (team) =>\n Client.zones(clientOptions, team.id).pipe(\n Effect.map((domains) =>\n domains.map((domain) => targetOf(domain, team.name ?? team.slug)),\n ),\n ),\n );\n return [...personal.map((domain) => targetOf(domain, \"personal\")), ...perTeam.flat()];\n });\n return {\n capabilities: () => Effect.succeed(capabilities),\n listTargets,\n resolveTarget: (domain) =>\n Effect.gen(function* () {\n const name = yield* DomainName.decode(domain);\n const targets = yield* listTargets();\n return Provider.resolveAmong(name, targets);\n }),\n dns: (target) => {\n const scope = Schema.decodeUnknownOption(TeamContext)(target.context);\n return Client.dns(clientOptions, scope._tag === \"Some\" ? scope.value.teamId : teamId);\n },\n };\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Vercel.mjs","names":["Client.exchangeCode","Errors.fail","Reason.Unauthenticated","Provider.make","Provider.tokenAuth","Client.user","Client.zones","teams","Client.teams","DomainName.decode","Provider.resolveAmong","Client.dns"],"sources":["../src/Vercel.ts"],"sourcesContent":["/** Vercel: personal tokens and marketplace integrations. */\nimport { type Config, Effect, Redacted, Schema } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { resolve } from \"./internal/config.ts\";\nimport { type Fetch, rejectedToken } from \"./internal/http.ts\";\nimport * as Client from \"./internal/vercel/client.ts\";\nimport type * as Protocol from \"./internal/vercel/protocol.ts\";\nimport * as Provider from \"./Provider.ts\";\n\n/** `teamId` is `null` for a personal account; targets carry the team that owns the zone. */\nexport const TeamContext = Schema.Struct({ teamId: Schema.NullOr(Schema.String) });\nexport type TeamContext = typeof TeamContext.Type;\n\n/** Token-method input: the access token, and a team id to scope the connection to one team. */\nexport const TokenFields = Schema.Struct({\n token: Schema.RedactedFromValue(Schema.String),\n teamId: Schema.optionalKey(Schema.String),\n});\n\nexport interface Options {\n /** Omit to offer tokens only. */\n readonly integration?: {\n readonly clientId: string | Config.Config<string>;\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n readonly slug: string;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nconst capabilities = [\"dns:read\", \"dns:write\"] as const;\n\nexport const provider = (options: Options = {}): Provider.Definition<TeamContext> => {\n const fetch = options.fetch ?? globalThis.fetch;\n const baseUrl = (options.baseUrl ?? \"https://api.vercel.com\").replace(/\\/$/, \"\");\n const client = (token: string): Client.Options => ({ token, fetch, baseUrl });\n\n const targetOf = (domain: Protocol.Domain, label: string): Provider.Target => ({\n zone: domain.name,\n context: { teamId: domain.teamId } satisfies TeamContext,\n label: `${domain.name} (${label})`,\n nameservers: domain.nameservers.length > 0 ? domain.nameservers : domain.intendedNameservers,\n });\n\n const integration =\n options.integration === undefined ? undefined : integrationAuth(options.integration);\n\n function integrationAuth(\n settings: NonNullable<Options[\"integration\"]>,\n ): Provider.IntegrationAuth {\n return {\n label: \"Install the Vercel integration\",\n start: (input) => {\n const authorizationUrl = new URL(\n `https://vercel.com/integrations/${encodeURIComponent(settings.slug)}/new`,\n );\n authorizationUrl.searchParams.set(\"source\", \"external\");\n authorizationUrl.searchParams.set(\"state\", input.state);\n return Effect.succeed({ authorizationUrl: authorizationUrl.toString() });\n },\n complete: (input) =>\n Effect.gen(function* () {\n const clientId = yield* resolve(settings.clientId, \"integration.clientId\");\n const clientSecret = yield* resolve(settings.clientSecret, \"integration.clientSecret\");\n const token = yield* Client.exchangeCode({\n options: client(\"\"),\n clientId,\n clientSecret: Redacted.value(clientSecret),\n code: input.code,\n callbackUrl: input.callbackUrl,\n });\n const callbackTeam = input.params.teamId;\n if (callbackTeam !== undefined && callbackTeam !== token.team_id) {\n return yield* Errors.fail(\n new Reason.Unauthenticated({\n message: \"Vercel integration callback team does not match the installed team\",\n }),\n );\n }\n return {\n secret: Redacted.make(token.access_token),\n context: { teamId: token.team_id } satisfies TeamContext,\n expiresAt: null,\n };\n }),\n };\n }\n\n return Provider.make<TeamContext>({\n id: \"vercel\",\n name: \"Vercel\",\n nameservers: [\"vercel-dns.com\"],\n context: TeamContext,\n contextVersion: \"vercel.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"Access token\",\n docsUrl: \"https://vercel.com/account/settings/tokens\",\n requiredCapabilities: capabilities,\n fields: TokenFields,\n authenticate: ({ token, teamId }) =>\n Client.user(client(Redacted.value(token))).pipe(\n Effect.map(() => ({\n secret: token,\n context: { teamId: teamId ?? null } satisfies TeamContext,\n expiresAt: null,\n })),\n rejectedToken,\n ),\n }),\n ...(integration === undefined ? {} : { integration }),\n },\n session: (credential) => {\n const clientOptions = client(Redacted.value(credential.secret));\n const context = Schema.decodeUnknownOption(TeamContext)(credential.context);\n const teamId = context._tag === \"Some\" ? context.value.teamId : null;\n const listTargets = (): Effect.Effect<\n ReadonlyArray<Provider.Target>,\n Errors.DomainKitError\n > =>\n teamId !== null\n ? Client.zones(clientOptions, teamId).pipe(\n Effect.map((domains) => domains.map((domain) => targetOf(domain, teamId))),\n )\n : Effect.gen(function* () {\n const personal = yield* Client.zones(clientOptions, null);\n const teams = yield* Client.teams(clientOptions).pipe(\n Effect.catchIf(\n (error) => error.reason._tag !== \"ProviderUnavailable\",\n () => Effect.succeed([]),\n ),\n );\n const perTeam = yield* Effect.forEach(teams, (team) =>\n Client.zones(clientOptions, team.id).pipe(\n Effect.map((domains) =>\n domains.map((domain) => targetOf(domain, team.name ?? team.slug)),\n ),\n ),\n );\n return [...personal.map((domain) => targetOf(domain, \"personal\")), ...perTeam.flat()];\n });\n return {\n capabilities: () => Effect.succeed(capabilities),\n listTargets,\n resolveTarget: (domain) =>\n Effect.gen(function* () {\n const name = yield* DomainName.decode(domain);\n const targets = yield* listTargets();\n return Provider.resolveAmong(name, targets);\n }),\n dns: (target) => {\n const scope = Schema.decodeUnknownOption(TeamContext)(target.context);\n return Client.dns(clientOptions, scope._tag === \"Some\" ? scope.value.teamId : teamId);\n },\n };\n },\n });\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { NotFound, ProviderConflict, Unauthenticated } from "../../Reason.mjs";
2
2
  import { decode, fail } from "../error.mjs";
3
- import { bearer, classify, rejected, requestJson } from "../http.mjs";
3
+ import { bearer, classify, rejected, rejectedToken, requestJson } from "../http.mjs";
4
4
  import { BaseEnvelope, Envelope, Record, Token, Zone } from "./protocol.mjs";
5
5
  import { decode as decode$1, encode, isWithinZone } from "./records.mjs";
6
6
  import { DateTime, Effect, Schema } from "effect";
@@ -16,6 +16,8 @@ const conflictCodes = /* @__PURE__ */ new Set([
16
16
  81057,
17
17
  81058
18
18
  ]);
19
+ /** Cloudflare answers a malformed bearer token with HTTP 400: 6003 invalid request headers, 6111 invalid Authorization format. */
20
+ const rejectedTokenCodes = /* @__PURE__ */ new Set([6003, 6111]);
19
21
  const call = (options, path, result, init) => Effect.gen(function* () {
20
22
  const reply = yield* requestJson({
21
23
  fetch: options.fetch,
@@ -27,6 +29,7 @@ const call = (options, path, result, init) => Effect.gen(function* () {
27
29
  const base = Schema.decodeUnknownOption(BaseEnvelope)(reply.body);
28
30
  if (!reply.ok || base._tag === "None" || !base.value.success) {
29
31
  const detail = base._tag === "Some" ? base.value.errors[0] : void 0;
32
+ if (detail !== void 0 && rejectedTokenCodes.has(detail.code)) return yield* fail(new Unauthenticated({ message: detail.message }));
30
33
  if (detail !== void 0 && conflictCodes.has(detail.code)) return yield* fail(new ProviderConflict({
31
34
  provider,
32
35
  code: String(detail.code),
@@ -60,11 +63,13 @@ const listZones = (options, accountId) => paginate(options, (page) => {
60
63
  if (accountId !== void 0) query.set("account.id", accountId);
61
64
  return `/zones?${query}`;
62
65
  }, Zone, 50).pipe(Effect.map((zones) => zones.filter((zone) => zone.type !== "internal")));
63
- /** Token expiry from the verify endpoint; `null` when the token never expires or verification is unavailable. */
64
- /** Token status and expiry from a verify endpoint; an inactive token is `Unauthenticated`. */
65
- const verifyToken = (options, path) => call(options, path, Token).pipe(Effect.flatMap(({ result }) => result.status === "active" ? Effect.succeed(typeof result.expires_on === "string" ? DateTime.makeUnsafe(result.expires_on) : null) : fail(new Unauthenticated({ message: `Cloudflare token is ${result.status}` }))));
66
+ /**
67
+ * Token status and expiry from a verify endpoint; `null` expiry means the token never expires. An
68
+ * inactive token, or a 403 from the verify endpoint, is `Unauthenticated`.
69
+ */
70
+ const verifyToken = (options, path) => call(options, path, Token).pipe(Effect.flatMap(({ result }) => result.status === "active" ? Effect.succeed(typeof result.expires_on === "string" ? DateTime.makeUnsafe(result.expires_on) : null) : fail(new Unauthenticated({ message: `Cloudflare token is ${result.status}` }))), rejectedToken);
66
71
  const tokenExpiry = (options, accountId) => {
67
- const verify = (path) => call(options, path, Token).pipe(Effect.flatMap(({ result }) => result.status === "active" ? Effect.succeed(typeof result.expires_on === "string" ? DateTime.makeUnsafe(result.expires_on) : null) : fail(new Unauthenticated({ message: `Cloudflare token is ${result.status}` }))));
72
+ const verify = (path) => verifyToken(options, path);
68
73
  return verify("/user/tokens/verify").pipe(Effect.map((expiresAt) => ({
69
74
  expiresAt,
70
75
  kind: "user"
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":["Reason.NotFound","Errors.fail","S","Protocol.BaseEnvelope","Reason.ProviderConflict","Errors.decode","Protocol.Envelope","Protocol.Zone","Protocol.Token","Reason.Unauthenticated","Protocol.Record","Records.isWithinZone","Records.decode","Records.encode"],"sources":["../../../src/internal/cloudflare/client.ts"],"sourcesContent":["import { DateTime, Effect, Schema as S } from \"effect\";\n\nimport type * as DnsRecord from \"../../DnsRecord.ts\";\nimport * as Errors from \"../error.ts\";\nimport * as Reason from \"../../Reason.ts\";\nimport type * as Provider from \"../../Provider.ts\";\nimport { bearer, classify, type Fetch, rejected, requestJson } from \"../http.ts\";\nimport * as Protocol from \"./protocol.ts\";\nimport * as Records from \"./records.ts\";\n\nexport const provider = \"cloudflare\";\n\nconst notFound = new Reason.NotFound({ entity: \"zone\", id: \"cloudflare\" });\n\n/** Cloudflare's \"record already exists\" family (81056 identical, 81057 same name and content, 81058 CNAME). */\nconst conflictCodes = new Set([81056, 81057, 81058]);\n\nexport interface Options {\n readonly token: string;\n readonly fetch: Fetch;\n readonly baseUrl: string;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\nconst call = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: string,\n result: R,\n init?: RequestInit,\n): Fx<{ readonly result: R[\"Type\"]; readonly totalPages: number | undefined }> =>\n Effect.gen(function* () {\n const reply = yield* requestJson({\n fetch: options.fetch,\n provider,\n url: `${options.baseUrl}${path}`,\n init: bearer(options.token, init),\n });\n if (reply.status === 404) return yield* Errors.fail(notFound);\n const base = S.decodeUnknownOption(Protocol.BaseEnvelope)(reply.body);\n if (!reply.ok || base._tag === \"None\" || !base.value.success) {\n const detail = base._tag === \"Some\" ? base.value.errors[0] : undefined;\n if (detail !== undefined && conflictCodes.has(detail.code)) {\n return yield* Errors.fail(\n new Reason.ProviderConflict({\n provider,\n code: String(detail.code),\n message: detail.message,\n }),\n );\n }\n return yield* Errors.fail(\n classify(\n provider,\n reply.status,\n reply.headers,\n detail?.message ?? `Cloudflare request failed with HTTP ${reply.status}`,\n detail === undefined ? undefined : String(detail.code),\n ),\n );\n }\n const contract = () =>\n rejected(provider, \"Cloudflare response did not match its API contract\", \"response\");\n const envelope = yield* Errors.decode(Protocol.Envelope, reply.body).pipe(\n Effect.mapError(contract),\n );\n const decoded: R[\"Type\"] = yield* Errors.decode(result, envelope.result).pipe(\n Effect.mapError(contract),\n );\n return { result: decoded, totalPages: envelope.result_info?.total_pages };\n });\n\nconst paginate = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: (page: number) => string,\n result: R,\n perPage: number,\n): Fx<ReadonlyArray<R[\"Type\"]>> =>\n Effect.gen(function* () {\n const rows: Array<R[\"Type\"]> = [];\n let page = 1;\n while (true) {\n const reply = yield* call(options, path(page), S.Array(result));\n rows.push(...reply.result);\n const last =\n reply.totalPages === undefined ? reply.result.length < perPage : page >= reply.totalPages;\n if (last) return rows;\n page += 1;\n }\n });\n\n/** Every zone the credential can see, across accounts; internal zones cannot host records. */\nexport const listZones = (options: Options, accountId?: string): Fx<ReadonlyArray<Protocol.Zone>> =>\n paginate(\n options,\n (page) => {\n const query = new URLSearchParams({ page: String(page), per_page: \"50\" });\n if (accountId !== undefined) query.set(\"account.id\", accountId);\n return `/zones?${query}`;\n },\n Protocol.Zone,\n 50,\n ).pipe(Effect.map((zones) => zones.filter((zone) => zone.type !== \"internal\")));\n\n/** Token expiry from the verify endpoint; `null` when the token never expires or verification is unavailable. */\n/** Token status and expiry from a verify endpoint; an inactive token is `Unauthenticated`. */\nexport const verifyToken = (options: Options, path: string): Fx<DateTime.Utc | null> =>\n call(options, path, Protocol.Token).pipe(\n Effect.flatMap(({ result }) =>\n result.status === \"active\"\n ? Effect.succeed(\n typeof result.expires_on === \"string\" ? DateTime.makeUnsafe(result.expires_on) : null,\n )\n : Errors.fail(\n new Reason.Unauthenticated({\n message: `Cloudflare token is ${result.status}`,\n }),\n ),\n ),\n );\n\nexport const tokenExpiry = (\n options: Options,\n accountId: string | null,\n): Fx<{ readonly expiresAt: DateTime.Utc | null; readonly kind: \"user\" | \"account\" }> => {\n const verify = (path: string) =>\n call(options, path, Protocol.Token).pipe(\n Effect.flatMap(({ result }) =>\n result.status === \"active\"\n ? Effect.succeed(\n typeof result.expires_on === \"string\" ? DateTime.makeUnsafe(result.expires_on) : null,\n )\n : Errors.fail(\n new Reason.Unauthenticated({\n message: `Cloudflare token is ${result.status}`,\n }),\n ),\n ),\n );\n return verify(\"/user/tokens/verify\").pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"user\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\" || accountId !== null,\n () =>\n accountId === null\n ? Effect.succeed({ expiresAt: null, kind: \"user\" as const })\n : verify(`/accounts/${encodeURIComponent(accountId)}/tokens/verify`).pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"account\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\",\n () => Effect.succeed({ expiresAt: null, kind: \"user\" as const }),\n ),\n ),\n ),\n );\n};\n\nexport const dns = (options: Options, zoneId: string): Provider.Dns => {\n const base = `/zones/${encodeURIComponent(zoneId)}/dns_records`;\n return {\n list: (zone) =>\n paginate(\n options,\n (page) => `${base}?${new URLSearchParams({ page: String(page), per_page: \"100\" })}`,\n Protocol.Record,\n 100,\n ).pipe(\n Effect.flatMap((records) =>\n Effect.forEach(\n records.filter((record) => Records.isWithinZone(record.name, zone)),\n Records.decode,\n ),\n ),\n ),\n create: (_zone, record: DnsRecord.Model) =>\n call(options, base, Protocol.Record, {\n method: \"POST\",\n body: JSON.stringify(Records.encode(record)),\n }).pipe(Effect.map(({ result }) => ({ providerRecordId: result.id }))),\n get: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, Protocol.Record).pipe(\n Effect.flatMap(({ result }) => Records.decode(result)),\n Effect.map(({ record }) => record),\n Effect.catchIf(\n (error) => error.reason === notFound,\n () => Effect.succeed(null),\n ),\n ),\n delete: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, S.Unknown, {\n method: \"DELETE\",\n }).pipe(Effect.asVoid),\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.mjs","names":["Reason.NotFound","Errors.fail","S","Protocol.BaseEnvelope","Reason.Unauthenticated","Reason.ProviderConflict","Errors.decode","Protocol.Envelope","Protocol.Zone","Protocol.Token","Protocol.Record","Records.isWithinZone","Records.decode","Records.encode"],"sources":["../../../src/internal/cloudflare/client.ts"],"sourcesContent":["import { DateTime, Effect, Schema as S } from \"effect\";\n\nimport type * as DnsRecord from \"../../DnsRecord.ts\";\nimport * as Errors from \"../error.ts\";\nimport * as Reason from \"../../Reason.ts\";\nimport type * as Provider from \"../../Provider.ts\";\nimport { bearer, classify, type Fetch, rejected, rejectedToken, requestJson } from \"../http.ts\";\nimport * as Protocol from \"./protocol.ts\";\nimport * as Records from \"./records.ts\";\n\nexport const provider = \"cloudflare\";\n\nconst notFound = new Reason.NotFound({ entity: \"zone\", id: \"cloudflare\" });\n\n/** Cloudflare's \"record already exists\" family (81056 identical, 81057 same name and content, 81058 CNAME). */\nconst conflictCodes = new Set([81056, 81057, 81058]);\n\n/** Cloudflare answers a malformed bearer token with HTTP 400: 6003 invalid request headers, 6111 invalid Authorization format. */\nconst rejectedTokenCodes = new Set([6003, 6111]);\n\nexport interface Options {\n readonly token: string;\n readonly fetch: Fetch;\n readonly baseUrl: string;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError>;\n\nconst call = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: string,\n result: R,\n init?: RequestInit,\n): Fx<{ readonly result: R[\"Type\"]; readonly totalPages: number | undefined }> =>\n Effect.gen(function* () {\n const reply = yield* requestJson({\n fetch: options.fetch,\n provider,\n url: `${options.baseUrl}${path}`,\n init: bearer(options.token, init),\n });\n if (reply.status === 404) return yield* Errors.fail(notFound);\n const base = S.decodeUnknownOption(Protocol.BaseEnvelope)(reply.body);\n if (!reply.ok || base._tag === \"None\" || !base.value.success) {\n const detail = base._tag === \"Some\" ? base.value.errors[0] : undefined;\n if (detail !== undefined && rejectedTokenCodes.has(detail.code)) {\n return yield* Errors.fail(new Reason.Unauthenticated({ message: detail.message }));\n }\n if (detail !== undefined && conflictCodes.has(detail.code)) {\n return yield* Errors.fail(\n new Reason.ProviderConflict({\n provider,\n code: String(detail.code),\n message: detail.message,\n }),\n );\n }\n return yield* Errors.fail(\n classify(\n provider,\n reply.status,\n reply.headers,\n detail?.message ?? `Cloudflare request failed with HTTP ${reply.status}`,\n detail === undefined ? undefined : String(detail.code),\n ),\n );\n }\n const contract = () =>\n rejected(provider, \"Cloudflare response did not match its API contract\", \"response\");\n const envelope = yield* Errors.decode(Protocol.Envelope, reply.body).pipe(\n Effect.mapError(contract),\n );\n const decoded: R[\"Type\"] = yield* Errors.decode(result, envelope.result).pipe(\n Effect.mapError(contract),\n );\n return { result: decoded, totalPages: envelope.result_info?.total_pages };\n });\n\nconst paginate = <R extends S.ConstraintDecoder<unknown>>(\n options: Options,\n path: (page: number) => string,\n result: R,\n perPage: number,\n): Fx<ReadonlyArray<R[\"Type\"]>> =>\n Effect.gen(function* () {\n const rows: Array<R[\"Type\"]> = [];\n let page = 1;\n while (true) {\n const reply = yield* call(options, path(page), S.Array(result));\n rows.push(...reply.result);\n const last =\n reply.totalPages === undefined ? reply.result.length < perPage : page >= reply.totalPages;\n if (last) return rows;\n page += 1;\n }\n });\n\n/** Every zone the credential can see, across accounts; internal zones cannot host records. */\nexport const listZones = (options: Options, accountId?: string): Fx<ReadonlyArray<Protocol.Zone>> =>\n paginate(\n options,\n (page) => {\n const query = new URLSearchParams({ page: String(page), per_page: \"50\" });\n if (accountId !== undefined) query.set(\"account.id\", accountId);\n return `/zones?${query}`;\n },\n Protocol.Zone,\n 50,\n ).pipe(Effect.map((zones) => zones.filter((zone) => zone.type !== \"internal\")));\n\n/**\n * Token status and expiry from a verify endpoint; `null` expiry means the token never expires. An\n * inactive token, or a 403 from the verify endpoint, is `Unauthenticated`.\n */\nexport const verifyToken = (options: Options, path: string): Fx<DateTime.Utc | null> =>\n call(options, path, Protocol.Token).pipe(\n Effect.flatMap(({ result }) =>\n result.status === \"active\"\n ? Effect.succeed(\n typeof result.expires_on === \"string\" ? DateTime.makeUnsafe(result.expires_on) : null,\n )\n : Errors.fail(\n new Reason.Unauthenticated({\n message: `Cloudflare token is ${result.status}`,\n }),\n ),\n ),\n rejectedToken,\n );\n\nexport const tokenExpiry = (\n options: Options,\n accountId: string | null,\n): Fx<{ readonly expiresAt: DateTime.Utc | null; readonly kind: \"user\" | \"account\" }> => {\n const verify = (path: string) => verifyToken(options, path);\n return verify(\"/user/tokens/verify\").pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"user\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\" || accountId !== null,\n () =>\n accountId === null\n ? Effect.succeed({ expiresAt: null, kind: \"user\" as const })\n : verify(`/accounts/${encodeURIComponent(accountId)}/tokens/verify`).pipe(\n Effect.map((expiresAt) => ({ expiresAt, kind: \"account\" as const })),\n Effect.catchIf(\n (error) => error.reason._tag !== \"Unauthenticated\",\n () => Effect.succeed({ expiresAt: null, kind: \"user\" as const }),\n ),\n ),\n ),\n );\n};\n\nexport const dns = (options: Options, zoneId: string): Provider.Dns => {\n const base = `/zones/${encodeURIComponent(zoneId)}/dns_records`;\n return {\n list: (zone) =>\n paginate(\n options,\n (page) => `${base}?${new URLSearchParams({ page: String(page), per_page: \"100\" })}`,\n Protocol.Record,\n 100,\n ).pipe(\n Effect.flatMap((records) =>\n Effect.forEach(\n records.filter((record) => Records.isWithinZone(record.name, zone)),\n Records.decode,\n ),\n ),\n ),\n create: (_zone, record: DnsRecord.Model) =>\n call(options, base, Protocol.Record, {\n method: \"POST\",\n body: JSON.stringify(Records.encode(record)),\n }).pipe(Effect.map(({ result }) => ({ providerRecordId: result.id }))),\n get: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, Protocol.Record).pipe(\n Effect.flatMap(({ result }) => Records.decode(result)),\n Effect.map(({ record }) => record),\n Effect.catchIf(\n (error) => error.reason === notFound,\n () => Effect.succeed(null),\n ),\n ),\n delete: (_zone, providerRecordId) =>\n call(options, `${base}/${encodeURIComponent(providerRecordId)}`, S.Unknown, {\n method: \"DELETE\",\n }).pipe(Effect.asVoid),\n };\n};\n"],"mappings":"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"}